Files
makelore/scripts/bundle-pi-runtime.mjs

124 lines
4.4 KiB
JavaScript

import { realpathSync } from 'node:fs';
import { arch, platform } from 'node:os';
import { join, relative, resolve } from 'node:path';
import { pathToFileURL } from 'node:url';
import {
PI_RUNTIME_PACKAGE,
defaultPiBundleTarget,
stagePiRuntimeBundle,
} from './lib/pi-runtime-bundle.mjs';
function parseTarget(value) {
const separator = value.lastIndexOf('-');
if (separator <= 0 || separator === value.length - 1) {
throw new Error(`Invalid Pi runtime target: ${value}`);
}
const target = {
platform: value.slice(0, separator),
arch: value.slice(separator + 1),
};
if (!['win32', 'darwin', 'linux'].includes(target.platform)) {
throw new Error(`Unsupported Pi runtime platform: ${target.platform}`);
}
if (!['x64', 'arm64'].includes(target.arch)) {
throw new Error(`Unsupported Pi runtime architecture: ${target.arch}`);
}
return target.platform === 'linux' ? { ...target, libc: 'glibc' } : target;
}
export function parseBundleArgs(argv) {
const options = { outputRoot: resolve('build/pi-runtime'), targets: [] };
for (let index = 0; index < argv.length; index += 1) {
const argument = argv[index];
if (argument === '--output') {
const value = argv[index + 1];
if (!value || value.startsWith('--')) throw new Error('--output requires a value');
options.outputRoot = resolve(value);
index += 1;
}
else if (argument === '--target') {
const value = argv[index + 1];
if (!value || value.startsWith('--')) throw new Error('--target requires a value');
options.targets.push(parseTarget(value));
index += 1;
}
else if (argument === '--release-targets') {
const currentPlatform = platform();
const arches = currentPlatform === 'win32' ? ['x64'] : ['x64', 'arm64'];
options.targets.push(...arches.map((targetArch) => parseTarget(`${currentPlatform}-${targetArch}`)));
}
else if (argument === '--help') options.help = true;
else throw new Error(`Unknown argument: ${argument}`);
}
if (options.targets.length === 0) options.targets.push(defaultPiBundleTarget());
const unique = new Map(options.targets.map((target) => [
`${target.platform}-${target.arch}`,
target,
]));
options.targets = [...unique.values()];
for (const target of options.targets) {
if (target.platform !== platform()) {
throw new Error(
`Pi runtime ${target.platform}-${target.arch} must be staged on ${target.platform}, current host is ${platform()}-${arch()}`,
);
}
}
return options;
}
function printHelp() {
process.stdout.write('Usage: node scripts/bundle-pi-runtime.mjs [options]\n\n');
process.stdout.write(' --target <platform-arch> Stage one runtime target (repeatable)\n');
process.stdout.write(' --release-targets Stage every configured architecture for the host OS\n');
process.stdout.write(' --output <directory> Output root (default: build/pi-runtime)\n');
}
export async function bundlePiRuntime(options, projectRoot = process.cwd()) {
const resolvedProjectRoot = resolve(projectRoot);
const sourcePackageRoot = realpathSync(join(
resolvedProjectRoot,
'node_modules',
...PI_RUNTIME_PACKAGE.split('/'),
));
const results = [];
for (const target of options.targets) {
const destination = join(options.outputRoot, `${target.platform}-${target.arch}`);
const destinationRelative = relative(options.outputRoot, destination);
if (destinationRelative.startsWith('..') || destinationRelative === '') {
throw new Error(`Unsafe Pi runtime destination: ${destination}`);
}
const manifest = await stagePiRuntimeBundle({
projectRoot: resolvedProjectRoot,
sourcePackageRoot,
destination,
target,
});
results.push({ destination, manifest });
}
return results;
}
async function main() {
const options = parseBundleArgs(process.argv.slice(2));
if (options.help) {
printHelp();
return;
}
const results = await bundlePiRuntime(options);
process.stdout.write(`${JSON.stringify(results.map(({ destination, manifest }) => ({
destination,
target: manifest.target,
packageCount: manifest.productionPackages.length,
assetCount: manifest.runtimeAssets.length,
})), null, 2)}\n`);
}
const isMain = process.argv[1] && pathToFileURL(resolve(process.argv[1])).href === import.meta.url;
if (isMain) {
main().catch((error) => {
process.stderr.write(`${error.stack ?? error.message}\n`);
process.exitCode = 1;
});
}