@xemahq/repo-build-tooling 0.27.1 → 0.27.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xemahq/repo-build-tooling",
|
|
3
|
-
"version": "0.27.
|
|
3
|
+
"version": "0.27.2",
|
|
4
4
|
"description": "Dev-time build tooling shared by every Xema repository. Ships as plain ESM, depending only on @xemahq/release-policy for registry access, so the published artifact is the reviewed source.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Neuralchowder Inc. <developer@xema.dev> (https://xema.dev)",
|
|
@@ -50,6 +50,6 @@
|
|
|
50
50
|
"test": "node src/scrub-swagger-plugin-paths.mjs --self-test && node --test src/check-workspace-range-matches-local.test.mjs && node --test src/check-no-vendored-deps.test.mjs && node --test src/readiness.test.mjs && node --test src/composed-image.test.mjs && node --test src/composed-image-coordinate-cli.test.mjs && node --test src/declaration-refusal.test.mjs && node --test src/smoke-refusal.test.mjs && node --test src/load-image.test.mjs && node --test src/composed-image-coordinate.test.mjs && node --test src/composed-image-dockerfile.test.mjs && node --test src/service-bootstrap.test.mjs && node --test src/service-bootstrap-protocols.test.mjs && node --test src/composed-image-prebuilt-dockerfile.test.mjs && node --test src/composed-image-in-image-closure.test.mjs && node --test src/composed-image-stage.test.mjs && node --test src/composed-image-stage-tree.test.mjs && node --test src/composed-image-install.test.mjs && node --test src/composed-image-proof.test.mjs && node --test src/composed-image-pnpm-context.test.mjs && node --test src/composed-image-sync-injected.test.mjs && node --test src/composed-image-prisma-check.test.mjs && node --test src/composed-image-prisma-engine.test.mjs && node --test src/composed-migrate.test.mjs && node --test src/promote-image.test.mjs && node --test src/arm64-supplement-needed.test.mjs && node --test src/service-image.test.mjs && node --test src/image-cpu-arch.test.mjs"
|
|
51
51
|
},
|
|
52
52
|
"xemaRelease": {
|
|
53
|
-
"sourceDigest": "sha256:
|
|
53
|
+
"sourceDigest": "sha256:3a4f2d10146fe209afa48b5f783954e50bd239602444a7a59920cc7196169c6b"
|
|
54
54
|
}
|
|
55
55
|
}
|
|
@@ -4,8 +4,12 @@
|
|
|
4
4
|
// for the members' closures only and checked to pin nothing new, then production dependencies are
|
|
5
5
|
// installed from the runner's pnpm store alone (`--offline`, `--ignore-scripts`), selected for the
|
|
6
6
|
// TARGET platform (`--os/--cpu/--libc`) so no foreign-platform binary is staged.
|
|
7
|
+
//
|
|
8
|
+
// The re-resolve runs in a scratch copy of the tree's manifests, never in the tree: pnpm 10.33 `--lockfile-only`
|
|
9
|
+
// with a filter that leaves workspace projects out still links a full `node_modules` (dev dependencies included),
|
|
10
|
+
// and the `--prod` install after it does not prune it, so dev-only packages would ship and be scanned.
|
|
7
11
|
import { execFileSync } from 'node:child_process';
|
|
8
|
-
import { copyFileSync, mkdtempSync, rmSync } from 'node:fs';
|
|
12
|
+
import { copyFileSync, mkdirSync, mkdtempSync, readdirSync, rmSync } from 'node:fs';
|
|
9
13
|
import { tmpdir } from 'node:os';
|
|
10
14
|
import { join } from 'node:path';
|
|
11
15
|
|
|
@@ -31,21 +35,41 @@ export const productionInstallCommand = (filters, arch) => [
|
|
|
31
35
|
'--os=linux', `--cpu=${nodeArch(arch)}`, `--libc=${libcOf(arch)}`,
|
|
32
36
|
];
|
|
33
37
|
|
|
38
|
+
/** The root files a resolve reads, beside every workspace `package.json` and patch file. */
|
|
39
|
+
const RESOLVE_ROOT_FILES = Object.freeze(['package.json', 'pnpm-lock.yaml', 'pnpm-workspace.yaml', '.npmrc', '.pnpmfile.cjs']);
|
|
40
|
+
|
|
41
|
+
/** Copy what `pnpm install --lockfile-only` reads from `treeRoot` into `resolveRoot`: no sources, no `node_modules`. */
|
|
42
|
+
export function copyResolveInputs(treeRoot, resolveRoot, directory = '') {
|
|
43
|
+
for (const entry of readdirSync(join(treeRoot, directory), { withFileTypes: true })) {
|
|
44
|
+
const path = join(directory, entry.name);
|
|
45
|
+
if (entry.isDirectory()) {
|
|
46
|
+
if (entry.name !== 'node_modules' && entry.name !== '.git') copyResolveInputs(treeRoot, resolveRoot, path);
|
|
47
|
+
} else if (entry.isFile() && (entry.name === 'package.json' || entry.name.endsWith('.patch') || (directory === '' && RESOLVE_ROOT_FILES.includes(entry.name)))) {
|
|
48
|
+
mkdirSync(join(resolveRoot, directory), { recursive: true });
|
|
49
|
+
copyFileSync(join(treeRoot, path), join(resolveRoot, path));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
34
54
|
const defaultExec = (file, args, { cwd }) => execFileSync(file, args, { cwd, stdio: 'inherit' });
|
|
35
55
|
|
|
36
56
|
/**
|
|
37
57
|
* Install the members' production closure into `treeRoot` (which holds the root manifests, the lockfile and the
|
|
38
58
|
* closure's package files). `filters` are the members' literal `--filter <package>...` words. Throws when the
|
|
39
|
-
* re-resolve pinned a registry package the committed lockfile does not.
|
|
59
|
+
* re-resolve pinned a registry package the committed lockfile does not. Only the re-resolved `pnpm-lock.yaml` comes
|
|
60
|
+
* back from the scratch copy, so the tree's `node_modules` is the production install's alone.
|
|
40
61
|
*/
|
|
41
62
|
export function installClosure({ treeRoot, filters, arch, exec = defaultExec }) {
|
|
42
63
|
const scratch = mkdtempSync(join(tmpdir(), 'xema-composed-lock-'));
|
|
43
64
|
try {
|
|
44
65
|
const committed = join(scratch, 'pnpm-lock.committed.yaml');
|
|
45
66
|
copyFileSync(join(treeRoot, 'pnpm-lock.yaml'), committed);
|
|
67
|
+
const resolveRoot = join(scratch, 'resolve');
|
|
68
|
+
copyResolveInputs(treeRoot, resolveRoot);
|
|
46
69
|
const [resolveFile, ...resolveArgs] = lockfileOnlyCommand(filters);
|
|
47
|
-
exec(resolveFile, resolveArgs, { cwd:
|
|
48
|
-
assertPinsUnchanged({ root:
|
|
70
|
+
exec(resolveFile, resolveArgs, { cwd: resolveRoot });
|
|
71
|
+
assertPinsUnchanged({ root: resolveRoot, committedLockfile: committed });
|
|
72
|
+
copyFileSync(join(resolveRoot, 'pnpm-lock.yaml'), join(treeRoot, 'pnpm-lock.yaml'));
|
|
49
73
|
const [installFile, ...installArgs] = productionInstallCommand(filters, arch);
|
|
50
74
|
exec(installFile, installArgs, { cwd: treeRoot });
|
|
51
75
|
// pnpm 10.33 frozen offline install does not filter by libc; cpu/os do.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
|
+
import { execFileSync } from 'node:child_process';
|
|
2
3
|
import fs from 'node:fs';
|
|
3
4
|
import os from 'node:os';
|
|
4
5
|
import path from 'node:path';
|
|
@@ -15,11 +16,23 @@ function tree(committed) {
|
|
|
15
16
|
return treeRoot;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
test('the re-resolve then the production install
|
|
19
|
+
test('the re-resolve runs in a scratch copy of the manifests, then the production install runs in the tree, offline and frozen, for the target platform, with the literal filters', () => {
|
|
19
20
|
const treeRoot = tree(lock('left-pad@1.3.0'));
|
|
21
|
+
fs.writeFileSync(path.join(treeRoot, 'package.json'), '{"name":"root"}');
|
|
22
|
+
fs.mkdirSync(path.join(treeRoot, 'apps/a/dist'), { recursive: true });
|
|
23
|
+
fs.writeFileSync(path.join(treeRoot, 'apps/a/package.json'), '{"name":"@t/a"}');
|
|
24
|
+
fs.writeFileSync(path.join(treeRoot, 'apps/a/dist/main.js'), '');
|
|
20
25
|
const calls = [];
|
|
21
|
-
|
|
22
|
-
|
|
26
|
+
const exec = (file, args, options) => {
|
|
27
|
+
const seen = fs.readdirSync(options.cwd, { recursive: true }).sort();
|
|
28
|
+
if (args.includes('--lockfile-only')) fs.writeFileSync(path.join(options.cwd, 'pnpm-lock.yaml'), `${lock('left-pad@1.3.0')}# re-resolved\n`);
|
|
29
|
+
calls.push({ line: [file, ...args], cwd: options.cwd, seen });
|
|
30
|
+
};
|
|
31
|
+
installClosure({ treeRoot, filters: FILTERS, arch: 'arm64', exec });
|
|
32
|
+
assert.notEqual(calls[0].cwd, treeRoot);
|
|
33
|
+
assert.deepEqual(calls[0].seen, ['apps', 'apps/a', 'apps/a/package.json', 'package.json', 'pnpm-lock.yaml']);
|
|
34
|
+
assert.equal(calls[1].cwd, treeRoot);
|
|
35
|
+
assert.match(fs.readFileSync(path.join(treeRoot, 'pnpm-lock.yaml'), 'utf8'), /# re-resolved/, 'the re-resolved lockfile came back into the tree');
|
|
23
36
|
assert.deepEqual(calls[0].line, lockfileOnlyCommand(FILTERS));
|
|
24
37
|
assert.deepEqual(calls[1].line, productionInstallCommand(FILTERS, 'arm64'));
|
|
25
38
|
const install = calls[1].line.join(' ');
|
|
@@ -32,10 +45,43 @@ test('the re-resolve then the production install run in the tree, offline and fr
|
|
|
32
45
|
test('a re-resolution that pins a registry package the committed lockfile does not stops before the install', () => {
|
|
33
46
|
const treeRoot = tree(lock('left-pad@1.3.0'));
|
|
34
47
|
const calls = [];
|
|
35
|
-
const exec = (file, args) => {
|
|
48
|
+
const exec = (file, args, options) => {
|
|
36
49
|
calls.push(args);
|
|
37
|
-
if (args.includes('--lockfile-only')) fs.writeFileSync(path.join(
|
|
50
|
+
if (args.includes('--lockfile-only')) fs.writeFileSync(path.join(options.cwd, 'pnpm-lock.yaml'), lock('left-pad@1.3.0', 'evil@9.9.9'));
|
|
38
51
|
};
|
|
39
52
|
assert.throws(() => installClosure({ treeRoot, filters: FILTERS, arch: 'amd64', exec }), /pinned registry package\(s\) the committed lockfile does not:\n {2}- evil@9\.9\.9/);
|
|
40
53
|
assert.equal(calls.length, 1, 'the production install never ran');
|
|
54
|
+
assert.doesNotMatch(fs.readFileSync(path.join(treeRoot, 'pnpm-lock.yaml'), 'utf8'), /evil/, 'the refused lockfile never reached the tree');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Real pnpm, no registry: `file:` packages only. pnpm 10.33 `--lockfile-only` with a filter that leaves a workspace
|
|
58
|
+
// project out links a full node_modules, dev dependencies included; the `--prod` install after it never prunes them.
|
|
59
|
+
test('after the install the tree holds the production closure only: no dev-only package, even when the filter leaves a project out', () => {
|
|
60
|
+
const treeRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'xema-install-real-'));
|
|
61
|
+
const write = (file, json) => {
|
|
62
|
+
fs.mkdirSync(path.dirname(path.join(treeRoot, file)), { recursive: true });
|
|
63
|
+
fs.writeFileSync(path.join(treeRoot, file), JSON.stringify(json));
|
|
64
|
+
};
|
|
65
|
+
try {
|
|
66
|
+
write('package.json', { name: 'root', private: true });
|
|
67
|
+
fs.writeFileSync(path.join(treeRoot, 'pnpm-workspace.yaml'), "packages:\n - 'apps/*'\n");
|
|
68
|
+
write('vendor/prod-only/package.json', { name: 'prod-only', version: '1.0.0' });
|
|
69
|
+
write('vendor/dev-only/package.json', { name: 'dev-only', version: '1.0.0' });
|
|
70
|
+
const deps = { dependencies: { 'prod-only': 'file:../../vendor/prod-only' }, devDependencies: { 'dev-only': 'file:../../vendor/dev-only' } };
|
|
71
|
+
write('apps/lib/package.json', { name: '@t/lib', version: '1.0.0', ...deps });
|
|
72
|
+
write('apps/a/package.json', { name: '@t/a', version: '1.0.0', ...deps, dependencies: { ...deps.dependencies, '@t/lib': 'workspace:*' } });
|
|
73
|
+
write('apps/other/package.json', { name: '@t/other', version: '1.0.0', devDependencies: deps.devDependencies });
|
|
74
|
+
execFileSync('pnpm', ['install', '--lockfile-only', '--offline'], { cwd: treeRoot, stdio: 'ignore' });
|
|
75
|
+
|
|
76
|
+
// CI=true as on the runner, where pnpm keeps a modules directory it would otherwise refuse to reuse without a TTY.
|
|
77
|
+
const exec = (file, args, { cwd }) => execFileSync(file, args, { cwd, stdio: 'pipe', env: { ...process.env, CI: 'true' } });
|
|
78
|
+
installClosure({ treeRoot, filters: ['--filter', '@t/a...'], arch: 'arm64', exec });
|
|
79
|
+
|
|
80
|
+
const entries = fs.readdirSync(path.join(treeRoot, 'node_modules', '.pnpm'));
|
|
81
|
+
assert.ok(entries.some((entry) => entry.startsWith('prod-only@')), `the prod dependency is installed: ${entries}`);
|
|
82
|
+
assert.ok(!entries.some((entry) => entry.startsWith('dev-only@')), `no dev-only package is installed: ${entries}`);
|
|
83
|
+
assert.match(fs.readFileSync(path.join(treeRoot, 'pnpm-lock.yaml'), 'utf8'), /injectWorkspacePackages: true/, 'the tree holds the re-resolved lockfile');
|
|
84
|
+
} finally {
|
|
85
|
+
fs.rmSync(treeRoot, { recursive: true, force: true });
|
|
86
|
+
}
|
|
41
87
|
});
|