Files
nexu-io-open-design/scripts/scopes.ts
PerishFire 0bf1b6d6b8 [codex] converge release workflows and stable dry-runs (#4390)
* fix(tools-pack): use junctions for Windows standalone peer deps

* fix(desktop): expose IPC during startup

* fix(tools-pack): preserve Windows inspect diagnostics

* fix(tools-pack): report Windows inspect status errors

* fix(packaged): use Electron net fetch for app protocol

* fix(packaged): load Windows renderer from web sidecar

* fix(desktop): show Windows packaged window during startup

* fix(packaged): disable Windows GPU startup

* fix(tools-pack): keep Windows core smoke observable

* fix(packaged): remove Windows startup probes

* fix(tools-pack): trace Windows desktop IPC status

* fix(tools-pack): add Windows IPC diagnose loop

* fix(release): default beta-s Windows updater feed

* chore: clean merged test eof

* refactor(release): unify prerelease channel model

* chore(release): close prerelease doc escape hatches

* refactor(release): converge release channel workflows

* fix(release): install toolchain in metadata jobs

* fix(release): build release package before contracts

* chore(release): bump development version to 0.10.1

* fix(e2e): seed windows packaged smoke runtime config

* fix(release): install toolchain for metadata publish

* fix(release): materialize betas metadata checkout

* chore(release): bump development version to 0.10.2

* fix(release): allow betas metadata cold start from s3

* fix(e2e): support betas packaged update scenarios

* fix(release): pass betas channel into packaged smoke

* fix(release): set betas channel during self-hosted builds

* fix(release): verify counted channel reservations

* fix(release): use pnpm cmd for betas windows publish

* fix(release): add betas manifest artifact fallback

* fix(release): skip beta-s public metadata fetch

* fix(release): read beta-s manifests from storage

* fix(release): cache beta windows tools-pack builds

* fix(release): inline beta mac tools-pack builds

* fix(pack): deep sign unsigned mac bundles

* docs(pack): document payload-first beta updater validation

* fix(release): align preview tools-pack cache flow

* fix(release): align prerelease tools-pack cache flow

* fix(release): pass github token to prerelease metadata

* fix(release): setup pnpm before feishu notify

* fix(release): add stable dry-run prepublish flow

* fix(release): accept completed prerelease metadata gate

* fix(release): require stable release branches

* fix(release): converge r2 access checks

* fix(updater): use release channel parser for defaults

* fix(updater): harden windows payload relaunch

* fix(release): converge updater smoke fixture contract

* test(e2e): require silent updater fixture output

* fix(release): align stable windows smoke build path

* fix(ci): include release workspace in validation

* fix(ci): repair release validation lanes

Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex)

* fix(ci): restore zero-install Feishu notification

Generated-By: looper 0.9.10+codex.autoclean (runner=fixer, agent=codex)

---------

Co-authored-by: Looper <looper@noreply.github.com>
2026-06-23 06:13:21 +00:00

444 lines
13 KiB
TypeScript

import { execFileSync } from "node:child_process";
import { appendFileSync, readFileSync } from "node:fs";
import { uiP0CiMatrix, visualCiMatrix } from "../e2e/lib/playwright/suites.ts";
type CiMode = "hot" | "full";
type ScopeOutputs = {
daemon_tests_required: boolean;
web_tests_required: boolean;
tools_dev_tests_required: boolean;
tools_pack_tests_required: boolean;
nix_validation_required: boolean;
ui_p0_validation_required: boolean;
visual_validation_required: boolean;
docker_validation_required: boolean;
workspace_validation_required: boolean;
};
type ScopePlan = ScopeOutputs & {
ci_mode: CiMode;
run_docker_build: boolean;
run_e2e_vitest: boolean;
run_nix_validation: boolean;
run_playwright_critical: boolean;
run_playwright_visual: boolean;
run_preflight: boolean;
run_ui_p0: boolean;
run_web_workspace_tests: boolean;
run_windows_tools_pack_payload_tests: boolean;
run_workspace_unit_tests: boolean;
ui_p0_matrix: string;
visual_matrix: string;
};
type GitHubEvent = {
pull_request?: {
number?: number;
};
inputs?: {
ci_mode?: string;
};
};
const commandName = process.argv[2] ?? "github-output";
if (commandName === "github-output") {
writeGithubOutputs(createScopePlan());
} else if (commandName === "print") {
console.log(JSON.stringify(createScopePlan(), null, 2));
} else if (commandName === "help") {
printUsage();
} else {
console.error(`Unknown scopes command: ${commandName}`);
printUsage();
process.exitCode = 1;
}
function createScopePlan(): ScopePlan {
const outputs: ScopeOutputs = {
daemon_tests_required: false,
web_tests_required: false,
tools_dev_tests_required: false,
tools_pack_tests_required: false,
nix_validation_required: false,
ui_p0_validation_required: false,
visual_validation_required: false,
docker_validation_required: false,
workspace_validation_required: false,
};
const eventName = requiredEnv("GITHUB_EVENT_NAME");
const ciMode = resolveCiMode(eventName);
if (eventName === "pull_request") {
for (const file of changedPullRequestFiles()) {
applyChangedFile(file, outputs);
if (allScopeOutputsTrue(outputs)) break;
}
if (
outputs.daemon_tests_required ||
outputs.web_tests_required ||
outputs.tools_dev_tests_required ||
outputs.tools_pack_tests_required
) {
outputs.workspace_validation_required = true;
}
} else if (eventName === "workflow_dispatch" && ciMode === "hot") {
for (const file of changedManualFiles()) {
applyChangedFile(file, outputs);
if (allScopeOutputsTrue(outputs)) break;
}
} else {
outputs.daemon_tests_required = true;
outputs.web_tests_required = true;
outputs.tools_dev_tests_required = true;
outputs.tools_pack_tests_required = true;
outputs.nix_validation_required = true;
outputs.ui_p0_validation_required = true;
outputs.visual_validation_required = true;
outputs.docker_validation_required = true;
outputs.workspace_validation_required = true;
}
return {
...outputs,
...createRunPlan(outputs, ciMode),
ui_p0_matrix: JSON.stringify(uiP0CiMatrix),
visual_matrix: JSON.stringify(visualCiMatrix),
};
}
function resolveCiMode(eventName: string): CiMode {
if (eventName === "pull_request") return "hot";
if (eventName === "workflow_dispatch") {
const event = JSON.parse(readFileSync(requiredEnv("GITHUB_EVENT_PATH"), "utf8")) as GitHubEvent;
const input = event.inputs?.ci_mode ?? "full";
if (input === "hot" || input === "full") return input;
throw new Error(`Unsupported workflow_dispatch ci_mode: ${input}`);
}
return "full";
}
function createRunPlan(
outputs: ScopeOutputs,
ciMode: CiMode,
): Omit<ScopePlan, keyof ScopeOutputs | "ui_p0_matrix" | "visual_matrix"> {
const isFull = ciMode === "full";
return {
ci_mode: ciMode,
run_docker_build: isFull || outputs.docker_validation_required,
run_e2e_vitest: isFull || outputs.web_tests_required || outputs.ui_p0_validation_required,
run_nix_validation: isFull || outputs.nix_validation_required,
run_playwright_critical: isFull || (outputs.workspace_validation_required && !outputs.ui_p0_validation_required),
run_playwright_visual: isFull || outputs.visual_validation_required,
run_preflight: true,
run_ui_p0: isFull || outputs.ui_p0_validation_required,
run_web_workspace_tests: isFull || outputs.web_tests_required,
run_windows_tools_pack_payload_tests: isFull || outputs.tools_pack_tests_required,
run_workspace_unit_tests: true,
};
}
function changedPullRequestFiles(): string[] {
const eventPath = requiredEnv("GITHUB_EVENT_PATH");
const repository = requiredEnv("GITHUB_REPOSITORY");
const event = JSON.parse(readFileSync(eventPath, "utf8")) as GitHubEvent;
const prNumber = event.pull_request?.number;
if (prNumber == null) {
throw new Error("pull_request event payload did not include pull_request.number");
}
const stdout = runGh(["api", "--paginate", `repos/${repository}/pulls/${prNumber}/files`, "--jq", ".[].filename"]);
return stdout.split(/\r?\n/).filter(Boolean);
}
function changedManualFiles(): string[] {
const repository = requiredEnv("GITHUB_REPOSITORY");
const sha = requiredEnv("GITHUB_SHA");
const stdout = runGh([
"api",
"--paginate",
`repos/${repository}/compare/main...${sha}`,
"--jq",
'(.files // [])[] | select(.status != "removed") | .filename',
]);
return stdout.split(/\r?\n/).filter(Boolean);
}
function runGh(args: string[]): string {
const nodeScript = process.env.OPEN_DESIGN_GH_NODE_SCRIPT;
if (nodeScript != null && nodeScript.length > 0) {
return execFileSync(process.execPath, [nodeScript, ...args], {
encoding: "utf8",
stdio: ["ignore", "pipe", "inherit"],
});
}
return execFileSync("gh", args, { encoding: "utf8", stdio: ["ignore", "pipe", "inherit"] });
}
function applyChangedFile(file: string, target: ScopeOutputs): void {
if (
startsWithAny(file, [
"apps/daemon/",
"packages/release/",
"packages/contracts/",
"packages/platform/",
"packages/sidecar/",
"packages/sidecar-proto/",
])
) {
target.daemon_tests_required = true;
}
if (
startsWithAny(file, [
"apps/web/",
"packages/release/",
"packages/components/",
"packages/contracts/",
"packages/host/",
"packages/platform/",
"packages/sidecar/",
"packages/sidecar-proto/",
])
) {
target.web_tests_required = true;
}
if (startsWithAny(file, ["scripts/", "assets/", "skills/", "prompt-templates/", "design-systems/", "design-templates/", "craft/"])) {
target.daemon_tests_required = true;
target.web_tests_required = true;
}
if (startsWithAny(file, ["tools/dev/", "packages/platform/", "packages/sidecar/", "packages/sidecar-proto/"])) {
target.tools_dev_tests_required = true;
}
if (
startsWithAny(file, [
"tools/pack/",
"apps/packaged/",
"apps/desktop/",
"packages/release/",
"packages/components/",
"packages/host/",
"packages/platform/",
"packages/sidecar/",
"packages/sidecar-proto/",
])
) {
target.tools_pack_tests_required = true;
}
if (isWorkspaceManifestOrCiFile(file)) {
target.daemon_tests_required = true;
target.web_tests_required = true;
target.tools_dev_tests_required = true;
target.tools_pack_tests_required = true;
}
if (isUiP0RelevantFile(file)) {
target.ui_p0_validation_required = true;
}
if (isVisualRelevantFile(file)) {
target.visual_validation_required = true;
}
if (isDockerRelevantFile(file)) {
target.docker_validation_required = true;
}
if (isNixRelevantFile(file)) {
target.nix_validation_required = true;
}
if (!isWorkspaceValidationExemptFile(file)) {
target.workspace_validation_required = true;
}
}
function isWorkspaceManifestOrCiFile(file: string): boolean {
return (
file === "package.json" ||
file === "pnpm-lock.yaml" ||
file === "pnpm-workspace.yaml" ||
file === ".github/workflows/ci.yml" ||
/^apps\/[^/]+\/package\.json$/.test(file) ||
/^packages\/[^/]+\/package\.json$/.test(file) ||
/^tools\/[^/]+\/package\.json$/.test(file) ||
file === "e2e/package.json"
);
}
function isUiP0RelevantFile(file: string): boolean {
return (
startsWithAny(file, [
"apps/web/",
"apps/daemon/",
"packages/release/",
"packages/components/",
"packages/contracts/",
"packages/host/",
"packages/platform/",
"packages/sidecar/",
"packages/sidecar-proto/",
"e2e/ui/",
"e2e/lib/",
"e2e/resources/",
"e2e/scripts/",
".github/actions/setup-playwright/",
".github/actions/setup-workspace/",
]) ||
[
"e2e/package.json",
"e2e/playwright.config.ts",
"package.json",
"pnpm-lock.yaml",
"pnpm-workspace.yaml",
".github/workflows/ci.yml",
".github/workflows/ui-extended-main.yml",
].includes(file)
);
}
function isVisualRelevantFile(file: string): boolean {
return (
startsWithAny(file, [
"apps/web/",
"e2e/lib/playwright/",
".github/actions/setup-playwright/",
".github/actions/setup-workspace/",
]) ||
/^e2e\/ui\/visual-[^/]+\.test\.ts$/.test(file) ||
[
"e2e/package.json",
"e2e/playwright.visual.config.ts",
"e2e/scripts/playwright.ts",
"e2e/scripts/visual-report.ts",
"pnpm-lock.yaml",
".github/scripts/handoff.py",
".github/workflows/ci.yml",
".github/workflows/comment.atom.yml",
".github/workflows/report.atom.yml",
".github/workflows/visual-baseline.yml",
].includes(file)
);
}
function isDockerRelevantFile(file: string): boolean {
return (
startsWithAny(file, [
"deploy/",
]) ||
[
"package.json",
"pnpm-lock.yaml",
"pnpm-workspace.yaml",
".dockerignore",
".github/workflows/ci.yml",
".github/workflows/docker-image.yml",
].includes(file) ||
isWorkspaceManifestOrCiFile(file)
);
}
function isNixRelevantFile(file: string): boolean {
return (
startsWithAny(file, [
"nix/",
"packages/release/",
]) ||
[
"package.json",
"pnpm-lock.yaml",
"pnpm-workspace.yaml",
"flake.nix",
"flake.lock",
".github/scripts/handoff.py",
".github/workflows/autofix.atom.yml",
".github/workflows/ci.yml",
".github/workflows/comment.atom.yml",
"scripts/update-nix-pnpm-deps-hash.ts",
].includes(file) ||
isWorkspaceManifestOrCiFile(file)
);
}
function isWorkspaceValidationExemptFile(file: string): boolean {
return (
isDocumentationOrMetadataFile(file) ||
startsWithAny(file, [
"apps/landing-page/",
"nix/",
".github/ISSUE_TEMPLATE/",
]) ||
[
"flake.nix",
"flake.lock",
".github/workflows/landing-page-ci.yml",
".github/workflows/landing-page-staging.yml",
".github/workflows/landing-page-production.yml",
".github/workflows/blog-indexing-on-deploy.yml",
".github/workflows/autofix.atom.yml",
".github/workflows/comment.atom.yml",
".github/workflows/report.atom.yml",
".github/workflows/docker-image.yml",
].includes(file)
);
}
function isDocumentationOrMetadataFile(file: string): boolean {
return (
/\.(?:md|mdx|txt)$/.test(file) ||
file === "LICENSE" ||
file === ".gitignore" ||
file === ".editorconfig" ||
startsWithAny(file, [".vscode/", ".idea/", "docs/"]) ||
file === ".github/CODEOWNERS"
);
}
function startsWithAny(value: string, prefixes: string[]): boolean {
return prefixes.some((prefix) => value.startsWith(prefix));
}
function allScopeOutputsTrue(value: ScopeOutputs): boolean {
return Object.values(value).every(Boolean);
}
function writeGithubOutputs(value: ScopePlan): void {
const lines = Object.entries(value).map(([key, output]) => `${key}=${formatOutput(output)}`);
console.log(lines.join("\n"));
const outputPath = process.env.GITHUB_OUTPUT;
if (outputPath != null && outputPath.length > 0) {
appendFileSync(outputPath, `${lines.join("\n")}\n`);
}
}
function formatOutput(value: boolean | string): string {
return typeof value === "boolean" ? (value ? "true" : "false") : value;
}
function requiredEnv(name: string): string {
const value = process.env[name];
if (value == null || value.length === 0) {
throw new Error(`${name} is required`);
}
return value;
}
function printUsage(): void {
console.log(`Usage: node --experimental-strip-types scripts/scopes.ts <command>
Commands:
github-output Write validation scope outputs for GitHub Actions
print Print the validation scope plan as JSON
help Show this help
`);
}