lockfile-subset 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/dist/index.mjs +83 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -46,6 +46,10 @@ lockfile-subset @prisma/client sharp -o /standalone --install
|
|
|
46
46
|
|
|
47
47
|
# Preview without writing files
|
|
48
48
|
lockfile-subset chalk --dry-run
|
|
49
|
+
|
|
50
|
+
# esbuild-style wildcards (single "*") match against direct dependencies.
|
|
51
|
+
# Quote them so the shell doesn't expand them.
|
|
52
|
+
lockfile-subset '@aws-sdk/*' sharp
|
|
49
53
|
```
|
|
50
54
|
|
|
51
55
|
The lockfile type (npm, pnpm, or yarn) is auto-detected from the project directory. This generates a minimal `package.json` and lockfile in the output directory. Then run `npm ci`, `pnpm install --frozen-lockfile`, or `yarn install --frozen-lockfile` to install exactly those packages.
|
package/dist/index.mjs
CHANGED
|
@@ -15,6 +15,47 @@ function normalizeWorkspacePath(p) {
|
|
|
15
15
|
return p.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+$/, "");
|
|
16
16
|
}
|
|
17
17
|
//#endregion
|
|
18
|
+
//#region src/wildcard.ts
|
|
19
|
+
/**
|
|
20
|
+
* esbuild-style wildcard matching for package names. A pattern may contain
|
|
21
|
+
* at most one `*`, which matches any (possibly empty) substring. Patterns
|
|
22
|
+
* without `*` are treated as exact names.
|
|
23
|
+
*/
|
|
24
|
+
function matchesPattern(pattern, name) {
|
|
25
|
+
const star = pattern.indexOf("*");
|
|
26
|
+
if (star === -1) return pattern === name;
|
|
27
|
+
if (pattern.indexOf("*", star + 1) !== -1) throw new Error(`Pattern "${pattern}" has more than one "*"; only one wildcard is supported.`);
|
|
28
|
+
const prefix = pattern.slice(0, star);
|
|
29
|
+
const suffix = pattern.slice(star + 1);
|
|
30
|
+
return name.length >= prefix.length + suffix.length && name.startsWith(prefix) && name.endsWith(suffix);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Expand wildcard patterns against the set of available (root-level) package
|
|
34
|
+
* names. Literal patterns pass through unchanged. Wildcard patterns that
|
|
35
|
+
* match nothing throw, so typos surface as errors rather than silent no-ops.
|
|
36
|
+
*/
|
|
37
|
+
function expandWildcards(patterns, available) {
|
|
38
|
+
const availableArr = [...available];
|
|
39
|
+
const result = [];
|
|
40
|
+
const seen = /* @__PURE__ */ new Set();
|
|
41
|
+
for (const pattern of patterns) {
|
|
42
|
+
if (!pattern.includes("*")) {
|
|
43
|
+
if (!seen.has(pattern)) {
|
|
44
|
+
seen.add(pattern);
|
|
45
|
+
result.push(pattern);
|
|
46
|
+
}
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const matched = availableArr.filter((name) => matchesPattern(pattern, name));
|
|
50
|
+
if (matched.length === 0) throw new Error(`Pattern "${pattern}" did not match any direct dependency.`);
|
|
51
|
+
for (const name of matched) if (!seen.has(name)) {
|
|
52
|
+
seen.add(name);
|
|
53
|
+
result.push(name);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
18
59
|
//#region src/extract.ts
|
|
19
60
|
/**
|
|
20
61
|
* Rewrite a package-lock.json location key so the output is a standalone project.
|
|
@@ -46,8 +87,11 @@ async function extractSubset({ projectPath, packageNames, includeOptional = true
|
|
|
46
87
|
}
|
|
47
88
|
startNode = found;
|
|
48
89
|
}
|
|
90
|
+
const nonDevDirectDeps = [];
|
|
91
|
+
for (const [name, edge] of startNode.edgesOut) if (edge.type !== "dev") nonDevDirectDeps.push(name);
|
|
92
|
+
const resolvedNames = expandWildcards(packageNames, nonDevDirectDeps);
|
|
49
93
|
const keep = /* @__PURE__ */ new Set();
|
|
50
|
-
for (const name of
|
|
94
|
+
for (const name of resolvedNames) {
|
|
51
95
|
const edge = startNode.edgesOut.get(name);
|
|
52
96
|
if (!edge?.to) throw new Error(`Package "${name}" not found in lockfile`);
|
|
53
97
|
if (edge.type === "workspace" || edge.to.isWorkspace) throw new Error(`Package "${name}" resolves to a workspace, not a published package`);
|
|
@@ -66,7 +110,7 @@ async function extractSubset({ projectPath, packageNames, includeOptional = true
|
|
|
66
110
|
}
|
|
67
111
|
}
|
|
68
112
|
const dependencies = {};
|
|
69
|
-
for (const name of
|
|
113
|
+
for (const name of resolvedNames) dependencies[name] = startNode.edgesOut.get(name).to.version;
|
|
70
114
|
const subsetPackages = {};
|
|
71
115
|
subsetPackages[""] = {
|
|
72
116
|
name: "lockfile-subset-output",
|
|
@@ -136,9 +180,10 @@ async function extractPnpmSubset({ projectPath, packageNames, includeOptional =
|
|
|
136
180
|
version: dep.version
|
|
137
181
|
};
|
|
138
182
|
}
|
|
183
|
+
const resolvedNames = expandWildcards(packageNames, Object.keys(rootDeps));
|
|
139
184
|
const keepSnapshots = /* @__PURE__ */ new Set();
|
|
140
185
|
const keepPackages = /* @__PURE__ */ new Set();
|
|
141
|
-
for (const name of
|
|
186
|
+
for (const name of resolvedNames) {
|
|
142
187
|
const dep = rootDeps[name];
|
|
143
188
|
if (!dep) throw new Error(`Package "${name}" not found in pnpm-lock.yaml`);
|
|
144
189
|
if (dep.version.startsWith("link:")) throw new Error(`Package "${name}" resolves to a workspace (${dep.version}), not a published package`);
|
|
@@ -164,13 +209,13 @@ async function extractPnpmSubset({ projectPath, packageNames, includeOptional =
|
|
|
164
209
|
}
|
|
165
210
|
}
|
|
166
211
|
const dependencies = {};
|
|
167
|
-
for (const name of
|
|
212
|
+
for (const name of resolvedNames) dependencies[name] = rootDeps[name].specifier;
|
|
168
213
|
const subsetPackages = {};
|
|
169
214
|
for (const key of keepPackages) if (lockfile.packages[key]) subsetPackages[key] = lockfile.packages[key];
|
|
170
215
|
const subsetSnapshots = {};
|
|
171
216
|
for (const key of keepSnapshots) if (lockfile.snapshots[key]) subsetSnapshots[key] = lockfile.snapshots[key];
|
|
172
217
|
const subsetImporter = { dependencies: {} };
|
|
173
|
-
for (const name of
|
|
218
|
+
for (const name of resolvedNames) subsetImporter.dependencies[name] = {
|
|
174
219
|
specifier: rootDeps[name].specifier,
|
|
175
220
|
version: rootDeps[name].version
|
|
176
221
|
};
|
|
@@ -201,6 +246,7 @@ async function extractPnpmSubset({ projectPath, packageNames, includeOptional =
|
|
|
201
246
|
//#endregion
|
|
202
247
|
//#region src/extract-yarn.ts
|
|
203
248
|
const { parse: parseYarnLockV1, stringify: stringifyYarnLockV1 } = createRequire(import.meta.url)("@yarnpkg/lockfile");
|
|
249
|
+
const OUTPUT_PACKAGE_NAME = "lockfile-subset-output";
|
|
204
250
|
function detectYarnVersion(content) {
|
|
205
251
|
return content.includes("# yarn lockfile v1") ? 1 : 2;
|
|
206
252
|
}
|
|
@@ -214,9 +260,10 @@ function extractV1({ projectPath, packageNames, includeOptional, lockfileContent
|
|
|
214
260
|
...pkgJson.dependencies,
|
|
215
261
|
...pkgJson.optionalDependencies
|
|
216
262
|
};
|
|
263
|
+
const resolvedNames = expandWildcards(packageNames, Object.keys(allDeps));
|
|
217
264
|
const keepKeys = /* @__PURE__ */ new Set();
|
|
218
265
|
const collected = [];
|
|
219
|
-
for (const name of
|
|
266
|
+
for (const name of resolvedNames) {
|
|
220
267
|
const range = allDeps[name];
|
|
221
268
|
if (!range) throw new Error(`Package "${name}" not found in yarn.lock`);
|
|
222
269
|
if (!lockfile[`${name}@${range}`]) throw new Error(`Package "${name}@${range}" has no lockfile entry. It is likely a workspace dependency, not a published package.`);
|
|
@@ -244,7 +291,7 @@ function extractV1({ projectPath, packageNames, includeOptional, lockfileContent
|
|
|
244
291
|
const subset = {};
|
|
245
292
|
for (const key of keepKeys) subset[key] = lockfile[key];
|
|
246
293
|
const dependencies = {};
|
|
247
|
-
for (const name of
|
|
294
|
+
for (const name of resolvedNames) dependencies[name] = allDeps[name];
|
|
248
295
|
const seen = /* @__PURE__ */ new Set();
|
|
249
296
|
const deduped = collected.filter((c) => {
|
|
250
297
|
const key = `${c.name}@${c.version}`;
|
|
@@ -256,7 +303,7 @@ function extractV1({ projectPath, packageNames, includeOptional, lockfileContent
|
|
|
256
303
|
type: "yarn",
|
|
257
304
|
yarnVersion: 1,
|
|
258
305
|
packageJson: {
|
|
259
|
-
name:
|
|
306
|
+
name: OUTPUT_PACKAGE_NAME,
|
|
260
307
|
version: "1.0.0",
|
|
261
308
|
dependencies
|
|
262
309
|
},
|
|
@@ -281,10 +328,11 @@ function extractBerry({ projectPath, packageNames, includeOptional, lockfileCont
|
|
|
281
328
|
...pkgJson.dependencies,
|
|
282
329
|
...pkgJson.optionalDependencies
|
|
283
330
|
};
|
|
331
|
+
const resolvedNames = expandWildcards(packageNames, Object.keys(allDeps));
|
|
284
332
|
const keepOriginalKeys = /* @__PURE__ */ new Set();
|
|
285
333
|
const visited = /* @__PURE__ */ new Set();
|
|
286
334
|
const collected = [];
|
|
287
|
-
for (const name of
|
|
335
|
+
for (const name of resolvedNames) {
|
|
288
336
|
const range = allDeps[name];
|
|
289
337
|
if (!range) throw new Error(`Package "${name}" not found in yarn.lock`);
|
|
290
338
|
if (range.startsWith("workspace:")) throw new Error(`Package "${name}" resolves to a workspace, not a published package`);
|
|
@@ -313,7 +361,7 @@ function extractBerry({ projectPath, packageNames, includeOptional, lockfileCont
|
|
|
313
361
|
}
|
|
314
362
|
}
|
|
315
363
|
const dependencies = {};
|
|
316
|
-
for (const name of
|
|
364
|
+
for (const name of resolvedNames) dependencies[name] = allDeps[name];
|
|
317
365
|
const lines = [];
|
|
318
366
|
lines.push("# This file is generated by running \"yarn install\" inside your project.");
|
|
319
367
|
lines.push("# Manual changes might be lost - proceed with caution!");
|
|
@@ -325,7 +373,22 @@ function extractBerry({ projectPath, packageNames, includeOptional, lockfileCont
|
|
|
325
373
|
if (metadata.cacheKey) lines.push(` cacheKey: ${metadata.cacheKey}`);
|
|
326
374
|
lines.push("");
|
|
327
375
|
}
|
|
328
|
-
|
|
376
|
+
const rootWorkspaceKey = `${OUTPUT_PACKAGE_NAME}@workspace:.`;
|
|
377
|
+
const sortedKeys = [...keepOriginalKeys, rootWorkspaceKey].sort();
|
|
378
|
+
for (const originalKey of sortedKeys) {
|
|
379
|
+
if (originalKey === rootWorkspaceKey) {
|
|
380
|
+
lines.push(`"${originalKey}":`);
|
|
381
|
+
lines.push(" version: 0.0.0-use.local");
|
|
382
|
+
lines.push(` resolution: "${originalKey}"`);
|
|
383
|
+
if (Object.keys(dependencies).length > 0) {
|
|
384
|
+
lines.push(" dependencies:");
|
|
385
|
+
for (const [k, v] of Object.entries(dependencies)) lines.push(` ${k}: "npm:${v}"`);
|
|
386
|
+
}
|
|
387
|
+
lines.push(" languageName: unknown");
|
|
388
|
+
lines.push(" linkType: soft");
|
|
389
|
+
lines.push("");
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
329
392
|
const entry = lockfile[originalKey];
|
|
330
393
|
lines.push(`"${originalKey}":`);
|
|
331
394
|
lines.push(` version: ${entry.version}`);
|
|
@@ -366,7 +429,7 @@ function extractBerry({ projectPath, packageNames, includeOptional, lockfileCont
|
|
|
366
429
|
type: "yarn",
|
|
367
430
|
yarnVersion: 2,
|
|
368
431
|
packageJson: {
|
|
369
|
-
name:
|
|
432
|
+
name: OUTPUT_PACKAGE_NAME,
|
|
370
433
|
version: "1.0.0",
|
|
371
434
|
dependencies
|
|
372
435
|
},
|
|
@@ -519,7 +582,11 @@ Extract a subset of package-lock.json, pnpm-lock.yaml, or yarn.lock for specifie
|
|
|
519
582
|
packages and their transitive dependencies.
|
|
520
583
|
|
|
521
584
|
Arguments:
|
|
522
|
-
packages Package names to extract (one or more, space-separated)
|
|
585
|
+
packages Package names to extract (one or more, space-separated).
|
|
586
|
+
esbuild-style wildcards (single "*") are expanded
|
|
587
|
+
against the workspace's direct dependencies, e.g.
|
|
588
|
+
"@aws-sdk/*" or "*-loader". Quote patterns to keep
|
|
589
|
+
the shell from globbing them.
|
|
523
590
|
|
|
524
591
|
Options:
|
|
525
592
|
--lockfile, -l <path> Path to lockfile (auto-detected by walking up from cwd)
|
|
@@ -536,6 +603,7 @@ Examples:
|
|
|
536
603
|
lockfile-subset @prisma/client sharp -l /build/package-lock.json
|
|
537
604
|
lockfile-subset @prisma/client sharp -l pnpm-lock.yaml --install
|
|
538
605
|
lockfile-subset chalk --dry-run
|
|
606
|
+
lockfile-subset '@aws-sdk/*' sharp
|
|
539
607
|
|
|
540
608
|
Monorepos: cd into the target workspace and run as usual.
|
|
541
609
|
The lockfile is found by walking up from the current directory, and the
|
|
@@ -580,7 +648,8 @@ async function main() {
|
|
|
580
648
|
includeOptional: args.includeOptional,
|
|
581
649
|
workspacePath
|
|
582
650
|
});
|
|
583
|
-
|
|
651
|
+
const directCount = Object.keys(result.packageJson.dependencies).length;
|
|
652
|
+
console.log(`Collected ${result.collected.length} packages (${directCount} direct, ${result.collected.length - directCount} transitive)`);
|
|
584
653
|
if (args.dryRun) {
|
|
585
654
|
console.log("\n--- package.json ---");
|
|
586
655
|
console.log(JSON.stringify(result.packageJson, null, 2));
|
package/package.json
CHANGED