이 문서는 Development 문서를 번역한 것이며 번역은 98% 완료했습니다.
오래된 번역은 이렇게 표시됩니다.

이 문서는 Wikven에 작업하고자 하는 사람들을 위해 Wikven이 어떻게 만들어지는지 설명합니다. Wikven은 위키텍스트 디렉터리를 정적 웹사이트로 바꿔 주는 MediaWiki 확장 기능과 소수의 관리 스크립트로 이루어져 있습니다. 새로운 렌더링 엔진을 더하는 것이 아니라, MediaWiki 자체를 구동한 다음 그 결과물을 갈무리합니다. Wikven 자체를 작업하는 것이 아니라 정적 내보내기에서 살아남아야 하는 확장 기능이나 스킨을 만드는 중이라면, 확장 기능 만들기가 그 문서입니다.

구조

Wikven 빌드는 네 개의 구성 요소로 이루어집니다:

  • 확장 기능(extension.json, includes/). Wikven의 설정과, MediaWiki의 출력을 정적 호스트에 맞게 조정하는 몇 가지 훅을 등록합니다. 편집/역사 UI와 사이드바의 위키 전용 링크를 숨기고, 푸터 링크를 설정된 WikvenFooterUrl/WikvenEditUrl/WikvenHistoryUrl로 바꾸며, 내부 URL이 index.php?title= 대신 .html 파일을 가리키도록 다시 씁니다.
  • 관리 스크립트(maintenance/). 아래에서 설명하는 빌드 파이프라인입니다.
  • WikvenSettings.php. 빌드가 그 아래에서 실행되는 LocalSettings.php입니다. 설정을 불러오고, 이미지 백엔드를 감지하며, Wikven의 기본값을 적용합니다.
  • bin/entrypoint 스크립트와 Dockerfile. MediaWiki와 확장 기능을, 기본 명령이 빌드 한 번을 실행하는 이미지로 패키징합니다.

이미지 빌드하기

docker build --tag wikven .

Dockerfile은 FROM mediawiki입니다. Composer(빌드 시 서드파티 구성 요소를 가져오기 위해)와 이미지 도구인 rsvg-convert, ImageMagick의 JPEG·WebP 델리게이트를 추가하고, MediaWiki 자체의 것 외에 Wikven이 번들하는 확장 기능 셋(SifterSearch, Translate, UniversalLanguageSelector)을 가져오며, 확장 기능을 /var/www/html/extensions/Wikven으로 복사하고, WikvenSettings.php를 MediaWiki 루트로 복사한 다음, bin/entrypoint 스크립트를 이미지의 엔트리포인트로 설정합니다.

빌드 실행하기

docker run --rm \
  -v "$(pwd)/docs:/workspace/src" \
  -v "$(pwd)/dist:/workspace/dist" \
  wikven

컨테이너는 소스를 /workspace/src에 마운트하고 렌더링된 사이트를 /workspace/dist에 기록합니다. 두 경로 모두 WIKVEN_WORKDIR(기본값 /workspace)에서 파생되며, 여기에는 일시적 상태를 위한 .cache/도 있습니다. 이 동일한 배치 덕분에 단독 실행 바이너리는 모든 것을 쓰기 가능한 호스트 디렉터리로 향하게 할 수 있습니다.

bin/entrypoint 스크립트는:

  1. 일회용 MediaWiki를 SQLite 데이터베이스에 설치하고(웹 서버 없음. 만들어지는 관리자 계정은 결코 사용되지 않습니다),
  2. 서드파티 확장 기능과 스킨을 가져오며(아래 참고),
  3. 생성된 LocalSettings.php에 require_once 'WikvenSettings.php';를 덧붙이고,
  4. 설치 관리자가 만들지 않는, 번들된 확장 기능이 요구하는 스키마 갱신을 적용한 다음,
  5. 빌드 파이프라인을 실행하고,
  6. 빌드가 root로 돌기 때문에 출력물을 마운트된 디렉터리의 소유자에게 돌려줍니다.

빌드 파이프라인

maintenance/build.php는 한 스크립트가 두 역할을 합니다. 환경 변수 없이 실행되면 오케스트레이터로서, MediaWiki를 한 번 부팅하여 일회용 위키를 여러분의 내용으로 채운 다음, 스킨마다 하나씩 렌더링 과정을 별도 프로세스로 시작합니다. 각 프로세스에는 WIKVEN_BUILD_SKIN이 스킨 이름을 담고 있습니다. 그 변수가 설정된 채 실행되면 같은 스크립트가 바로 그 렌더링 과정이 됩니다. 어느 쪽이든 모든 단계는 자식 관리 스크립트이므로, 빌드 전체가 MediaWiki 부트스트랩 비용을 단계마다가 아니라 프로세스마다 한 번씩만 치릅니다.

이 구분이 렌더링 과정들을 나란히 돌려도 안전하게 만듭니다. 쓰기는 모두 오케스트레이터가 하고, 렌더링이 시작될 때 내용은 이미 확정되어 있어 각 과정은 읽기만 합니다. 렌더링은 쓸 수 있는 프로세서 수만큼 동시에 진행됩니다.

1단계: 위키 채우기

checkLuaAgainstThisBuild
이 사이트의 Lua와 이 빌드의 Lua가 서로를 어떻게 보는지를, 다른 어떤 일이 벌어지기 전에 말합니다. Scribunto를 나열했지만 그것을 실행할 엔진이 없는 사이트는 빌드를 끝내는데, 이 빌드가 할 수 없는 일을 요구했기 때문입니다. 반면 Scribunto를 나열한 적 없이 Module: 파일만 있는 사이트에는 그 파일들이 무엇이 되는지를 알려주기만 합니다. 실행할 엔진이 없으면 #invoke는 호출문 그대로 문서에 실리기 때문입니다. 가장 먼저 실행되므로, 사이트가 거부될 때 지난 빌드 결과가 아직 그대로 남아 있습니다.
clearOutputDirectory
출력 디렉터리를 비워, 이전 빌드의 파일이 남지 않도록 합니다.
setMainPage
MediaWiki:Mainpage를 가져온 index 문서로 지정하여, 사이트 루트가 index.html로 해석되도록 합니다.
importImages
소스 디렉터리의 이미지 파일을 File: 네임스페이스로 업로드합니다. 위키텍스트 가져오기 전에 실행되므로, 로컬 이미지를 삽입한 문서가 깨진 미디어 자리표시자 대신 실제 섬네일을 렌더링합니다.
importWikitext
모든 *.wikitext 파일을 가져옵니다(하위 디렉터리까지 재귀하므로 Template:Foo/styles.css 같은 문서는 중첩된 파일 Template/Foo/styles.css일 수 있습니다). File: 설명 문서와 MediaWiki: 시스템 문서는 편집 훅이 발동하도록 현재 판으로 저장되고, 일반 문서는 옛 판으로 가져옵니다. 주 문서가 없으면 빌드는 여기서 중단됩니다.
setLicensesPage와 setSettingsPage
빌드가 생성하는 두 문서를 씁니다. 사이트가 게시하는 것과 그 라이선스를 담았고 모든 문서의 바닥글이 링크하는 라이선스 문서와, 독자의 색 테마와 스킨을 담은 설정 문서입니다. 같은 이름의 소스 문서가 있으면 생성본 대신 그것이 쓰입니다.
dropDeadPlaceLinks와 dropDeadCategoryLink
내보내기에 없는 문서로 이어질 푸터와 메뉴 항목(개인정보 정책, 면책 조항, 커뮤니티 포털)의 시스템 메시지를 비웁니다.
buildTranslations
<translate>로 표시된 각 문서의 번역을 실제 문서로 만들어, 작업 큐와 렌더링 과정이 다른 문서와 똑같이 다루도록 합니다. 번역하기를 참고하십시오.
RunJobs
MediaWiki의 작업 큐를 실행합니다. 주로 섬네일 생성과 링크 테이블 갱신이며, 작업 종류별로 이름 순서대로 하나씩 처리합니다. 검색 색인 작업은 맨 끝으로 미룹니다. 판마다 큐에 들어가기 때문에, 그러지 않으면 큐를 돌 때마다 실행되고 마지막 것을 뺀 나머지 결과를 모두 버리게 됩니다.
stampSourceHistory와 hideBuildAuthors
각 문서에 그 소스 파일을 마지막으로 바꾼 커밋의 시각을 찍고 그 커밋의 작성자를 밝혀, 푸터의 "마지막 편집" 줄이 그 줄에서 이어지는 역사의 첫 항목과 맞아떨어지게 합니다. 저장소의 역사에 닿을 수 없다면 — 얕은 체크아웃이거나, 소스 디렉터리가 저장소가 아닌 경우 — 모든 문서가 빌드 중인 커밋의 시각을 갖고 작성자는 아무도 아니게 됩니다.
freezePageTouched
모든 문서의 page_touched를 고정합니다. 이 값은 버전 콜백을 지닌 모듈의 버전 입력이기 때문입니다. 데이터베이스에 대한 마지막 쓰기이며, 이후는 모두 읽기입니다.

2단계: 스킨마다 한 번씩

각 렌더링 과정은 사이트 전체를 자기 스킨으로, 자기 디렉터리에 렌더링합니다. skins의 첫 스킨은 출력 루트에, 나머지는 dist/<skin>/에 렌더링됩니다. 각 과정은 데이터베이스 사본을 하나씩 받는데, SQLite는 한 번에 하나만 쓸 수 있고 렌더링 과정도 객체 캐시에는 쓰기를 하기 때문입니다.

RebuildFileCache
모든 콘텐츠 문서를 HTML 파일로 렌더링합니다. MediaWiki 자체의 파일 캐시이며, Wikven은 File:도 콘텐츠 네임스페이스로 취급하므로 모든 틀을 쏟아내지 않으면서 파일 설명 문서까지 내보냅니다.
retranslateChrome
위키 자신의 언어가 아닌 언어로 쓰인 각 문서 — 번역 문서와, 빌드가 언어별로 쓰는 라이선스 문서 사본 — 를 그 언어를 인터페이스 언어로 삼아 다시 렌더링합니다. 파일 캐시는 표준 익명 열람만 캐시하므로, 그대로 두면 그 문서들이 모두 콘텐츠 언어의 인터페이스를 두르게 됩니다.
stripBuildStamps
MediaWiki가 렌더링된 문서마다 찍는 요청 id, 렌더 id, 시각을 제거합니다. 빌드마다 달라지는 값들입니다.
buildStyles
문서가 참조하는 각 CSS 모듈을 로컬 파일로 다시 렌더링하고, site.styles 모듈(MediaWiki:Common.css와 스킨의 사이트 CSS)을 자체 파일로 렌더링합니다.
bakeWebfonts
WikvenBundleWebfonts가 설정되어 있으면, 내보내기의 언어들에 대한 UniversalLanguageSelector의 웹폰트를 글꼴 파일과 함께 평범한 스타일시트로 씁니다. 그렇지 않으면 아무 일도 하지 않습니다.
buildScripts
문서에 필요한 자바스크립트를 두 파일로 덤프합니다. startup-static.js(네트워크 자동 로드를 제거한 로더 매니페스트)와 modules-static.js(전체 의존성 클로저이므로 모든 모듈이 스스로 실행됩니다)입니다. 살아 있는 위키가 암묵적으로 끌어오는 site 모듈(MediaWiki:Common.js와 스킨의 JS)과 기본 소도구도 함께 심습니다.
rewriteScripts
캐시된 HTML이 load.php 대신 로컬 번들을 불러오도록 다시 씁니다. 비동기 시작 태그를 정적 파일과 명시적인 mw.loader.load()로 바꾸고, 사이트 스타일이 캐스케이드에서 이기도록 마지막에 링크하며, 살아 있는 API가 필요한 검색 상자를 제거합니다. 단 SifterSearch가 정적 색인을 제공하는 경우는 예외입니다.
fillMinervaMenu
렌더링된 각 문서의 Minerva 주 메뉴에 사이트 자체의 내비게이션을 써넣습니다. Minerva는 그 메뉴를 자체 정의에서 만들고 MediaWiki:Sidebar를 결코 읽지 않으며, PHP 쪽에서 항목을 더할 방법도 없습니다.
storeImages
참조된 모든 이미지를(InstantCommons를 통한 위키미디어 공용의 것이든 로컬 업로드든) 내용 해시가 붙은 로컬 파일로 복사하고 URL을 다시 써서, 열람 시점에 네트워크로 가져오는 것이 없게 합니다.
rename
네임스페이스 접두사가 붙은 캐시 파일에 읽을 수 있는 이름을 줍니다(예: ns6%3A...가 File:...이 됩니다). 각 문서의 상대 링크도 그 문서 자신의 깊이에 맞게 고칩니다.
resolveTranslationLinks
각 번역이 어느 경로에 있는지 확정된 뒤, 완성된 트리 위에서 Special:MyLanguage/ 링크를 다시 써서 그 링크가 놓인 문서의 언어로 이어지게 합니다.
buildSitemap
WikvenSiteUrl이 설정되어 있으면, 그 과정이 내보낸 모든 문서를 절대 URL로 담은 sitemap.xml을 씁니다. 크롤러에게는 절대 URL이 필요한데 사이트가 게시될 곳을 말해주기 전까지 빌드는 그것을 알 수 없으므로, 설정되어 있지 않으면 아무것도 쓰지 않습니다. 색인되지 않기를 요청하는 문서는 빠집니다. 사이트맵은 색인해 달라는 초대이므로 그런 문서를 담으면 문서 자신과 어긋나기 때문입니다. 남는 문서가 없으면 파일을 쓰지 않습니다. 사이트맵은 출력 루트로 렌더링하는 과정만 쓰는데, 이유는 같습니다. 나머지는 미리보기이고 그 문서들이 바로 그 거절을 달고 있습니다.

주 스킨이 아닌 스킨을 렌더링하는 과정은 검색 색인을 자기 디렉터리로 복사하기도 합니다. 그 사본에서 검색하면 그 안에서 답이 나오도록 하기 위해서입니다.

설정

설정은 계층으로 이루어집니다. default.yml은 Wikven의 기본 MediaWiki 설정을 제공하고, 사이트의 .wikven.yaml이 그 위에 병합됩니다. WikvenSettings.php는 둘 다를 MediaWiki 자체의 YAML 설정 파서로 읽어, 병합된 config 맵을 적용하고, 나열된 extensions와 skins를 불러오며, $wgLogos를 업로드된 WikvenLogos 파일로 향하게 합니다. 또한 실행 시점에 섬네일 백엔드를 감지하는데, 가능하면 ImageMagick과 rsvg를, 그렇지 않으면 내장 GD 라이브러리와 네이티브 인라인 SVG를 사용하므로, 동일한 빌드가 이미지에서도 GD만 있는 바이너리에서도 동작합니다.

이 단계들이 존재하는 이유

실제로 구동 중인 위키는 load.php, 액션 API, 데이터베이스에 의존하지만, 정적 호스트에는 이 중 무엇도 없습니다. 각 파이프라인 단계는 그런 의존성을 하나씩 없앱니다. 파일 캐시가 렌더러를 대체하고, buildStyles/buildScripts/rewriteScripts가 load.php를 대체하며, storeImages가 섬네일/공용 엔드포인트를 대체하고, 확장 기능의 훅이 API를 호출할 UI를 제거합니다. 남는 것은 평범한 HTML, CSS, JS, 그리고 이미지입니다.

서드파티 확장 기능과 스킨

extensions/skins에 있는 이름 중 Wikven에 번들되지 않은 것은 빌드 시점에 maintenance/fetchExtensions.php가 WikvenRepositories 맵에 선언된 소스(tarball, Git 저장소, 또는 Composer 패키지)에서 가져옵니다. 이는 설치 후 WikvenSettings.php가 연결되기 전에 실행되므로, MediaWiki가 구성 요소를 불러올 때쯤이면 이미 디스크에 있습니다.

단독 실행 바이너리

동일한 MediaWiki + Wikven 트리가 단일 FrankenPHP 실행 파일로 내장되어(binary.Dockerfile 참고), Docker 없이도 사이트를 빌드할 수 있습니다. 바이너리는 시작 시 자신의 앱을 쓰기 가능한 임시 디렉터리로 추출하고 동일한 파이프라인을 실행하는데, 이것이 모든 쓰기 가능한 경로를 WIKVEN_WORKDIR 아래에 두는 이유입니다. build와 serve 하위 명령은 caddy/의 작은 Caddy 모듈이 등록하며, 그 명령들이 실행하는 진입점이 bin/build.php입니다.

테스트

네 가지 스위트가 있고, 각각 나머지가 다루지 못하는 것을 다룹니다:

# PHPUnit, run against a MediaWiki install (CI does this with quibble).
php tests/phpunit/phpunit.php extensions/Wikven/tests/phpunit/unit
php tests/phpunit/phpunit.php extensions/Wikven/tests/phpunit/integration

# Linters and coding standards.
composer phpcs
composer test:comments # the comment budget: tests/comments/Budget.php
composer test          # minus-x: file modes and shebangs

# Assertions on a site you have already baked into dist/. Plain PHP, so no install.
composer test:bake

# Browser tests, against a site you have already baked into dist/.
npm ci
npm run test:e2e
  • 단위 테스트(tests/phpunit/unit/)는 순수한 문자열·경로 처리인 도우미 클래스를 다룹니다.
  • 통합 테스트(tests/phpunit/integration/)는 훅과, 주변에 서비스가 있어야 하는 클래스를 다룹니다.
  • 베이크 검증(tests/bake/)은 이미 구워진 사이트를 읽고 그것이 완결된 자립형 사이트인지 말합니다. CSS에 load.php가 남지 않았는지, 스킨 사본마다 검색 번들이 있는지, 인쇄용 푸터의 링크가 모두 해석되는지. Wikven의 것이 아니라 이 사이트의 것 — 어디에 발행되는지, 어떤 문서를 색인에서 빼는지, 어떤 스킨을 빌드하는지 — 은 tests/bake/docs.php에 적혀 있어서, 같은 명령이 어느 사이트의 베이크든 읽습니다. smoke.yml은 실제 이미지로 이 문서 사이트를 굽고, 그것들을 돌리고, 두 번째 베이크와 사이트 전체를 대조합니다. 한 소스를 두 번 구우면 바이트까지 같아야 하기 때문입니다.
  • 브라우저 테스트(tests/e2e/, Playwright)는 구워진 사이트를 Chromium에서 조작합니다. 스킨 전환기, 설정 문서, 스킨별 검색, Minerva의 메뉴를 다룹니다.

e2e 실행은 GitHub Pages가 그러하듯 dist/를 /wikven/ 경로 접두사 아래에서 제공하므로, 같은 명령이 로컬에서도 CI에서도 작동합니다. 다른 곳을 가리키려면 WIKVEN_BASE_URL을 쓰십시오.

지속적 통합

  • lint.yml은 포매터와 린터(Biome, mago, rumdl, taplo, typos, yamllint, zizmor, phpcs)를 실행하고, 모든 소스 주석을 낱말 수 예산 안에 묶어 두며, 이 사이트가 Wikven의 모든 설정 변수와 빌드의 모든 단계를 문서화하고 있는지 검사합니다.
  • test.yml은 실제 MediaWiki를 상대로 PHPUnit 스위트와 phan, 커버리지를 실행하고 Caddy 플러그인을 빌드합니다.
  • semantic-pull-request.yml은 풀 리퀘스트 제목을 읽습니다. 이 제목이 스쿼시 커밋의 제목이 되므로 Conventional Commits를 따라야 하며, Wikven 자체가 아니라 저장소를 바꾸는 변경(docs/, 리드미, .tf/, .github/)은 feat나 fix를 칭할 수 없습니다. release-please가 그것을 변경 로그에 넣고 버전을 올릴 것으로 읽기 때문입니다.
  • smoke.yml은 이 사이트를 굽고 그 출력을 브라우저에서, 그리고 바이트 단위로 검증합니다.
  • translations.yml은 check-translations 액션을 통해 docs/의 오래된 번역을 보고합니다.
  • docker-image.yml은 이미지를 빌드하되, 릴리스나 나이틀리가 요청할 때만 게시합니다. main에 푸시하면 빌드하고 멈추는데, 이것이 다른 이미지 작업들이 읽는 레이어 캐시를 식지 않게 합니다. 게시는 한 번의 빌드에서 ghcr.io/chaotic-ground/wikven과 quay.io/chaotic-ground/wikven 양쪽으로 나가며, 게시한 뒤 모든 태그를 되읽으므로 한쪽 레지스트리에만 도달한 게시는 통과하지 않고 실패합니다. quay.io 인증은 이 저장소의 OIDC 신원에 연합된 로봇 계정으로 이루어지므로, 저장소 시크릿에 레지스트리 토큰이 없습니다.
  • deploy-docs.yml은 이미지로 docs/ 디렉터리를 빌드하고 그 결과를 GitHub Pages에 배포하므로, 이 사이트 자체가 하나의 Wikven 빌드입니다. pr-preview.yml은 풀 리퀘스트마다 같은 일을 하되, 풀 리퀘스트가 닫히면 사라지는 하위 디렉터리에 배포합니다.
  • binary.yml은 단독 실행 바이너리를 빌드합니다. 해당 파일을 건드리는 풀 리퀘스트에서, 그리고 릴리스 때는 각 플랫폼용으로 실행됩니다. release-please.yml이 이를 호출하여 버전 릴리스에 바이너리를 첨부하고, nightly.yml이 매일 이를 호출하여 날짜가 붙은 nightly-YYYY-MM-DD 프리릴리스를 게시합니다. 둘 다 릴리스를 초안으로 만들고, 바이너리를 첨부한 다음 게시하는데, GitHub의 불변 릴리스는 릴리스가 게시되고 나면 자산을 잠그기 때문입니다. nightly.yml은 같은 날짜 태그로 이미지도 게시하며, 릴리스된 버전이 넘겨받기 전까지는 :latest도 함께 붙입니다.
  • updatecli.yml은 이미지와 문서 사이트가 지닌 버전 핀을 옮깁니다. tofu.yml은 .tf/에 있는 저장소 자체의 설정을 GitHub의 현재 상태와 비교해 그 차이를 풀 리퀘스트에 코멘트로 답니다. GitHub를 바꾸는 것은 적용하지 않고 이미 있는 것을 상태로 들이는 임포트만 적용하므로, 머지된 .tf/ 변경은 누군가 tofu apply를 실행해야 비로소 적용됩니다.
  • image-diff.yml은 컨테이너 이미지의 핀을 옮기는 풀 리퀘스트에, 두 이미지가 각각 빌드된 커밋을 서로 비교하는 링크를 코멘트로 답니다. 다이제스트는 빌드를 가리킬 뿐 비교할 대상이 없는데, 이미지 자신이 어느 커밋에서 나왔는지를 담고 있습니다.

버전은 Conventional Commits를 따르며, release-please가 이를 사용해 변경 로그를 생성하고 버전을 올립니다.