Files
dev/third_party/pi/scripts/local-package-install.mjs
alexion 6081e3e325 chore(vendor): import pi 1.3.0
Imported as a linear vendor commit so forge rebases do not replay the upstream root tree.

git-subtree-dir: third_party/pi

git-subtree-split: 71023aec1546d6d677a65669c5665c0927ce0dd0
2026-10-15 17:58:48 -04:00

206 lines
11 KiB
JavaScript

import { execFileSync } from "node:child_process";
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { isAbsolute, join, relative, resolve } from "node:path";
import { pathToFileURL } from "node:url";
import { parseDocument } from "yaml";
import { execNpmSync } from "./npm-command.mjs";
const dependencySections = ["dependencies", "devDependencies", "optionalDependencies"];
function createFileSpecifier(fromDirectory, path) {
const relativePath = relative(fromDirectory, path);
return isAbsolute(relativePath) ? pathToFileURL(path).href : `file:./${relativePath.replaceAll("\\", "/")}`;
}
function detectIndentation(contents) {
return contents.match(/\n([\t ]+)"/)?.[1] ?? "\t";
}
function packageDirectory(directory, packageName) {
return join(directory, "node_modules", ...packageName.split("/"));
}
// Generic consumers run Node ESM, so other export conditions are outside this smoke test's scope.
function runtimeTarget(value) {
if (typeof value === "string") return value;
if (Array.isArray(value)) {
for (const candidate of value) {
const target = runtimeTarget(candidate);
if (target) return target;
}
return undefined;
}
if (!value || typeof value !== "object") return undefined;
for (const condition of ["import", "node", "default"]) {
const target = runtimeTarget(value[condition]);
if (target) return target;
}
return undefined;
}
function typesTarget(value) {
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
return typeof value.types === "string" ? value.types : undefined;
}
// Generic validation covers literal exports only; wildcard exports need package-specific test inputs.
function literalExports(manifest) {
// Packages without an exports map are still checked through main, types, and the root import.
if (manifest.exports === undefined) return [];
if (typeof manifest.exports === "string" || Array.isArray(manifest.exports)) return [[".", manifest.exports]];
const keys = Object.keys(manifest.exports);
if (!keys.some((key) => key.startsWith("."))) return [[".", manifest.exports]];
return Object.entries(manifest.exports).filter(([subpath]) => !subpath.includes("*"));
}
function assertPackageFile(directory, target, description) {
if (typeof target !== "string") throw new Error(`${description} must be a package path, found ${String(target)}`);
const path = resolve(directory, target);
const relativePath = relative(directory, path);
if (relativePath.startsWith("..") || isAbsolute(relativePath)) {
throw new Error(`${description} must stay inside the package, found ${target}`);
}
if (!existsSync(path)) throw new Error(`${description} does not exist: ${path}`);
}
function lockfilePackageName(path) {
const marker = "node_modules/";
const index = path.lastIndexOf(marker);
return index === -1 ? undefined : path.slice(index + marker.length);
}
function verifyLocalResolutions(directory, artifactSet, directPackageName) {
const lockPath = join(directory, "package-lock.json");
const lock = JSON.parse(readFileSync(lockPath, "utf8"));
if (!lock.packages || typeof lock.packages !== "object" || Array.isArray(lock.packages)) {
throw new Error(`Invalid npm lockfile: packages object is missing from ${lockPath}`);
}
const artifacts = new Map(artifactSet.packages.map((pkg) => [pkg.name, pkg]));
const resolvedNames = new Set();
for (const [path, installed] of Object.entries(lock.packages)) {
const name = lockfilePackageName(path);
const artifact = artifacts.get(name);
if (!artifact) continue;
if (typeof installed.resolved !== "string" || !installed.resolved.startsWith("file:")) {
throw new Error(`${name} did not resolve from a local artifact: ${String(installed.resolved)}`);
}
const resolvedPath = new URL(installed.resolved, pathToFileURL(`${directory}/`));
if (resolvedPath.protocol !== "file:" || resolvedPath.pathname !== pathToFileURL(artifact.tarballPath).pathname) {
throw new Error(`${name} resolved from the wrong local artifact: ${installed.resolved}`);
}
resolvedNames.add(name);
}
if (!resolvedNames.has(directPackageName)) throw new Error(`${directPackageName} is missing from the consumer lockfile`);
}
function preparePnpmWorkspace(consumerDirectory, specifiers) {
const workspacePath = join(consumerDirectory, "pnpm-workspace.yaml");
const contents = existsSync(workspacePath) ? readFileSync(workspacePath, "utf8") : "";
const document = parseDocument(contents);
if (document.errors.length > 0) {
throw new Error(`Invalid pnpm workspace file ${workspacePath}: ${document.errors.map((error) => error.message).join("; ")}`);
}
const workspace = document.toJS();
if (workspace !== null && (typeof workspace !== "object" || Array.isArray(workspace))) {
throw new Error(`Invalid pnpm workspace file ${workspacePath}: root must be a mapping`);
}
if (workspace?.overrides !== undefined && (typeof workspace.overrides !== "object" || workspace.overrides === null || Array.isArray(workspace.overrides))) {
throw new Error(`Invalid pnpm workspace file ${workspacePath}: overrides must be a mapping`);
}
if (document.contents === null) document.contents = document.createNode({});
for (const [name, specifier] of Object.entries(specifiers)) document.setIn(["overrides", name], specifier);
return { contents: String(document), path: workspacePath };
}
export function wireConsumer({ artifactSet, consumerDirectory, packageNames, packageManager = "npm" }) {
if (packageManager !== "npm" && packageManager !== "pnpm") throw new Error(`Unsupported package manager: ${packageManager}`);
if (packageNames.length === 0) throw new Error("At least one package is required");
const packageJsonPath = join(consumerDirectory, "package.json");
if (!existsSync(packageJsonPath)) throw new Error(`Consumer package.json does not exist: ${packageJsonPath}`);
const contents = readFileSync(packageJsonPath, "utf8");
const manifest = JSON.parse(contents);
const artifacts = new Map(artifactSet.packages.map((pkg) => [pkg.name, pkg]));
for (const packageName of packageNames) artifactSet.getPackage(packageName);
const specifiers = Object.fromEntries(
artifactSet.packages.map((pkg) => [pkg.name, createFileSpecifier(consumerDirectory, pkg.tarballPath)]),
);
const directPackages = new Set(packageNames);
for (const section of dependencySections) {
for (const name of Object.keys(manifest[section] ?? {})) {
if (artifacts.has(name)) directPackages.add(name);
}
}
for (const name of directPackages) {
const targetSection = dependencySections.find((section) => Object.hasOwn(manifest[section] ?? {}, name)) ?? "dependencies";
for (const section of dependencySections) {
if (section !== targetSection && manifest[section]) delete manifest[section][name];
}
manifest[targetSection] = { ...manifest[targetSection], [name]: specifiers[name] };
}
if (packageManager === "npm") manifest.overrides = { ...manifest.overrides, ...specifiers };
const pnpmWorkspace = packageManager === "pnpm" ? preparePnpmWorkspace(consumerDirectory, specifiers) : undefined;
writeFileSync(packageJsonPath, `${JSON.stringify(manifest, null, detectIndentation(contents))}\n`);
if (pnpmWorkspace) writeFileSync(pnpmWorkspace.path, pnpmWorkspace.contents);
return manifest;
}
export function installConsumer({ artifactSet, directory, packageNames, packageManager = "npm" }) {
if (packageManager !== "npm" && packageManager !== "bun") throw new Error(`Unsupported package manager: ${packageManager}`);
if (packageNames.length === 0) throw new Error("At least one package is required");
for (const packageName of packageNames) artifactSet.getPackage(packageName);
mkdirSync(directory, { recursive: true });
writeFileSync(join(directory, "package.json"), '{"private":true,"type":"module"}\n');
wireConsumer({ artifactSet, consumerDirectory: directory, packageNames });
const installArgs = packageManager === "bun" ? ["--production"] : ["--omit=dev", "--no-audit", "--no-fund"];
const args = ["install", "--ignore-scripts", ...installArgs];
const options = { cwd: directory, stdio: "inherit", timeout: 300_000 };
if (packageManager === "npm") execNpmSync(args, options);
else execFileSync(packageManager, args, options);
}
export function smokeTestNpmConsumer({ directory, artifactSet, packageName }) {
const artifact = artifactSet.getPackage(packageName);
const installedDirectory = packageDirectory(directory, packageName);
const manifest = JSON.parse(readFileSync(join(installedDirectory, "package.json"), "utf8"));
if (manifest.version !== artifact.version) {
throw new Error(`${packageName} version mismatch: expected ${artifact.version}, found ${manifest.version}`);
}
if (typeof manifest.main !== "string") throw new Error(`Public Pi package ${packageName} must declare a string main field`);
if (typeof manifest.types !== "string") throw new Error(`Public Pi package ${packageName} must declare a string types field`);
assertPackageFile(installedDirectory, manifest.main, `${packageName} main`);
assertPackageFile(installedDirectory, manifest.types, `${packageName} types`);
const runtimeSpecifiers = [];
for (const [subpath, value] of literalExports(manifest)) {
const runtime = runtimeTarget(value);
const types = typesTarget(value);
if (runtime) {
assertPackageFile(installedDirectory, runtime, `${packageName} export ${subpath}`);
runtimeSpecifiers.push(subpath === "." ? packageName : `${packageName}${subpath.slice(1)}`);
}
if (types) assertPackageFile(installedDirectory, types, `${packageName} types export ${subpath}`);
}
for (const [name, target] of Object.entries(typeof manifest.bin === "string" ? { [packageName.split("/").at(-1)]: manifest.bin } : (manifest.bin ?? {}))) {
assertPackageFile(installedDirectory, target.startsWith("./") ? target : `./${target}`, `${packageName} bin ${name}`);
}
if (!existsSync(join(directory, "package-lock.json"))) throw new Error(`npm consumer smoke test requires package-lock.json: ${directory}`);
verifyLocalResolutions(directory, artifactSet, packageName);
const smokePath = join(directory, "artifact-smoke.mjs");
const resolveChecks = runtimeSpecifiers.map((specifier) => `assert.ok(existsSync(fileURLToPath(import.meta.resolve(${JSON.stringify(specifier)}))));`).join("\n");
writeFileSync(
smokePath,
`import assert from "node:assert/strict";\nimport { existsSync } from "node:fs";\nimport { fileURLToPath } from "node:url";\nimport ${JSON.stringify(packageName)};\n${resolveChecks}\n`,
);
try {
execFileSync(process.execPath, [smokePath], { cwd: directory, stdio: "inherit", timeout: 300_000 });
} finally {
rmSync(smokePath, { force: true });
}
}
export function packageConsumerDirectoryName(packageName) {
return packageName.replace(/^@/, "").replaceAll("/", "-");
}