@memberjunction/cli 6.1.0-edge.1 → 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.
- package/LICENSE +180 -4
- package/README.md +1 -1
- package/dist/commands/artifacts/reclassify.d.ts.map +1 -1
- package/dist/commands/artifacts/reclassify.js +2 -1
- package/dist/commands/artifacts/reclassify.js.map +1 -1
- package/dist/commands/codegen/manifest.d.ts +14 -0
- package/dist/commands/codegen/manifest.d.ts.map +1 -1
- package/dist/commands/codegen/manifest.js +26 -7
- package/dist/commands/codegen/manifest.js.map +1 -1
- package/dist/commands/dev/index.d.ts +7 -0
- package/dist/commands/dev/index.d.ts.map +1 -0
- package/dist/commands/dev/index.js +11 -0
- package/dist/commands/dev/index.js.map +1 -0
- package/dist/commands/dev/usage.d.ts +7 -0
- package/dist/commands/dev/usage.d.ts.map +1 -0
- package/dist/commands/dev/usage.js +10 -0
- package/dist/commands/dev/usage.js.map +1 -0
- package/dist/commands/dev/workspace/clean.d.ts +27 -0
- package/dist/commands/dev/workspace/clean.d.ts.map +1 -0
- package/dist/commands/dev/workspace/clean.js +87 -0
- package/dist/commands/dev/workspace/clean.js.map +1 -0
- package/dist/commands/dev/workspace/doctor.d.ts +28 -0
- package/dist/commands/dev/workspace/doctor.d.ts.map +1 -0
- package/dist/commands/dev/workspace/doctor.js +62 -0
- package/dist/commands/dev/workspace/doctor.js.map +1 -0
- package/dist/commands/dev/workspace/index.d.ts +60 -0
- package/dist/commands/dev/workspace/index.d.ts.map +1 -0
- package/dist/commands/dev/workspace/index.js +279 -0
- package/dist/commands/dev/workspace/index.js.map +1 -0
- package/dist/commands/dev/workspace/status.d.ts +19 -0
- package/dist/commands/dev/workspace/status.d.ts.map +1 -0
- package/dist/commands/dev/workspace/status.js +46 -0
- package/dist/commands/dev/workspace/status.js.map +1 -0
- package/dist/lib/cli-plugins.d.ts.map +1 -1
- package/dist/lib/cli-plugins.js +5 -0
- package/dist/lib/cli-plugins.js.map +1 -1
- package/dist/lib/dev-workspace/build.d.ts +180 -0
- package/dist/lib/dev-workspace/build.d.ts.map +1 -0
- package/dist/lib/dev-workspace/build.js +570 -0
- package/dist/lib/dev-workspace/build.js.map +1 -0
- package/dist/lib/dev-workspace/clean.d.ts +40 -0
- package/dist/lib/dev-workspace/clean.d.ts.map +1 -0
- package/dist/lib/dev-workspace/clean.js +193 -0
- package/dist/lib/dev-workspace/clean.js.map +1 -0
- package/dist/lib/dev-workspace/detect.d.ts +56 -0
- package/dist/lib/dev-workspace/detect.d.ts.map +1 -0
- package/dist/lib/dev-workspace/detect.js +343 -0
- package/dist/lib/dev-workspace/detect.js.map +1 -0
- package/dist/lib/dev-workspace/dir-flag.d.ts +23 -0
- package/dist/lib/dev-workspace/dir-flag.d.ts.map +1 -0
- package/dist/lib/dev-workspace/dir-flag.js +24 -0
- package/dist/lib/dev-workspace/dir-flag.js.map +1 -0
- package/dist/lib/dev-workspace/doctor.d.ts +96 -0
- package/dist/lib/dev-workspace/doctor.d.ts.map +1 -0
- package/dist/lib/dev-workspace/doctor.js +400 -0
- package/dist/lib/dev-workspace/doctor.js.map +1 -0
- package/dist/lib/dev-workspace/lockfile.d.ts +62 -0
- package/dist/lib/dev-workspace/lockfile.d.ts.map +1 -0
- package/dist/lib/dev-workspace/lockfile.js +456 -0
- package/dist/lib/dev-workspace/lockfile.js.map +1 -0
- package/dist/lib/dev-workspace/member-installs.d.ts +35 -0
- package/dist/lib/dev-workspace/member-installs.d.ts.map +1 -0
- package/dist/lib/dev-workspace/member-installs.js +139 -0
- package/dist/lib/dev-workspace/member-installs.js.map +1 -0
- package/dist/lib/dev-workspace/pnpm.d.ts +29 -0
- package/dist/lib/dev-workspace/pnpm.d.ts.map +1 -0
- package/dist/lib/dev-workspace/pnpm.js +59 -0
- package/dist/lib/dev-workspace/pnpm.js.map +1 -0
- package/dist/lib/dev-workspace/status.d.ts +16 -0
- package/dist/lib/dev-workspace/status.d.ts.map +1 -0
- package/dist/lib/dev-workspace/status.js +185 -0
- package/dist/lib/dev-workspace/status.js.map +1 -0
- package/dist/lib/dev-workspace/types.d.ts +312 -0
- package/dist/lib/dev-workspace/types.d.ts.map +1 -0
- package/dist/lib/dev-workspace/types.js +14 -0
- package/dist/lib/dev-workspace/types.js.map +1 -0
- package/dist/lib/dev-workspace/usage.d.ts +30 -0
- package/dist/lib/dev-workspace/usage.d.ts.map +1 -0
- package/dist/lib/dev-workspace/usage.js +177 -0
- package/dist/lib/dev-workspace/usage.js.map +1 -0
- package/dist/lib/dev-workspace/write.d.ts +25 -0
- package/dist/lib/dev-workspace/write.d.ts.map +1 -0
- package/dist/lib/dev-workspace/write.js +75 -0
- package/dist/lib/dev-workspace/write.js.map +1 -0
- package/dist/light-commands.d.ts.map +1 -1
- package/dist/light-commands.js +7 -0
- package/dist/light-commands.js.map +1 -1
- package/dist/utils/open-app-context.js +1 -1
- package/dist/utils/open-app-context.js.map +1 -1
- package/oclif.manifest.json +3432 -3173
- 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
|