diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e51352c8e..e1fa7baed 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -788,7 +788,12 @@ jobs: - name: Generate download manifest if: steps.r2.outputs.configured == 'true' - run: node scripts/generate-download-manifest.mjs r2-assets "$GITHUB_REF_NAME" "$R2_PUBLIC_BASE_URL" manifest.json + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + pub_date=$(gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" --json publishedAt --jq .publishedAt) + node scripts/generate-download-manifest.mjs r2-assets "$GITHUB_REF_NAME" "$R2_PUBLIC_BASE_URL" manifest.json "$pub_date" - name: Upload assets and manifest to R2 if: steps.r2.outputs.configured == 'true' diff --git a/scripts/generate-download-manifest.mjs b/scripts/generate-download-manifest.mjs index 85059308b..30ed434dd 100644 --- a/scripts/generate-download-manifest.mjs +++ b/scripts/generate-download-manifest.mjs @@ -4,15 +4,23 @@ // schema is mirrored in cc-switch-website/src/lib/downloads.ts — keep both in // sync when changing fields or classification rules. // -// Usage: node scripts/generate-download-manifest.mjs [output] +// Usage: node scripts/generate-download-manifest.mjs [output] [pub-date] -import { readdirSync, statSync, writeFileSync } from 'node:fs'; +import { createHash } from 'node:crypto'; +import { readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs'; import { join } from 'node:path'; -const [assetsDir, tag, baseUrl, output = 'manifest.json'] = process.argv.slice(2); +const [assetsDir, tag, baseUrl, output = 'manifest.json', pubDateArg] = process.argv.slice(2); if (!assetsDir || !tag || !baseUrl) { - console.error('Usage: node scripts/generate-download-manifest.mjs [output]'); + console.error('Usage: node scripts/generate-download-manifest.mjs [output] [pub-date]'); + process.exit(1); +} + +// Prefer the release's real publishedAt (passed by CI) over generation time. +const pubDate = pubDateArg ? new Date(pubDateArg) : new Date(); +if (Number.isNaN(pubDate.getTime())) { + console.error(`Invalid pub-date: ${pubDateArg}`); process.exit(1); } @@ -41,12 +49,14 @@ for (const name of readdirSync(assetsDir).sort()) { // deliberately skipped — they are not user-facing downloads. const rule = RULES.find((entry) => name.endsWith(entry.suffix)); if (!rule) continue; + const path = join(assetsDir, name); files.push({ platform: rule.platform, kind: rule.kind, arch: rule.arch, name, - size: statSync(join(assetsDir, name)).size, + size: statSync(path).size, + sha256: createHash('sha256').update(readFileSync(path)).digest('hex'), url: `${normalizedBase}/${tag}/${encodeURIComponent(name)}`, }); } @@ -59,7 +69,7 @@ if (files.length === 0) { const manifest = { version: tag.replace(/^v/, ''), tag, - pubDate: new Date().toISOString(), + pubDate: pubDate.toISOString(), files, };