@memberjunction/cli 6.1.0-edge.2 → 6.1.0-edge.3

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.
Files changed (86) hide show
  1. package/LICENSE +180 -4
  2. package/README.md +1 -1
  3. package/dist/commands/codegen/manifest.d.ts +14 -0
  4. package/dist/commands/codegen/manifest.d.ts.map +1 -1
  5. package/dist/commands/codegen/manifest.js +26 -7
  6. package/dist/commands/codegen/manifest.js.map +1 -1
  7. package/dist/commands/dev/index.d.ts +7 -0
  8. package/dist/commands/dev/index.d.ts.map +1 -0
  9. package/dist/commands/dev/index.js +11 -0
  10. package/dist/commands/dev/index.js.map +1 -0
  11. package/dist/commands/dev/usage.d.ts +7 -0
  12. package/dist/commands/dev/usage.d.ts.map +1 -0
  13. package/dist/commands/dev/usage.js +10 -0
  14. package/dist/commands/dev/usage.js.map +1 -0
  15. package/dist/commands/dev/workspace/clean.d.ts +27 -0
  16. package/dist/commands/dev/workspace/clean.d.ts.map +1 -0
  17. package/dist/commands/dev/workspace/clean.js +87 -0
  18. package/dist/commands/dev/workspace/clean.js.map +1 -0
  19. package/dist/commands/dev/workspace/doctor.d.ts +28 -0
  20. package/dist/commands/dev/workspace/doctor.d.ts.map +1 -0
  21. package/dist/commands/dev/workspace/doctor.js +62 -0
  22. package/dist/commands/dev/workspace/doctor.js.map +1 -0
  23. package/dist/commands/dev/workspace/index.d.ts +60 -0
  24. package/dist/commands/dev/workspace/index.d.ts.map +1 -0
  25. package/dist/commands/dev/workspace/index.js +279 -0
  26. package/dist/commands/dev/workspace/index.js.map +1 -0
  27. package/dist/commands/dev/workspace/status.d.ts +19 -0
  28. package/dist/commands/dev/workspace/status.d.ts.map +1 -0
  29. package/dist/commands/dev/workspace/status.js +46 -0
  30. package/dist/commands/dev/workspace/status.js.map +1 -0
  31. package/dist/lib/cli-plugins.d.ts.map +1 -1
  32. package/dist/lib/cli-plugins.js +5 -0
  33. package/dist/lib/cli-plugins.js.map +1 -1
  34. package/dist/lib/dev-workspace/build.d.ts +180 -0
  35. package/dist/lib/dev-workspace/build.d.ts.map +1 -0
  36. package/dist/lib/dev-workspace/build.js +570 -0
  37. package/dist/lib/dev-workspace/build.js.map +1 -0
  38. package/dist/lib/dev-workspace/clean.d.ts +40 -0
  39. package/dist/lib/dev-workspace/clean.d.ts.map +1 -0
  40. package/dist/lib/dev-workspace/clean.js +193 -0
  41. package/dist/lib/dev-workspace/clean.js.map +1 -0
  42. package/dist/lib/dev-workspace/detect.d.ts +56 -0
  43. package/dist/lib/dev-workspace/detect.d.ts.map +1 -0
  44. package/dist/lib/dev-workspace/detect.js +343 -0
  45. package/dist/lib/dev-workspace/detect.js.map +1 -0
  46. package/dist/lib/dev-workspace/dir-flag.d.ts +23 -0
  47. package/dist/lib/dev-workspace/dir-flag.d.ts.map +1 -0
  48. package/dist/lib/dev-workspace/dir-flag.js +24 -0
  49. package/dist/lib/dev-workspace/dir-flag.js.map +1 -0
  50. package/dist/lib/dev-workspace/doctor.d.ts +96 -0
  51. package/dist/lib/dev-workspace/doctor.d.ts.map +1 -0
  52. package/dist/lib/dev-workspace/doctor.js +400 -0
  53. package/dist/lib/dev-workspace/doctor.js.map +1 -0
  54. package/dist/lib/dev-workspace/lockfile.d.ts +62 -0
  55. package/dist/lib/dev-workspace/lockfile.d.ts.map +1 -0
  56. package/dist/lib/dev-workspace/lockfile.js +456 -0
  57. package/dist/lib/dev-workspace/lockfile.js.map +1 -0
  58. package/dist/lib/dev-workspace/member-installs.d.ts +35 -0
  59. package/dist/lib/dev-workspace/member-installs.d.ts.map +1 -0
  60. package/dist/lib/dev-workspace/member-installs.js +139 -0
  61. package/dist/lib/dev-workspace/member-installs.js.map +1 -0
  62. package/dist/lib/dev-workspace/pnpm.d.ts +29 -0
  63. package/dist/lib/dev-workspace/pnpm.d.ts.map +1 -0
  64. package/dist/lib/dev-workspace/pnpm.js +59 -0
  65. package/dist/lib/dev-workspace/pnpm.js.map +1 -0
  66. package/dist/lib/dev-workspace/status.d.ts +16 -0
  67. package/dist/lib/dev-workspace/status.d.ts.map +1 -0
  68. package/dist/lib/dev-workspace/status.js +185 -0
  69. package/dist/lib/dev-workspace/status.js.map +1 -0
  70. package/dist/lib/dev-workspace/types.d.ts +312 -0
  71. package/dist/lib/dev-workspace/types.d.ts.map +1 -0
  72. package/dist/lib/dev-workspace/types.js +14 -0
  73. package/dist/lib/dev-workspace/types.js.map +1 -0
  74. package/dist/lib/dev-workspace/usage.d.ts +30 -0
  75. package/dist/lib/dev-workspace/usage.d.ts.map +1 -0
  76. package/dist/lib/dev-workspace/usage.js +177 -0
  77. package/dist/lib/dev-workspace/usage.js.map +1 -0
  78. package/dist/lib/dev-workspace/write.d.ts +25 -0
  79. package/dist/lib/dev-workspace/write.d.ts.map +1 -0
  80. package/dist/lib/dev-workspace/write.js +75 -0
  81. package/dist/lib/dev-workspace/write.js.map +1 -0
  82. package/dist/light-commands.d.ts.map +1 -1
  83. package/dist/light-commands.js +7 -0
  84. package/dist/light-commands.js.map +1 -1
  85. package/oclif.manifest.json +3105 -2846
  86. package/package.json +22 -22
@@ -0,0 +1,456 @@
1
+ /**
2
+ * Committed-lockfile reading and override-pin derivation for `mj dev workspace`.
3
+ *
4
+ * WHY: a pnpm workspace has ONE lockfile, so joining N repos discards N committed
5
+ * lockfiles — every `^`/`~` range re-floats to whatever is latest on generation
6
+ * day. Measured in the field (#3795): 945 packages resolved differently from MJ's
7
+ * committed lockfile, breaking real builds one float at a time. The cure that
8
+ * worked was mechanical: pin the parent to what the members' lockfiles already
9
+ * resolved. This module is that derivation — PURE file reading, NO network.
10
+ *
11
+ * SIDE EFFECTS: read-only filesystem access in {@link ReadMemberLockfile} only;
12
+ * the parsers and {@link DeriveLockfilePins} are pure.
13
+ *
14
+ * What is read, per member with a committed lockfile:
15
+ * - the DIRECT dependencies of the member's importers and every `@types/*` at
16
+ * any depth — these define WHICH names get pinned;
17
+ * - EVERY resolved `name@version` at any depth — these define WHAT versions
18
+ * exist per name, so multi-major names are pinned per major.
19
+ * Entries that cannot be pinned (workspace links, npm-alias resolutions,
20
+ * git/url specifiers, non-semver versions) are returned as {@link LockfileSkip}s
21
+ * — dropped loudly, never silently. A lockfile in a format this module cannot
22
+ * read (npm lockfileVersion 1, pnpm < 9) is returned as an
23
+ * {@link UnsupportedLockfile} so the command can warn — never a silent zero.
24
+ *
25
+ * Pin semantics (adversarial-review revision): values are EXACT versions —
26
+ * `^resolved` let 6 of the 7 field-measured breaks straight through, including
27
+ * a `@babel/*` PATCH bump that broke type compat. Keys are per-major when a
28
+ * name resolves in more than one major anywhere in the members' committed
29
+ * graphs (`"chalk@^5": "5.6.2"`, `"chalk@^4": "4.1.2"`), because one global
30
+ * pin would force every transitive consumer cross-major; single-major names
31
+ * get a plain `"name": "exact"` key.
32
+ *
33
+ * @module lib/dev-workspace/lockfile
34
+ */
35
+ import { existsSync, readFileSync, statSync } from 'node:fs';
36
+ import path from 'node:path';
37
+ /** Largest lockfile we will parse (MJ's is ~4MB; anything past this is not plausibly a lockfile). */
38
+ const MAX_LOCKFILE_BYTES = 50_000_000;
39
+ /** A concrete semver resolution: `X.Y.Z` with optional prerelease/build suffix. */
40
+ const RESOLVED_VERSION_PATTERN = /^(\d+)\.(\d+)\.(\d+)(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$/;
41
+ /** Declared-specifier prefixes that resolve outside the registry — never pinnable. */
42
+ const GIT_OR_URL_SPECIFIER = /^(git\+|git:|github:|https?:|file:)/;
43
+ /** True when the string is a plain concrete semver version. */
44
+ export function IsResolvedVersion(version) {
45
+ return RESOLVED_VERSION_PATTERN.test(version);
46
+ }
47
+ /** Major-version number of a concrete resolution. Throws on non-semver input (precondition). */
48
+ export function MajorOf(version) {
49
+ const match = RESOLVED_VERSION_PATTERN.exec(version);
50
+ if (match === null) {
51
+ throw new Error(`MajorOf requires a concrete semver version, got: ${version}`);
52
+ }
53
+ return Number(match[1]);
54
+ }
55
+ /**
56
+ * Compares two CONCRETE versions: numeric triple first; on a triple tie a
57
+ * release beats a prerelease, and two prereleases compare by tag string.
58
+ * Throws on non-semver input — callers filter through {@link IsResolvedVersion} first.
59
+ */
60
+ export function CompareResolvedVersions(a, b) {
61
+ const ma = RESOLVED_VERSION_PATTERN.exec(a);
62
+ const mb = RESOLVED_VERSION_PATTERN.exec(b);
63
+ if (ma === null || mb === null) {
64
+ throw new Error(`CompareResolvedVersions requires concrete versions, got: '${a}' vs '${b}'`);
65
+ }
66
+ for (let i = 1; i <= 3; i++) {
67
+ if (Number(ma[i]) !== Number(mb[i]))
68
+ return Number(ma[i]) - Number(mb[i]);
69
+ }
70
+ const preA = ma[4] ?? '';
71
+ const preB = mb[4] ?? '';
72
+ if (preA === preB)
73
+ return 0;
74
+ if (preA === '')
75
+ return 1; // release > prerelease
76
+ if (preB === '')
77
+ return -1;
78
+ return preA < preB ? -1 : 1;
79
+ }
80
+ /** Strips the pnpm peer-suffix (`1.2.3(peer@x)(...)`) off a lockfile version. */
81
+ function stripPeerSuffix(version) {
82
+ const paren = version.indexOf('(');
83
+ return paren >= 0 ? version.slice(0, paren) : version;
84
+ }
85
+ /** Classifies one importer resolution: a usable entry, or a skip with the reason. */
86
+ function classifyResolution(name, rawVersion) {
87
+ const version = stripPeerSuffix(rawVersion);
88
+ if (version.startsWith('link:')) {
89
+ return { Skip: { Name: name, Version: version, Reason: 'workspace-internal link' } };
90
+ }
91
+ if (GIT_OR_URL_SPECIFIER.test(version)) {
92
+ return { Skip: { Name: name, Version: version, Reason: 'git/url resolution' } };
93
+ }
94
+ if (!IsResolvedVersion(version)) {
95
+ return { Skip: { Name: name, Version: version, Reason: 'non-semver resolution (npm alias / catalog / file)' } };
96
+ }
97
+ return { Entry: { Name: name, Version: version } };
98
+ }
99
+ /** Strips surrounding single/double quotes off a YAML key. */
100
+ function unquoteKey(key) {
101
+ const match = /^'([^']*)'$|^"([^"]*)"$/.exec(key);
102
+ return match === null ? key : (match[1] ?? match[2] ?? '');
103
+ }
104
+ /** Splits a pnpm packages-section key (`@types/node@24.10.11`) into name + version, or null. */
105
+ function splitPackageKey(key) {
106
+ const at = key.lastIndexOf('@');
107
+ if (at <= 0)
108
+ return null; // leading @ is a scope, not a separator
109
+ return { Name: key.slice(0, at), Version: key.slice(at + 1) };
110
+ }
111
+ /** Matches ` name:` / ` 'name':` section lines at a given indent, capturing the key. */
112
+ function keyAtIndent(line, indent) {
113
+ if (!line.startsWith(' '.repeat(indent)) || line.charAt(indent) === ' ')
114
+ return null;
115
+ const body = line.slice(indent);
116
+ if (!body.endsWith(':'))
117
+ return null;
118
+ return unquoteKey(body.slice(0, -1));
119
+ }
120
+ /** Extracts the `lockfileVersion` value from a pnpm-lock.yaml, or null. */
121
+ function pnpmLockVersion(lines) {
122
+ for (const line of lines) {
123
+ const match = /^lockfileVersion:\s*'?([0-9.]+)'?\s*$/.exec(line.trimEnd());
124
+ if (match !== null)
125
+ return match[1];
126
+ }
127
+ return null;
128
+ }
129
+ /** Parses the `importers:` section of a pnpm-lock.yaml into direct-dep resolutions. */
130
+ function parsePnpmImporters(lines) {
131
+ const direct = [];
132
+ const skipped = [];
133
+ let inImporters = false;
134
+ let currentDep = null;
135
+ for (const raw of lines) {
136
+ const line = raw.trimEnd();
137
+ if (line === 'importers:') {
138
+ inImporters = true;
139
+ continue;
140
+ }
141
+ if (!inImporters)
142
+ continue;
143
+ if (line.length > 0 && !line.startsWith(' '))
144
+ break; // next top-level key ends the section
145
+ const depName = keyAtIndent(line, 6);
146
+ if (depName !== null)
147
+ currentDep = depName;
148
+ const versionMatch = /^ {8}version: (.+)$/.exec(line);
149
+ if (versionMatch === null || currentDep === null)
150
+ continue;
151
+ const classified = classifyResolution(currentDep, versionMatch[1].trim());
152
+ if (classified.Entry)
153
+ direct.push(classified.Entry);
154
+ if (classified.Skip)
155
+ skipped.push(classified.Skip);
156
+ currentDep = null;
157
+ }
158
+ return { Direct: direct, Skipped: skipped };
159
+ }
160
+ /** Parses the `packages:` section keys of a pnpm-lock.yaml into EVERY resolved name@version. */
161
+ function parsePnpmResolutions(lines) {
162
+ const resolutions = [];
163
+ let inPackages = false;
164
+ for (const raw of lines) {
165
+ const line = raw.trimEnd();
166
+ if (line === 'packages:' || line === 'snapshots:') {
167
+ inPackages = line === 'packages:';
168
+ continue;
169
+ }
170
+ if (!inPackages)
171
+ continue;
172
+ if (line.length > 0 && !line.startsWith(' '))
173
+ inPackages = false;
174
+ const key = keyAtIndent(line, 2);
175
+ if (key === null)
176
+ continue;
177
+ const split = splitPackageKey(key);
178
+ if (split !== null && IsResolvedVersion(split.Version))
179
+ resolutions.push(split);
180
+ }
181
+ return resolutions;
182
+ }
183
+ /**
184
+ * Parses the `snapshots:` section into a dependents map: `@types/x@V` -> the
185
+ * package NAMES whose snapshots depend on that exact resolution. This is how
186
+ * pnpm parentage is derived, so `@types` that exist only beneath a registry
187
+ * copy of a family package can be excluded from pin authority.
188
+ */
189
+ function parsePnpmTypesDependents(lines) {
190
+ const dependents = new Map();
191
+ let inSnapshots = false;
192
+ let currentSnapshotName = null;
193
+ for (const raw of lines) {
194
+ const line = raw.trimEnd();
195
+ if (line === 'snapshots:' || line === 'packages:') {
196
+ inSnapshots = line === 'snapshots:';
197
+ continue;
198
+ }
199
+ if (!inSnapshots)
200
+ continue;
201
+ if (line.length > 0 && !line.startsWith(' '))
202
+ break; // next top-level key ends the section
203
+ const snapshotKey = keyAtIndent(line.replace(/: \{\}$/, ':'), 2);
204
+ if (snapshotKey !== null) {
205
+ const split = splitPackageKey(stripPeerSuffix(snapshotKey));
206
+ currentSnapshotName = split?.Name ?? null;
207
+ continue;
208
+ }
209
+ const depMatch = /^ {6}(?:'([^']+)'|([^\s:]+)): (.+)$/.exec(line);
210
+ if (depMatch === null || currentSnapshotName === null)
211
+ continue;
212
+ const depName = depMatch[1] ?? depMatch[2];
213
+ if (!depName.startsWith('@types/'))
214
+ continue;
215
+ const depVersion = stripPeerSuffix(depMatch[3].trim());
216
+ const key = `${depName}@${depVersion}`;
217
+ const set = dependents.get(key) ?? new Set();
218
+ set.add(currentSnapshotName);
219
+ dependents.set(key, set);
220
+ }
221
+ return dependents;
222
+ }
223
+ /** Parses a committed pnpm-lock.yaml. Only v9+ is readable; older formats return an unsupported marker. */
224
+ export function ParsePnpmLockfile(lockText) {
225
+ const lines = lockText.split('\n');
226
+ const version = pnpmLockVersion(lines);
227
+ if (version === null || Number(version.split('.')[0]) < 9) {
228
+ return { Kind: 'unsupported', File: 'pnpm-lock.yaml', Version: version ?? 'unknown' };
229
+ }
230
+ const importers = parsePnpmImporters(lines);
231
+ const resolutions = parsePnpmResolutions(lines);
232
+ const typesDependents = parsePnpmTypesDependents(lines);
233
+ return {
234
+ Kind: 'pnpm',
235
+ Direct: importers.Direct,
236
+ Types: resolutions
237
+ .filter((entry) => entry.Name.startsWith('@types/'))
238
+ .map((entry) => ({ ...entry, Dependents: [...(typesDependents.get(`${entry.Name}@${entry.Version}`) ?? [])].sort() })),
239
+ Resolutions: resolutions,
240
+ Skipped: importers.Skipped,
241
+ };
242
+ }
243
+ /** Resolves one declared name from an importer via npm's nearest-node_modules walk. */
244
+ function resolveNpmDep(packages, importerKey, name) {
245
+ const segments = importerKey === '' ? [] : importerKey.split('/');
246
+ for (let depth = segments.length; depth >= 0; depth--) {
247
+ const prefix = segments.slice(0, depth).join('/');
248
+ const entry = packages[`${prefix === '' ? '' : `${prefix}/`}node_modules/${name}`];
249
+ if (entry !== undefined)
250
+ return entry;
251
+ }
252
+ return null;
253
+ }
254
+ /** Classifies one npm importer dependency: entry, or skip with a stated reason. */
255
+ function classifyNpmDep(name, declaredSpec, entry) {
256
+ if (GIT_OR_URL_SPECIFIER.test(declaredSpec)) {
257
+ return { Skip: { Name: name, Version: declaredSpec, Reason: 'git/url specifier' } };
258
+ }
259
+ if (entry === null) {
260
+ return { Skip: { Name: name, Version: declaredSpec, Reason: 'not present in lockfile' } };
261
+ }
262
+ if (entry.link === true) {
263
+ return { Skip: { Name: name, Version: entry.version ?? 'link', Reason: 'workspace-internal link' } };
264
+ }
265
+ if (entry.resolved !== undefined && /^git/.test(entry.resolved)) {
266
+ return { Skip: { Name: name, Version: entry.version ?? entry.resolved, Reason: 'git/url resolution' } };
267
+ }
268
+ const version = entry.version ?? '';
269
+ if (!IsResolvedVersion(version)) {
270
+ return { Skip: { Name: name, Version: version, Reason: 'non-semver resolution' } };
271
+ }
272
+ return { Entry: { Name: name, Version: version } };
273
+ }
274
+ /** Collects direct-dep resolutions for one npm importer entry. */
275
+ function collectNpmImporterDeps(packages, importerKey, importer, direct, skipped) {
276
+ const declared = { ...importer.dependencies, ...importer.devDependencies, ...importer.optionalDependencies };
277
+ for (const [name, spec] of Object.entries(declared)) {
278
+ const classified = classifyNpmDep(name, spec, resolveNpmDep(packages, importerKey, name));
279
+ if (classified.Entry)
280
+ direct.push(classified.Entry);
281
+ if (classified.Skip)
282
+ skipped.push(classified.Skip);
283
+ }
284
+ }
285
+ /** Parses a committed npm package-lock.json. Only lockfileVersion 2/3 is readable. */
286
+ export function ParseNpmLockfile(lockText) {
287
+ const parsed = JSON.parse(lockText);
288
+ if (typeof parsed !== 'object' || parsed === null) {
289
+ throw new Error('package-lock.json did not parse to an object');
290
+ }
291
+ const shape = parsed;
292
+ if (shape.lockfileVersion === undefined || shape.lockfileVersion < 2 || shape.packages === undefined) {
293
+ return { Kind: 'unsupported', File: 'package-lock.json', Version: String(shape.lockfileVersion ?? 'unknown') };
294
+ }
295
+ const packages = shape.packages;
296
+ const direct = [];
297
+ const skipped = [];
298
+ const resolutions = [];
299
+ const types = [];
300
+ for (const [key, entry] of Object.entries(packages)) {
301
+ if (!key.includes('node_modules')) {
302
+ collectNpmImporterDeps(packages, key, entry, direct, skipped);
303
+ continue;
304
+ }
305
+ const name = key.slice(key.lastIndexOf('node_modules/') + 'node_modules/'.length);
306
+ const gitResolved = entry.resolved !== undefined && /^git/.test(entry.resolved);
307
+ if (entry.version === undefined || gitResolved || entry.link === true || !IsResolvedVersion(entry.version))
308
+ continue;
309
+ resolutions.push({ Name: name, Version: entry.version });
310
+ if (name.startsWith('@types/')) {
311
+ types.push({ Name: name, Version: entry.version, Dependents: npmNestingParent(key) });
312
+ }
313
+ }
314
+ return { Kind: 'npm', Direct: direct, Types: types, Resolutions: resolutions, Skipped: skipped };
315
+ }
316
+ /**
317
+ * The package this npm lockfile entry is physically nested under, from its key
318
+ * path: `node_modules/@memberjunction/x/node_modules/@types/y` -> `@memberjunction/x`.
319
+ * A top-level (hoisted) entry has no derivable single parent -> [] (parentage
320
+ * unknown, treated as legitimate authority).
321
+ */
322
+ function npmNestingParent(key) {
323
+ const segments = key.split(/node_modules\//).map((s) => s.replace(/\/$/, '')).filter((s) => s.length > 0);
324
+ // segments = [<importer prefix if any>, parentPkg?, ..., ownName]; nesting parent = the segment before the last
325
+ if (segments.length < 2)
326
+ return [];
327
+ const parent = segments[segments.length - 2];
328
+ return parent.includes('/') && !parent.startsWith('@') ? [] : [parent]; // importer paths (e.g. packages/core) are not registry parents
329
+ }
330
+ /** Reads a size-checked lockfile off disk; throws over the cap (not plausibly a lockfile). */
331
+ function readBoundedLockfile(filePath) {
332
+ const size = statSync(filePath).size;
333
+ if (size > MAX_LOCKFILE_BYTES) {
334
+ throw new Error(`${filePath} is ${size} bytes — over the ${MAX_LOCKFILE_BYTES} cap; not a plausible lockfile`);
335
+ }
336
+ return readFileSync(filePath, 'utf8');
337
+ }
338
+ /**
339
+ * Reads a member's committed lockfile: `pnpm-lock.yaml` first, `package-lock.json`
340
+ * otherwise, null when the repo commits neither. An unsupported format comes back
341
+ * as a marker (the command warns); unparseable content throws — a corrupt
342
+ * lockfile must never silently become "no pins for this member".
343
+ */
344
+ export function ReadMemberLockfile(repoPath) {
345
+ const pnpmPath = path.join(repoPath, 'pnpm-lock.yaml');
346
+ if (existsSync(pnpmPath)) {
347
+ return ParsePnpmLockfile(readBoundedLockfile(pnpmPath));
348
+ }
349
+ const npmPath = path.join(repoPath, 'package-lock.json');
350
+ if (!existsSync(npmPath))
351
+ return null;
352
+ try {
353
+ return ParseNpmLockfile(readBoundedLockfile(npmPath));
354
+ }
355
+ catch (error) {
356
+ const message = error instanceof Error ? error.message : String(error);
357
+ throw new Error(`Unparseable package-lock.json at ${npmPath}: ${message}`);
358
+ }
359
+ }
360
+ /**
361
+ * True when an at-depth `@types` resolution is legitimate pin authority: its
362
+ * parentage is unknown (hoisted / importer-level), or at least one dependent is
363
+ * NOT a family package. A `@types` living ONLY beneath registry copies of
364
+ * family packages describes a graph that cannot exist in the generated
365
+ * workspace (family packages are workspace-linked there), so it must not vote.
366
+ */
367
+ function isLegitimateTypesAuthority(entry, familyNames) {
368
+ if (entry.Dependents === undefined || entry.Dependents.length === 0)
369
+ return true;
370
+ return entry.Dependents.some((dependent) => !familyNames.has(dependent));
371
+ }
372
+ /** Decides one (name, major) group's exact pin; records a conflict when sources disagreed. */
373
+ function decideMajorPin(key, candidates, result) {
374
+ let winner = candidates[0];
375
+ for (const candidate of candidates.slice(1)) {
376
+ if (CompareResolvedVersions(candidate.Version, winner.Version) > 0)
377
+ winner = candidate;
378
+ }
379
+ result.Pins[key] = winner.Version;
380
+ const losers = candidates.filter((c) => c.Version !== winner.Version);
381
+ if (losers.length > 0) {
382
+ result.Conflicts.push({ Package: key, Winner: winner, Losers: losers });
383
+ }
384
+ }
385
+ /**
386
+ * Derives the parent `pnpm.overrides` pins from the members' committed lockfiles.
387
+ * Pure.
388
+ *
389
+ * AUTHORITY RULE (adversarial-review round 3): a version may vote only where a
390
+ * member DIRECTLY declares the name (its importers resolve it), plus `@types/*`
391
+ * at any depth minus those parented solely by registry copies of family
392
+ * packages. At-depth resolutions of ordinary packages are NOT candidates — a
393
+ * bizapps lockfile resolved with registry `@memberjunction/*` describes a graph
394
+ * that cannot exist in the generated workspace, and letting it vote handed the
395
+ * field's floated versions (`@cerebras` 1.91.0, `@foblex` 18.6.1) a win over
396
+ * MJ's committed 1.64.1/18.0.0.
397
+ *
398
+ * KEY SCOPING: at-depth majors still decide the KEY SHAPE — when any member's
399
+ * graph holds another major of a pinned name anywhere, the pin is emitted as a
400
+ * `name@^N` selector so transitive consumers of the other majors float within
401
+ * their declared ranges (never forced cross-major); only majors present among
402
+ * the candidates get a selector at all. Values are EXACT — `^resolved` provably
403
+ * passed 6 of 7 field-measured breaks through. Disagreements within a major
404
+ * among direct declarations are genuine: highest committed exact wins, reported.
405
+ */
406
+ export function DeriveLockfilePins(members, familyNames) {
407
+ const excludedFamily = new Set();
408
+ const sorted = [...members].sort((a, b) => (a.Repo < b.Repo ? -1 : a.Repo > b.Repo ? 1 : 0));
409
+ const byNameMajor = new Map();
410
+ for (const member of sorted) {
411
+ const legitimateTypes = member.Lockfile.Types.filter((t) => isLegitimateTypesAuthority(t, familyNames));
412
+ for (const entry of [...member.Lockfile.Direct, ...legitimateTypes]) {
413
+ if (familyNames.has(entry.Name)) {
414
+ excludedFamily.add(entry.Name);
415
+ continue;
416
+ }
417
+ const majors = byNameMajor.get(entry.Name) ?? new Map();
418
+ const candidates = majors.get(MajorOf(entry.Version)) ?? [];
419
+ if (!candidates.some((c) => c.Repo === member.Repo && c.Version === entry.Version)) {
420
+ candidates.push({ Repo: member.Repo, Version: entry.Version });
421
+ }
422
+ majors.set(MajorOf(entry.Version), candidates);
423
+ byNameMajor.set(entry.Name, majors);
424
+ }
425
+ }
426
+ const majorsAnywhere = collectMajorsAnywhere(sorted, byNameMajor);
427
+ const result = { Pins: {}, Conflicts: [], ExcludedFamilyNames: [...excludedFamily].sort() };
428
+ for (const name of [...byNameMajor.keys()].sort()) {
429
+ const majors = byNameMajor.get(name);
430
+ const singleMajorEverywhere = (majorsAnywhere.get(name)?.size ?? majors.size) === 1;
431
+ for (const major of [...majors.keys()].sort((a, b) => a - b)) {
432
+ decideMajorPin(singleMajorEverywhere ? name : `${name}@^${major}`, majors.get(major), result);
433
+ }
434
+ }
435
+ return result;
436
+ }
437
+ /**
438
+ * Majors present ANYWHERE (any depth, any member) for each pinned name — used
439
+ * only to decide key shape: a plain `name` key is safe only when no other major
440
+ * exists anywhere in any member's graph; otherwise every pin must be a scoped
441
+ * selector so at-depth consumers of other majors are never forced cross-major.
442
+ */
443
+ function collectMajorsAnywhere(members, byNameMajor) {
444
+ const majorsAnywhere = new Map();
445
+ for (const member of members) {
446
+ for (const entry of [...member.Lockfile.Resolutions, ...member.Lockfile.Direct, ...member.Lockfile.Types]) {
447
+ if (!byNameMajor.has(entry.Name))
448
+ continue;
449
+ const majors = majorsAnywhere.get(entry.Name) ?? new Set();
450
+ majors.add(MajorOf(entry.Version));
451
+ majorsAnywhere.set(entry.Name, majors);
452
+ }
453
+ }
454
+ return majorsAnywhere;
455
+ }
456
+ //# sourceMappingURL=lockfile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lockfile.js","sourceRoot":"","sources":["../../../src/lib/dev-workspace/lockfile.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ /** True when `childPath` is strictly inside `parentDir` (never equal, never outside). */
2
+ export declare function IsInsideDirectory(parentDir: string, childPath: string): boolean;
3
+ /** What one member walk found: install trees plus any directories it could not read. */
4
+ export interface InstallTreeScan {
5
+ /** Absolute paths of every dir named exactly `node_modules`, sorted. */
6
+ Trees: string[];
7
+ /** Directories the walk could not list (permissions etc.) — reported, never a mid-run abort. */
8
+ UnreadableDirs: string[];
9
+ }
10
+ /**
11
+ * Finds every directory named EXACTLY `node_modules` (case-sensitive comparison,
12
+ * real dirs only — symlinks are invisible to `Dirent.isDirectory`) anywhere under
13
+ * a member repo — depth-independent, never descending INTO a found tree.
14
+ * Bounded by {@link MAX_WALK_DIRS}; exceeding it throws.
15
+ */
16
+ export declare function FindMemberInstallTrees(repoPath: string): InstallTreeScan;
17
+ /** Outcome of a removal run: what was deleted, what was already gone, what was refused (with reasons). */
18
+ export interface RemoveTreesResult {
19
+ Removed: string[];
20
+ AlreadyGone: string[];
21
+ /** Paths NOT deleted, each with the reason — reported by the command, never silent. */
22
+ Skipped: Array<{
23
+ Path: string;
24
+ Reason: string;
25
+ }>;
26
+ }
27
+ /**
28
+ * Removes the enumerated `node_modules` trees. Precondition (asserted): every
29
+ * path is absolute and ends in `node_modules` — this function deletes install
30
+ * trees and nothing else. Each deletion re-verifies the exact on-disk entry
31
+ * name first. Paths already gone are reported, not errors, so a repeat run is
32
+ * safe; refusals come back in `Skipped` with reasons. rmSync failures propagate.
33
+ */
34
+ export declare function RemoveMemberInstallTrees(treePaths: readonly string[]): RemoveTreesResult;
35
+ //# sourceMappingURL=member-installs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"member-installs.d.ts","sourceRoot":"","sources":["../../../src/lib/dev-workspace/member-installs.ts"],"names":[],"mappings":"AAqCA,yFAAyF;AACzF,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAM/E;AAED,wFAAwF;AACxF,MAAM,WAAW,eAAe;IAC9B,wEAAwE;IACxE,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,gGAAgG;IAChG,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B;AAcD;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,CAuBxE;AAED,0GAA0G;AAC1G,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,uFAAuF;IACvF,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClD;AAqBD;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,GAAG,iBAAiB,CAyBxF"}
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Standalone-install detection and cleanup for `mj dev workspace` members.
3
+ *
4
+ * WHY: a member that was installed standalone carries its own `node_modules` —
5
+ * the root one AND per-package ones throughout its tree (290 in MJ, some 5
6
+ * levels deep). Installing at the parent over those leaves two overlapping
7
+ * stores; removing only the root swaps one broken state for another, because the
8
+ * nested trees keep symlinks into the deleted store — a rotating cast of
9
+ * `TS2307 Cannot find module` errors that look like missing dependencies but
10
+ * aren't (field finding on #3795). The walk here is therefore DEPTH-INDEPENDENT
11
+ * by design: "a `-maxdepth` guess is exactly the mistake I made" — AN-BC.
12
+ *
13
+ * SIDE EFFECTS: read-only walk in {@link FindMemberInstallTrees};
14
+ * {@link RemoveMemberInstallTrees} deletes exactly the enumerated paths, with
15
+ * the same rmSync discipline `clean.ts` uses. Never a glob delete — every
16
+ * removed path was individually enumerated and can be printed first.
17
+ *
18
+ * Deletion hardening (adversarial-review revision):
19
+ * - an unreadable directory mid-walk is skipped and REPORTED, never a mid-run
20
+ * abort (an EACCES throw between deletions is worse than either extreme);
21
+ * - removal re-verifies the on-disk entry is named exactly `node_modules` —
22
+ * on case-insensitive APFS, `rmSync('.../node_modules')` would otherwise
23
+ * delete a dir actually named `Node_Modules`;
24
+ * - {@link IsInsideDirectory} lets the command exclude out-of-parent members
25
+ * (possible via --include) from deletion enumeration entirely.
26
+ *
27
+ * @module lib/dev-workspace/member-installs
28
+ */
29
+ import { readdirSync, rmSync } from 'node:fs';
30
+ import path from 'node:path';
31
+ /** Directory-visit cap for one member walk; hitting it is an error, not a truncation. */
32
+ const MAX_WALK_DIRS = 200_000;
33
+ /** Matches clean.ts: Windows and virus scanners hold file handles briefly. */
34
+ const RM_MAX_RETRIES = 3;
35
+ /** True when `childPath` is strictly inside `parentDir` (never equal, never outside). */
36
+ export function IsInsideDirectory(parentDir, childPath) {
37
+ if (!path.isAbsolute(parentDir) || !path.isAbsolute(childPath)) {
38
+ throw new Error(`IsInsideDirectory requires absolute paths, got: '${parentDir}', '${childPath}'`);
39
+ }
40
+ const rel = path.relative(parentDir, childPath);
41
+ return rel.length > 0 && !rel.startsWith('..') && !path.isAbsolute(rel);
42
+ }
43
+ /** Real (non-symlink) subdirectories of a dir, skipping dot-dirs; unreadable dirs are recorded, not thrown. */
44
+ function subDirs(dirPath, unreadable) {
45
+ try {
46
+ return readdirSync(dirPath, { withFileTypes: true })
47
+ .filter((e) => e.isDirectory() && !e.name.startsWith('.'))
48
+ .map((e) => e.name);
49
+ }
50
+ catch {
51
+ unreadable.push(dirPath); // recorded for the caller's report — never swallowed silently
52
+ return [];
53
+ }
54
+ }
55
+ /**
56
+ * Finds every directory named EXACTLY `node_modules` (case-sensitive comparison,
57
+ * real dirs only — symlinks are invisible to `Dirent.isDirectory`) anywhere under
58
+ * a member repo — depth-independent, never descending INTO a found tree.
59
+ * Bounded by {@link MAX_WALK_DIRS}; exceeding it throws.
60
+ */
61
+ export function FindMemberInstallTrees(repoPath) {
62
+ if (!path.isAbsolute(repoPath)) {
63
+ throw new Error(`FindMemberInstallTrees requires an absolute path, got: ${repoPath}`);
64
+ }
65
+ const trees = [];
66
+ const unreadable = [];
67
+ const stack = [repoPath];
68
+ let visited = 0;
69
+ while (stack.length > 0) {
70
+ const dir = stack.pop();
71
+ if (++visited > MAX_WALK_DIRS) {
72
+ throw new Error(`Walk of ${repoPath} exceeded ${MAX_WALK_DIRS} directories — refusing to continue`);
73
+ }
74
+ for (const child of subDirs(dir, unreadable)) {
75
+ const childPath = path.join(dir, child);
76
+ if (child === 'node_modules') {
77
+ trees.push(childPath); // record, never descend into it
78
+ }
79
+ else {
80
+ stack.push(childPath);
81
+ }
82
+ }
83
+ }
84
+ return { Trees: trees.sort(), UnreadableDirs: unreadable.sort() };
85
+ }
86
+ /**
87
+ * Verifies the on-disk directory entry for `treePath` is named exactly
88
+ * `node_modules`: 'gone' when absent, 'exact' on a true match, 'mismatch' when
89
+ * only a case-variant exists (case-insensitive filesystems would let rmSync
90
+ * delete `Node_Modules` through a `node_modules` path), 'unreadable' when the
91
+ * containing dir cannot be listed.
92
+ */
93
+ function classifyOnDiskEntry(treePath) {
94
+ let entries;
95
+ try {
96
+ entries = readdirSync(path.dirname(treePath));
97
+ }
98
+ catch {
99
+ return 'unreadable';
100
+ }
101
+ if (entries.includes('node_modules'))
102
+ return 'exact';
103
+ const caseVariant = entries.some((e) => e.toLowerCase() === 'node_modules');
104
+ return caseVariant ? 'mismatch' : 'gone';
105
+ }
106
+ /**
107
+ * Removes the enumerated `node_modules` trees. Precondition (asserted): every
108
+ * path is absolute and ends in `node_modules` — this function deletes install
109
+ * trees and nothing else. Each deletion re-verifies the exact on-disk entry
110
+ * name first. Paths already gone are reported, not errors, so a repeat run is
111
+ * safe; refusals come back in `Skipped` with reasons. rmSync failures propagate.
112
+ */
113
+ export function RemoveMemberInstallTrees(treePaths) {
114
+ for (const treePath of treePaths) {
115
+ if (!path.isAbsolute(treePath) || path.basename(treePath) !== 'node_modules') {
116
+ throw new Error(`RemoveMemberInstallTrees only deletes node_modules trees; refusing: ${treePath}`);
117
+ }
118
+ }
119
+ const result = { Removed: [], AlreadyGone: [], Skipped: [] };
120
+ for (const treePath of treePaths) {
121
+ const onDisk = classifyOnDiskEntry(treePath);
122
+ if (onDisk === 'gone') {
123
+ result.AlreadyGone.push(treePath);
124
+ continue;
125
+ }
126
+ if (onDisk === 'mismatch') {
127
+ result.Skipped.push({ Path: treePath, Reason: 'on-disk entry is a case variant of node_modules — not an install tree' });
128
+ continue;
129
+ }
130
+ if (onDisk === 'unreadable') {
131
+ result.Skipped.push({ Path: treePath, Reason: 'containing directory is unreadable — cannot verify the entry' });
132
+ continue;
133
+ }
134
+ rmSync(treePath, { recursive: true, force: true, maxRetries: RM_MAX_RETRIES });
135
+ result.Removed.push(treePath);
136
+ }
137
+ return result;
138
+ }
139
+ //# sourceMappingURL=member-installs.js.map