lockfile-subset 1.0.2 → 1.2.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.
Files changed (3) hide show
  1. package/README.md +20 -24
  2. package/dist/index.mjs +388 -20
  3. package/package.json +9 -3
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # lockfile-subset
2
2
 
3
- Extract a subset of `package-lock.json` for specified packages and their transitive dependencies.
3
+ Extract a subset of `package-lock.json` or `pnpm-lock.yaml` for specified packages and their transitive dependencies.
4
4
 
5
5
  ## Why?
6
6
 
@@ -11,8 +11,9 @@ When using bundlers like esbuild with `--external`, you need to ship those exter
11
11
  | Manually copy `node_modules` dirs | Breaks when transitive deps change (e.g., Prisma v6 added new deps) |
12
12
  | `npm install <pkg>` in runner stage | Resolves versions independently — may differ from your lockfile |
13
13
  | `npm ci --omit=dev` | Installs *all* prod dependencies, not just the ones you need |
14
+ | `pnpm deploy` | Only works with workspaces, not arbitrary packages |
14
15
 
15
- **lockfile-subset** solves this by extracting a precise subset from your existing `package-lock.json` — only the packages you specify and their transitive dependencies, with versions exactly matching the original lockfile.
16
+ **lockfile-subset** solves this by extracting a precise subset from your existing lockfile — only the packages you specify and their transitive dependencies, with versions exactly matching the original lockfile.
16
17
 
17
18
  ## Install
18
19
 
@@ -32,16 +33,19 @@ lockfile-subset @prisma/client sharp
32
33
  lockfile-subset @prisma/client sharp -o /standalone
33
34
 
34
35
  # Use a different lockfile path
35
- lockfile-subset @prisma/client sharp --lockfile /build/package-lock.json
36
+ lockfile-subset @prisma/client sharp -l /build/package-lock.json
37
+
38
+ # Use a pnpm lockfile
39
+ lockfile-subset @prisma/client sharp -l pnpm-lock.yaml
36
40
 
37
41
  # Generate + install in one step
38
42
  lockfile-subset @prisma/client sharp -o /standalone --install
39
43
 
40
44
  # Preview without writing files
41
- lockfile-subset prisma --dry-run
45
+ lockfile-subset chalk --dry-run
42
46
  ```
43
47
 
44
- This generates a minimal `package.json` and `package-lock.json` in the output directory. Then run `npm ci` to install exactly those packages at the exact versions from your original lockfile.
48
+ The lockfile type (npm or pnpm) is auto-detected from the project directory. This generates a minimal `package.json` and lockfile in the output directory. Then run `npm ci` or `pnpm install --frozen-lockfile` to install exactly those packages.
45
49
 
46
50
  ### Dockerfile example
47
51
 
@@ -73,35 +77,27 @@ CMD ["node", "dist/index.js"]
73
77
 
74
78
  ### Options
75
79
 
76
- ```
77
- lockfile-subset <packages...> [options]
78
-
79
- Arguments:
80
- packages Package names to extract (space-separated)
81
-
82
- Options:
83
- --lockfile, -l <path> Path to project directory (default: .)
84
- --output, -o <dir> Output directory (default: ./lockfile-subset-output)
85
- --no-optional Exclude optional dependencies
86
- --install Run npm ci after generating the subset
87
- --dry-run Print the result without writing files
88
- --version, -v Show version
89
- --help, -h Show help
90
- ```
80
+ Run `lockfile-subset --help` for the full list of options.
91
81
 
92
82
  ## How it works
93
83
 
94
- 1. Loads your `package-lock.json` using [`@npmcli/arborist`](https://github.com/npm/cli/tree/latest/workspaces/arborist) (npm's own dependency resolver)
84
+ 1. Loads your lockfile (`package-lock.json` via [@npmcli/arborist](https://github.com/npm/cli/tree/latest/workspaces/arborist), or `pnpm-lock.yaml` directly)
95
85
  2. Starting from the specified packages, walks the dependency tree via BFS to collect all transitive dependencies
96
86
  3. Copies the matching entries from the original lockfile — no re-resolution, no version drift
97
- 4. Outputs a minimal `package.json` + `package-lock.json` ready for `npm ci`
87
+ 4. Outputs a minimal `package.json` + lockfile ready for `npm ci` or `pnpm install --frozen-lockfile`
98
88
 
99
89
  Dev dependencies of each package are excluded from traversal. Optional dependencies are included by default (use `--no-optional` to exclude).
100
90
 
91
+ ## Supported lockfile formats
92
+
93
+ | Package manager | Lockfile | Supported versions |
94
+ |---|---|---|
95
+ | npm | `package-lock.json` | v2 (npm 7-8), v3 (npm 9+) |
96
+ | pnpm | `pnpm-lock.yaml` | v9 (pnpm 9-10) |
97
+
101
98
  ## Limitations
102
99
 
103
- - **Lockfile v2/v3 only** — Requires npm 7+ (lockfile v2 or v3). The legacy v1 format (npm 5-6) is not supported.
104
- - **npm only** — pnpm and yarn have different lockfile formats. pnpm users can use `pnpm deploy`; yarn users can use `yarn workspaces focus`.
100
+ - **yarn is not supported** — yarn users can use `yarn workspaces focus`.
105
101
  - **Platform-specific optional deps** — Packages like `sharp` have OS/arch-specific optional dependencies (e.g., `@img/sharp-linux-x64`). If your lockfile was generated on macOS but you run `npm ci` on Linux (e.g., in Docker), those Linux-specific packages may be missing from the lockfile. In that case, generate the lockfile on the target platform, or use `npm install` instead of `npm ci`.
106
102
 
107
103
  ## License
package/dist/index.mjs CHANGED
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import { join, resolve } from "path";
3
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
3
4
  import { execSync } from "child_process";
4
5
  import { createRequire } from "module";
5
6
  import Arborist from "@npmcli/arborist";
6
- import { mkdirSync, writeFileSync } from "fs";
7
+ import yaml from "js-yaml";
7
8
  //#region src/extract.ts
8
9
  async function extractSubset({ projectPath, packageNames, includeOptional = true }) {
9
10
  const tree = await new Arborist({ path: projectPath }).loadVirtual();
@@ -44,6 +45,7 @@ async function extractSubset({ projectPath, packageNames, includeOptional = true
44
45
  location: node.location
45
46
  }));
46
47
  return {
48
+ type: "npm",
47
49
  packageJson: {
48
50
  name: "lockfile-subset-output",
49
51
  version: "1.0.0",
@@ -60,11 +62,300 @@ async function extractSubset({ projectPath, packageNames, includeOptional = true
60
62
  };
61
63
  }
62
64
  //#endregion
65
+ //#region src/extract-pnpm.ts
66
+ /** Parse "name@version" or "@scope/name@version" into [name, version] */
67
+ function parseSnapshotKey(key) {
68
+ const withoutPeers = key.replace(/\(.*\)$/, "");
69
+ const lastAt = withoutPeers.lastIndexOf("@");
70
+ if (lastAt <= 0) throw new Error(`Invalid snapshot key: ${key}`);
71
+ return {
72
+ name: withoutPeers.slice(0, lastAt),
73
+ version: withoutPeers.slice(lastAt + 1)
74
+ };
75
+ }
76
+ /** Build snapshot key from name and version */
77
+ function snapshotKey(name, version) {
78
+ return `${name}@${version}`;
79
+ }
80
+ async function extractPnpmSubset({ projectPath, packageNames, includeOptional = true }) {
81
+ const content = readFileSync(join(projectPath, "pnpm-lock.yaml"), "utf8");
82
+ const lockfile = yaml.load(content);
83
+ 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 rootImporter = lockfile.importers["."];
85
+ if (!rootImporter) throw new Error("No root importer found in pnpm-lock.yaml");
86
+ const rootDeps = {};
87
+ if (rootImporter.dependencies) for (const [name, info] of Object.entries(rootImporter.dependencies)) rootDeps[name] = info.version;
88
+ if (rootImporter.optionalDependencies) for (const [name, info] of Object.entries(rootImporter.optionalDependencies)) rootDeps[name] = info.version;
89
+ const keepSnapshots = /* @__PURE__ */ new Set();
90
+ const keepPackages = /* @__PURE__ */ new Set();
91
+ for (const name of packageNames) {
92
+ const version = rootDeps[name];
93
+ if (!version) throw new Error(`Package "${name}" not found in pnpm-lock.yaml`);
94
+ const queue = [snapshotKey(name, version)];
95
+ while (queue.length > 0) {
96
+ const current = queue.shift();
97
+ if (keepSnapshots.has(current)) continue;
98
+ keepSnapshots.add(current);
99
+ const parsed = parseSnapshotKey(current);
100
+ keepPackages.add(snapshotKey(parsed.name, parsed.version));
101
+ const snapshot = lockfile.snapshots[current];
102
+ if (!snapshot) continue;
103
+ if (snapshot.dependencies) for (const [depName, depVersion] of Object.entries(snapshot.dependencies)) {
104
+ const depKey = snapshotKey(depName, depVersion);
105
+ if (!keepSnapshots.has(depKey)) queue.push(depKey);
106
+ }
107
+ if (includeOptional && snapshot.optionalDependencies) for (const [depName, depVersion] of Object.entries(snapshot.optionalDependencies)) {
108
+ const depKey = snapshotKey(depName, depVersion);
109
+ if (!keepSnapshots.has(depKey)) queue.push(depKey);
110
+ }
111
+ }
112
+ }
113
+ const dependencies = {};
114
+ for (const name of packageNames) dependencies[name] = parseSnapshotKey(snapshotKey(name, rootDeps[name])).version;
115
+ const subsetPackages = {};
116
+ for (const key of keepPackages) if (lockfile.packages[key]) subsetPackages[key] = lockfile.packages[key];
117
+ const subsetSnapshots = {};
118
+ for (const key of keepSnapshots) if (lockfile.snapshots[key]) subsetSnapshots[key] = lockfile.snapshots[key];
119
+ const subsetImporter = { dependencies: {} };
120
+ for (const name of packageNames) subsetImporter.dependencies[name] = {
121
+ specifier: dependencies[name],
122
+ version: rootDeps[name]
123
+ };
124
+ const collected = [...keepPackages].map((key) => {
125
+ const parsed = parseSnapshotKey(key);
126
+ return {
127
+ name: parsed.name,
128
+ version: parsed.version
129
+ };
130
+ });
131
+ return {
132
+ type: "pnpm",
133
+ packageJson: {
134
+ name: "lockfile-subset-output",
135
+ version: "1.0.0",
136
+ dependencies
137
+ },
138
+ lockfileYaml: {
139
+ lockfileVersion: lockfile.lockfileVersion,
140
+ settings: lockfile.settings,
141
+ importers: { ".": subsetImporter },
142
+ packages: subsetPackages,
143
+ snapshots: subsetSnapshots
144
+ },
145
+ collected
146
+ };
147
+ }
148
+ //#endregion
149
+ //#region src/extract-yarn.ts
150
+ const { parse: parseYarnLockV1, stringify: stringifyYarnLockV1 } = createRequire(import.meta.url)("@yarnpkg/lockfile");
151
+ function detectYarnVersion(content) {
152
+ return content.includes("# yarn lockfile v1") ? 1 : 2;
153
+ }
154
+ function extractV1({ projectPath, packageNames, includeOptional, lockfileContent }) {
155
+ const parsed = parseYarnLockV1(lockfileContent);
156
+ if (parsed.type !== "success") throw new Error(`Failed to parse yarn.lock: ${parsed.type}`);
157
+ const lockfile = parsed.object;
158
+ const pkgJsonPath = join(projectPath, "package.json");
159
+ const pkgJson = JSON.parse(readFileSync(pkgJsonPath, "utf8"));
160
+ const allDeps = {
161
+ ...pkgJson.dependencies,
162
+ ...pkgJson.optionalDependencies
163
+ };
164
+ const keepKeys = /* @__PURE__ */ new Set();
165
+ const collected = [];
166
+ for (const name of packageNames) {
167
+ const range = allDeps[name];
168
+ if (!range) throw new Error(`Package "${name}" not found in yarn.lock`);
169
+ const queue = [`${name}@${range}`];
170
+ while (queue.length > 0) {
171
+ const key = queue.shift();
172
+ if (keepKeys.has(key)) continue;
173
+ const entry = lockfile[key];
174
+ if (!entry) continue;
175
+ keepKeys.add(key);
176
+ collected.push({
177
+ name: key.slice(0, key.lastIndexOf("@")),
178
+ version: entry.version
179
+ });
180
+ if (entry.dependencies) for (const [depName, depRange] of Object.entries(entry.dependencies)) {
181
+ const depKey = `${depName}@${depRange}`;
182
+ if (!keepKeys.has(depKey)) queue.push(depKey);
183
+ }
184
+ if (includeOptional && entry.optionalDependencies) for (const [depName, depRange] of Object.entries(entry.optionalDependencies)) {
185
+ const depKey = `${depName}@${depRange}`;
186
+ if (!keepKeys.has(depKey)) queue.push(depKey);
187
+ }
188
+ }
189
+ }
190
+ const subset = {};
191
+ for (const key of keepKeys) subset[key] = lockfile[key];
192
+ const dependencies = {};
193
+ for (const name of packageNames) dependencies[name] = lockfile[`${name}@${allDeps[name]}`].version;
194
+ const seen = /* @__PURE__ */ new Set();
195
+ const deduped = collected.filter((c) => {
196
+ const key = `${c.name}@${c.version}`;
197
+ if (seen.has(key)) return false;
198
+ seen.add(key);
199
+ return true;
200
+ });
201
+ return {
202
+ type: "yarn",
203
+ yarnVersion: 1,
204
+ packageJson: {
205
+ name: "lockfile-subset-output",
206
+ version: "1.0.0",
207
+ dependencies
208
+ },
209
+ lockfileContent: stringifyYarnLockV1(subset),
210
+ collected: deduped
211
+ };
212
+ }
213
+ function extractBerry({ projectPath, packageNames, includeOptional, lockfileContent }) {
214
+ const lockfile = yaml.load(lockfileContent);
215
+ const descriptorMap = /* @__PURE__ */ new Map();
216
+ for (const [compoundKey, entry] of Object.entries(lockfile)) {
217
+ if (compoundKey === "__metadata") continue;
218
+ const descriptors = compoundKey.split(", ");
219
+ for (const descriptor of descriptors) descriptorMap.set(descriptor, {
220
+ entry,
221
+ originalKey: compoundKey
222
+ });
223
+ }
224
+ const pkgJsonPath = join(projectPath, "package.json");
225
+ const pkgJson = JSON.parse(readFileSync(pkgJsonPath, "utf8"));
226
+ const allDeps = {
227
+ ...pkgJson.dependencies,
228
+ ...pkgJson.optionalDependencies
229
+ };
230
+ const keepOriginalKeys = /* @__PURE__ */ new Set();
231
+ const visited = /* @__PURE__ */ new Set();
232
+ const collected = [];
233
+ for (const name of packageNames) {
234
+ const range = allDeps[name];
235
+ if (!range) throw new Error(`Package "${name}" not found in yarn.lock`);
236
+ const queue = [`${name}@npm:${range}`];
237
+ while (queue.length > 0) {
238
+ const desc = queue.shift();
239
+ if (visited.has(desc)) continue;
240
+ visited.add(desc);
241
+ const match = descriptorMap.get(desc);
242
+ if (!match) continue;
243
+ keepOriginalKeys.add(match.originalKey);
244
+ collected.push({
245
+ name: parseDescriptorName(desc),
246
+ version: match.entry.version
247
+ });
248
+ if (match.entry.dependencies) for (const [depName, depRange] of Object.entries(match.entry.dependencies)) {
249
+ const depDesc = `${depName}@${depRange}`;
250
+ if (!visited.has(depDesc)) queue.push(depDesc);
251
+ }
252
+ if (includeOptional && match.entry.optionalDependencies) for (const [depName, depRange] of Object.entries(match.entry.optionalDependencies)) {
253
+ const depDesc = `${depName}@${depRange}`;
254
+ if (!visited.has(depDesc)) queue.push(depDesc);
255
+ }
256
+ }
257
+ }
258
+ const dependencies = {};
259
+ for (const name of packageNames) {
260
+ const descriptor = `${name}@npm:${allDeps[name]}`;
261
+ dependencies[name] = descriptorMap.get(descriptor).entry.version;
262
+ }
263
+ const lines = [];
264
+ lines.push("# This file is generated by running \"yarn install\" inside your project.");
265
+ lines.push("# Manual changes might be lost - proceed with caution!");
266
+ lines.push("");
267
+ const metadata = lockfile.__metadata;
268
+ if (metadata) {
269
+ lines.push("__metadata:");
270
+ lines.push(` version: ${metadata.version}`);
271
+ if (metadata.cacheKey) lines.push(` cacheKey: ${metadata.cacheKey}`);
272
+ lines.push("");
273
+ }
274
+ for (const originalKey of keepOriginalKeys) {
275
+ const entry = lockfile[originalKey];
276
+ lines.push(`"${originalKey}":`);
277
+ lines.push(` version: ${entry.version}`);
278
+ lines.push(` resolution: "${entry.resolution}"`);
279
+ if (entry.dependencies && Object.keys(entry.dependencies).length > 0) {
280
+ lines.push(" dependencies:");
281
+ for (const [k, v] of Object.entries(entry.dependencies)) lines.push(` ${k}: "${v}"`);
282
+ }
283
+ if (entry.optionalDependencies && Object.keys(entry.optionalDependencies).length > 0) {
284
+ lines.push(" optionalDependencies:");
285
+ for (const [k, v] of Object.entries(entry.optionalDependencies)) lines.push(` ${k}: "${v}"`);
286
+ }
287
+ if (entry.dependenciesMeta && Object.keys(entry.dependenciesMeta).length > 0) {
288
+ lines.push(" dependenciesMeta:");
289
+ for (const [k, v] of Object.entries(entry.dependenciesMeta)) {
290
+ lines.push(` ${k}:`);
291
+ if (v.optional !== void 0) lines.push(` optional: ${v.optional}`);
292
+ }
293
+ }
294
+ if (entry.bin && Object.keys(entry.bin).length > 0) {
295
+ lines.push(" bin:");
296
+ for (const [k, v] of Object.entries(entry.bin)) lines.push(` ${k}: ${v}`);
297
+ }
298
+ if (entry.conditions) lines.push(` conditions: ${entry.conditions}`);
299
+ if (entry.checksum) lines.push(` checksum: ${entry.checksum}`);
300
+ lines.push(` languageName: ${entry.languageName || "node"}`);
301
+ lines.push(` linkType: ${entry.linkType || "hard"}`);
302
+ lines.push("");
303
+ }
304
+ const seen = /* @__PURE__ */ new Set();
305
+ const deduped = collected.filter((c) => {
306
+ const key = `${c.name}@${c.version}`;
307
+ if (seen.has(key)) return false;
308
+ seen.add(key);
309
+ return true;
310
+ });
311
+ return {
312
+ type: "yarn",
313
+ yarnVersion: 2,
314
+ packageJson: {
315
+ name: "lockfile-subset-output",
316
+ version: "1.0.0",
317
+ dependencies
318
+ },
319
+ lockfileContent: lines.join("\n"),
320
+ collected: deduped
321
+ };
322
+ }
323
+ /** Extract package name from a descriptor like "chalk@npm:4.1.2" or "@scope/pkg@npm:^1.0.0" */
324
+ function parseDescriptorName(descriptor) {
325
+ const npmIdx = descriptor.indexOf("@npm:");
326
+ if (npmIdx > 0) return descriptor.slice(0, npmIdx);
327
+ const lastAt = descriptor.lastIndexOf("@");
328
+ if (lastAt > 0) return descriptor.slice(0, lastAt);
329
+ return descriptor;
330
+ }
331
+ async function extractYarnSubset({ projectPath, packageNames, includeOptional = true }) {
332
+ const lockfileContent = readFileSync(join(projectPath, "yarn.lock"), "utf8");
333
+ if (detectYarnVersion(lockfileContent) === 1) return extractV1({
334
+ projectPath,
335
+ packageNames,
336
+ includeOptional,
337
+ lockfileContent
338
+ });
339
+ else return extractBerry({
340
+ projectPath,
341
+ packageNames,
342
+ includeOptional,
343
+ lockfileContent
344
+ });
345
+ }
346
+ //#endregion
63
347
  //#region src/write.ts
64
348
  function writeOutput(outputDir, result) {
65
349
  mkdirSync(outputDir, { recursive: true });
66
350
  writeFileSync(join(outputDir, "package.json"), JSON.stringify(result.packageJson, null, 2) + "\n");
67
- writeFileSync(join(outputDir, "package-lock.json"), JSON.stringify(result.lockfileJson, null, 2) + "\n");
351
+ if (result.type === "npm") writeFileSync(join(outputDir, "package-lock.json"), JSON.stringify(result.lockfileJson, null, 2) + "\n");
352
+ else if (result.type === "pnpm") writeFileSync(join(outputDir, "pnpm-lock.yaml"), yaml.dump(result.lockfileYaml, {
353
+ lineWidth: -1,
354
+ noCompatMode: true,
355
+ quotingType: "'",
356
+ forceQuotes: false
357
+ }));
358
+ else writeFileSync(join(outputDir, "yarn.lock"), result.lockfileContent);
68
359
  }
69
360
  //#endregion
70
361
  //#region src/index.ts
@@ -72,7 +363,7 @@ const { version: VERSION } = createRequire(import.meta.url)("../package.json");
72
363
  function parseArgs(argv) {
73
364
  const args = {
74
365
  packages: [],
75
- lockfile: ".",
366
+ lockfile: "",
76
367
  output: "./lockfile-subset-output",
77
368
  includeOptional: true,
78
369
  install: false,
@@ -120,29 +411,62 @@ function parseArgs(argv) {
120
411
  }
121
412
  return args;
122
413
  }
414
+ function resolveLockfile(lockfilePath) {
415
+ if (!lockfilePath) {
416
+ if (existsSync(resolve("pnpm-lock.yaml"))) return {
417
+ projectPath: resolve("."),
418
+ type: "pnpm"
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.");
429
+ }
430
+ const resolved = resolve(lockfilePath);
431
+ const basename = resolved.split("/").pop();
432
+ if (basename === "pnpm-lock.yaml") return {
433
+ projectPath: resolve(resolved, ".."),
434
+ type: "pnpm"
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"
443
+ };
444
+ throw new Error(`Invalid lockfile path: ${lockfilePath}. Expected a path to package-lock.json, pnpm-lock.yaml, or yarn.lock.`);
445
+ }
123
446
  const HELP = `
124
447
  lockfile-subset <packages...> [options]
125
448
 
126
- Extract a subset of package-lock.json for specified packages and their transitive dependencies.
449
+ Extract a subset of package-lock.json, pnpm-lock.yaml, or yarn.lock for specified
450
+ packages and their transitive dependencies.
127
451
 
128
452
  Arguments:
129
453
  packages Package names to extract (one or more, space-separated)
130
454
 
131
455
  Options:
132
- --lockfile, -l <path> Path to project dir or package-lock.json (default: .)
456
+ --lockfile, -l <path> Path to lockfile (auto-detected from cwd by default)
133
457
  --output, -o <dir> Output directory (default: ./lockfile-subset-output)
134
458
  --no-optional Exclude optional dependencies
135
- --install Run npm ci after generating the subset
459
+ --install Run npm ci / pnpm install / yarn install after generating
136
460
  --dry-run Print the result without writing files
137
461
  --version, -v Show version
138
462
  --help, -h Show this help
139
463
 
140
464
  Examples:
141
- lockfile-subset prisma sharp
142
- lockfile-subset prisma sharp -o /lambda-standalone
143
- lockfile-subset prisma sharp --lockfile /build/package-lock.json
144
- lockfile-subset prisma sharp -o /lambda-standalone --install
145
- lockfile-subset prisma --dry-run
465
+ lockfile-subset @prisma/client sharp
466
+ lockfile-subset @prisma/client sharp -o /standalone
467
+ lockfile-subset @prisma/client sharp -l /build/package-lock.json
468
+ lockfile-subset @prisma/client sharp -l pnpm-lock.yaml --install
469
+ lockfile-subset chalk --dry-run
146
470
  `.trim();
147
471
  async function main() {
148
472
  const args = parseArgs(process.argv.slice(2));
@@ -159,9 +483,20 @@ async function main() {
159
483
  console.log(HELP);
160
484
  process.exit(1);
161
485
  }
162
- const projectPath = resolve(args.lockfile);
486
+ const { projectPath, type } = resolveLockfile(args.lockfile);
163
487
  const outputDir = resolve(args.output);
164
- const result = await extractSubset({
488
+ let result;
489
+ if (type === "pnpm") result = await extractPnpmSubset({
490
+ projectPath,
491
+ packageNames: args.packages,
492
+ includeOptional: args.includeOptional
493
+ });
494
+ else if (type === "yarn") result = await extractYarnSubset({
495
+ projectPath,
496
+ packageNames: args.packages,
497
+ includeOptional: args.includeOptional
498
+ });
499
+ else result = await extractSubset({
165
500
  projectPath,
166
501
  packageNames: args.packages,
167
502
  includeOptional: args.includeOptional
@@ -170,18 +505,51 @@ async function main() {
170
505
  if (args.dryRun) {
171
506
  console.log("\n--- package.json ---");
172
507
  console.log(JSON.stringify(result.packageJson, null, 2));
173
- console.log("\n--- package-lock.json ---");
174
- console.log(JSON.stringify(result.lockfileJson, null, 2));
508
+ if (result.type === "npm") {
509
+ console.log("\n--- package-lock.json ---");
510
+ console.log(JSON.stringify(result.lockfileJson, null, 2));
511
+ } else if (result.type === "pnpm") {
512
+ const yaml = (await import("js-yaml")).default;
513
+ console.log("\n--- pnpm-lock.yaml ---");
514
+ console.log(yaml.dump(result.lockfileYaml, {
515
+ lineWidth: -1,
516
+ noCompatMode: true
517
+ }));
518
+ } else {
519
+ console.log("\n--- yarn.lock ---");
520
+ console.log(result.lockfileContent);
521
+ }
175
522
  return;
176
523
  }
177
524
  writeOutput(outputDir, result);
178
525
  console.log(`Written to ${outputDir}`);
179
526
  if (args.install) {
180
- console.log("Running npm ci...");
181
- execSync("npm ci", {
182
- cwd: outputDir,
183
- stdio: "inherit"
184
- });
527
+ if (type === "pnpm") {
528
+ console.log("Running pnpm install --frozen-lockfile...");
529
+ execSync("pnpm install --frozen-lockfile", {
530
+ cwd: outputDir,
531
+ stdio: "inherit"
532
+ });
533
+ } else if (type === "yarn") if (result.type === "yarn" && result.yarnVersion === 1) {
534
+ console.log("Running yarn install --frozen-lockfile...");
535
+ execSync("yarn install --frozen-lockfile", {
536
+ cwd: outputDir,
537
+ stdio: "inherit"
538
+ });
539
+ } else {
540
+ console.log("Running yarn install --immutable...");
541
+ execSync("yarn install --immutable", {
542
+ cwd: outputDir,
543
+ stdio: "inherit"
544
+ });
545
+ }
546
+ else {
547
+ console.log("Running npm ci...");
548
+ execSync("npm ci", {
549
+ cwd: outputDir,
550
+ stdio: "inherit"
551
+ });
552
+ }
185
553
  console.log("Done.");
186
554
  }
187
555
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "lockfile-subset",
3
- "version": "1.0.2",
4
- "description": "Extract a subset of package-lock.json for specified packages and their transitive dependencies",
3
+ "version": "1.2.0",
4
+ "description": "Extract a subset of package-lock.json, pnpm-lock.yaml, or yarn.lock for specified packages and their transitive dependencies",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "lockfile-subset": "./dist/index.mjs"
@@ -16,6 +16,8 @@
16
16
  },
17
17
  "keywords": [
18
18
  "npm",
19
+ "pnpm",
20
+ "yarn",
19
21
  "lockfile",
20
22
  "package-lock",
21
23
  "subset",
@@ -33,14 +35,18 @@
33
35
  "devDependencies": {
34
36
  "@semantic-release/changelog": "^6.0.3",
35
37
  "@semantic-release/git": "^10.0.1",
38
+ "@types/js-yaml": "^4.0.9",
36
39
  "@types/node": "^25.5.0",
37
40
  "@types/npmcli__arborist": "^6.3.3",
41
+ "@types/yarnpkg__lockfile": "^1.1.9",
38
42
  "semantic-release": "^25.0.3",
39
43
  "tsdown": "^0.21.4",
40
44
  "typescript": "^5.9.3",
41
45
  "vitest": "^4.1.0"
42
46
  },
43
47
  "dependencies": {
44
- "@npmcli/arborist": "^9.4.2"
48
+ "@npmcli/arborist": "^9.4.2",
49
+ "@yarnpkg/lockfile": "^1.1.0",
50
+ "js-yaml": "^4.1.1"
45
51
  }
46
52
  }