lockfile-subset 1.3.1 → 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 +64 -11
- 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
|
};
|
|
@@ -215,9 +260,10 @@ function extractV1({ projectPath, packageNames, includeOptional, lockfileContent
|
|
|
215
260
|
...pkgJson.dependencies,
|
|
216
261
|
...pkgJson.optionalDependencies
|
|
217
262
|
};
|
|
263
|
+
const resolvedNames = expandWildcards(packageNames, Object.keys(allDeps));
|
|
218
264
|
const keepKeys = /* @__PURE__ */ new Set();
|
|
219
265
|
const collected = [];
|
|
220
|
-
for (const name of
|
|
266
|
+
for (const name of resolvedNames) {
|
|
221
267
|
const range = allDeps[name];
|
|
222
268
|
if (!range) throw new Error(`Package "${name}" not found in yarn.lock`);
|
|
223
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.`);
|
|
@@ -245,7 +291,7 @@ function extractV1({ projectPath, packageNames, includeOptional, lockfileContent
|
|
|
245
291
|
const subset = {};
|
|
246
292
|
for (const key of keepKeys) subset[key] = lockfile[key];
|
|
247
293
|
const dependencies = {};
|
|
248
|
-
for (const name of
|
|
294
|
+
for (const name of resolvedNames) dependencies[name] = allDeps[name];
|
|
249
295
|
const seen = /* @__PURE__ */ new Set();
|
|
250
296
|
const deduped = collected.filter((c) => {
|
|
251
297
|
const key = `${c.name}@${c.version}`;
|
|
@@ -282,10 +328,11 @@ function extractBerry({ projectPath, packageNames, includeOptional, lockfileCont
|
|
|
282
328
|
...pkgJson.dependencies,
|
|
283
329
|
...pkgJson.optionalDependencies
|
|
284
330
|
};
|
|
331
|
+
const resolvedNames = expandWildcards(packageNames, Object.keys(allDeps));
|
|
285
332
|
const keepOriginalKeys = /* @__PURE__ */ new Set();
|
|
286
333
|
const visited = /* @__PURE__ */ new Set();
|
|
287
334
|
const collected = [];
|
|
288
|
-
for (const name of
|
|
335
|
+
for (const name of resolvedNames) {
|
|
289
336
|
const range = allDeps[name];
|
|
290
337
|
if (!range) throw new Error(`Package "${name}" not found in yarn.lock`);
|
|
291
338
|
if (range.startsWith("workspace:")) throw new Error(`Package "${name}" resolves to a workspace, not a published package`);
|
|
@@ -314,7 +361,7 @@ function extractBerry({ projectPath, packageNames, includeOptional, lockfileCont
|
|
|
314
361
|
}
|
|
315
362
|
}
|
|
316
363
|
const dependencies = {};
|
|
317
|
-
for (const name of
|
|
364
|
+
for (const name of resolvedNames) dependencies[name] = allDeps[name];
|
|
318
365
|
const lines = [];
|
|
319
366
|
lines.push("# This file is generated by running \"yarn install\" inside your project.");
|
|
320
367
|
lines.push("# Manual changes might be lost - proceed with caution!");
|
|
@@ -535,7 +582,11 @@ Extract a subset of package-lock.json, pnpm-lock.yaml, or yarn.lock for specifie
|
|
|
535
582
|
packages and their transitive dependencies.
|
|
536
583
|
|
|
537
584
|
Arguments:
|
|
538
|
-
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.
|
|
539
590
|
|
|
540
591
|
Options:
|
|
541
592
|
--lockfile, -l <path> Path to lockfile (auto-detected by walking up from cwd)
|
|
@@ -552,6 +603,7 @@ Examples:
|
|
|
552
603
|
lockfile-subset @prisma/client sharp -l /build/package-lock.json
|
|
553
604
|
lockfile-subset @prisma/client sharp -l pnpm-lock.yaml --install
|
|
554
605
|
lockfile-subset chalk --dry-run
|
|
606
|
+
lockfile-subset '@aws-sdk/*' sharp
|
|
555
607
|
|
|
556
608
|
Monorepos: cd into the target workspace and run as usual.
|
|
557
609
|
The lockfile is found by walking up from the current directory, and the
|
|
@@ -596,7 +648,8 @@ async function main() {
|
|
|
596
648
|
includeOptional: args.includeOptional,
|
|
597
649
|
workspacePath
|
|
598
650
|
});
|
|
599
|
-
|
|
651
|
+
const directCount = Object.keys(result.packageJson.dependencies).length;
|
|
652
|
+
console.log(`Collected ${result.collected.length} packages (${directCount} direct, ${result.collected.length - directCount} transitive)`);
|
|
600
653
|
if (args.dryRun) {
|
|
601
654
|
console.log("\n--- package.json ---");
|
|
602
655
|
console.log(JSON.stringify(result.packageJson, null, 2));
|
package/package.json
CHANGED