@xemahq/repo-build-tooling 0.2.3 → 0.3.0

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.2.3",
3
+ "version": "0.3.0",
4
4
  "description": "Dev-time build tooling shared by every Xema repository. Ships as plain ESM with zero dependencies 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)",
@@ -17,18 +17,21 @@
17
17
  },
18
18
  "type": "module",
19
19
  "files": [
20
- "README.md"
20
+ "README.md",
21
+ "src"
21
22
  ],
22
23
  "bin": {
23
24
  "xema-scrub-swagger-paths": "src/scrub-swagger-plugin-paths.mjs",
24
- "xema-check-workspace-ranges": "src/check-workspace-range-matches-local.mjs"
25
+ "xema-check-workspace-ranges": "src/check-workspace-range-matches-local.mjs",
26
+ "xema-check-no-vendored-deps": "src/check-no-vendored-deps.mjs"
25
27
  },
26
28
  "exports": {
27
- "./scrub-swagger-plugin-paths": "./src/scrub-swagger-plugin-paths.mjs",
29
+ "./check-no-vendored-deps": "./src/check-no-vendored-deps.mjs",
30
+ "./check-workspace-range-matches-local": "./src/check-workspace-range-matches-local.mjs",
28
31
  "./package.json": "./package.json",
29
- "./check-workspace-range-matches-local": "./src/check-workspace-range-matches-local.mjs"
32
+ "./scrub-swagger-plugin-paths": "./src/scrub-swagger-plugin-paths.mjs"
30
33
  },
31
34
  "scripts": {
32
- "test": "node src/scrub-swagger-plugin-paths.mjs --self-test && node --test src/check-workspace-range-matches-local.test.mjs"
35
+ "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"
33
36
  }
34
37
  }
@@ -0,0 +1,231 @@
1
+ #!/usr/bin/env node
2
+ // ═══════════════════════════════════════════════════════════════════════════
3
+ // A `workspace:` range for a package this repo does NOT own is a VENDORED
4
+ // UPSTREAM DEP: the name resolves to a local directory instead of the registry
5
+ // version the rest of the fleet installs. Two copies of one class, one of them
6
+ // invisible to every version gate, because a lockfile that never resolved the
7
+ // name has nothing to compare.
8
+ //
9
+ // ── Why this is one shared bin and not four copies ──
10
+ //
11
+ // It was four: xema-kernel-sdk, xema-kernel-contracts, xema-biome-sdk and
12
+ // xema-runtime-core each carried `tooling/boundaries/check-no-vendored-deps.mjs`.
13
+ // All four asserted the IDENTICAL predicate — byte-equivalent modulo quote
14
+ // style — and differed only in how they walked `packages/`. That is not
15
+ // per-repo configuration: all four `pnpm-workspace.yaml` files declare the same
16
+ // two globs, so there was nothing to configure and three of the four traversals
17
+ // were elaborations nobody needed.
18
+ //
19
+ // They had also drifted in a way that MATTERS. xema-kernel-contracts' copy
20
+ // recursed without bound and without stopping at a package leaf, so applied to
21
+ // xema-biome-sdk's tree it ingested two TEST FIXTURES as locally-owned packages
22
+ // — widening the allowed set, latent rather than live only because both
23
+ // fixtures happen to declare registry ranges today. Nothing bound the four
24
+ // copies to each other; `check-carved-lib-parity` covers `lib/` helpers, not
25
+ // gates.
26
+ //
27
+ // ── The vacuous-green hole, fixed here ──
28
+ //
29
+ // All four copies guarded that `packages/` EXISTS and never that any manifest
30
+ // was FOUND. A directory rename below it, or a layout change, and every one of
31
+ // them printed `OK: … (0 local packages scanned)` and exited 0 — green while
32
+ // executing nothing, this fleet's most-repeated defect. The two shallow
33
+ // traversals were precisely the ones a layout change would silently empty.
34
+ // `assertNonEmptyCorpus` below refuses a zero-manifest scan.
35
+ //
36
+ // Usage (from the repository root):
37
+ // xema-check-no-vendored-deps
38
+ // ═══════════════════════════════════════════════════════════════════════════
39
+ import { execFileSync } from 'node:child_process';
40
+ import { existsSync, readFileSync, realpathSync } from 'node:fs';
41
+ import { join, relative } from 'node:path';
42
+ import { pathToFileURL } from 'node:url';
43
+
44
+ const DEP_FIELDS = ['dependencies', 'devDependencies', 'peerDependencies'];
45
+
46
+ /**
47
+ * The DEPTHS at which a workspace manifest may sit, derived from
48
+ * `pnpm-workspace.yaml` rather than assumed.
49
+ *
50
+ * Deriving is what stops the kernel-contracts defect recurring: an unbounded
51
+ * walk ingests a `test/fixtures/**\/package.json` as a locally-owned package,
52
+ * which WIDENS the allowed set — the one direction a gate must never drift in.
53
+ * A glob of `packages/*` admits `packages/<a>/package.json` (2 segments below
54
+ * the root); `packages/*\/*` admits 3.
55
+ *
56
+ * Returns null when the file declares no `packages/`-rooted glob at all, which
57
+ * the caller treats as "this repo is not shaped for this check" rather than as
58
+ * an empty result.
59
+ *
60
+ * The depth is `find`'s, i.e. levels BELOW the `packages` start path, and it
61
+ * equals the glob's segment count: `packages/*` admits `packages/a/package.json`
62
+ * at depth 2, `packages/*\/*` admits `packages/a/b/package.json` at depth 3.
63
+ *
64
+ * Written as `length + 1` first, which passed every repo — because BOTH entries
65
+ * were off by one and the two ranges overlapped on a tree whose packages all
66
+ * sit at one level. The counts matched the four incumbent gates exactly, which
67
+ * is precisely what made it look right. A repo with a manifest directly at
68
+ * `packages/<name>/package.json` would have been silently skipped, and a
69
+ * narrowed scan is the one failure this check exists to prevent.
70
+ */
71
+ export function manifestDepthsFor(workspaceYaml) {
72
+ const depths = new Set();
73
+ for (const line of workspaceYaml.split('\n')) {
74
+ const m = line.match(/^\s*-\s*['"]?(packages\/[^'"#\s]*)['"]?\s*$/);
75
+ if (!m) continue;
76
+ depths.add(m[1].split('/').length);
77
+ }
78
+ return depths.size > 0 ? [...depths].sort((a, b) => a - b) : null;
79
+ }
80
+
81
+ /**
82
+ * Every manifest at exactly the declared depths. `find` rather than a hand
83
+ * walk, for the same reason the sibling check uses it: no dependency, and the
84
+ * depth bounds are expressed once instead of reimplemented per level.
85
+ */
86
+ function listManifests(repoRoot, depths) {
87
+ const out = [];
88
+ for (const depth of depths) {
89
+ let stdout;
90
+ try {
91
+ stdout = execFileSync(
92
+ 'find',
93
+ [
94
+ 'packages',
95
+ '-mindepth', String(depth),
96
+ '-maxdepth', String(depth),
97
+ '-name', 'package.json',
98
+ '-not', '-path', '*/node_modules/*',
99
+ '-not', '-path', '*/dist/*',
100
+ ],
101
+ { cwd: repoRoot, encoding: 'utf8' },
102
+ );
103
+ } catch {
104
+ continue;
105
+ }
106
+ for (const line of stdout.split('\n')) if (line.trim()) out.push(line.trim());
107
+ }
108
+ return [...new Set(out)].sort();
109
+ }
110
+
111
+ /**
112
+ * The finding set. Pure over its inputs so it is testable without a tree.
113
+ *
114
+ * @param {Array<{path:string, manifest:object}>} manifests
115
+ * @returns {Array<{path:string, field:string, depName:string, depRange:string}>}
116
+ */
117
+ export function findVendoredDeps(manifests) {
118
+ const localPackageNames = new Set(
119
+ manifests.map(({ manifest }) => manifest.name).filter(Boolean),
120
+ );
121
+ const offenders = [];
122
+ for (const { path, manifest } of manifests) {
123
+ for (const field of DEP_FIELDS) {
124
+ const deps = manifest[field];
125
+ if (!deps) continue;
126
+ for (const [depName, depRange] of Object.entries(deps)) {
127
+ if (typeof depRange !== 'string') continue;
128
+ if (!depRange.startsWith('workspace:')) continue;
129
+ if (localPackageNames.has(depName)) continue;
130
+ offenders.push({ path, field, depName, depRange });
131
+ }
132
+ }
133
+ }
134
+ return offenders;
135
+ }
136
+
137
+ /**
138
+ * A corpus of zero manifests is a FAILURE, never a pass.
139
+ *
140
+ * This is the assertion all four predecessors lacked. `packages/` existing says
141
+ * nothing about whether anything was read, and a check that reports OK over an
142
+ * empty scan is worse than no check because it reads as protection in review.
143
+ */
144
+ function assertNonEmptyCorpus(count, repoRoot, depths) {
145
+ if (count > 0) return null;
146
+ return (
147
+ `found ZERO package manifests under ${join(repoRoot, 'packages')} at the depth(s) ` +
148
+ `${depths.join(', ')} declared by pnpm-workspace.yaml.\n` +
149
+ 'An empty scan must never read as a passing one. Either the workspace globs and the ' +
150
+ 'tree have diverged, or this check is pointed at the wrong root.'
151
+ );
152
+ }
153
+
154
+ export function main() {
155
+ const repoRoot = process.cwd();
156
+ if (!existsSync(join(repoRoot, 'package.json'))) {
157
+ console.error(
158
+ `::error::${repoRoot} has no package.json — run this from the repository root ` +
159
+ 'so it scans the repository, not whatever directory it was invoked from.',
160
+ );
161
+ return 1;
162
+ }
163
+
164
+ const wsPath = join(repoRoot, 'pnpm-workspace.yaml');
165
+ if (!existsSync(wsPath)) {
166
+ console.error(`::error::no pnpm-workspace.yaml at ${repoRoot}; cannot derive the scan depth.`);
167
+ return 1;
168
+ }
169
+ const depths = manifestDepthsFor(readFileSync(wsPath, 'utf8'));
170
+ if (depths === null) {
171
+ console.error(
172
+ `::error::pnpm-workspace.yaml at ${repoRoot} declares no \`packages/\` glob. ` +
173
+ 'This check asserts a property of that tree; it must not silently pass over its absence.',
174
+ );
175
+ return 1;
176
+ }
177
+
178
+ const manifests = [];
179
+ for (const rel of listManifests(repoRoot, depths)) {
180
+ try {
181
+ manifests.push({ path: rel, manifest: JSON.parse(readFileSync(join(repoRoot, rel), 'utf8')) });
182
+ } catch (error) {
183
+ console.error(`::error::${rel} is not readable JSON: ${error.message}`);
184
+ return 1;
185
+ }
186
+ }
187
+
188
+ const empty = assertNonEmptyCorpus(manifests.length, repoRoot, depths);
189
+ if (empty) {
190
+ console.error(`::error::${empty}`);
191
+ return 1;
192
+ }
193
+
194
+ const offenders = findVendoredDeps(manifests);
195
+ if (offenders.length > 0) {
196
+ console.error('Vendored upstream deps detected (must use the registry version):');
197
+ for (const o of offenders) {
198
+ console.error(` ${o.path} ${o.field}.${o.depName} = ${o.depRange}`);
199
+ }
200
+ console.error(
201
+ '\nA `workspace:` range for a package this repo does not own resolves to a local ' +
202
+ 'directory instead of the registry version the rest of the fleet installs.',
203
+ );
204
+ return 1;
205
+ }
206
+
207
+ console.log(
208
+ `OK: no vendored upstream workspace deps (${manifests.length} local packages scanned ` +
209
+ `at depth ${depths.join(', ')})`,
210
+ );
211
+ return 0;
212
+ }
213
+
214
+ // The sibling `xema-check-workspace-ranges` documents why this guard is written
215
+ // with `realpathSync`: invoked through a pnpm bin shim, `process.argv[1]` is the
216
+ // SYMLINK, so a naive `import.meta.url === pathToFileURL(argv[1]).href` is false
217
+ // and the bin prints nothing and exits 0. That hole shipped, and it was
218
+ // invisible in carved CI — which installs the published tarball, where argv[1]
219
+ // is already a real path. Same shape, same treatment.
220
+ const invokedPath = process.argv[1];
221
+ let invokedRealPath = null;
222
+ if (typeof invokedPath === 'string' && invokedPath.length > 0) {
223
+ try {
224
+ invokedRealPath = realpathSync(invokedPath);
225
+ } catch {
226
+ invokedRealPath = null;
227
+ }
228
+ }
229
+ if (invokedRealPath !== null && import.meta.url === pathToFileURL(invokedRealPath).href) {
230
+ process.exit(main());
231
+ }
@@ -0,0 +1,103 @@
1
+ import { deepStrictEqual, strictEqual } from 'node:assert/strict';
2
+ import { test } from 'node:test';
3
+
4
+ import { findVendoredDeps, manifestDepthsFor } from './check-no-vendored-deps.mjs';
5
+
6
+ // ── manifestDepthsFor ─────────────────────────────────────────────────────
7
+ // The depth is `find`'s: levels BELOW the `packages` start path. It equals the
8
+ // glob's segment count. Both cases below are pinned because the first draft
9
+ // used `length + 1` and PASSED on all four real repositories — the two entries
10
+ // were each off by one and their ranges overlapped on a tree whose packages all
11
+ // sit at one level. Matching counts is exactly what made the bug invisible.
12
+
13
+ test('a one-level glob admits packages/<name>/package.json at depth 2', () => {
14
+ deepStrictEqual(manifestDepthsFor("packages:\n - 'packages/*'\n"), [2]);
15
+ });
16
+
17
+ test('a two-level glob admits packages/<a>/<b>/package.json at depth 3', () => {
18
+ deepStrictEqual(manifestDepthsFor("packages:\n - 'packages/*/*'\n"), [3]);
19
+ });
20
+
21
+ test('both globs together admit both depths, sorted', () => {
22
+ deepStrictEqual(
23
+ manifestDepthsFor("packages:\n - 'packages/*'\n - 'packages/*/*'\n"),
24
+ [2, 3],
25
+ );
26
+ });
27
+
28
+ test('a repo declaring no packages/ glob returns null, never an empty scan', () => {
29
+ // null is "not shaped for this check", which the caller REFUSES. Returning []
30
+ // would let it scan nothing and report OK — the defect this file replaces.
31
+ strictEqual(manifestDepthsFor("packages:\n - 'apps/*'\n"), null);
32
+ });
33
+
34
+ test('unquoted and double-quoted globs parse identically', () => {
35
+ deepStrictEqual(manifestDepthsFor('packages:\n - packages/*\n'), [2]);
36
+ deepStrictEqual(manifestDepthsFor('packages:\n - "packages/*/*"\n'), [3]);
37
+ });
38
+
39
+ // ── findVendoredDeps ──────────────────────────────────────────────────────
40
+
41
+ const local = (name, deps) => ({ path: `packages/${name}/package.json`, manifest: { name, ...deps } });
42
+
43
+ test('a workspace: range for a package this repo OWNS is fine', () => {
44
+ const found = findVendoredDeps([
45
+ local('@x/a', { dependencies: { '@x/b': 'workspace:*' } }),
46
+ local('@x/b', {}),
47
+ ]);
48
+ deepStrictEqual(found, []);
49
+ });
50
+
51
+ test('a workspace: range for a package this repo does NOT own is a finding', () => {
52
+ const found = findVendoredDeps([
53
+ local('@x/a', { dependencies: { '@upstream/c': 'workspace:*' } }),
54
+ ]);
55
+ strictEqual(found.length, 1);
56
+ strictEqual(found[0].depName, '@upstream/c');
57
+ strictEqual(found[0].field, 'dependencies');
58
+ });
59
+
60
+ test('all three dependency fields are examined', () => {
61
+ const found = findVendoredDeps([
62
+ local('@x/a', {
63
+ dependencies: { '@u/one': 'workspace:*' },
64
+ devDependencies: { '@u/two': 'workspace:^1.0.0' },
65
+ peerDependencies: { '@u/three': 'workspace:~2.0.0' },
66
+ }),
67
+ ]);
68
+ deepStrictEqual(found.map((f) => f.field).sort(), [
69
+ 'dependencies', 'devDependencies', 'peerDependencies',
70
+ ]);
71
+ });
72
+
73
+ test('a registry range is never a finding, whatever it names', () => {
74
+ deepStrictEqual(
75
+ findVendoredDeps([local('@x/a', { dependencies: { '@upstream/c': '^1.2.3' } })]),
76
+ [],
77
+ );
78
+ });
79
+
80
+ test('a non-string range is skipped rather than crashing the run', () => {
81
+ deepStrictEqual(
82
+ findVendoredDeps([local('@x/a', { dependencies: { '@u/c': null } })]),
83
+ [],
84
+ );
85
+ });
86
+
87
+ test('a fixture ingested as a local package WIDENS the allowed set', () => {
88
+ // The measured xema-kernel-contracts defect, pinned as behaviour rather than
89
+ // as prose: its unbounded walk ingested two test fixtures under
90
+ // packages/*/*/test/fixtures/, and a fixture whose name matches a declared
91
+ // dependency makes a real finding disappear. The depth bound is what stops
92
+ // it; this asserts the CONSEQUENCE so the bound cannot be relaxed silently.
93
+ const withoutFixture = findVendoredDeps([
94
+ local('@x/a', { dependencies: { '@u/fixture': 'workspace:*' } }),
95
+ ]);
96
+ strictEqual(withoutFixture.length, 1, 'the finding exists when the fixture is out of scope');
97
+
98
+ const withFixture = findVendoredDeps([
99
+ local('@x/a', { dependencies: { '@u/fixture': 'workspace:*' } }),
100
+ { path: 'packages/a/test/fixtures/f/package.json', manifest: { name: '@u/fixture' } },
101
+ ]);
102
+ strictEqual(withFixture.length, 0, 'ingesting the fixture SILENCES it — the widening');
103
+ });
@@ -0,0 +1,54 @@
1
+ /**
2
+ * The whole check rests on ONE predicate — `satisfies` — so that is what these
3
+ * tests attack.
4
+ *
5
+ * The failure this guard exists to prevent is subtle in exactly one place: npm's
6
+ * `^0.y.z` rule pins the MINOR, not the major. A `satisfies` that treats `^` as
7
+ * "same major" uniformly would call `0.3.0` a match for `^0.2.0` and report a
8
+ * clean scan over all 58 real violations — a check that reports green while
9
+ * examining everything and concluding nothing. Every 0.x case below is aimed at
10
+ * that specific wrong implementation.
11
+ */
12
+ import assert from 'node:assert/strict';
13
+ import test from 'node:test';
14
+
15
+ import { satisfies } from './check-workspace-range-matches-local.mjs';
16
+
17
+ test('^0.y.z pins the MINOR — the rule the whole check turns on', () => {
18
+ // The real defect: a client moved 0.2.0 -> 0.3.0 and fell out of every
19
+ // consumer's range, silently dropping the build edge.
20
+ assert.equal(satisfies('0.3.0', '^0.2.0'), false);
21
+ assert.equal(satisfies('0.2.9', '^0.2.0'), true);
22
+ assert.equal(satisfies('0.2.0', '^0.2.1'), false); // below the floor
23
+ assert.equal(satisfies('1.0.0', '^0.2.0'), false);
24
+ });
25
+
26
+ test('^x.y.z (x > 0) pins the MAJOR', () => {
27
+ assert.equal(satisfies('1.9.3', '^1.2.0'), true);
28
+ assert.equal(satisfies('2.0.0', '^1.2.0'), false);
29
+ assert.equal(satisfies('1.1.0', '^1.2.0'), false); // below the floor
30
+ });
31
+
32
+ test('~ pins the minor at every major', () => {
33
+ assert.equal(satisfies('1.2.9', '~1.2.0'), true);
34
+ assert.equal(satisfies('1.3.0', '~1.2.0'), false);
35
+ assert.equal(satisfies('0.2.9', '~0.2.0'), true);
36
+ assert.equal(satisfies('0.3.0', '~0.2.0'), false);
37
+ });
38
+
39
+ test('>= is a floor with no ceiling', () => {
40
+ assert.equal(satisfies('7.5.0', '>=0.14.0'), true);
41
+ assert.equal(satisfies('0.13.0', '>=0.14.0'), false);
42
+ });
43
+
44
+ test('an exact range means exactly that version', () => {
45
+ assert.equal(satisfies('1.2.3', '1.2.3'), true);
46
+ assert.equal(satisfies('1.2.4', '1.2.3'), false);
47
+ });
48
+
49
+ test('a prerelease is compared on its release part, never string-wise', () => {
50
+ // `0.3.0-rc.1` must not read as "less than 0.3.0 therefore outside ^0.2.0
51
+ // is fine" — the range check has to reach the same verdict as `0.3.0`.
52
+ assert.equal(satisfies('0.3.0-rc.1', '^0.2.0'), false);
53
+ assert.equal(satisfies('0.2.5-rc.1', '^0.2.0'), true);
54
+ });