返回 DeepSeek-Reasonix
verify.mjs
根目录 / desktop / packaging / verify.mjs
1 #!/usr/bin/env node
2 // Lists a desktop artifact (bundle directory, .zip, .tar.gz or .deb) and fails
3 // unless every member the install layout relies on is present. Linux archives
4 // also fail when a directory or file would be unreadable to other users, or
5 // when one of their Go binaries is dynamically linked.
6 //
7 // usage: node desktop/packaging/verify.mjs <artifact> [--kind <kind>] [--list]
8 import { spawnSync } from "node:child_process";
9 import { readdirSync, readFileSync } from "node:fs";
10 import { join, resolve } from "node:path";
11 import { ARTIFACT_KINDS, WINDOWS_PORTABLE_LAYOUTS, checkEntryModes, checkMembers, checkStaticGoMembers, normalizeEntry, inferArtifactKind, isDirectory, listZipEntries, readZipMember, parseVerboseListing, validateMacServiceLink, walkFiles } from "./lib.mjs";
12
13 const args = process.argv.slice(2);
14 const artifactArg = args.find((arg) => !arg.startsWith("--"));
15 const kindIndex = args.indexOf("--kind");
16 const kindArg = kindIndex >= 0 ? args[kindIndex + 1] : undefined;
17 const layoutIndex = args.indexOf("--portable-layout");
18 const portableLayout = layoutIndex >= 0 ? args[layoutIndex + 1] : "canonical";
19 if (!artifactArg || (kindArg && !ARTIFACT_KINDS.includes(kindArg)) || !WINDOWS_PORTABLE_LAYOUTS.includes(portableLayout)) {
20 console.error(`usage: verify.mjs <artifact> [--kind <${ARTIFACT_KINDS.join("|")}>] [--portable-layout canonical|legacy-dual] [--list]`);
21 process.exit(2);
22 }
23 const artifact = resolve(artifactArg);
24
25 function tool(command, toolArgs) {
26 const result = spawnSync(command, toolArgs, { encoding: "utf8", maxBuffer: 64 * 1024 * 1024 });
27 if (result.status !== 0) throw new Error(`${command} ${toolArgs.join(" ")} failed: ${result.stderr || result.error?.message || result.status}`);
28 return result.stdout.split(/\r?\n/).filter((line) => line !== "");
29 }
30
31 function listingOf(path) {
32 if (isDirectory(path)) return { entries: walkFiles(path), rows: [] };
33 if (path.endsWith(".zip")) return { entries: listZipEntries(path), rows: [] };
34 let rows;
35 if (path.endsWith(".tar.gz")) rows = parseVerboseListing(tool("tar", ["-tvzf", path]));
36 else if (path.endsWith(".deb")) rows = parseVerboseListing(tool("dpkg-deb", ["-c", path]));
37 else throw new Error(`unsupported artifact ${path}`);
38 return { entries: rows.map((row) => row.name), rows };
39 }
40
41 const directory = isDirectory(artifact);
42 const kind = kindArg ?? inferArtifactKind(artifact, directory, directory ? readdirSync(artifact) : []);
43 const { entries, rows } = listingOf(artifact);
44 if (args.includes("--list")) for (const entry of entries) console.log(entry);
45 const { missing, forbidden } = checkMembers(entries, kind, portableLayout);
46 const layoutErrors = kind === "darwin-app-dir" ? validateMacServiceLink(artifact) : [];
47 if (kind === "windows-portable-zip" && missing.length === 0 && forbidden.length === 0) {
48 const readMember = name => directory ? readFileSync(join(artifact, name)) : readZipMember(artifact, name);
49 const gui = readMember("Reasonix.exe");
50 if (gui.equals(readMember("reasonix-cli.exe"))) layoutErrors.push("GUI entry contains CLI bytes");
51 if (portableLayout === "legacy-dual" && !gui.equals(readMember("reasonix-launcher.exe"))) {
52 layoutErrors.push("legacy GUI entry differs from Reasonix.exe");
53 }
54 }
55 const modeErrors = checkEntryModes(rows, kind);
56 if ((kind === "linux-tar" || kind === "linux-deb") && missing.length === 0) {
57 const archived = new Map(rows.map((row) => [normalizeEntry(row.name), row.name]));
58 const extract = kind === "linux-tar" ? 'tar -xzOf "$1" "$2"' : 'dpkg-deb --fsys-tarfile "$1" | tar -xOf - "$2"';
59 const readMember = (name) => {
60 const result = spawnSync("sh", ["-c", extract, "sh", artifact, archived.get(name)], { maxBuffer: 512 * 1024 * 1024 });
61 if (result.status !== 0) throw new Error(`extracting ${name} failed: ${result.stderr?.toString() || result.error?.message || result.status}`);
62 return result.stdout;
63 };
64 layoutErrors.push(...checkStaticGoMembers(kind, readMember));
65 }
66 for (const name of missing) console.error(`verify: ${kind} is missing ${name}`);
67 for (const name of forbidden) console.error(`verify: ${kind} must not contain ${name}`);
68 for (const error of layoutErrors) console.error(`verify: ${kind} ${error}`);
69 for (const error of modeErrors) console.error(`verify: ${kind} ${error}`);
70 if (missing.length > 0 || forbidden.length > 0 || layoutErrors.length > 0 || modeErrors.length > 0) process.exit(1);
71 console.log(`verify: ${kind} ok (${entries.length} entries in ${artifact})`);
72
72 lines Plain Text