Files
tutti/apps/desktop/scripts/vendor-managed-uv.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

115 lines
4.2 KiB
JavaScript

import { createHash, randomUUID } from "node:crypto";
import { mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { basename, dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const scriptDir = dirname(fileURLToPath(import.meta.url));
const repoRoot = resolve(scriptDir, "../../..");
const defaults = JSON.parse(
await readFile(join(repoRoot, "config/tutti.defaults.json"), "utf8")
);
const uv = defaults.agentRuntimeTools?.uv;
if (!uv?.version || !Array.isArray(uv.artifacts)) {
throw new Error("config/tutti.defaults.json has no managed uv artifact catalog");
}
const platforms = parsePlatformArguments(process.argv.slice(2));
const outputRoot = resolve(
process.env.TUTTI_UV_STAGING_DIR?.trim() ||
join(repoRoot, "apps/desktop/build/managed-uv")
);
await rm(outputRoot, { recursive: true, force: true });
for (const platform of platforms) {
const artifact = uv.artifacts.find((entry) => entry.platform === platform);
if (!artifact) throw new Error(`managed uv catalog does not contain ${platform}`);
const targetDir = join(outputRoot, platform, uv.version);
const target = join(targetDir, basename(artifact.url));
const source = await resolveArchive(artifact);
await mkdir(targetDir, { recursive: true });
await copyVerifiedArchive(source, target, artifact);
}
await writeFile(
join(outputRoot, "THIRD_PARTY_NOTICES.md"),
`# uv third-party notice
Tutti packages unmodified uv ${uv.version} release archives for offline Agent
Target setup. Each archive is selected from config/tutti.defaults.json and is
verified by its pinned byte size and SHA-256 before packaging and again before
extraction by tuttid.
- Project: https://github.com/astral-sh/uv
- Release: https://github.com/astral-sh/uv/releases/tag/${uv.version}
- Licenses: https://github.com/astral-sh/uv/tree/${uv.version}#license
`,
"utf8"
);
function parsePlatformArguments(args) {
const values = args
.filter((argument) => argument.startsWith("--platform="))
.flatMap((argument) => argument.slice("--platform=".length).split(","))
.map((value) => value.trim())
.filter(Boolean);
if (values.length === 0 || values.length !== args.length) {
throw new Error("provide one or more --platform=<runtime-platform> arguments");
}
return [...new Set(values)];
}
async function resolveArchive(artifact) {
const envKey = `TUTTI_UV_ARCHIVE_${artifact.platform.toUpperCase().replaceAll("-", "_")}`;
const override = process.env[envKey]?.trim();
if (override) {
const path = resolve(override);
await verifyArchive(path, artifact);
return path;
}
const cacheRoot = join(tmpdir(), "tutti-build-cache", "uv", uv.version);
const path = join(cacheRoot, basename(artifact.url));
await mkdir(cacheRoot, { recursive: true });
try {
await verifyArchive(path, artifact);
return path;
} catch {
await rm(path, { force: true });
}
const response = await fetch(artifact.url, { redirect: "follow" });
if (!response.ok) throw new Error(`download ${artifact.url}: HTTP ${response.status}`);
const bytes = Buffer.from(await response.arrayBuffer());
const temporary = `${path}.${randomUUID()}.tmp`;
await writeFile(temporary, bytes, { flag: "wx" });
try {
await verifyArchive(temporary, artifact);
await rename(temporary, path);
} finally {
await rm(temporary, { force: true });
}
return path;
}
async function copyVerifiedArchive(source, target, artifact) {
const bytes = await readFile(source);
const temporary = `${target}.${randomUUID()}.tmp`;
await writeFile(temporary, bytes, { flag: "wx" });
try {
await verifyArchive(temporary, artifact);
await rename(temporary, target);
} finally {
await rm(temporary, { force: true });
}
}
async function verifyArchive(path, artifact) {
const info = await stat(path);
if (!info.isFile() || info.size !== artifact.sizeBytes) {
throw new Error(`uv archive size mismatch for ${path}: got ${info.size}, want ${artifact.sizeBytes}`);
}
const actual = createHash("sha256").update(await readFile(path)).digest("hex");
if (actual !== artifact.sha256) {
throw new Error(`uv archive checksum mismatch for ${path}: got ${actual}, want ${artifact.sha256}`);
}
}