diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a4e7910..7fb99d0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,11 +57,17 @@ jobs: - name: Check TypeScript run: npm.cmd run check + - name: Check project site TypeScript + run: npm.cmd run check:site + - name: Run frontend tests run: npm.cmd run test:frontend - name: Build frontend run: npm.cmd run build + - name: Build project site + run: npm.cmd run build:site + - name: Run Rust tests run: cargo test --locked --manifest-path src-tauri/Cargo.toml diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000..47a00f5 --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,73 @@ +name: Pages + +on: + push: + branches: + - master + paths: + - ".github/workflows/pages.yml" + - "package.json" + - "package-lock.json" + - "site/**" + - "src/assets/eraw-icon.svg" + - "tests/site.test.mjs" + - "vite.pages.config.ts" + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: false + +jobs: + build: + name: Build project site + runs-on: ubuntu-latest + + steps: + - name: Check out repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Set up Node.js + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: 22 + cache: npm + cache-dependency-path: package-lock.json + + - name: Configure GitHub Pages + uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0 + + - name: Install dependencies + run: npm ci + + - name: Check project site TypeScript + run: npm run check:site + + - name: Run project site tests + run: npm run test:site + + - name: Build project site + run: npm run build:site + + - name: Upload GitHub Pages artifact + uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 + with: + path: ./dist-site + + deploy: + name: Deploy project site + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + steps: + - name: Deploy GitHub Pages + id: deployment + uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0 diff --git a/docs/PUBLICATION.md b/docs/PUBLICATION.md index c6c3d7f..5ee9c94 100644 --- a/docs/PUBLICATION.md +++ b/docs/PUBLICATION.md @@ -35,11 +35,13 @@ GitHub 免费私有仓库不开放以下配置;可见性切换后应在同一 5. 确认 fork Pull Request 的工作流需要首次贡献者批准,且不会向 fork 暴露 secrets。 6. 检查 About 区域、Topics、Social preview、README 徽章和 Latest Release 的公开显示。 7. 从未登录会话检查 README、Releases、Issues、Security policy 与下载资产。 +8. 将 GitHub Pages 的构建来源设为 GitHub Actions,并由 `.github/workflows/pages.yml` 发布 `dist-site/`。 ## 发布与供应链 - GitHub Actions 使用允许列表并固定到完整 commit SHA;Dependabot 负责跟踪 Action 更新。 - 默认 `GITHUB_TOKEN` 为只读;只有 Release job 获得 `contents: write`,公开时的 attestation step 另获 `id-token: write` 与 `attestations: write`。 +- Pages 工作流仅获得 `contents: read`、`pages: write` 与 `id-token: write`,只部署静态站点产物,不创建 tag 或 Release。 - Release EXE 目前没有商业代码签名证书。SHA-256、SBOM 与构建来源证明用于完整性和来源核验,但不能替代 Windows Authenticode。 - 历史 Release 保留用于追溯;新用户应下载 Latest Release。 diff --git a/site/README.md b/site/README.md index 49ef0ab..7588198 100644 --- a/site/README.md +++ b/site/README.md @@ -5,11 +5,12 @@ ```powershell npm.cmd run dev:site npm.cmd run check:site +npm.cmd run test:site npm.cmd run build:site ``` -开发服务器固定使用 `http://127.0.0.1:4174/eRAW/`,避免落入 Windows 可能保留的默认 Vite 端口范围。 +开发服务器默认使用 `http://127.0.0.1:44174/eRAW/`。如果该端口在本机被占用或保留,可以通过 `npm.cmd run dev:site -- --port <端口>` 临时覆盖。 - 本地开发入口由 `vite.pages.config.ts` 管理。 - 生产构建输出到被 Git 忽略的 `dist-site/`。 -- GitHub Pages 发布暂未启用;等仓库公开且中英文页面通过最终审阅后再增加部署工作流。 +- GitHub Pages 由 `.github/workflows/pages.yml` 构建并部署;对站点相关文件的 `master` 推送会自动发布,也可以手动触发。 diff --git a/site/styles.css b/site/styles.css index 29bf041..f990126 100644 --- a/site/styles.css +++ b/site/styles.css @@ -16,7 +16,7 @@ * { box-sizing: border-box; } html { background: var(--page); scroll-behavior: smooth; } -body { min-width: 320px; margin: 0; background: var(--page); color: var(--text); font-size: 14px; line-height: 1.65; } +body { margin: 0; background: var(--page); color: var(--text); font-size: 14px; line-height: 1.65; } a { color: inherit; text-decoration: none; } img { display: block; max-width: 100%; } .page-width { width: min(1344px, calc(100% - 64px)); margin-inline: auto; } diff --git a/tests/repository-publication.test.mjs b/tests/repository-publication.test.mjs index 9c135c1..44d25aa 100644 --- a/tests/repository-publication.test.mjs +++ b/tests/repository-publication.test.mjs @@ -14,6 +14,7 @@ const [ helpWindowSource, ciSource, releaseSource, + pagesSource, dependabotSource, contributingSource, securitySource, @@ -30,6 +31,7 @@ const [ read("src/help-window.ts"), read(".github/workflows/ci.yml"), read(".github/workflows/release.yml"), + read(".github/workflows/pages.yml"), read(".github/dependabot.yml"), read("CONTRIBUTING.md"), read("SECURITY.md"), @@ -94,18 +96,27 @@ test("dependency automation covers npm, Cargo, and GitHub Actions", () => { assert.match(publicationSource, /RUSTSEC-2026-0195/); }); -test("workflows pin actions and release integrity metadata", () => { - for (const workflow of [ciSource, releaseSource]) { +test("workflows pin actions and protect build and release integrity", () => { + for (const workflow of [ciSource, releaseSource, pagesSource]) { const uses = [...workflow.matchAll(/^\s*uses:\s*([^\s#]+).*$/gm)].map((match) => match[1]); assert.ok(uses.length > 0); for (const action of uses) assert.match(action, /@[0-9a-f]{40}$/); } assert.match(ciSource, /Verify third-party dependency notices/); + assert.match(ciSource, /npm\.cmd run check:site/); + assert.match(ciSource, /npm\.cmd run build:site/); assert.match(releaseSource, /SHA256SUMS/); assert.match(releaseSource, /dependency-graph\/sbom/); assert.match(releaseSource, /THIRD_PARTY_LICENSES\.txt/); assert.match(releaseSource, /attest-build-provenance@[0-9a-f]{40}/); assert.match(releaseSource, /repository\.visibility == 'public'/); + assert.match(pagesSource, /pages:\s*write/); + assert.match(pagesSource, /id-token:\s*write/); + assert.match(pagesSource, /npm run check:site/); + assert.match(pagesSource, /npm run test:site/); + assert.match(pagesSource, /npm run build:site/); + assert.match(pagesSource, /path:\s*\.\/dist-site/); + assert.match(pagesSource, /name:\s*github-pages/); assert.match(publicationSource, /Secret scanning/); assert.match(publicationSource, /enforce_admins=false/); assert.match(publicationSource, /GHSA-wrw7-89jp-8q8g/); diff --git a/tests/site.test.mjs b/tests/site.test.mjs index 3d1c927..2e98ccb 100644 --- a/tests/site.test.mjs +++ b/tests/site.test.mjs @@ -37,7 +37,7 @@ test("the bilingual project site builds independently of the application version assert.match(configSource, /root: "site"/); assert.match(configSource, /base: "\/eRAW\/"/); assert.match(configSource, /host: "127\.0\.0\.1"/); - assert.match(configSource, /port: 4174/); + assert.match(configSource, /port: 44174/); assert.match(configSource, /strictPort: true/); assert.match(configSource, /outDir: "\.\.\/dist-site"/); assert.match(configSource, /site\/index\.html/); @@ -127,5 +127,6 @@ test("both locales use replaceable theme captures and a release-resolved x64 dow assert.match(styleSource, /@media \(max-width: 980px\)/); assert.match(styleSource, /@media \(max-width: 620px\)/); assert.match(styleSource, /@media \(prefers-reduced-motion: reduce\)/); + assert.doesNotMatch(styleSource, /body\s*\{[^}]*min-width:\s*320px/); assert.doesNotMatch(`${zhHtmlSource}\n${enHtmlSource}`, /google-analytics|googletagmanager|fonts\.googleapis|use\.typekit/i); }); diff --git a/vite.pages.config.ts b/vite.pages.config.ts index cac5084..5274361 100644 --- a/vite.pages.config.ts +++ b/vite.pages.config.ts @@ -6,7 +6,7 @@ export default defineConfig({ base: "/eRAW/", server: { host: "127.0.0.1", - port: 4174, + port: 44174, strictPort: true, }, build: {