diff --git a/.github/workflows/frontend-nightly.yml b/.github/workflows/frontend-nightly.yml index c441b0bd7..7ad149c8f 100644 --- a/.github/workflows/frontend-nightly.yml +++ b/.github/workflows/frontend-nightly.yml @@ -9,6 +9,11 @@ on: schedule: - cron: "30 13 * * *" # 13:30 UTC = 19:00 IST daily workflow_dispatch: + inputs: + important: + description: 'Mark this nightly as an important update (escalates the in-app restart prompt). Retro-flag an already-published nightly by re-running only the publish-feed job with this input set.' + type: boolean + default: false jobs: guard: @@ -201,6 +206,7 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} NIGHTLY_VERSION: ${{ needs.guard.outputs.version }} + NIGHTLY_IMPORTANT: ${{ inputs.important || 'false' }} run: | # Forge stamps package.json to VERSION without +build metadata and # publishes to v. Match it. @@ -208,7 +214,9 @@ jobs: tag="v$version" mkdir -p dist gh release download "$tag" --dir dist --clobber - node scripts/feed.mjs dist "$version" nightly + important_flag="" + [ "$NIGHTLY_IMPORTANT" = "true" ] && important_flag="--important" + node scripts/feed.mjs dist "$version" nightly $important_flag shopt -s nullglob assets=(dist/nightly*.yml dist/*.blockmap) if [ ${#assets[@]} -eq 0 ]; then echo "no feed assets generated" >&2; exit 1; fi diff --git a/frontend/scripts/feed.mjs b/frontend/scripts/feed.mjs index ee77b05f4..f9f5f73ab 100644 --- a/frontend/scripts/feed.mjs +++ b/frontend/scripts/feed.mjs @@ -3,6 +3,10 @@ // ESM (mirrors nightly-version.mjs) so CI runs `node scripts/feed.mjs` directly // and vitest unit-tests the pure functions. The only non-stdlib reach is the // blockmap wrapper (Task 1). +// Pass --important to emit `important: true` in each generated yml. An +// already-published nightly can be retro-flagged by re-running the feed job +// with --important set (or editing the yml and running +// `gh release upload TAG nightly*.yml --clobber`). import { readdirSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { writeBlockmap } from "./blockmap.mjs"; @@ -34,7 +38,9 @@ export function feedFilename(channel, platform) { // buildYml serializes one platform's feed. files is [{ url, sha512, size }]; // for mac the arm64 entry comes first. The deprecated top-level path/sha512 // point at files[0]. blockMapSize is never written (forces sidecar differential). -export function buildYml(version, files, releaseDate) { +// When important is true, emits `important: true` after releaseDate so the +// in-app update prompt is escalated. +export function buildYml(version, files, releaseDate, important = false) { const lines = [`version: ${version}`, "files:"]; for (const f of files) { lines.push(` - url: ${f.url}`); @@ -44,12 +50,13 @@ export function buildYml(version, files, releaseDate) { lines.push(`path: ${files[0].url}`); lines.push(`sha512: ${files[0].sha512}`); lines.push(`releaseDate: '${releaseDate}'`); + if (important) lines.push("important: true"); return lines.join("\n") + "\n"; } // generateFeeds writes the yml + sidecar blockmaps for every platform present in // dir. version may carry +build metadata (nightly); strip it for the yml. -async function generateFeeds(dir, rawVersion, channel, releaseDate) { +async function generateFeeds(dir, rawVersion, channel, releaseDate, important = false) { const version = rawVersion.split("+")[0]; const sel = selectInstallers(readdirSync(dir), version); const groups = [ @@ -64,18 +71,19 @@ async function generateFeeds(dir, rawVersion, channel, releaseDate) { const { sha512, size } = await writeBlockmap(join(dir, name)); files.push({ url: name, sha512, size }); } - writeFileSync(join(dir, feedFilename(channel, platform)), buildYml(version, files, releaseDate)); + writeFileSync(join(dir, feedFilename(channel, platform)), buildYml(version, files, releaseDate, important)); } } -// CLI: node scripts/feed.mjs +// CLI: node scripts/feed.mjs [--important] if (import.meta.url === `file://${process.argv[1]}`) { const [, , dir, version, channel] = process.argv; if (!dir || !version || !channel) { process.stderr.write("usage: node feed.mjs \n"); process.exit(2); } - generateFeeds(dir, version, channel, new Date().toISOString()).catch((err) => { + const important = process.argv.includes("--important"); + generateFeeds(dir, version, channel, new Date().toISOString(), important).catch((err) => { process.stderr.write(`${err.stack || err}\n`); process.exit(1); }); diff --git a/frontend/scripts/feed.test.mjs b/frontend/scripts/feed.test.mjs index 745d07066..fd5c1bab3 100644 --- a/frontend/scripts/feed.test.mjs +++ b/frontend/scripts/feed.test.mjs @@ -69,4 +69,27 @@ describe("buildYml", () => { expect(lines[5]).toBe(" - url: Agent.Orchestrator-darwin-x64-0.10.4.zip"); expect(yml).toContain("path: Agent.Orchestrator-darwin-arm64-0.10.4.zip"); }); + + it("omits important key when flag is false (byte-identical to old output)", () => { + const yml = buildYml( + "0.10.4", + [{ url: "Agent.Orchestrator.Setup.0.10.4.exe", sha512: "AA/BB+cc==", size: 123 }], + "2026-06-27T12:00:00.000Z", + false, + ); + expect(yml).not.toContain("important"); + }); + + it("emits important: true as top-level key when flag is true", () => { + const yml = buildYml( + "0.10.4", + [{ url: "Agent.Orchestrator.Setup.0.10.4.exe", sha512: "AA/BB+cc==", size: 123 }], + "2026-06-27T12:00:00.000Z", + true, + ); + expect(yml).toContain("important: true\n"); + // must still have all existing fields + expect(yml).toContain("version: 0.10.4"); + expect(yml).toContain("releaseDate:"); + }); });