mirror of
https://github.com/earendil-works/pi.git
synced 2026-10-28 17:54:40 +08:00
* feat: unify package artifact validation * fix: restrict local artifact output paths * fix: clarify package artifact module names * feat: support pnpm package artifact consumers * fix: preserve local release package installs Keep npm and Bun consumer installs as runnable release artifacts while retaining isolated validation for other packages. Simplify smoke fixtures and avoid redundant release work.
211 lines
9.3 KiB
JavaScript
211 lines
9.3 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { execFileSync } from "node:child_process";
|
|
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { basename, dirname, join } from "node:path";
|
|
import test from "node:test";
|
|
import { fileURLToPath } from "node:url";
|
|
import { parse } from "yaml";
|
|
import { installConsumer, smokeTestNpmConsumer, wireConsumer } from "./local-package-install.mjs";
|
|
import { produceArtifactSet } from "./package-artifacts.mjs";
|
|
|
|
function writePackage(directory, manifest, files) {
|
|
mkdirSync(directory, { recursive: true });
|
|
writeFileSync(join(directory, "package.json"), `${JSON.stringify(manifest, null, "\t")}\n`);
|
|
for (const [path, contents] of Object.entries(files)) {
|
|
mkdirSync(dirname(join(directory, path)), { recursive: true });
|
|
writeFileSync(join(directory, path), contents);
|
|
}
|
|
}
|
|
|
|
function createArtifactSet(t) {
|
|
const temporaryRoot = mkdtempSync(join(tmpdir(), "pi-local-package-install-test-"));
|
|
t.after(() => rmSync(temporaryRoot, { recursive: true, force: true }));
|
|
const root = join(temporaryRoot, "fixture with spaces");
|
|
const repoRoot = join(root, "repo");
|
|
mkdirSync(repoRoot, { recursive: true });
|
|
writeFileSync(join(repoRoot, "package.json"), '{"name":"fixture","private":true}\n');
|
|
writePackage(
|
|
join(repoRoot, "packages", "shared"),
|
|
{
|
|
name: "@pi-package-test/shared",
|
|
version: "1.0.0",
|
|
type: "module",
|
|
main: "./dist/index.js",
|
|
types: "./dist/index.d.ts",
|
|
exports: { ".": { types: "./dist/index.d.ts", import: "./dist/index.js" } },
|
|
files: ["dist"],
|
|
},
|
|
{
|
|
"dist/index.js": 'export const marker = "local artifact";\n',
|
|
"dist/index.d.ts": 'export declare const marker: "local artifact";\n',
|
|
},
|
|
);
|
|
writePackage(
|
|
join(repoRoot, "packages", "target"),
|
|
{
|
|
name: "@pi-package-test/target",
|
|
version: "1.0.0",
|
|
type: "module",
|
|
main: "./dist/index.js",
|
|
types: "./dist/index.d.ts",
|
|
exports: {
|
|
".": { types: "./dist/index.d.ts", import: "./dist/index.js" },
|
|
"./feature": { types: "./dist/feature.d.ts", import: "./dist/feature.js" },
|
|
},
|
|
bin: { target: "./dist/cli.js" },
|
|
files: ["dist"],
|
|
dependencies: { "@pi-package-test/shared": "1.0.0" },
|
|
},
|
|
{
|
|
"dist/index.js": 'import { marker } from "@pi-package-test/shared";\nexport { marker };\n',
|
|
"dist/index.d.ts": 'export { marker } from "@pi-package-test/shared";\n',
|
|
"dist/feature.js": "export const feature = true;\n",
|
|
"dist/feature.d.ts": "export declare const feature: true;\n",
|
|
"dist/cli.js": "#!/usr/bin/env node\n",
|
|
},
|
|
);
|
|
writePackage(
|
|
join(repoRoot, "packages", "second"),
|
|
{
|
|
name: "@pi-package-test/second",
|
|
version: "1.0.0",
|
|
type: "module",
|
|
main: "./dist/index.js",
|
|
types: "./dist/index.d.ts",
|
|
files: ["dist"],
|
|
},
|
|
{
|
|
"dist/index.js": "export const second = true;\n",
|
|
"dist/index.d.ts": "export declare const second: true;\n",
|
|
},
|
|
);
|
|
return {
|
|
artifactSet: produceArtifactSet({ build: false, outDir: join(root, "artifacts"), repoRoot, source: null }),
|
|
root,
|
|
};
|
|
}
|
|
|
|
test("wires multiple direct packages without registry fallbacks", (t) => {
|
|
const { artifactSet, root } = createArtifactSet(t);
|
|
const consumerDirectory = join(root, "consumer");
|
|
mkdirSync(consumerDirectory);
|
|
writeFileSync(
|
|
join(consumerDirectory, "package.json"),
|
|
'{"private":true,"type":"module","dependencies":{"@pi-package-test/target":"1.0.0","@pi-package-test/second":"1.0.0"}}\n',
|
|
);
|
|
wireConsumer({
|
|
artifactSet,
|
|
consumerDirectory,
|
|
packageNames: ["@pi-package-test/target", "@pi-package-test/second"],
|
|
});
|
|
execFileSync("npm", ["install", "--ignore-scripts", "--no-audit", "--no-fund"], { cwd: consumerDirectory, stdio: "pipe" });
|
|
|
|
const manifest = JSON.parse(readFileSync(join(consumerDirectory, "package.json"), "utf8"));
|
|
assert.match(manifest.dependencies["@pi-package-test/target"], /^file:/);
|
|
assert.match(manifest.dependencies["@pi-package-test/second"], /^file:/);
|
|
smokeTestNpmConsumer({ artifactSet, directory: consumerDirectory, packageName: "@pi-package-test/target" });
|
|
smokeTestNpmConsumer({ artifactSet, directory: consumerDirectory, packageName: "@pi-package-test/second" });
|
|
|
|
const lockPath = join(consumerDirectory, "package-lock.json");
|
|
const lockContents = readFileSync(lockPath, "utf8");
|
|
const lock = JSON.parse(lockContents);
|
|
const targetLockEntry = Object.values(lock.packages).find((entry) => entry.resolved?.includes("pi-package-test-target"));
|
|
assert.ok(targetLockEntry);
|
|
targetLockEntry.resolved = "https://registry.npmjs.org/@pi-package-test/target/-/target-1.0.0.tgz";
|
|
writeFileSync(lockPath, JSON.stringify(lock));
|
|
assert.throws(
|
|
() => smokeTestNpmConsumer({ artifactSet, directory: consumerDirectory, packageName: "@pi-package-test/target" }),
|
|
/did not resolve from a local artifact/,
|
|
);
|
|
writeFileSync(lockPath, lockContents);
|
|
|
|
rmSync(join(consumerDirectory, "node_modules", "@pi-package-test", "target", "dist", "feature.js"));
|
|
assert.throws(
|
|
() => smokeTestNpmConsumer({ artifactSet, directory: consumerDirectory, packageName: "@pi-package-test/target" }),
|
|
/export \.\/feature does not exist/,
|
|
);
|
|
});
|
|
|
|
test("installs a package as the only direct dependency", (t) => {
|
|
const { artifactSet, root } = createArtifactSet(t);
|
|
const unsupportedDirectory = join(root, "unsupported-consumer");
|
|
assert.throws(
|
|
() => installConsumer({ artifactSet, directory: unsupportedDirectory, packageManager: "yarn", packageNames: ["@pi-package-test/target"] }),
|
|
/Unsupported package manager: yarn/,
|
|
);
|
|
assert.equal(existsSync(unsupportedDirectory), false);
|
|
const consumerDirectory = join(root, "isolated-consumer");
|
|
installConsumer({ artifactSet, directory: consumerDirectory, packageNames: ["@pi-package-test/target"] });
|
|
const manifest = JSON.parse(readFileSync(join(consumerDirectory, "package.json"), "utf8"));
|
|
assert.deepEqual(Object.keys(manifest.dependencies), ["@pi-package-test/target"]);
|
|
smokeTestNpmConsumer({ artifactSet, directory: consumerDirectory, packageName: "@pi-package-test/target" });
|
|
const installedManifestPath = join(consumerDirectory, "node_modules", "@pi-package-test", "target", "package.json");
|
|
const installedManifestContents = readFileSync(installedManifestPath, "utf8");
|
|
const installedManifest = JSON.parse(installedManifestContents);
|
|
delete installedManifest.main;
|
|
writeFileSync(installedManifestPath, JSON.stringify(installedManifest));
|
|
assert.throws(
|
|
() => smokeTestNpmConsumer({ artifactSet, directory: consumerDirectory, packageName: "@pi-package-test/target" }),
|
|
/Public Pi package @pi-package-test\/target must declare a string main field/,
|
|
);
|
|
writeFileSync(installedManifestPath, installedManifestContents);
|
|
const lockPath = join(consumerDirectory, "package-lock.json");
|
|
const lockContents = readFileSync(lockPath, "utf8");
|
|
rmSync(lockPath);
|
|
assert.throws(
|
|
() => smokeTestNpmConsumer({ artifactSet, directory: consumerDirectory, packageName: "@pi-package-test/target" }),
|
|
/npm consumer smoke test requires package-lock\.json/,
|
|
);
|
|
writeFileSync(lockPath, lockContents);
|
|
const lock = JSON.parse(lockContents);
|
|
delete lock.packages;
|
|
writeFileSync(lockPath, JSON.stringify(lock));
|
|
assert.throws(
|
|
() => smokeTestNpmConsumer({ artifactSet, directory: consumerDirectory, packageName: "@pi-package-test/target" }),
|
|
/Invalid npm lockfile/,
|
|
);
|
|
});
|
|
|
|
test("wires and installs a pnpm consumer without registry fallbacks", (t) => {
|
|
const { artifactSet, root } = createArtifactSet(t);
|
|
const consumerDirectory = join(root, "pnpm-consumer");
|
|
mkdirSync(consumerDirectory);
|
|
writeFileSync(join(consumerDirectory, "package.json"), '{"private":true,"type":"module"}\n');
|
|
writeFileSync(join(consumerDirectory, "pnpm-workspace.yaml"), "# existing workspace comment\npackages: []\noverrides:\n existing: 1.2.3\n");
|
|
wireConsumer({
|
|
artifactSet,
|
|
consumerDirectory,
|
|
packageManager: "pnpm",
|
|
packageNames: ["@pi-package-test/target"],
|
|
});
|
|
|
|
const manifest = JSON.parse(readFileSync(join(consumerDirectory, "package.json"), "utf8"));
|
|
assert.deepEqual(Object.keys(manifest.dependencies), ["@pi-package-test/target"]);
|
|
assert.equal(manifest.overrides, undefined);
|
|
const workspaceContents = readFileSync(join(consumerDirectory, "pnpm-workspace.yaml"), "utf8");
|
|
assert.match(workspaceContents, /^# existing workspace comment/m);
|
|
const workspace = parse(workspaceContents);
|
|
assert.equal(workspace.overrides.existing, "1.2.3");
|
|
for (const artifact of artifactSet.packages) {
|
|
assert.ok(workspace.overrides[artifact.name].endsWith(basename(artifact.tarballPath)), `${artifact.name} override must reference its artifact`);
|
|
}
|
|
|
|
const pnpmCli = fileURLToPath(new URL("../node_modules/pnpm/bin/pnpm.mjs", import.meta.url));
|
|
execFileSync(process.execPath, [pnpmCli, "install", "--prod", "--ignore-scripts", "--offline"], {
|
|
cwd: consumerDirectory,
|
|
stdio: "pipe",
|
|
timeout: 300_000,
|
|
});
|
|
const marker = execFileSync(process.execPath, ["--input-type=module", "--eval", 'import("@pi-package-test/target").then(({ marker }) => console.log(marker))'], {
|
|
cwd: consumerDirectory,
|
|
encoding: "utf8",
|
|
});
|
|
assert.equal(marker.trim(), "local artifact");
|
|
const lockContents = readFileSync(join(consumerDirectory, "pnpm-lock.yaml"), "utf8");
|
|
assert.doesNotMatch(lockContents, /https?:\/\//);
|
|
for (const packageName of ["@pi-package-test/target", "@pi-package-test/shared"]) {
|
|
assert.ok(lockContents.includes(basename(artifactSet.getPackage(packageName).tarballPath)), `${packageName} lock entry must reference its artifact`);
|
|
}
|
|
});
|