[Fix] Clean
Some checks failed
CI / checks (push) Failing after 16m34s

This commit is contained in:
Alex Emmet 2026-08-18 21:53:02 +02:00
commit b331b9f6a3
No known key found for this signature in database
12 changed files with 433 additions and 75 deletions

243
create-web-release.mjs Normal file
View file

@ -0,0 +1,243 @@
#!/usr/bin/env node
import { execFile, spawn } from "node:child_process";
import { access, cp, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { promisify } from "node:util";
import { fileURLToPath } from "node:url";
const execFileAsync = promisify(execFile);
const repositoryRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".");
const packageJsonPath = path.join(repositoryRoot, "package.json");
function usage() {
return `Usage: node create-web-release.mjs [options]
Build and pack the browser package using the version of the root Cargo package.
Options:
--skip-build Pack the existing dist/ and wasm/pkg/ artifacts
--output-dir <path> Write the archive to this directory (default: repository root)
--help Show this help
`;
}
function parseArguments(arguments_) {
const options = {
outputDir: repositoryRoot,
skipBuild: false,
};
for (let index = 0; index < arguments_.length; index += 1) {
const argument = arguments_[index];
if (argument === "--help") {
options.help = true;
} else if (argument === "--skip-build") {
options.skipBuild = true;
} else if (argument === "--output-dir") {
const outputDir = arguments_[index + 1];
if (!outputDir || outputDir.startsWith("--")) {
throw new Error("--output-dir requires a directory path");
}
options.outputDir = path.resolve(repositoryRoot, outputDir);
index += 1;
} else if (argument.startsWith("--output-dir=")) {
const outputDir = argument.slice("--output-dir=".length);
if (!outputDir) {
throw new Error("--output-dir requires a directory path");
}
options.outputDir = path.resolve(repositoryRoot, outputDir);
} else {
throw new Error(`Unknown option: ${argument}`);
}
}
return options;
}
async function readJson(filePath) {
const source = await readFile(filePath, "utf8");
try {
return JSON.parse(source);
} catch (error) {
throw new Error(`Invalid JSON in ${path.relative(repositoryRoot, filePath)}`, {
cause: error,
});
}
}
async function run(command, arguments_, options = {}) {
const renderedArguments = arguments_.map((argument) => JSON.stringify(argument)).join(" ");
console.log(`\n> ${command}${renderedArguments ? ` ${renderedArguments}` : ""}`);
await new Promise((resolve, reject) => {
const child = spawn(command, arguments_, {
cwd: options.cwd ?? repositoryRoot,
env: options.env ?? process.env,
stdio: "inherit",
});
child.once("error", (error) => {
reject(new Error(`Failed to run ${command}: ${error.message}`, { cause: error }));
});
child.once("exit", (code, signal) => {
if (code === 0) {
resolve();
return;
}
const reason = signal ? `signal ${signal}` : `exit code ${code}`;
reject(new Error(`${command} failed with ${reason}`));
});
});
}
async function readCargoVersion() {
let stdout;
try {
({ stdout } = await execFileAsync(
"cargo",
[
"metadata",
"--no-deps",
"--format-version",
"1",
"--manifest-path",
path.join(repositoryRoot, "Cargo.toml"),
],
{ cwd: repositoryRoot, maxBuffer: 1024 * 1024 },
));
} catch (error) {
throw new Error(`Unable to read the root Cargo package version: ${error.message}`, {
cause: error,
});
}
let metadata;
try {
metadata = JSON.parse(stdout);
} catch (error) {
throw new Error("cargo metadata returned invalid JSON", { cause: error });
}
const rootPackage = metadata.packages?.find((packageMetadata) => packageMetadata.name === "mtp");
if (!rootPackage || typeof rootPackage.version !== "string") {
throw new Error("The root Cargo package named 'mtp' was not found");
}
return rootPackage.version;
}
function packageRelativePath(entry) {
if (typeof entry !== "string" || entry.length === 0) {
throw new Error("package.json files entries must be non-empty strings");
}
const relativePath = entry.replace(/\/$/, "");
if (
!relativePath ||
path.isAbsolute(relativePath) ||
relativePath.split(/[\\/]/u).includes("..") ||
relativePath.includes("*")
) {
throw new Error(`Unsupported package file entry: ${entry}`);
}
return relativePath;
}
async function copyPackageFiles(stageRoot, packageJson) {
if (!Array.isArray(packageJson.files)) {
throw new Error("package.json must declare a files array for Web releases");
}
for (const entry of packageJson.files) {
const relativePath = packageRelativePath(entry);
const sourcePath = path.join(repositoryRoot, relativePath);
const destinationPath = path.join(stageRoot, relativePath);
try {
await access(sourcePath);
} catch (error) {
throw new Error(`Release file is missing: ${relativePath}`, { cause: error });
}
await mkdir(path.dirname(destinationPath), { recursive: true });
await cp(sourcePath, destinationPath, { recursive: true });
}
}
async function createRelease({ outputDir, packageJson, version }) {
const stageRoot = await mkdtemp(path.join(os.tmpdir(), "mtp-web-release-"));
const stagedPackageJson = {
...packageJson,
version,
};
try {
await writeFile(
path.join(stageRoot, "package.json"),
`${JSON.stringify(stagedPackageJson, null, 2)}\n`,
);
await copyPackageFiles(stageRoot, packageJson);
const stagedWasmPackagePath = path.join(stageRoot, "wasm", "pkg", "package.json");
const stagedWasmPackageJson = await readJson(stagedWasmPackagePath);
stagedWasmPackageJson.version = version;
await writeFile(
stagedWasmPackagePath,
`${JSON.stringify(stagedWasmPackageJson, null, 2)}\n`,
);
await mkdir(outputDir, { recursive: true });
const archiveName = `${packageJson.name}-${version}.tgz`;
const archivePath = path.join(outputDir, archiveName);
await rm(archivePath, { force: true });
await run("npm", ["pack", "--pack-destination", outputDir], { cwd: stageRoot });
try {
await access(archivePath);
} catch (error) {
throw new Error(`npm pack did not create ${archiveName}`, { cause: error });
}
return archivePath;
} finally {
await rm(stageRoot, { recursive: true, force: true });
}
}
async function main() {
const options = parseArguments(process.argv.slice(2));
if (options.help) {
console.log(usage());
return;
}
const packageJson = await readJson(packageJsonPath);
if (packageJson.name !== "mtp") {
throw new Error("package.json must describe the 'mtp' Web package");
}
const version = await readCargoVersion();
console.log(`Using Cargo package version ${version}`);
if (!options.skipBuild) {
await run("pnpm", ["run", "clean"]);
await run("pnpm", ["run", "build"]);
}
const archivePath = await createRelease({
outputDir: options.outputDir,
packageJson,
version,
});
console.log(`\nCreated ${path.relative(repositoryRoot, archivePath) || archivePath}`);
}
main().catch((error) => {
console.error(`\n${error.message}`);
process.exitCode = 1;
});