lockfile-subset 1.2.1 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.mjs +159 -64
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,33 +1,72 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { join, resolve } from "path";
|
|
2
|
+
import { basename, dirname, join, relative, resolve, sep } from "path";
|
|
3
3
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
4
4
|
import { execSync } from "child_process";
|
|
5
5
|
import { createRequire } from "module";
|
|
6
6
|
import Arborist from "@npmcli/arborist";
|
|
7
7
|
import yaml from "js-yaml";
|
|
8
|
+
//#region src/workspace-path.ts
|
|
9
|
+
/**
|
|
10
|
+
* Normalize a workspace/importer path to a forward-slash relative key.
|
|
11
|
+
* Returns "." for the root (empty input, ".", "./").
|
|
12
|
+
*/
|
|
13
|
+
function normalizeWorkspacePath(p) {
|
|
14
|
+
if (!p || p === "." || p === "./") return ".";
|
|
15
|
+
return p.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+$/, "");
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
8
18
|
//#region src/extract.ts
|
|
9
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Rewrite a package-lock.json location key so the output is a standalone project.
|
|
21
|
+
* Locations inside the chosen workspace get their workspace prefix stripped;
|
|
22
|
+
* hoisted entries at the root stay where they are.
|
|
23
|
+
*/
|
|
24
|
+
function rewritePackageLocation(location, workspacePath) {
|
|
25
|
+
if (workspacePath === ".") return location;
|
|
26
|
+
if (location === workspacePath) return "";
|
|
27
|
+
const prefix = workspacePath + "/";
|
|
28
|
+
if (location.startsWith(prefix)) return location.slice(prefix.length);
|
|
29
|
+
return location;
|
|
30
|
+
}
|
|
31
|
+
async function extractSubset({ projectPath, packageNames, includeOptional = true, workspacePath = "." }) {
|
|
10
32
|
const tree = await new Arborist({ path: projectPath }).loadVirtual();
|
|
11
33
|
const originalLockfileVersion = tree.meta.originalLockfileVersion;
|
|
12
34
|
if (originalLockfileVersion < 2) throw new Error(`Lockfile version ${originalLockfileVersion} is not supported. Please upgrade to npm 7+ (lockfile v2/v3) by running: npm install --package-lock-only`);
|
|
35
|
+
const normalizedWorkspace = normalizeWorkspacePath(workspacePath);
|
|
36
|
+
let startNode = tree;
|
|
37
|
+
if (normalizedWorkspace !== ".") {
|
|
38
|
+
let found;
|
|
39
|
+
for (const child of tree.fsChildren) if (child.location === normalizedWorkspace) {
|
|
40
|
+
found = child;
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
if (!found) {
|
|
44
|
+
const available = [...tree.fsChildren].map((c) => c.location).join(", ");
|
|
45
|
+
throw new Error(`Workspace "${normalizedWorkspace}" not found in package-lock.json. Available workspaces: ${available || "(none)"}`);
|
|
46
|
+
}
|
|
47
|
+
startNode = found;
|
|
48
|
+
}
|
|
13
49
|
const keep = /* @__PURE__ */ new Set();
|
|
14
50
|
for (const name of packageNames) {
|
|
15
|
-
const edge =
|
|
51
|
+
const edge = startNode.edgesOut.get(name);
|
|
16
52
|
if (!edge?.to) throw new Error(`Package "${name}" not found in lockfile`);
|
|
53
|
+
if (edge.type === "workspace" || edge.to.isWorkspace) throw new Error(`Package "${name}" resolves to a workspace, not a published package`);
|
|
17
54
|
const queue = [edge.to];
|
|
18
55
|
while (queue.length > 0) {
|
|
19
56
|
const node = queue.shift();
|
|
20
57
|
if (keep.has(node)) continue;
|
|
58
|
+
if (node.isWorkspace) continue;
|
|
21
59
|
keep.add(node);
|
|
22
60
|
for (const e of node.edgesOut.values()) {
|
|
23
61
|
if (e.type === "dev") continue;
|
|
62
|
+
if (e.type === "workspace") continue;
|
|
24
63
|
if (e.type === "optional" && !includeOptional) continue;
|
|
25
|
-
if (e.to && !keep.has(e.to)) queue.push(e.to);
|
|
64
|
+
if (e.to && !e.to.isWorkspace && !keep.has(e.to)) queue.push(e.to);
|
|
26
65
|
}
|
|
27
66
|
}
|
|
28
67
|
}
|
|
29
68
|
const dependencies = {};
|
|
30
|
-
for (const name of packageNames) dependencies[name] =
|
|
69
|
+
for (const name of packageNames) dependencies[name] = startNode.edgesOut.get(name).to.version;
|
|
31
70
|
const subsetPackages = {};
|
|
32
71
|
subsetPackages[""] = {
|
|
33
72
|
name: "lockfile-subset-output",
|
|
@@ -36,8 +75,10 @@ async function extractSubset({ projectPath, packageNames, includeOptional = true
|
|
|
36
75
|
};
|
|
37
76
|
const originalPackages = tree.meta.data.packages;
|
|
38
77
|
for (const node of keep) {
|
|
39
|
-
const
|
|
40
|
-
if (
|
|
78
|
+
const original = originalPackages[node.location];
|
|
79
|
+
if (!original) continue;
|
|
80
|
+
const rewritten = rewritePackageLocation(node.location, normalizedWorkspace);
|
|
81
|
+
subsetPackages[rewritten] = original;
|
|
41
82
|
}
|
|
42
83
|
const collected = [...keep].map((node) => ({
|
|
43
84
|
name: node.name,
|
|
@@ -77,21 +118,31 @@ function parseSnapshotKey(key) {
|
|
|
77
118
|
function snapshotKey(name, version) {
|
|
78
119
|
return `${name}@${version}`;
|
|
79
120
|
}
|
|
80
|
-
async function extractPnpmSubset({ projectPath, packageNames, includeOptional = true }) {
|
|
121
|
+
async function extractPnpmSubset({ projectPath, packageNames, includeOptional = true, workspacePath = "." }) {
|
|
81
122
|
const content = readFileSync(join(projectPath, "pnpm-lock.yaml"), "utf8");
|
|
82
123
|
const lockfile = yaml.load(content);
|
|
83
124
|
if (!lockfile.lockfileVersion || !String(lockfile.lockfileVersion).startsWith("9")) throw new Error(`pnpm lockfile version ${lockfile.lockfileVersion} is not supported. Please upgrade to pnpm 9+ (lockfile v9).`);
|
|
84
|
-
const
|
|
85
|
-
|
|
125
|
+
const importerKey = normalizeWorkspacePath(workspacePath);
|
|
126
|
+
const importer = lockfile.importers[importerKey];
|
|
127
|
+
if (!importer) {
|
|
128
|
+
const available = Object.keys(lockfile.importers).join(", ");
|
|
129
|
+
throw new Error(`Importer "${importerKey}" not found in pnpm-lock.yaml. Available importers: ${available}`);
|
|
130
|
+
}
|
|
86
131
|
const rootDeps = {};
|
|
87
|
-
|
|
88
|
-
|
|
132
|
+
for (const info of [importer.dependencies, importer.optionalDependencies]) {
|
|
133
|
+
if (!info) continue;
|
|
134
|
+
for (const [name, dep] of Object.entries(info)) rootDeps[name] = {
|
|
135
|
+
specifier: dep.specifier,
|
|
136
|
+
version: dep.version
|
|
137
|
+
};
|
|
138
|
+
}
|
|
89
139
|
const keepSnapshots = /* @__PURE__ */ new Set();
|
|
90
140
|
const keepPackages = /* @__PURE__ */ new Set();
|
|
91
141
|
for (const name of packageNames) {
|
|
92
|
-
const
|
|
93
|
-
if (!
|
|
94
|
-
|
|
142
|
+
const dep = rootDeps[name];
|
|
143
|
+
if (!dep) throw new Error(`Package "${name}" not found in pnpm-lock.yaml`);
|
|
144
|
+
if (dep.version.startsWith("link:")) throw new Error(`Package "${name}" resolves to a workspace (${dep.version}), not a published package`);
|
|
145
|
+
const queue = [snapshotKey(name, dep.version)];
|
|
95
146
|
while (queue.length > 0) {
|
|
96
147
|
const current = queue.shift();
|
|
97
148
|
if (keepSnapshots.has(current)) continue;
|
|
@@ -101,25 +152,27 @@ async function extractPnpmSubset({ projectPath, packageNames, includeOptional =
|
|
|
101
152
|
const snapshot = lockfile.snapshots[current];
|
|
102
153
|
if (!snapshot) continue;
|
|
103
154
|
if (snapshot.dependencies) for (const [depName, depVersion] of Object.entries(snapshot.dependencies)) {
|
|
155
|
+
if (depVersion.startsWith("link:")) continue;
|
|
104
156
|
const depKey = snapshotKey(depName, depVersion);
|
|
105
157
|
if (!keepSnapshots.has(depKey)) queue.push(depKey);
|
|
106
158
|
}
|
|
107
159
|
if (includeOptional && snapshot.optionalDependencies) for (const [depName, depVersion] of Object.entries(snapshot.optionalDependencies)) {
|
|
160
|
+
if (depVersion.startsWith("link:")) continue;
|
|
108
161
|
const depKey = snapshotKey(depName, depVersion);
|
|
109
162
|
if (!keepSnapshots.has(depKey)) queue.push(depKey);
|
|
110
163
|
}
|
|
111
164
|
}
|
|
112
165
|
}
|
|
113
166
|
const dependencies = {};
|
|
114
|
-
for (const name of packageNames) dependencies[name] =
|
|
167
|
+
for (const name of packageNames) dependencies[name] = rootDeps[name].specifier;
|
|
115
168
|
const subsetPackages = {};
|
|
116
169
|
for (const key of keepPackages) if (lockfile.packages[key]) subsetPackages[key] = lockfile.packages[key];
|
|
117
170
|
const subsetSnapshots = {};
|
|
118
171
|
for (const key of keepSnapshots) if (lockfile.snapshots[key]) subsetSnapshots[key] = lockfile.snapshots[key];
|
|
119
172
|
const subsetImporter = { dependencies: {} };
|
|
120
173
|
for (const name of packageNames) subsetImporter.dependencies[name] = {
|
|
121
|
-
specifier:
|
|
122
|
-
version: rootDeps[name]
|
|
174
|
+
specifier: rootDeps[name].specifier,
|
|
175
|
+
version: rootDeps[name].version
|
|
123
176
|
};
|
|
124
177
|
const collected = [...keepPackages].map((key) => {
|
|
125
178
|
const parsed = parseSnapshotKey(key);
|
|
@@ -148,14 +201,15 @@ async function extractPnpmSubset({ projectPath, packageNames, includeOptional =
|
|
|
148
201
|
//#endregion
|
|
149
202
|
//#region src/extract-yarn.ts
|
|
150
203
|
const { parse: parseYarnLockV1, stringify: stringifyYarnLockV1 } = createRequire(import.meta.url)("@yarnpkg/lockfile");
|
|
204
|
+
const OUTPUT_PACKAGE_NAME = "lockfile-subset-output";
|
|
151
205
|
function detectYarnVersion(content) {
|
|
152
206
|
return content.includes("# yarn lockfile v1") ? 1 : 2;
|
|
153
207
|
}
|
|
154
|
-
function extractV1({ projectPath, packageNames, includeOptional, lockfileContent }) {
|
|
208
|
+
function extractV1({ projectPath, packageNames, includeOptional, lockfileContent, workspacePath }) {
|
|
155
209
|
const parsed = parseYarnLockV1(lockfileContent);
|
|
156
210
|
if (parsed.type !== "success") throw new Error(`Failed to parse yarn.lock: ${parsed.type}`);
|
|
157
211
|
const lockfile = parsed.object;
|
|
158
|
-
const pkgJsonPath = join(projectPath, "package.json");
|
|
212
|
+
const pkgJsonPath = join(projectPath, workspacePath, "package.json");
|
|
159
213
|
const pkgJson = JSON.parse(readFileSync(pkgJsonPath, "utf8"));
|
|
160
214
|
const allDeps = {
|
|
161
215
|
...pkgJson.dependencies,
|
|
@@ -166,6 +220,7 @@ function extractV1({ projectPath, packageNames, includeOptional, lockfileContent
|
|
|
166
220
|
for (const name of packageNames) {
|
|
167
221
|
const range = allDeps[name];
|
|
168
222
|
if (!range) throw new Error(`Package "${name}" not found in yarn.lock`);
|
|
223
|
+
if (!lockfile[`${name}@${range}`]) throw new Error(`Package "${name}@${range}" has no lockfile entry. It is likely a workspace dependency, not a published package.`);
|
|
169
224
|
const queue = [`${name}@${range}`];
|
|
170
225
|
while (queue.length > 0) {
|
|
171
226
|
const key = queue.shift();
|
|
@@ -190,7 +245,7 @@ function extractV1({ projectPath, packageNames, includeOptional, lockfileContent
|
|
|
190
245
|
const subset = {};
|
|
191
246
|
for (const key of keepKeys) subset[key] = lockfile[key];
|
|
192
247
|
const dependencies = {};
|
|
193
|
-
for (const name of packageNames) dependencies[name] =
|
|
248
|
+
for (const name of packageNames) dependencies[name] = allDeps[name];
|
|
194
249
|
const seen = /* @__PURE__ */ new Set();
|
|
195
250
|
const deduped = collected.filter((c) => {
|
|
196
251
|
const key = `${c.name}@${c.version}`;
|
|
@@ -202,7 +257,7 @@ function extractV1({ projectPath, packageNames, includeOptional, lockfileContent
|
|
|
202
257
|
type: "yarn",
|
|
203
258
|
yarnVersion: 1,
|
|
204
259
|
packageJson: {
|
|
205
|
-
name:
|
|
260
|
+
name: OUTPUT_PACKAGE_NAME,
|
|
206
261
|
version: "1.0.0",
|
|
207
262
|
dependencies
|
|
208
263
|
},
|
|
@@ -210,7 +265,7 @@ function extractV1({ projectPath, packageNames, includeOptional, lockfileContent
|
|
|
210
265
|
collected: deduped
|
|
211
266
|
};
|
|
212
267
|
}
|
|
213
|
-
function extractBerry({ projectPath, packageNames, includeOptional, lockfileContent }) {
|
|
268
|
+
function extractBerry({ projectPath, packageNames, includeOptional, lockfileContent, workspacePath }) {
|
|
214
269
|
const lockfile = yaml.load(lockfileContent);
|
|
215
270
|
const descriptorMap = /* @__PURE__ */ new Map();
|
|
216
271
|
for (const [compoundKey, entry] of Object.entries(lockfile)) {
|
|
@@ -221,7 +276,7 @@ function extractBerry({ projectPath, packageNames, includeOptional, lockfileCont
|
|
|
221
276
|
originalKey: compoundKey
|
|
222
277
|
});
|
|
223
278
|
}
|
|
224
|
-
const pkgJsonPath = join(projectPath, "package.json");
|
|
279
|
+
const pkgJsonPath = join(projectPath, workspacePath, "package.json");
|
|
225
280
|
const pkgJson = JSON.parse(readFileSync(pkgJsonPath, "utf8"));
|
|
226
281
|
const allDeps = {
|
|
227
282
|
...pkgJson.dependencies,
|
|
@@ -233,6 +288,7 @@ function extractBerry({ projectPath, packageNames, includeOptional, lockfileCont
|
|
|
233
288
|
for (const name of packageNames) {
|
|
234
289
|
const range = allDeps[name];
|
|
235
290
|
if (!range) throw new Error(`Package "${name}" not found in yarn.lock`);
|
|
291
|
+
if (range.startsWith("workspace:")) throw new Error(`Package "${name}" resolves to a workspace, not a published package`);
|
|
236
292
|
const queue = [`${name}@npm:${range}`];
|
|
237
293
|
while (queue.length > 0) {
|
|
238
294
|
const desc = queue.shift();
|
|
@@ -246,20 +302,19 @@ function extractBerry({ projectPath, packageNames, includeOptional, lockfileCont
|
|
|
246
302
|
version: match.entry.version
|
|
247
303
|
});
|
|
248
304
|
if (match.entry.dependencies) for (const [depName, depRange] of Object.entries(match.entry.dependencies)) {
|
|
305
|
+
if (depRange.startsWith("workspace:")) continue;
|
|
249
306
|
const depDesc = `${depName}@${depRange}`;
|
|
250
307
|
if (!visited.has(depDesc)) queue.push(depDesc);
|
|
251
308
|
}
|
|
252
309
|
if (includeOptional && match.entry.optionalDependencies) for (const [depName, depRange] of Object.entries(match.entry.optionalDependencies)) {
|
|
310
|
+
if (depRange.startsWith("workspace:")) continue;
|
|
253
311
|
const depDesc = `${depName}@${depRange}`;
|
|
254
312
|
if (!visited.has(depDesc)) queue.push(depDesc);
|
|
255
313
|
}
|
|
256
314
|
}
|
|
257
315
|
}
|
|
258
316
|
const dependencies = {};
|
|
259
|
-
for (const name of packageNames)
|
|
260
|
-
const descriptor = `${name}@npm:${allDeps[name]}`;
|
|
261
|
-
dependencies[name] = descriptorMap.get(descriptor).entry.version;
|
|
262
|
-
}
|
|
317
|
+
for (const name of packageNames) dependencies[name] = allDeps[name];
|
|
263
318
|
const lines = [];
|
|
264
319
|
lines.push("# This file is generated by running \"yarn install\" inside your project.");
|
|
265
320
|
lines.push("# Manual changes might be lost - proceed with caution!");
|
|
@@ -271,7 +326,22 @@ function extractBerry({ projectPath, packageNames, includeOptional, lockfileCont
|
|
|
271
326
|
if (metadata.cacheKey) lines.push(` cacheKey: ${metadata.cacheKey}`);
|
|
272
327
|
lines.push("");
|
|
273
328
|
}
|
|
274
|
-
|
|
329
|
+
const rootWorkspaceKey = `${OUTPUT_PACKAGE_NAME}@workspace:.`;
|
|
330
|
+
const sortedKeys = [...keepOriginalKeys, rootWorkspaceKey].sort();
|
|
331
|
+
for (const originalKey of sortedKeys) {
|
|
332
|
+
if (originalKey === rootWorkspaceKey) {
|
|
333
|
+
lines.push(`"${originalKey}":`);
|
|
334
|
+
lines.push(" version: 0.0.0-use.local");
|
|
335
|
+
lines.push(` resolution: "${originalKey}"`);
|
|
336
|
+
if (Object.keys(dependencies).length > 0) {
|
|
337
|
+
lines.push(" dependencies:");
|
|
338
|
+
for (const [k, v] of Object.entries(dependencies)) lines.push(` ${k}: "npm:${v}"`);
|
|
339
|
+
}
|
|
340
|
+
lines.push(" languageName: unknown");
|
|
341
|
+
lines.push(" linkType: soft");
|
|
342
|
+
lines.push("");
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
275
345
|
const entry = lockfile[originalKey];
|
|
276
346
|
lines.push(`"${originalKey}":`);
|
|
277
347
|
lines.push(` version: ${entry.version}`);
|
|
@@ -312,7 +382,7 @@ function extractBerry({ projectPath, packageNames, includeOptional, lockfileCont
|
|
|
312
382
|
type: "yarn",
|
|
313
383
|
yarnVersion: 2,
|
|
314
384
|
packageJson: {
|
|
315
|
-
name:
|
|
385
|
+
name: OUTPUT_PACKAGE_NAME,
|
|
316
386
|
version: "1.0.0",
|
|
317
387
|
dependencies
|
|
318
388
|
},
|
|
@@ -328,19 +398,23 @@ function parseDescriptorName(descriptor) {
|
|
|
328
398
|
if (lastAt > 0) return descriptor.slice(0, lastAt);
|
|
329
399
|
return descriptor;
|
|
330
400
|
}
|
|
331
|
-
async function extractYarnSubset({ projectPath, packageNames, includeOptional = true }) {
|
|
401
|
+
async function extractYarnSubset({ projectPath, packageNames, includeOptional = true, workspacePath = "." }) {
|
|
332
402
|
const lockfileContent = readFileSync(join(projectPath, "yarn.lock"), "utf8");
|
|
333
|
-
|
|
403
|
+
const version = detectYarnVersion(lockfileContent);
|
|
404
|
+
const normalizedWorkspace = normalizeWorkspacePath(workspacePath);
|
|
405
|
+
if (version === 1) return extractV1({
|
|
334
406
|
projectPath,
|
|
335
407
|
packageNames,
|
|
336
408
|
includeOptional,
|
|
337
|
-
lockfileContent
|
|
409
|
+
lockfileContent,
|
|
410
|
+
workspacePath: normalizedWorkspace
|
|
338
411
|
});
|
|
339
412
|
else return extractBerry({
|
|
340
413
|
projectPath,
|
|
341
414
|
packageNames,
|
|
342
415
|
includeOptional,
|
|
343
|
-
lockfileContent
|
|
416
|
+
lockfileContent,
|
|
417
|
+
workspacePath: normalizedWorkspace
|
|
344
418
|
});
|
|
345
419
|
}
|
|
346
420
|
//#endregion
|
|
@@ -411,37 +485,48 @@ function parseArgs(argv) {
|
|
|
411
485
|
}
|
|
412
486
|
return args;
|
|
413
487
|
}
|
|
488
|
+
const LOCKFILE_BASENAMES = {
|
|
489
|
+
"pnpm-lock.yaml": "pnpm",
|
|
490
|
+
"yarn.lock": "yarn",
|
|
491
|
+
"package-lock.json": "npm"
|
|
492
|
+
};
|
|
493
|
+
/** Walk up from `start` looking for any known lockfile. Returns null if none found. */
|
|
494
|
+
function findLockfileUpwards(start) {
|
|
495
|
+
let dir = start;
|
|
496
|
+
while (true) {
|
|
497
|
+
for (const [name, type] of Object.entries(LOCKFILE_BASENAMES)) if (existsSync(resolve(dir, name))) return {
|
|
498
|
+
projectPath: dir,
|
|
499
|
+
type
|
|
500
|
+
};
|
|
501
|
+
const parent = dirname(dir);
|
|
502
|
+
if (parent === dir) return null;
|
|
503
|
+
dir = parent;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
414
506
|
function resolveLockfile(lockfilePath) {
|
|
415
507
|
if (!lockfilePath) {
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
};
|
|
420
|
-
if (existsSync(resolve("yarn.lock"))) return {
|
|
421
|
-
projectPath: resolve("."),
|
|
422
|
-
type: "yarn"
|
|
423
|
-
};
|
|
424
|
-
if (existsSync(resolve("package-lock.json"))) return {
|
|
425
|
-
projectPath: resolve("."),
|
|
426
|
-
type: "npm"
|
|
427
|
-
};
|
|
428
|
-
throw new Error("No lockfile found in current directory. Expected package-lock.json, pnpm-lock.yaml, or yarn.lock.");
|
|
508
|
+
const found = findLockfileUpwards(resolve("."));
|
|
509
|
+
if (found) return found;
|
|
510
|
+
throw new Error("No lockfile found in current directory or any parent. Expected package-lock.json, pnpm-lock.yaml, or yarn.lock.");
|
|
429
511
|
}
|
|
430
512
|
const resolved = resolve(lockfilePath);
|
|
431
|
-
const
|
|
432
|
-
if (
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
if (basename === "yarn.lock") return {
|
|
437
|
-
projectPath: resolve(resolved, ".."),
|
|
438
|
-
type: "yarn"
|
|
439
|
-
};
|
|
440
|
-
if (basename === "package-lock.json") return {
|
|
441
|
-
projectPath: resolve(resolved, ".."),
|
|
442
|
-
type: "npm"
|
|
513
|
+
const type = LOCKFILE_BASENAMES[basename(resolved)];
|
|
514
|
+
if (!type) throw new Error(`Invalid lockfile path: ${lockfilePath}. Expected a path to package-lock.json, pnpm-lock.yaml, or yarn.lock.`);
|
|
515
|
+
return {
|
|
516
|
+
projectPath: dirname(resolved),
|
|
517
|
+
type
|
|
443
518
|
};
|
|
444
|
-
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Resolve the workspace path (relative to projectPath, forward slashes).
|
|
522
|
+
* Inferred from process.cwd() vs the lockfile's project directory: if cwd
|
|
523
|
+
* sits inside a sub-workspace, that path is used; otherwise "." (root).
|
|
524
|
+
*/
|
|
525
|
+
function resolveWorkspacePath(projectPath) {
|
|
526
|
+
const rel = relative(projectPath, resolve("."));
|
|
527
|
+
if (rel === "" || rel === ".") return ".";
|
|
528
|
+
if (rel.startsWith("..")) return ".";
|
|
529
|
+
return rel.split(sep).join("/");
|
|
445
530
|
}
|
|
446
531
|
const HELP = `
|
|
447
532
|
lockfile-subset <packages...> [options]
|
|
@@ -453,7 +538,7 @@ Arguments:
|
|
|
453
538
|
packages Package names to extract (one or more, space-separated)
|
|
454
539
|
|
|
455
540
|
Options:
|
|
456
|
-
--lockfile, -l <path> Path to lockfile (auto-detected from cwd
|
|
541
|
+
--lockfile, -l <path> Path to lockfile (auto-detected by walking up from cwd)
|
|
457
542
|
--output, -o <dir> Output directory (default: ./lockfile-subset-output)
|
|
458
543
|
--no-optional Exclude optional dependencies
|
|
459
544
|
--install Run npm ci / pnpm install / yarn install after generating
|
|
@@ -467,6 +552,11 @@ Examples:
|
|
|
467
552
|
lockfile-subset @prisma/client sharp -l /build/package-lock.json
|
|
468
553
|
lockfile-subset @prisma/client sharp -l pnpm-lock.yaml --install
|
|
469
554
|
lockfile-subset chalk --dry-run
|
|
555
|
+
|
|
556
|
+
Monorepos: cd into the target workspace and run as usual.
|
|
557
|
+
The lockfile is found by walking up from the current directory, and the
|
|
558
|
+
sub-workspace is inferred from cwd relative to the lockfile.
|
|
559
|
+
cd apps/web && lockfile-subset next
|
|
470
560
|
`.trim();
|
|
471
561
|
async function main() {
|
|
472
562
|
const args = parseArgs(process.argv.slice(2));
|
|
@@ -484,22 +574,27 @@ async function main() {
|
|
|
484
574
|
process.exit(1);
|
|
485
575
|
}
|
|
486
576
|
const { projectPath, type } = resolveLockfile(args.lockfile);
|
|
577
|
+
const workspacePath = resolveWorkspacePath(projectPath);
|
|
487
578
|
const outputDir = resolve(args.output);
|
|
579
|
+
if (workspacePath !== ".") console.log(`Using workspace: ${workspacePath} (lockfile root: ${projectPath})`);
|
|
488
580
|
let result;
|
|
489
581
|
if (type === "pnpm") result = await extractPnpmSubset({
|
|
490
582
|
projectPath,
|
|
491
583
|
packageNames: args.packages,
|
|
492
|
-
includeOptional: args.includeOptional
|
|
584
|
+
includeOptional: args.includeOptional,
|
|
585
|
+
workspacePath
|
|
493
586
|
});
|
|
494
587
|
else if (type === "yarn") result = await extractYarnSubset({
|
|
495
588
|
projectPath,
|
|
496
589
|
packageNames: args.packages,
|
|
497
|
-
includeOptional: args.includeOptional
|
|
590
|
+
includeOptional: args.includeOptional,
|
|
591
|
+
workspacePath
|
|
498
592
|
});
|
|
499
593
|
else result = await extractSubset({
|
|
500
594
|
projectPath,
|
|
501
595
|
packageNames: args.packages,
|
|
502
|
-
includeOptional: args.includeOptional
|
|
596
|
+
includeOptional: args.includeOptional,
|
|
597
|
+
workspacePath
|
|
503
598
|
});
|
|
504
599
|
console.log(`Collected ${result.collected.length} packages (${args.packages.length} direct, ${result.collected.length - args.packages.length} transitive)`);
|
|
505
600
|
if (args.dryRun) {
|
package/package.json
CHANGED