@xemahq/repo-build-tooling 0.15.0 → 0.15.1
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/README.md
CHANGED
|
@@ -33,7 +33,7 @@ pnpm exec xema-scrub-swagger-paths dist
|
|
|
33
33
|
| `xema-check-no-vendored-deps` | Fails on a `workspace:` range for a package the repository does not own |
|
|
34
34
|
| `xema-check-workspace-ranges` | Fails on a `workspace:` range the local package does not satisfy |
|
|
35
35
|
|
|
36
|
-
`xema-composed-image members --write|--check` accepts `--manifests <glob>` (repeatable, default `**/xema-biome.json`), `--canonical-dockerfile <path>` and `--cross-repo-manifest <path>`. `xema-composed-image dockerfile` requires `--copy`, `--cache-id` and `--build-source`; it pins the version of this tool itself, and there is no flag for it (`--tooling-version` is refused as an unknown option). `dockerfile --prebuilt` renders the Dockerfile and `Dockerfile.dockerignore` of an image assembled from `stage <dir> [--output <rel>]… [--also <pkg>]…` output (requires `--cache-id`, `--build-source`; `--copy` and `--stage-dir` optional); `load --each` loads every member in its own process. `stage` and `filters` accept `--also <workspace package name>` (repeatable): the package's closure is built and staged (built outputs only, no Prisma checks) alongside the members; an unknown name is refused. `pnpm-context <dir> (--subject <svc>… | --image-dir <composed>)` runs on the CI runner after `pnpm install --frozen-lockfile` and builds the offline `xema-pnpm` build context (`staged.json`, `corepack/pnpm.tgz`, `cache/`); it prints `XEMA_PNPM_CONTEXT=<dir>` and `XEMA_PNPM_STORE=<store root>` on stdout (everything else on stderr) and refuses a non-empty `<dir>`. Run `xema-composed-image` with no arguments for the full usage.
|
|
36
|
+
`xema-composed-image members --write|--check` accepts `--manifests <glob>` (repeatable, default `**/xema-biome.json`), `--canonical-dockerfile <path>` and `--cross-repo-manifest <path>`. `xema-composed-image dockerfile` requires `--copy`, `--cache-id` and `--build-source`; it pins the version of this tool itself, and there is no flag for it (`--tooling-version` is refused as an unknown option). `dockerfile --prebuilt` renders the Dockerfile and `Dockerfile.dockerignore` of an image assembled from `stage <dir> [--output <rel>]… [--also <pkg>]…` output (requires `--cache-id`, `--build-source`; `--copy` and `--stage-dir` optional); `load --each` loads every member in its own process. `stage` and `filters` accept `--also <workspace package name>` (repeatable): the package's closure is built and staged (built outputs only, no Prisma checks) alongside the members; an unknown name is refused. `pnpm-context <dir> (--subject <svc>… | --image-dir <composed>)` runs on the CI runner after `pnpm install --frozen-lockfile` and builds the offline `xema-pnpm` build context (`staged.json`, `corepack/pnpm.tgz`, and, with `--image-dir` only, a warmed `cache/` of registry metadata; `--subject` writes no `cache/` and `staged.json` says `cache: false`); it prints `XEMA_PNPM_CONTEXT=<dir>` and `XEMA_PNPM_STORE=<store root>` on stdout (everything else on stderr) and refuses a non-empty `<dir>`. Run `xema-composed-image` with no arguments for the full usage.
|
|
37
37
|
|
|
38
38
|
Every step refuses rather than skips. The package has no dependencies.
|
|
39
39
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xemahq/repo-build-tooling",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.1",
|
|
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)",
|
|
@@ -44,6 +44,6 @@
|
|
|
44
44
|
"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-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-migrate.test.mjs"
|
|
45
45
|
},
|
|
46
46
|
"xemaRelease": {
|
|
47
|
-
"sourceDigest": "sha256:
|
|
47
|
+
"sourceDigest": "sha256:fa45efe38d666b11ffa8841595cec6f19d065d77f286bf2b7c1e6eb3c93aed0e"
|
|
48
48
|
}
|
|
49
49
|
}
|
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
// offline prebuilt image build consumes as the named BuildKit context `xema-pnpm`. The runner's
|
|
4
4
|
// own pnpm store is the second context (`xema-pnpm-store`); it is never copied.
|
|
5
5
|
//
|
|
6
|
-
// <dir>/staged.json { tool, pnpm, storeDir, subjects[], imageDir? } — the sentinel the image checks
|
|
6
|
+
// <dir>/staged.json { tool, pnpm, storeDir, subjects[], cache, imageDir? } — the sentinel the image checks
|
|
7
7
|
// <dir>/corepack/pnpm.tgz `corepack pack pnpm@<root packageManager>`
|
|
8
|
-
// <dir>/cache/ pnpm `cache-dir` (registry metadata), seeded from the runner's and warmed
|
|
8
|
+
// <dir>/cache/ --image-dir only: pnpm `cache-dir` (registry metadata), seeded from the runner's and warmed
|
|
9
|
+
// (--subject writes no cache/: the member deploy is lockfile-driven and offline)
|
|
9
10
|
//
|
|
10
11
|
// stdout is exactly two lines for `$GITHUB_ENV`: `XEMA_PNPM_CONTEXT=<dir>` and
|
|
11
12
|
// `XEMA_PNPM_STORE=<store root>`. Everything else goes to stderr. pnpm's own request-level
|
|
@@ -83,37 +84,27 @@ export function buildPnpmContext({
|
|
|
83
84
|
if (storePath === '' || !existsSync(storePath)) {
|
|
84
85
|
throw new Error(`the runner's pnpm store ${storePath || '(unknown)'} does not exist — run pnpm install --frozen-lockfile before pnpm-context`);
|
|
85
86
|
}
|
|
86
|
-
const cacheSource = exec('pnpm', ['config', 'get', 'cache-dir'], { cwd: root }).trim();
|
|
87
|
-
|
|
88
87
|
mkdirSync(join(target, 'corepack'), { recursive: true });
|
|
89
88
|
exec('corepack', ['pack', `pnpm@${pnpm}`, '-o', join(target, 'corepack', 'pnpm.tgz')], { cwd: root });
|
|
90
|
-
const cache = join(target, 'cache');
|
|
91
|
-
log(`pnpm-context: cache ${seedCache(exec, cacheSource, cache, log)} from ${cacheSource}`);
|
|
92
89
|
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
log(`pnpm-context: warmed ${subject}`);
|
|
101
|
-
}
|
|
102
|
-
} finally {
|
|
103
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
104
|
-
}
|
|
105
|
-
} else {
|
|
90
|
+
// --subject: the member image runs the lockfile-driven `pnpm deploy --offline` from the store alone, so no
|
|
91
|
+
// metadata cache is seeded or warmed. --image-dir: its `--lockfile-only` resolve needs registry metadata, warmed here.
|
|
92
|
+
const withCache = subjects.length === 0;
|
|
93
|
+
if (withCache) {
|
|
94
|
+
const cacheSource = exec('pnpm', ['config', 'get', 'cache-dir'], { cwd: root }).trim();
|
|
95
|
+
const cache = join(target, 'cache');
|
|
96
|
+
log(`pnpm-context: cache ${seedCache(exec, cacheSource, cache, log)} from ${cacheSource}`);
|
|
106
97
|
const scratch = scratchLockfileInputs({ root, imageDir, exec, copy });
|
|
107
98
|
try {
|
|
108
99
|
const [file, ...args] = lockfileOnlyCommand(memberFilters({ root, imageDir }).flatMap((f) => f.split(' ')));
|
|
109
|
-
exec(file, [...args,
|
|
100
|
+
exec(file, [...args, `--config.cache-dir=${cache}`], { cwd: scratch });
|
|
110
101
|
log(`pnpm-context: warmed the ${imageDir} lockfile-only resolve`);
|
|
111
102
|
} finally {
|
|
112
103
|
rmSync(scratch, { recursive: true, force: true });
|
|
113
104
|
}
|
|
114
105
|
}
|
|
115
106
|
|
|
116
|
-
const sentinel = { tool: toolVersion(), pnpm, storeDir: storeRoot, subjects: [...subjects], ...(imageDir === undefined ? {} : { imageDir }) };
|
|
107
|
+
const sentinel = { tool: toolVersion(), pnpm, storeDir: storeRoot, subjects: [...subjects], cache: withCache, ...(imageDir === undefined ? {} : { imageDir }) };
|
|
117
108
|
writeFileSync(join(target, 'staged.json'), `${JSON.stringify(sentinel, null, 2)}\n`);
|
|
118
109
|
return { context: target, store: storeRoot };
|
|
119
110
|
}
|
|
@@ -38,23 +38,20 @@ function setup({ store = true, cache = true } = {}) {
|
|
|
38
38
|
return { root, out, exec, calls, storePath, home };
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
test('--subject: writes corepack tgz
|
|
41
|
+
test('--subject: writes the corepack tgz and the sentinel, no cache dir and no deploy warm-up; returns the two env values', () => {
|
|
42
42
|
const { root, out, exec, calls, home } = setup();
|
|
43
43
|
const result = buildPnpmContext({ root, dir: out, subjects: ['svc-a', 'svc-b'], exec });
|
|
44
44
|
assert.deepEqual(result, { context: out, store: path.join(home, '.pnpm-store') });
|
|
45
45
|
assert.equal(fs.readFileSync(path.join(out, 'corepack/pnpm.tgz'), 'utf8'), 'tgz');
|
|
46
|
-
assert.
|
|
46
|
+
assert.equal(fs.existsSync(path.join(out, 'cache')), false);
|
|
47
47
|
const staged = JSON.parse(fs.readFileSync(path.join(out, 'staged.json'), 'utf8'));
|
|
48
|
-
assert.deepEqual(Object.keys(staged), ['tool', 'pnpm', 'storeDir', 'subjects']);
|
|
48
|
+
assert.deepEqual(Object.keys(staged), ['tool', 'pnpm', 'storeDir', 'subjects', 'cache']);
|
|
49
49
|
assert.equal(staged.pnpm, '10.33.0');
|
|
50
|
+
assert.equal(staged.cache, false);
|
|
50
51
|
assert.deepEqual(staged.subjects, ['svc-a', 'svc-b']);
|
|
51
52
|
assert.deepEqual(calls.find((c) => c.file === 'corepack').args.slice(0, 2), ['pack', 'pnpm@10.33.0']);
|
|
52
|
-
|
|
53
|
-
assert.
|
|
54
|
-
for (const d of deploys) {
|
|
55
|
-
for (const flag of ['--prod', '--ignore-scripts', '--prefer-offline', `--config.cache-dir=${path.join(out, 'cache')}`]) assert.ok(d.args.includes(flag), flag);
|
|
56
|
-
assert.ok(!fs.existsSync(d.args.at(-1)), 'the deploy target is deleted');
|
|
57
|
-
}
|
|
53
|
+
assert.equal(calls.filter((c) => c.args[0] === 'deploy' || c.args.includes('--lockfile-only')).length, 0, 'no warm-up');
|
|
54
|
+
assert.ok(!calls.some((c) => c.args[0] === 'config'));
|
|
58
55
|
});
|
|
59
56
|
|
|
60
57
|
test('refuses a non-empty directory, a missing store, and a bad invocation, writing nothing', () => {
|
|
@@ -77,7 +74,9 @@ test('--image-dir: runs the generator\'s lockfile-only command on a scratch copy
|
|
|
77
74
|
assert.ok(resolve.args.includes(`--config.cache-dir=${path.join(out, 'cache')}`));
|
|
78
75
|
assert.equal(fs.existsSync(resolve.cwd), false, 'the scratch tree is deleted');
|
|
79
76
|
assert.equal(fs.readFileSync(path.join(root, 'pnpm-lock.yaml'), 'utf8'), 'lockfileVersion: 9\n');
|
|
80
|
-
|
|
77
|
+
const staged = JSON.parse(fs.readFileSync(path.join(out, 'staged.json'), 'utf8'));
|
|
78
|
+
assert.equal(staged.imageDir, 'infra/composed-process');
|
|
79
|
+
assert.equal(staged.cache, true);
|
|
81
80
|
});
|
|
82
81
|
|
|
83
82
|
test('the lockfile-only command is the one the generated Dockerfile runs (no drift)', () => {
|
|
@@ -97,7 +96,7 @@ test('a failed cp -al falls back to a copy and logs why', () => {
|
|
|
97
96
|
if (file === 'cp') throw new Error('cross-device link');
|
|
98
97
|
return exec(file, args, options);
|
|
99
98
|
};
|
|
100
|
-
buildPnpmContext({ root, dir: out,
|
|
99
|
+
buildPnpmContext({ root, dir: out, imageDir: 'infra/composed-process', exec: failing, log: (m) => logs.push(m) });
|
|
101
100
|
assert.ok(logs.some((m) => /cp -al failed \(cross-device link\)/.test(m)));
|
|
102
101
|
assert.ok(fs.existsSync(path.join(out, 'cache/metadata-v1.3/registry.npmjs.org/x.json')));
|
|
103
102
|
assert.ok(home);
|