Files
openclaw/test/package-manager-config.test.ts
Peter Steinberger f6131a4fbf build(deps): remove npm shrinkwrap; mirror pnpm lock into transient package locks (#114006)
* build(deps): remove npm shrinkwrap; mirror pnpm lock into transient package locks

npm 12 removed shrinkwrap (command + tarball/root loading). Delete all 82
committed npm-shrinkwrap.json files and stop publishing lockfiles; keep
pnpm-lock.yaml as the single reviewed dependency boundary. The generator
becomes scripts/generate-npm-package-lock.mjs and feeds plugin bundling via
a transient package-lock.json + npm ci (works on npm 11 and 12). Tarball
validation treats the published 2026.7.2 beta train as a shrinkwrap
transition; self-update npm detection now uses install topology instead of
the shipped shrinkwrap.

* fix(deps): repair lint, deadcode, and test-type lanes for the npm 12 migration

- sort integrity comparisons with an explicit comparator (oxlint)
- keep resolveBunGlobalNodeModules module-local (knip unused-export gate)
- model npm pack --json as npm<=11 array / npm 12 name-keyed object
- default calver destructuring in the tarball test fixture
2026-07-26 01:29:55 -04:00

171 lines
5.3 KiB
TypeScript

// Package manager config tests validate workspace package manager settings.
import fs from "node:fs";
import { describe, expect, it } from "vitest";
import { parse } from "yaml";
import {
mergeOverrides,
parsePnpmPackageKey,
readNpmLockOverrides,
} from "../scripts/generate-npm-package-lock.mjs";
type PnpmBuildConfig = {
allowBuilds?: Record<string, boolean>;
blockExoticSubdeps?: boolean;
ignoredBuiltDependencies?: string[];
onlyBuiltDependencies?: string[];
};
type RootPackageJson = {
files?: string[];
pnpm?: PnpmBuildConfig;
};
type WorkspaceConfig = PnpmBuildConfig;
function readJson(filePath: string): unknown {
return JSON.parse(fs.readFileSync(filePath, "utf8")) as unknown;
}
function collectPnpmLockPackages(): Set<string> {
const lockfile = parse(fs.readFileSync("pnpm-lock.yaml", "utf8")) as {
packages?: Record<string, { version?: unknown }>;
};
const packages = new Set<string>();
for (const [packageKey, metadata] of Object.entries(lockfile.packages ?? {})) {
const parsed = parsePnpmPackageKey(packageKey);
if (!parsed) {
continue;
}
packages.add(`${parsed.name}@${parsed.version}`);
if (typeof metadata.version === "string") {
packages.add(`${parsed.name}@${metadata.version}`);
}
}
return packages;
}
describe("package manager build policy", () => {
it("keeps optional native Discord opus builds disabled by default", () => {
const packageJson = readJson("package.json") as RootPackageJson;
const workspace = parse(fs.readFileSync("pnpm-workspace.yaml", "utf8")) as WorkspaceConfig;
expect(packageJson.pnpm).toBeUndefined();
expect(workspace.allowBuilds?.["@discordjs/opus"]).toBe(false);
expect(workspace.allowBuilds?.["node-llama-cpp"]).toBe(false);
expect(workspace.blockExoticSubdeps).toBe(true);
expect(workspace.onlyBuiltDependencies).toBeUndefined();
});
it("includes third-party notices in the published root package", () => {
const packageJson = readJson("package.json") as RootPackageJson;
expect(packageJson.files).toContain("THIRD_PARTY_NOTICES.md");
});
it("includes the Crabbox wrapper runtime modules in the published root package", () => {
const packageJson = readJson("package.json") as RootPackageJson;
expect(packageJson.files).toEqual(
expect.arrayContaining([
"scripts/crabbox-wrapper.mjs",
"scripts/crabbox-wrapper-providers.mjs",
"scripts/testbox-lease-freshness.mjs",
]),
);
});
it("pins forked transitive dependencies with parent-scoped npm-lock overrides", () => {
const overrides = readNpmLockOverrides() as Record<string, unknown>;
const packages = collectPnpmLockPackages();
expect(overrides["lru-cache"]).toBeUndefined();
expect(overrides["lru-memoizer@2.3.0"]).toMatchObject({
"lru-cache": { ".": "6.0.0", yallist: "4.0.0" },
});
if (packages.has("lru-memoizer@3.0.0")) {
const lruCacheVersion = (overrides["lru-memoizer@3.0.0"] as Record<string, string>)[
"lru-cache"
];
expect(lruCacheVersion).toMatch(/^11\.\d+\.\d+$/u);
expect(packages.has(`lru-cache@${lruCacheVersion}`)).toBe(true);
}
});
it("merges exact npm-lock pins with nested lock-derived pins", () => {
expect(
mergeOverrides(
{ "@mistralai/mistralai": "2.2.1" },
{ "@mistralai/mistralai": { ".": "2.2.1", zod: "4.4.3" } },
{},
),
).toEqual({
"@mistralai/mistralai": { ".": "2.2.1", zod: "4.4.3" },
});
});
it("preserves npm alias pins when merging nested lock-derived pins", () => {
expect(
mergeOverrides(
{ "node-domexception": "npm:@nolyfill/domexception@1.0.28" },
{ "node-domexception": { ".": "1.0.28", child: "2.0.0" } },
{},
),
).toEqual({
"node-domexception": {
".": "npm:@nolyfill/domexception@1.0.28",
child: "2.0.0",
},
});
});
it("preserves later npm alias pins when nested pins are already merged", () => {
expect(
mergeOverrides(
{ "node-domexception": { ".": "1.0.28", child: "2.0.0" } },
{ "node-domexception": "npm:@nolyfill/domexception@1.0.28" },
{},
),
).toEqual({
"node-domexception": {
".": "npm:@nolyfill/domexception@1.0.28",
child: "2.0.0",
},
});
});
it("rejects non-exact root pins when merging nested pins", () => {
expect(() =>
mergeOverrides(
{ "floating-package": "^1.0.0" },
{ "floating-package": { ".": "~1.0.0", child: "2.0.0" } },
{},
),
).toThrow(/conflicts with pnpm lock policy/u);
expect(() =>
mergeOverrides(
{ "floating-package": { ".": "^1.0.0", child: "2.0.0" } },
{ "floating-package": "~1.0.0" },
{},
),
).toThrow(/conflicts with pnpm lock policy/u);
});
it("rejects distinct npm alias targets with matching versions", () => {
expect(() =>
mergeOverrides(
{ "aliased-package": "npm:@safe/foo@1.0.0" },
{ "aliased-package": { ".": "npm:@other/foo@1.0.0", child: "2.0.0" } },
{},
),
).toThrow(/conflicts with pnpm lock policy/u);
expect(() =>
mergeOverrides(
{ "aliased-package": { ".": "npm:@safe/foo@1.0.0", child: "2.0.0" } },
{ "aliased-package": "npm:@other/foo@1.0.0" },
{},
),
).toThrow(/conflicts with pnpm lock policy/u);
});
});