mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
feat: unify package artifact validation (#10197)
* 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.
This commit is contained in:
parent
428a12bc77
commit
23cf2b948d
21 changed files with 1172 additions and 512 deletions
210
scripts/local-package-install.test.mjs
Normal file
210
scripts/local-package-install.test.mjs
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
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`);
|
||||
}
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue