Files
tutti/tools/scripts/verify-tutti-app-runtime-release.mjs
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

165 lines
4.7 KiB
JavaScript

#!/usr/bin/env node
import { readFile } from "node:fs/promises";
import { pathToFileURL } from "node:url";
export const defaultTuttiAppRuntimeCatalogURL =
"https://d1x7gb6wqsqmnm.cloudfront.net/tutti-app-runtimes/catalog.json";
export function validateTuttiAppRuntimeRelease({ catalog, lock }) {
const expectedVersion = requiredString(
lock?.runtimeVersion,
"runtimeVersion"
);
const expectedVersionParts = parseRuntimeVersion(
expectedVersion,
"runtime lock runtimeVersion"
);
const platforms = Object.keys(lock?.platforms ?? {});
if (platforms.length === 0) {
throw new Error("runtime lock must declare at least one platform");
}
if (catalog?.schemaVersion !== "tutti.app.runtimes.v2") {
throw new Error(
`runtime catalog schemaVersion must be tutti.app.runtimes.v2, got ${JSON.stringify(catalog?.schemaVersion)}`
);
}
const mismatches = [];
for (const platform of platforms) {
const publishedVersion = catalog.runtimes?.[platform]?.version;
if (typeof publishedVersion !== "string") {
mismatches.push(
`${platform}: expected at least ${expectedVersion}, got missing`
);
continue;
}
let publishedVersionParts;
try {
publishedVersionParts = parseRuntimeVersion(
publishedVersion,
`${platform} published runtime version`
);
} catch (error) {
mismatches.push(`${platform}: ${error.message}`);
continue;
}
if (
compareRuntimeVersions(publishedVersionParts, expectedVersionParts) < 0
) {
mismatches.push(
`${platform}: expected at least ${expectedVersion}, got ${publishedVersion}`
);
}
}
if (mismatches.length > 0) {
throw new Error(
[
`managed app runtime ${expectedVersion} is not published for every desktop platform:`,
...mismatches.map((mismatch) => `- ${mismatch}`),
"Run Publish Tutti App Runtime for this release target and verify the production catalog before promoting the desktop release."
].join("\n")
);
}
return { platforms, runtimeVersion: expectedVersion };
}
export async function verifyTuttiAppRuntimeRelease({
catalogURL = defaultTuttiAppRuntimeCatalogURL,
fetchImpl = globalThis.fetch,
lockFile = "config/tutti.app-runtime.lock.json"
} = {}) {
const lock = JSON.parse(await readFile(lockFile, "utf8"));
const response = await fetchImpl(catalogURL, {
headers: {
accept: "application/json",
"cache-control": "no-cache"
},
redirect: "follow",
signal: AbortSignal.timeout(15_000)
});
if (!response.ok) {
throw new Error(
`failed to fetch managed app runtime catalog ${catalogURL}: HTTP ${response.status}`
);
}
const result = validateTuttiAppRuntimeRelease({
catalog: await response.json(),
lock
});
return { ...result, catalogURL };
}
export async function main(argv = process.argv.slice(2)) {
const options = parseArgs(argv);
const result = await verifyTuttiAppRuntimeRelease(options);
console.log(
`Verified managed app runtime ${result.runtimeVersion} for ${result.platforms.join(", ")} at ${result.catalogURL}`
);
}
function parseArgs(argv) {
const options = {};
for (let index = 0; index < argv.length; index += 1) {
const arg = argv[index];
switch (arg) {
case "--catalog-url":
options.catalogURL = requiredArgument(argv[++index], arg);
break;
case "--lock-file":
options.lockFile = requiredArgument(argv[++index], arg);
break;
default:
throw new Error(`unknown argument ${arg}`);
}
}
return options;
}
function requiredArgument(value, flag) {
if (!value || value.startsWith("--")) {
throw new Error(`${flag} requires a value`);
}
return value;
}
function requiredString(value, name) {
if (typeof value !== "string" || value.trim() === "") {
throw new Error(`runtime lock ${name} is required`);
}
return value.trim();
}
function parseRuntimeVersion(value, name) {
const match = /^(\d{4})\.(\d{2})\.(0|[1-9]\d*)$/u.exec(value);
if (!match) {
throw new Error(
`${name} must use YYYY.MM.PATCH, got ${JSON.stringify(value)}`
);
}
const parts = match.slice(1).map(Number);
if (parts[1] < 1 || parts[1] > 12) {
throw new Error(`${name} has an invalid month in ${JSON.stringify(value)}`);
}
return parts;
}
function compareRuntimeVersions(left, right) {
for (let index = 0; index < left.length; index += 1) {
if (left[index] !== right[index]) {
return left[index] - right[index];
}
}
return 0;
}
if (
process.argv[1] &&
import.meta.url === pathToFileURL(process.argv[1]).href
) {
main().catch((error) => {
console.error(error.message);
process.exitCode = 1;
});
}