lockfile-subset 1.1.0 → 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.
- package/dist/index.mjs +235 -7
- package/package.json +5 -2
package/dist/index.mjs
CHANGED
|
@@ -146,17 +146,216 @@ async function extractPnpmSubset({ projectPath, packageNames, includeOptional =
|
|
|
146
146
|
};
|
|
147
147
|
}
|
|
148
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
|
|
149
347
|
//#region src/write.ts
|
|
150
348
|
function writeOutput(outputDir, result) {
|
|
151
349
|
mkdirSync(outputDir, { recursive: true });
|
|
152
350
|
writeFileSync(join(outputDir, "package.json"), JSON.stringify(result.packageJson, null, 2) + "\n");
|
|
153
351
|
if (result.type === "npm") writeFileSync(join(outputDir, "package-lock.json"), JSON.stringify(result.lockfileJson, null, 2) + "\n");
|
|
154
|
-
else writeFileSync(join(outputDir, "pnpm-lock.yaml"), yaml.dump(result.lockfileYaml, {
|
|
352
|
+
else if (result.type === "pnpm") writeFileSync(join(outputDir, "pnpm-lock.yaml"), yaml.dump(result.lockfileYaml, {
|
|
155
353
|
lineWidth: -1,
|
|
156
354
|
noCompatMode: true,
|
|
157
355
|
quotingType: "'",
|
|
158
356
|
forceQuotes: false
|
|
159
357
|
}));
|
|
358
|
+
else writeFileSync(join(outputDir, "yarn.lock"), result.lockfileContent);
|
|
160
359
|
}
|
|
161
360
|
//#endregion
|
|
162
361
|
//#region src/index.ts
|
|
@@ -218,11 +417,15 @@ function resolveLockfile(lockfilePath) {
|
|
|
218
417
|
projectPath: resolve("."),
|
|
219
418
|
type: "pnpm"
|
|
220
419
|
};
|
|
420
|
+
if (existsSync(resolve("yarn.lock"))) return {
|
|
421
|
+
projectPath: resolve("."),
|
|
422
|
+
type: "yarn"
|
|
423
|
+
};
|
|
221
424
|
if (existsSync(resolve("package-lock.json"))) return {
|
|
222
425
|
projectPath: resolve("."),
|
|
223
426
|
type: "npm"
|
|
224
427
|
};
|
|
225
|
-
throw new Error("No lockfile found in current directory. Expected package-lock.json
|
|
428
|
+
throw new Error("No lockfile found in current directory. Expected package-lock.json, pnpm-lock.yaml, or yarn.lock.");
|
|
226
429
|
}
|
|
227
430
|
const resolved = resolve(lockfilePath);
|
|
228
431
|
const basename = resolved.split("/").pop();
|
|
@@ -230,17 +433,21 @@ function resolveLockfile(lockfilePath) {
|
|
|
230
433
|
projectPath: resolve(resolved, ".."),
|
|
231
434
|
type: "pnpm"
|
|
232
435
|
};
|
|
436
|
+
if (basename === "yarn.lock") return {
|
|
437
|
+
projectPath: resolve(resolved, ".."),
|
|
438
|
+
type: "yarn"
|
|
439
|
+
};
|
|
233
440
|
if (basename === "package-lock.json") return {
|
|
234
441
|
projectPath: resolve(resolved, ".."),
|
|
235
442
|
type: "npm"
|
|
236
443
|
};
|
|
237
|
-
throw new Error(`Invalid lockfile path: ${lockfilePath}. Expected a path to package-lock.json
|
|
444
|
+
throw new Error(`Invalid lockfile path: ${lockfilePath}. Expected a path to package-lock.json, pnpm-lock.yaml, or yarn.lock.`);
|
|
238
445
|
}
|
|
239
446
|
const HELP = `
|
|
240
447
|
lockfile-subset <packages...> [options]
|
|
241
448
|
|
|
242
|
-
Extract a subset of package-lock.json
|
|
243
|
-
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.
|
|
244
451
|
|
|
245
452
|
Arguments:
|
|
246
453
|
packages Package names to extract (one or more, space-separated)
|
|
@@ -249,7 +456,7 @@ Options:
|
|
|
249
456
|
--lockfile, -l <path> Path to lockfile (auto-detected from cwd by default)
|
|
250
457
|
--output, -o <dir> Output directory (default: ./lockfile-subset-output)
|
|
251
458
|
--no-optional Exclude optional dependencies
|
|
252
|
-
--install Run npm ci / pnpm install
|
|
459
|
+
--install Run npm ci / pnpm install / yarn install after generating
|
|
253
460
|
--dry-run Print the result without writing files
|
|
254
461
|
--version, -v Show version
|
|
255
462
|
--help, -h Show this help
|
|
@@ -284,6 +491,11 @@ async function main() {
|
|
|
284
491
|
packageNames: args.packages,
|
|
285
492
|
includeOptional: args.includeOptional
|
|
286
493
|
});
|
|
494
|
+
else if (type === "yarn") result = await extractYarnSubset({
|
|
495
|
+
projectPath,
|
|
496
|
+
packageNames: args.packages,
|
|
497
|
+
includeOptional: args.includeOptional
|
|
498
|
+
});
|
|
287
499
|
else result = await extractSubset({
|
|
288
500
|
projectPath,
|
|
289
501
|
packageNames: args.packages,
|
|
@@ -296,13 +508,16 @@ async function main() {
|
|
|
296
508
|
if (result.type === "npm") {
|
|
297
509
|
console.log("\n--- package-lock.json ---");
|
|
298
510
|
console.log(JSON.stringify(result.lockfileJson, null, 2));
|
|
299
|
-
} else {
|
|
511
|
+
} else if (result.type === "pnpm") {
|
|
300
512
|
const yaml = (await import("js-yaml")).default;
|
|
301
513
|
console.log("\n--- pnpm-lock.yaml ---");
|
|
302
514
|
console.log(yaml.dump(result.lockfileYaml, {
|
|
303
515
|
lineWidth: -1,
|
|
304
516
|
noCompatMode: true
|
|
305
517
|
}));
|
|
518
|
+
} else {
|
|
519
|
+
console.log("\n--- yarn.lock ---");
|
|
520
|
+
console.log(result.lockfileContent);
|
|
306
521
|
}
|
|
307
522
|
return;
|
|
308
523
|
}
|
|
@@ -315,7 +530,20 @@ async function main() {
|
|
|
315
530
|
cwd: outputDir,
|
|
316
531
|
stdio: "inherit"
|
|
317
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
|
+
});
|
|
318
539
|
} else {
|
|
540
|
+
console.log("Running yarn install --immutable...");
|
|
541
|
+
execSync("yarn install --immutable", {
|
|
542
|
+
cwd: outputDir,
|
|
543
|
+
stdio: "inherit"
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
else {
|
|
319
547
|
console.log("Running npm ci...");
|
|
320
548
|
execSync("npm ci", {
|
|
321
549
|
cwd: outputDir,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lockfile-subset",
|
|
3
|
-
"version": "1.
|
|
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"
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"keywords": [
|
|
18
18
|
"npm",
|
|
19
19
|
"pnpm",
|
|
20
|
+
"yarn",
|
|
20
21
|
"lockfile",
|
|
21
22
|
"package-lock",
|
|
22
23
|
"subset",
|
|
@@ -37,6 +38,7 @@
|
|
|
37
38
|
"@types/js-yaml": "^4.0.9",
|
|
38
39
|
"@types/node": "^25.5.0",
|
|
39
40
|
"@types/npmcli__arborist": "^6.3.3",
|
|
41
|
+
"@types/yarnpkg__lockfile": "^1.1.9",
|
|
40
42
|
"semantic-release": "^25.0.3",
|
|
41
43
|
"tsdown": "^0.21.4",
|
|
42
44
|
"typescript": "^5.9.3",
|
|
@@ -44,6 +46,7 @@
|
|
|
44
46
|
},
|
|
45
47
|
"dependencies": {
|
|
46
48
|
"@npmcli/arborist": "^9.4.2",
|
|
49
|
+
"@yarnpkg/lockfile": "^1.1.0",
|
|
47
50
|
"js-yaml": "^4.1.1"
|
|
48
51
|
}
|
|
49
52
|
}
|