feat(release): add important flag to nightly feed manifests (#2378)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Harshit Singh Bhandari 2026-07-03 22:23:03 +05:30 committed by GitHub
parent 52d8c6051a
commit f92ff1111a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 45 additions and 6 deletions

View File

@ -9,6 +9,11 @@ on:
schedule: schedule:
- cron: "30 13 * * *" # 13:30 UTC = 19:00 IST daily - cron: "30 13 * * *" # 13:30 UTC = 19:00 IST daily
workflow_dispatch: 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: jobs:
guard: guard:
@ -201,6 +206,7 @@ jobs:
env: env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NIGHTLY_VERSION: ${{ needs.guard.outputs.version }} NIGHTLY_VERSION: ${{ needs.guard.outputs.version }}
NIGHTLY_IMPORTANT: ${{ inputs.important || 'false' }}
run: | run: |
# Forge stamps package.json to VERSION without +build metadata and # Forge stamps package.json to VERSION without +build metadata and
# publishes to v<that>. Match it. # publishes to v<that>. Match it.
@ -208,7 +214,9 @@ jobs:
tag="v$version" tag="v$version"
mkdir -p dist mkdir -p dist
gh release download "$tag" --dir dist --clobber 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 shopt -s nullglob
assets=(dist/nightly*.yml dist/*.blockmap) assets=(dist/nightly*.yml dist/*.blockmap)
if [ ${#assets[@]} -eq 0 ]; then echo "no feed assets generated" >&2; exit 1; fi if [ ${#assets[@]} -eq 0 ]; then echo "no feed assets generated" >&2; exit 1; fi

View File

@ -3,6 +3,10 @@
// ESM (mirrors nightly-version.mjs) so CI runs `node scripts/feed.mjs` directly // 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 // and vitest unit-tests the pure functions. The only non-stdlib reach is the
// blockmap wrapper (Task 1). // 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 { readdirSync, writeFileSync } from "node:fs";
import { join } from "node:path"; import { join } from "node:path";
import { writeBlockmap } from "./blockmap.mjs"; 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 }]; // buildYml serializes one platform's feed. files is [{ url, sha512, size }];
// for mac the arm64 entry comes first. The deprecated top-level path/sha512 // for mac the arm64 entry comes first. The deprecated top-level path/sha512
// point at files[0]. blockMapSize is never written (forces sidecar differential). // 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:"]; const lines = [`version: ${version}`, "files:"];
for (const f of files) { for (const f of files) {
lines.push(` - url: ${f.url}`); lines.push(` - url: ${f.url}`);
@ -44,12 +50,13 @@ export function buildYml(version, files, releaseDate) {
lines.push(`path: ${files[0].url}`); lines.push(`path: ${files[0].url}`);
lines.push(`sha512: ${files[0].sha512}`); lines.push(`sha512: ${files[0].sha512}`);
lines.push(`releaseDate: '${releaseDate}'`); lines.push(`releaseDate: '${releaseDate}'`);
if (important) lines.push("important: true");
return lines.join("\n") + "\n"; return lines.join("\n") + "\n";
} }
// generateFeeds writes the yml + sidecar blockmaps for every platform present in // generateFeeds writes the yml + sidecar blockmaps for every platform present in
// dir. version may carry +build metadata (nightly); strip it for the yml. // 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 version = rawVersion.split("+")[0];
const sel = selectInstallers(readdirSync(dir), version); const sel = selectInstallers(readdirSync(dir), version);
const groups = [ const groups = [
@ -64,18 +71,19 @@ async function generateFeeds(dir, rawVersion, channel, releaseDate) {
const { sha512, size } = await writeBlockmap(join(dir, name)); const { sha512, size } = await writeBlockmap(join(dir, name));
files.push({ url: name, sha512, size }); 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 <dir> <version> <channel> // CLI: node scripts/feed.mjs <dir> <version> <channel> [--important]
if (import.meta.url === `file://${process.argv[1]}`) { if (import.meta.url === `file://${process.argv[1]}`) {
const [, , dir, version, channel] = process.argv; const [, , dir, version, channel] = process.argv;
if (!dir || !version || !channel) { if (!dir || !version || !channel) {
process.stderr.write("usage: node feed.mjs <dir> <version> <channel>\n"); process.stderr.write("usage: node feed.mjs <dir> <version> <channel>\n");
process.exit(2); 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.stderr.write(`${err.stack || err}\n`);
process.exit(1); process.exit(1);
}); });

View File

@ -69,4 +69,27 @@ describe("buildYml", () => {
expect(lines[5]).toBe(" - url: Agent.Orchestrator-darwin-x64-0.10.4.zip"); 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"); 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:");
});
}); });