159 lines
4.2 KiB
JavaScript
159 lines
4.2 KiB
JavaScript
#!/usr/bin/env node
|
|
import { createHash } from "node:crypto";
|
|
import { spawn } from "node:child_process";
|
|
import {
|
|
access,
|
|
mkdtemp,
|
|
readFile,
|
|
rm,
|
|
stat,
|
|
writeFile,
|
|
} from "node:fs/promises";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
|
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
const workspace = path.dirname(root);
|
|
const portal = path.resolve(
|
|
process.env.TOOLBOX_PORTAL_DIR ?? path.join(workspace, "toolbox-portal"),
|
|
);
|
|
const appPackage = JSON.parse(
|
|
await readFile(path.join(root, "package.json"), "utf8"),
|
|
);
|
|
const portalPackage = JSON.parse(
|
|
await readFile(path.join(portal, "package.json"), "utf8"),
|
|
);
|
|
const artifact = path.join(
|
|
root,
|
|
"release",
|
|
`svg-tools-${appPackage.version}.zip`,
|
|
);
|
|
|
|
async function run(command, arguments_, cwd) {
|
|
await new Promise((resolve, reject) => {
|
|
const child = spawn(command, arguments_, {
|
|
cwd,
|
|
stdio: "inherit",
|
|
env: { ...process.env, CI: "1" },
|
|
});
|
|
child.once("error", reject);
|
|
child.once("exit", (code, signal) => {
|
|
if (code === 0) resolve();
|
|
else
|
|
reject(
|
|
new Error(
|
|
`${command} exited with ${code ?? `signal ${signal ?? "unknown"}`}`,
|
|
),
|
|
);
|
|
});
|
|
});
|
|
}
|
|
|
|
const details = await stat(artifact).catch(() => null);
|
|
if (!details?.isFile()) {
|
|
throw new Error(`Build the app release first: ${artifact}`);
|
|
}
|
|
const digest = createHash("sha256")
|
|
.update(await readFile(artifact))
|
|
.digest("hex");
|
|
const sidecar = await readFile(`${artifact}.sha256`, "utf8");
|
|
if (!sidecar.startsWith(`${digest} ${path.basename(artifact)}`)) {
|
|
throw new Error("The app release checksum sidecar does not match the ZIP");
|
|
}
|
|
|
|
await access(path.join(portal, "scripts", "assemble.mjs"));
|
|
await run(
|
|
process.platform === "win32" ? "npm.cmd" : "npm",
|
|
["run", "build"],
|
|
portal,
|
|
);
|
|
|
|
const temporary = await mkdtemp(
|
|
path.join(os.tmpdir(), "svg-tools-portal-smoke-"),
|
|
);
|
|
try {
|
|
const example = JSON.parse(
|
|
await readFile(
|
|
path.join(portal, "release", "toolbox.lock.example.json"),
|
|
"utf8",
|
|
),
|
|
);
|
|
const lock = {
|
|
...example,
|
|
releaseVersion: "0.10.0-smoke.0",
|
|
portalVersion: portalPackage.version,
|
|
apps: [
|
|
{
|
|
id: "de.add-ideas.svg-tools",
|
|
version: appPackage.version,
|
|
artifact: pathToFileURL(artifact).href,
|
|
sha256: digest,
|
|
target: "svg",
|
|
},
|
|
],
|
|
};
|
|
const lockFile = path.join(temporary, "toolbox.lock.json");
|
|
const output = path.join(temporary, "assembled", "toolbox");
|
|
const archive = path.join(temporary, "assembled", "toolbox.zip");
|
|
await writeFile(lockFile, `${JSON.stringify(lock, null, 2)}\n`, {
|
|
flag: "wx",
|
|
});
|
|
await run(
|
|
process.execPath,
|
|
[
|
|
path.join(portal, "scripts", "assemble.mjs"),
|
|
"--lock",
|
|
lockFile,
|
|
"--portal-dist",
|
|
path.join(portal, "dist"),
|
|
"--output",
|
|
output,
|
|
"--archive",
|
|
archive,
|
|
],
|
|
portal,
|
|
);
|
|
|
|
const catalogue = JSON.parse(
|
|
await readFile(path.join(output, "toolbox.catalog.json"), "utf8"),
|
|
);
|
|
const manifest = JSON.parse(
|
|
await readFile(
|
|
path.join(output, "apps", "svg", "toolbox-app.json"),
|
|
"utf8",
|
|
),
|
|
);
|
|
const appIndex = await readFile(
|
|
path.join(output, "apps", "svg", "index.html"),
|
|
"utf8",
|
|
);
|
|
if (
|
|
catalogue.apps.length !== 1 ||
|
|
catalogue.apps[0]?.manifest !== "./apps/svg/toolbox-app.json" ||
|
|
manifest.id !== "de.add-ideas.svg-tools" ||
|
|
manifest.version !== appPackage.version ||
|
|
!manifest.assets?.includes("./canvas-frame-controller.js") ||
|
|
/\b(?:src|href)=["']\//iu.test(appIndex)
|
|
) {
|
|
throw new Error(
|
|
"Assembled SVG Tools identity or relocatable assets changed",
|
|
);
|
|
}
|
|
for (const relative of [
|
|
"apps/svg/favicon.svg",
|
|
"apps/svg/canvas-frame-controller.js",
|
|
"apps/svg/LICENSE",
|
|
"apps/svg/SOURCE.md",
|
|
"toolbox.release.json",
|
|
]) {
|
|
const file = await stat(path.join(output, relative)).catch(() => null);
|
|
if (!file?.isFile()) throw new Error(`Assembly is missing ${relative}`);
|
|
}
|
|
process.stdout.write(
|
|
`Portal assembly smoke passed for SVG Tools ${appPackage.version} (${digest})\n`,
|
|
);
|
|
} finally {
|
|
await rm(temporary, { recursive: true, force: true });
|
|
}
|