import { spawn } from 'node:child_process'; import { cp, mkdir, readFile, readdir, rm, stat, writeFile } from 'node:fs/promises'; import { realpathSync } from 'node:fs'; import { createRequire } from 'node:module'; import { arch as hostArch, platform as hostPlatform } from 'node:os'; import { dirname, join, relative, resolve, sep } from 'node:path'; export const PI_RUNTIME_PACKAGE = '@earendil-works/pi-coding-agent'; export const PI_RUNTIME_VERSION = '0.84.2'; export const PI_RUNTIME_CLI_ENTRY = 'dist/cli.js'; export const PI_RUNTIME_MANIFEST = 'makelore-pi-runtime.json'; export const PI_RUNTIME_STAGING_NPM_VERSION = '11.6.2'; function readJson(path) { return readFile(path, 'utf8').then((source) => JSON.parse(source)); } async function pathExists(path) { try { await stat(path); return true; } catch { return false; } } function portable(path) { return path.split(sep).join('/'); } function constraintMatches(constraints, current) { if (!constraints || constraints.length === 0 || !current) return true; const positive = constraints.filter((value) => !value.startsWith('!')); const negative = constraints.filter((value) => value.startsWith('!')).map((value) => value.slice(1)); return !negative.includes(current) && (positive.length === 0 || positive.includes(current)); } export function lockEntrySupportsTarget(entry, target) { return constraintMatches(entry.os, target.platform) && constraintMatches(entry.cpu, target.arch) && constraintMatches(entry.libc, target.libc); } function packageNameFromLockPath(packagePath) { const marker = packagePath.lastIndexOf('node_modules/'); if (marker === -1) return null; const remainder = packagePath.slice(marker + 'node_modules/'.length).split('/'); return remainder[0]?.startsWith('@') ? remainder.slice(0, 2).join('/') : remainder[0]; } function packageIdentity(name, version) { return `${name}@${version}`; } export function requiredPackageIdentities(shrinkwrap, target) { const identities = new Set(); let platformSkippedPackages = 0; for (const [packagePath, entry] of Object.entries(shrinkwrap.packages ?? {})) { if (!packagePath || entry.dev) continue; if (!lockEntrySupportsTarget(entry, target)) { platformSkippedPackages += 1; continue; } const name = entry.name ?? packageNameFromLockPath(packagePath); if (!name || !entry.version) { throw new Error(`Pi shrinkwrap package identity is incomplete at ${packagePath}`); } identities.add(packageIdentity(name, entry.version)); } return { identities: [...identities].sort(), platformSkippedPackages, }; } async function visitInstalledPackages(nodeModulesPath, identities) { if (!await pathExists(nodeModulesPath)) return; for (const entry of await readdir(nodeModulesPath, { withFileTypes: true })) { if (!entry.isDirectory() || entry.name === '.bin') continue; if (entry.name.startsWith('@')) { const scopePath = join(nodeModulesPath, entry.name); for (const scopedEntry of await readdir(scopePath, { withFileTypes: true })) { if (scopedEntry.isDirectory()) { await visitInstalledPackage(join(scopePath, scopedEntry.name), identities); } } continue; } await visitInstalledPackage(join(nodeModulesPath, entry.name), identities); } } async function visitInstalledPackage(packageRoot, identities) { const packagePath = join(packageRoot, 'package.json'); if (!await pathExists(packagePath)) return; const packageJson = await readJson(packagePath); if (packageJson.name && packageJson.version) { identities.add(packageIdentity(packageJson.name, packageJson.version)); } await visitInstalledPackages(join(packageRoot, 'node_modules'), identities); } export async function installedPackageIdentities(stageRoot) { const identities = new Set(); const rootPackage = await readJson(join(stageRoot, 'package.json')); identities.add(packageIdentity(rootPackage.name, rootPackage.version)); await visitInstalledPackages(join(stageRoot, 'node_modules'), identities); return [...identities].sort(); } export function assertPackageIdentities(required, installed) { const installedSet = new Set(installed); const missing = required.filter((identity) => !installedSet.has(identity)); if (missing.length > 0) { throw new Error(`Pi production closure is missing package identities: ${missing.join(', ')}`); } } async function collectRuntimeAssets(stageRoot) { const assets = []; const visit = async (directory) => { if (!await pathExists(directory)) return; for (const entry of await readdir(directory, { withFileTypes: true })) { const path = join(directory, entry.name); if (entry.isDirectory()) await visit(path); else if (entry.name.endsWith('.node') || entry.name.endsWith('.wasm')) { assets.push(portable(relative(stageRoot, path))); } } }; await visit(join(stageRoot, 'dist')); await visit(join(stageRoot, 'node_modules')); return assets.sort(); } export async function preparePublishedPackageRoot(sourcePackageRoot, destination) { await mkdir(dirname(destination), { recursive: true }); await rm(destination, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 }); await cp(sourcePackageRoot, destination, { recursive: true, filter(source) { return relative(sourcePackageRoot, source).split(sep)[0] !== 'node_modules'; }, }); const packagePath = join(destination, 'package.json'); const packageJson = await readJson(packagePath); const omittedDevDependencies = Object.keys(packageJson.devDependencies ?? {}).sort(); delete packageJson.devDependencies; await writeFile(packagePath, `${JSON.stringify(packageJson, null, 2)}\n`); return { omittedDevDependencies }; } function runCommand(executable, args, options) { return new Promise((resolvePromise, reject) => { const child = spawn(executable, args, { cwd: options.cwd, env: options.env, stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true, }); let stdout = ''; let stderr = ''; child.stdout.on('data', (chunk) => { stdout += chunk.toString(); }); child.stderr.on('data', (chunk) => { stderr += chunk.toString(); }); child.once('error', reject); child.once('exit', (code, signal) => { if (code === 0) resolvePromise({ stdout, stderr }); else { reject(new Error( `${executable} exited with code ${code ?? 'null'} signal ${signal ?? 'none'}: ${stderr || stdout}`, )); } }); }); } export function resolvePinnedNpmCli(projectRoot) { const requireFromProject = createRequire(join(resolve(projectRoot), 'package.json')); return join(realpathSync(dirname(requireFromProject.resolve('npm/package.json'))), 'bin', 'npm-cli.js'); } export async function validatePinnedNpm(projectRoot) { const resolvedRoot = resolve(projectRoot); const requireFromProject = createRequire(join(resolvedRoot, 'package.json')); const projectPackage = await readJson(join(resolvedRoot, 'package.json')); const npmPackagePath = requireFromProject.resolve('npm/package.json'); const npmPackage = await readJson(npmPackagePath); if (projectPackage.dependencies?.npm !== PI_RUNTIME_STAGING_NPM_VERSION) { throw new Error(`Root npm dependency must be exactly ${PI_RUNTIME_STAGING_NPM_VERSION}`); } if (npmPackage.version !== PI_RUNTIME_STAGING_NPM_VERSION) { throw new Error( `Installed npm must be ${PI_RUNTIME_STAGING_NPM_VERSION}, got ${npmPackage.version ?? 'missing'}`, ); } return npmPackagePath; } export async function installProductionShrinkwrap({ projectRoot, stageRoot, target, npmCli = resolvePinnedNpmCli(projectRoot), }) { await validatePinnedNpm(projectRoot); return await runCommand(process.execPath, [ npmCli, 'ci', '--omit=dev', '--ignore-scripts', '--no-audit', '--no-fund', `--os=${target.platform}`, `--cpu=${target.arch}`, ], { cwd: stageRoot, env: { ...process.env, npm_config_update_notifier: 'false', }, }); } export async function createPiRuntimeManifest(stageRoot, target, omittedDevDependencies) { const rootPackage = await readJson(join(stageRoot, 'package.json')); const shrinkwrap = await readJson(join(stageRoot, 'npm-shrinkwrap.json')); if (rootPackage.name !== PI_RUNTIME_PACKAGE || rootPackage.version !== PI_RUNTIME_VERSION) { throw new Error( `Pi runtime must be ${PI_RUNTIME_PACKAGE}@${PI_RUNTIME_VERSION}, got ${rootPackage.name}@${rootPackage.version}`, ); } if (rootPackage.bin?.pi !== PI_RUNTIME_CLI_ENTRY) { throw new Error(`Pi CLI entry must be ${PI_RUNTIME_CLI_ENTRY}`); } if (!await pathExists(join(stageRoot, ...PI_RUNTIME_CLI_ENTRY.split('/')))) { throw new Error(`Pi CLI is missing at ${PI_RUNTIME_CLI_ENTRY}`); } const required = requiredPackageIdentities(shrinkwrap, target); const installed = await installedPackageIdentities(stageRoot); assertPackageIdentities(required.identities, installed); const assets = await collectRuntimeAssets(stageRoot); const requiredPhotonAsset = 'node_modules/@silvia-odwyer/photon-node/photon_rs_bg.wasm'; if (!assets.includes(requiredPhotonAsset)) { throw new Error(`Pi runtime asset is missing: ${requiredPhotonAsset}`); } return { schemaVersion: 1, runtime: { packageName: PI_RUNTIME_PACKAGE, version: PI_RUNTIME_VERSION, cliEntry: PI_RUNTIME_CLI_ENTRY, nodeEngine: rootPackage.engines?.node ?? null, }, target: { platform: target.platform, arch: target.arch, ...(target.libc ? { libc: target.libc } : {}), }, lockfileVersion: shrinkwrap.lockfileVersion, productionPackages: required.identities, platformSkippedPackages: required.platformSkippedPackages, runtimeAssets: assets, staging: { npmVersion: PI_RUNTIME_STAGING_NPM_VERSION, omitDev: true, ignoreScripts: true, omittedPublishedDevDependencies: [...omittedDevDependencies].sort(), }, }; } export async function stagePiRuntimeBundle({ projectRoot, sourcePackageRoot, destination, target, }) { const prepared = await preparePublishedPackageRoot(sourcePackageRoot, destination); await installProductionShrinkwrap({ projectRoot, stageRoot: destination, target }); const manifest = await createPiRuntimeManifest( destination, target, prepared.omittedDevDependencies, ); await writeFile( join(destination, PI_RUNTIME_MANIFEST), `${JSON.stringify(manifest, null, 2)}\n`, ); return manifest; } export function defaultPiBundleTarget() { return { platform: hostPlatform(), arch: hostArch(), ...(hostPlatform() === 'linux' ? { libc: 'glibc' } : {}), }; }