lockdelta 0.1.0 → 0.1.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/cli.js CHANGED
@@ -10,152 +10,148 @@ import { readFileSync } from "fs";
10
10
  // src/core/report.ts
11
11
  import { posix as posix2 } from "path";
12
12
 
13
- // src/ecosystems/python/parsers/toml.ts
14
- import { parse } from "smol-toml";
15
- function parseTomlPackages(content) {
16
- try {
17
- const data = parse(content);
18
- const packages = {};
19
- for (const pkg of [...data.package ?? [], ...data.packages ?? []]) {
20
- if (typeof pkg.name === "string" && typeof pkg.version === "string") {
21
- packages[pkg.name] = pkg.version;
22
- }
23
- }
24
- return packages;
25
- } catch {
26
- return parseTomlPackagesRegex(content);
27
- }
28
- }
29
- function parseTomlPackagesRegex(content) {
30
- const packages = {};
31
- const blocks = content.split(/\[\[packages?\]\]/);
32
- for (const block of blocks) {
33
- const nameMatch = block.match(/\nname\s*=\s*"([^"]+)"/);
34
- const versionMatch = block.match(/\nversion\s*=\s*"([^"]+)"/);
35
- if (nameMatch && versionMatch) {
36
- packages[nameMatch[1]] = versionMatch[1];
37
- }
38
- }
39
- return packages;
40
- }
41
-
42
- // src/ecosystems/python/pyproject.ts
43
- import { parse as parse2 } from "smol-toml";
44
- function normalizePythonName(name) {
45
- return name.toLowerCase().replace(/[-_.]+/g, "_");
46
- }
47
- function extractPkgName(dep) {
48
- const match = String(dep).match(/^([\w][\w.-]*)/);
49
- return match ? normalizePythonName(match[1]) : null;
13
+ // src/ecosystems/deno/deno-json.ts
14
+ function normalizeDenoName(name) {
15
+ return name.toLowerCase();
50
16
  }
51
17
  function parseDirectDeps(content) {
52
18
  const prod = /* @__PURE__ */ new Set();
53
- const dev = /* @__PURE__ */ new Set();
54
19
  let data;
55
20
  try {
56
- data = parse2(content);
21
+ data = JSON.parse(content);
57
22
  } catch {
58
- return { prod, dev };
59
- }
60
- const project = data["project"];
61
- const pep517Deps = project?.["dependencies"];
62
- if (Array.isArray(pep517Deps)) {
63
- for (const dep of pep517Deps) {
64
- const name = extractPkgName(dep);
65
- if (name) prod.add(name);
66
- }
23
+ return { prod, dev: /* @__PURE__ */ new Set() };
67
24
  }
68
- const optDeps = project?.["optional-dependencies"];
69
- if (optDeps && typeof optDeps === "object") {
70
- for (const group of Object.values(optDeps)) {
71
- if (Array.isArray(group)) {
72
- for (const dep of group) {
73
- const name = extractPkgName(dep);
74
- if (name && !prod.has(name)) dev.add(name);
75
- }
76
- }
25
+ const imports = data.imports;
26
+ if (imports) {
27
+ for (const specifier of Object.values(imports)) {
28
+ const name = extractPackageName(specifier);
29
+ if (name) prod.add(normalizeDenoName(name));
77
30
  }
78
31
  }
79
- const tool = data["tool"];
80
- const poetry = tool?.["poetry"];
81
- if (poetry) {
82
- const poetryDeps = poetry["dependencies"];
83
- if (poetryDeps) {
84
- for (const key of Object.keys(poetryDeps)) {
85
- if (key.toLowerCase() !== "python") prod.add(normalizePythonName(key));
86
- }
87
- }
88
- const devDeps = poetry["dev-dependencies"];
89
- if (devDeps) {
90
- for (const key of Object.keys(devDeps)) {
91
- const normalized = normalizePythonName(key);
92
- if (!prod.has(normalized)) dev.add(normalized);
93
- }
94
- }
95
- const groups = poetry["group"];
96
- if (groups) {
97
- for (const group of Object.values(groups)) {
98
- const groupDeps = group["dependencies"];
99
- if (groupDeps) {
100
- for (const key of Object.keys(groupDeps)) {
101
- const normalized = normalizePythonName(key);
102
- if (!prod.has(normalized)) dev.add(normalized);
103
- }
104
- }
105
- }
106
- }
32
+ const workspace = data.workspace;
33
+ for (const specifier of workspace?.dependencies ?? []) {
34
+ const name = extractPackageName(specifier);
35
+ if (name) prod.add(normalizeDenoName(name));
107
36
  }
108
- const uv = tool?.["uv"];
109
- const uvDevDeps = uv?.["dev-dependencies"];
110
- if (Array.isArray(uvDevDeps)) {
111
- for (const dep of uvDevDeps) {
112
- const name = extractPkgName(dep);
113
- if (name && !prod.has(name)) dev.add(name);
114
- }
37
+ return { prod, dev: /* @__PURE__ */ new Set() };
38
+ }
39
+ function extractPackageName(specifier) {
40
+ const withoutProtocol = specifier.replace(/^(?:npm|jsr|node):/, "");
41
+ if (specifier.startsWith("node:")) return null;
42
+ if (withoutProtocol.startsWith("@")) {
43
+ const atIdx2 = withoutProtocol.indexOf("@", 1);
44
+ return atIdx2 > 0 ? withoutProtocol.slice(0, atIdx2) : withoutProtocol;
115
45
  }
116
- const depGroups = data["dependency-groups"];
117
- if (depGroups && typeof depGroups === "object") {
118
- for (const group of Object.values(depGroups)) {
119
- if (Array.isArray(group)) {
120
- for (const entry of group) {
121
- if (typeof entry === "string") {
122
- const name = extractPkgName(entry);
123
- if (name && !prod.has(name)) dev.add(name);
124
- }
125
- }
46
+ const atIdx = withoutProtocol.indexOf("@");
47
+ return atIdx > 0 ? withoutProtocol.slice(0, atIdx) : withoutProtocol || null;
48
+ }
49
+
50
+ // src/ecosystems/deno/parsers/deno-lock.ts
51
+ function parseDenoLock(content) {
52
+ const data = JSON.parse(content);
53
+ const result = {};
54
+ for (const [key, registry2] of [
55
+ ["npm", data.packages?.npm],
56
+ ["jsr", data.packages?.jsr]
57
+ ]) {
58
+ if (!registry2) continue;
59
+ for (const specifier of Object.keys(registry2)) {
60
+ const { name, version } = splitSpecifier(specifier);
61
+ const resultKey = key === "jsr" ? `jsr:${name}` : name;
62
+ if (name && version && !result[resultKey]) {
63
+ result[resultKey] = version;
126
64
  }
127
65
  }
128
66
  }
129
- return { prod, dev };
67
+ return result;
68
+ }
69
+ function splitSpecifier(specifier) {
70
+ if (specifier.startsWith("@")) {
71
+ const atIdx2 = specifier.indexOf("@", 1);
72
+ if (atIdx2 < 0) return { name: specifier, version: "" };
73
+ return { name: specifier.slice(0, atIdx2), version: specifier.slice(atIdx2 + 1) };
74
+ }
75
+ const atIdx = specifier.indexOf("@");
76
+ if (atIdx < 0) return { name: specifier, version: "" };
77
+ return { name: specifier.slice(0, atIdx), version: specifier.slice(atIdx + 1) };
130
78
  }
131
79
 
132
- // src/ecosystems/python/index.ts
133
- var SUPPORTED_LOCKFILES = [
134
- { filename: "uv.lock", type: "uv" },
135
- { filename: "poetry.lock", type: "poetry" },
136
- { filename: "pdm.lock", type: "pdm" },
137
- { filename: "pylock.toml", type: "pylock" }
138
- // PEP 751
139
- ];
140
- var lockfileTypeMap = new Map(SUPPORTED_LOCKFILES.map((l) => [l.filename, l.type]));
141
- var pythonEcosystem = {
142
- name: "python",
80
+ // src/ecosystems/deno/index.ts
81
+ var SUPPORTED_LOCKFILES = [{ filename: "deno.lock", type: "deno" }];
82
+ var denoEcosystem = {
83
+ name: "deno",
143
84
  supportedLockfiles: SUPPORTED_LOCKFILES,
144
- manifestName: "pyproject.toml",
85
+ manifestName: "deno.json",
145
86
  getLockfileType(filename) {
146
- return lockfileTypeMap.get(filename);
87
+ return filename === "deno.lock" ? "deno" : void 0;
147
88
  },
148
89
  parseLockfile(content, _lockfileType) {
149
- return parseTomlPackages(content);
90
+ return parseDenoLock(content);
150
91
  },
151
92
  parseDirectDeps(manifestContent) {
152
93
  return parseDirectDeps(manifestContent);
153
94
  },
154
95
  normalizeName(name) {
155
- return normalizePythonName(name);
96
+ return normalizeDenoName(name);
156
97
  }
157
98
  };
158
99
 
100
+ // src/ecosystems/javascript/package-json.ts
101
+ var PROD_SECTIONS = ["dependencies", "optionalDependencies", "peerDependencies"];
102
+ function normalizeJsName(name) {
103
+ return name.toLowerCase();
104
+ }
105
+ function parseDirectDeps2(content) {
106
+ const prod = /* @__PURE__ */ new Set();
107
+ const dev = /* @__PURE__ */ new Set();
108
+ let data;
109
+ try {
110
+ data = JSON.parse(content);
111
+ } catch {
112
+ return { prod, dev };
113
+ }
114
+ for (const section of PROD_SECTIONS) {
115
+ const deps = data[section];
116
+ if (deps && typeof deps === "object") {
117
+ for (const name of Object.keys(deps)) {
118
+ prod.add(normalizeJsName(name));
119
+ }
120
+ }
121
+ }
122
+ const devDeps = data.devDependencies;
123
+ if (devDeps && typeof devDeps === "object") {
124
+ for (const name of Object.keys(devDeps)) {
125
+ const normalized = normalizeJsName(name);
126
+ if (!prod.has(normalized)) dev.add(normalized);
127
+ }
128
+ }
129
+ return { prod, dev };
130
+ }
131
+
132
+ // src/ecosystems/javascript/parsers/bun.ts
133
+ function parseBunLock(content) {
134
+ const data = JSON.parse(content);
135
+ const result = {};
136
+ for (const [name, entry] of Object.entries(data.packages ?? {})) {
137
+ if (!Array.isArray(entry)) continue;
138
+ const nameAtVersion = entry[0];
139
+ if (typeof nameAtVersion !== "string") continue;
140
+ const version = extractVersion(nameAtVersion);
141
+ if (!version || version.startsWith("workspace:")) continue;
142
+ result[name] = version;
143
+ }
144
+ return result;
145
+ }
146
+ function extractVersion(nameAtVersion) {
147
+ if (nameAtVersion.startsWith("@")) {
148
+ const atIdx2 = nameAtVersion.indexOf("@", 1);
149
+ return atIdx2 > 0 ? nameAtVersion.slice(atIdx2 + 1) : "";
150
+ }
151
+ const atIdx = nameAtVersion.indexOf("@");
152
+ return atIdx > 0 ? nameAtVersion.slice(atIdx + 1) : "";
153
+ }
154
+
159
155
  // src/ecosystems/javascript/parsers/npm.ts
160
156
  function parseNpmLock(content) {
161
157
  const data = JSON.parse(content);
@@ -195,69 +191,10 @@ function parseV1Dependencies(deps, result = {}) {
195
191
  return result;
196
192
  }
197
193
 
198
- // src/ecosystems/javascript/parsers/yarn.ts
199
- import { parse as parseYaml } from "yaml";
200
- function parseYarnLock(content) {
201
- return isYarnBerry(content) ? parseYarnBerry(content) : parseYarnV1(content);
202
- }
203
- function isYarnBerry(content) {
204
- return content.includes("__metadata:");
205
- }
206
- function extractNameFromSpecifier(spec) {
207
- const trimmed = spec.trim().replace(/^"|"$/g, "");
208
- if (trimmed.startsWith("@")) {
209
- const idx = trimmed.indexOf("@", 1);
210
- return idx > 0 ? trimmed.slice(0, idx) : trimmed;
211
- }
212
- const atIdx = trimmed.indexOf("@");
213
- return atIdx > 0 ? trimmed.slice(0, atIdx) : trimmed;
214
- }
215
- function parseYarnV1(content) {
216
- const packages = {};
217
- const blocks = content.split(/\n\n+/);
218
- for (const block of blocks) {
219
- const trimmed = block.trim();
220
- if (!trimmed || trimmed.startsWith("#")) continue;
221
- const versionMatch = trimmed.match(/^[ \t]+version "([^"]+)"/m);
222
- if (!versionMatch) continue;
223
- const headerLine = trimmed.split("\n")[0].trim().replace(/:$/, "");
224
- const firstSpecifier = headerLine.split(",")[0].trim().replace(/^"|"$/g, "");
225
- const name = extractNameFromSpecifier(firstSpecifier);
226
- if (name && !packages[name]) {
227
- packages[name] = versionMatch[1];
228
- }
229
- }
230
- return packages;
231
- }
232
- function parseYarnBerry(content) {
233
- const data = parseYaml(content);
234
- const packages = {};
235
- for (const [key, value] of Object.entries(data)) {
236
- if (key === "__metadata") continue;
237
- if (typeof value !== "object" || !value) continue;
238
- const entry = value;
239
- if (entry.linkType === "soft") continue;
240
- if (!entry.version) continue;
241
- const cleanKey = key.replace(/^"|"$/g, "");
242
- const name = extractNameFromBerryKey(cleanKey);
243
- if (name && !packages[name]) {
244
- packages[name] = entry.version;
245
- }
246
- }
247
- return packages;
248
- }
249
- function extractNameFromBerryKey(key) {
250
- if (key.startsWith("@")) {
251
- const idx = key.indexOf("@", 1);
252
- return idx > 0 ? key.slice(0, idx) : key;
253
- }
254
- return key.split("@")[0];
255
- }
256
-
257
194
  // src/ecosystems/javascript/parsers/pnpm.ts
258
- import { parse as parseYaml2 } from "yaml";
195
+ import { parse as parseYaml } from "yaml";
259
196
  function parsePnpmLock(content) {
260
- const data = parseYaml2(content);
197
+ const data = parseYaml(content);
261
198
  if (!data?.packages) return {};
262
199
  const lockfileVersion = parseLockfileVersion(data.lockfileVersion);
263
200
  if (lockfileVersion >= 9) {
@@ -267,13 +204,14 @@ function parsePnpmLock(content) {
267
204
  }
268
205
  function parseLockfileVersion(v) {
269
206
  if (typeof v === "number") return v;
270
- if (typeof v === "string") return parseFloat(v);
207
+ if (typeof v === "string") return Number.parseFloat(v);
271
208
  return 0;
272
209
  }
273
210
  function parsePnpmV9(packages) {
274
211
  const result = {};
275
212
  for (const key of Object.keys(packages)) {
276
- let name, version;
213
+ let name;
214
+ let version;
277
215
  if (key.startsWith("@")) {
278
216
  const atIdx = key.indexOf("@", 1);
279
217
  if (atIdx < 0) continue;
@@ -296,7 +234,8 @@ function parsePnpmLegacy(packages) {
296
234
  const result = {};
297
235
  for (const key of Object.keys(packages)) {
298
236
  const cleaned = key.startsWith("/") ? key.slice(1) : key;
299
- let name, version;
237
+ let name;
238
+ let version;
300
239
  if (cleaned.startsWith("@")) {
301
240
  const secondSlash = cleaned.indexOf("/", cleaned.indexOf("/") + 1);
302
241
  const secondAt = cleaned.indexOf("@", 1);
@@ -333,59 +272,63 @@ function stripVersionSuffix(version) {
333
272
  return version.split("(")[0].split("_")[0].trim();
334
273
  }
335
274
 
336
- // src/ecosystems/javascript/parsers/bun.ts
337
- function parseBunLock(content) {
338
- const data = JSON.parse(content);
339
- const result = {};
340
- for (const [name, entry] of Object.entries(data.packages ?? {})) {
341
- if (!Array.isArray(entry)) continue;
342
- const nameAtVersion = entry[0];
343
- if (typeof nameAtVersion !== "string") continue;
344
- const version = extractVersion(nameAtVersion);
345
- if (!version || version.startsWith("workspace:")) continue;
346
- result[name] = version;
347
- }
348
- return result;
349
- }
350
- function extractVersion(nameAtVersion) {
351
- if (nameAtVersion.startsWith("@")) {
352
- const atIdx2 = nameAtVersion.indexOf("@", 1);
353
- return atIdx2 > 0 ? nameAtVersion.slice(atIdx2 + 1) : "";
354
- }
355
- const atIdx = nameAtVersion.indexOf("@");
356
- return atIdx > 0 ? nameAtVersion.slice(atIdx + 1) : "";
275
+ // src/ecosystems/javascript/parsers/yarn.ts
276
+ import { parse as parseYaml2 } from "yaml";
277
+ function parseYarnLock(content) {
278
+ return isYarnBerry(content) ? parseYarnBerry(content) : parseYarnV1(content);
357
279
  }
358
-
359
- // src/ecosystems/javascript/package-json.ts
360
- var PROD_SECTIONS = ["dependencies", "optionalDependencies", "peerDependencies"];
361
- function normalizeJsName(name) {
362
- return name.toLowerCase();
280
+ function isYarnBerry(content) {
281
+ return content.includes("__metadata:");
363
282
  }
364
- function parseDirectDeps2(content) {
365
- const prod = /* @__PURE__ */ new Set();
366
- const dev = /* @__PURE__ */ new Set();
367
- let data;
368
- try {
369
- data = JSON.parse(content);
370
- } catch {
371
- return { prod, dev };
372
- }
373
- for (const section of PROD_SECTIONS) {
374
- const deps = data[section];
375
- if (deps && typeof deps === "object") {
376
- for (const name of Object.keys(deps)) {
377
- prod.add(normalizeJsName(name));
378
- }
283
+ function extractNameFromSpecifier(spec) {
284
+ const trimmed = spec.trim().replace(/^"|"$/g, "");
285
+ if (trimmed.startsWith("@")) {
286
+ const idx = trimmed.indexOf("@", 1);
287
+ return idx > 0 ? trimmed.slice(0, idx) : trimmed;
288
+ }
289
+ const atIdx = trimmed.indexOf("@");
290
+ return atIdx > 0 ? trimmed.slice(0, atIdx) : trimmed;
291
+ }
292
+ function parseYarnV1(content) {
293
+ const packages = {};
294
+ const blocks = content.split(/\n\n+/);
295
+ for (const block of blocks) {
296
+ const trimmed = block.trim();
297
+ if (!trimmed || trimmed.startsWith("#")) continue;
298
+ const versionMatch = trimmed.match(/^[ \t]+version "([^"]+)"/m);
299
+ if (!versionMatch) continue;
300
+ const headerLine = trimmed.split("\n")[0].trim().replace(/:$/, "");
301
+ const firstSpecifier = headerLine.split(",")[0].trim().replace(/^"|"$/g, "");
302
+ const name = extractNameFromSpecifier(firstSpecifier);
303
+ if (name && !packages[name]) {
304
+ packages[name] = versionMatch[1];
379
305
  }
380
306
  }
381
- const devDeps = data["devDependencies"];
382
- if (devDeps && typeof devDeps === "object") {
383
- for (const name of Object.keys(devDeps)) {
384
- const normalized = normalizeJsName(name);
385
- if (!prod.has(normalized)) dev.add(normalized);
307
+ return packages;
308
+ }
309
+ function parseYarnBerry(content) {
310
+ const data = parseYaml2(content);
311
+ const packages = {};
312
+ for (const [key, value] of Object.entries(data)) {
313
+ if (key === "__metadata") continue;
314
+ if (typeof value !== "object" || !value) continue;
315
+ const entry = value;
316
+ if (entry.linkType === "soft") continue;
317
+ if (!entry.version) continue;
318
+ const cleanKey = key.replace(/^"|"$/g, "");
319
+ const name = extractNameFromBerryKey(cleanKey);
320
+ if (name && !packages[name]) {
321
+ packages[name] = entry.version;
386
322
  }
387
323
  }
388
- return { prod, dev };
324
+ return packages;
325
+ }
326
+ function extractNameFromBerryKey(key) {
327
+ if (key.startsWith("@")) {
328
+ const idx = key.indexOf("@", 1);
329
+ return idx > 0 ? key.slice(0, idx) : key;
330
+ }
331
+ return key.split("@")[0];
389
332
  }
390
333
 
391
334
  // src/ecosystems/javascript/index.ts
@@ -395,13 +338,13 @@ var SUPPORTED_LOCKFILES2 = [
395
338
  { filename: "pnpm-lock.yaml", type: "pnpm" },
396
339
  { filename: "bun.lock", type: "bun" }
397
340
  ];
398
- var lockfileTypeMap2 = new Map(SUPPORTED_LOCKFILES2.map((l) => [l.filename, l.type]));
341
+ var lockfileTypeMap = new Map(SUPPORTED_LOCKFILES2.map((l) => [l.filename, l.type]));
399
342
  var javascriptEcosystem = {
400
343
  name: "javascript",
401
344
  supportedLockfiles: SUPPORTED_LOCKFILES2,
402
345
  manifestName: "package.json",
403
346
  getLockfileType(filename) {
404
- return lockfileTypeMap2.get(filename);
347
+ return lockfileTypeMap.get(filename);
405
348
  },
406
349
  parseLockfile(content, lockfileType) {
407
350
  switch (lockfileType) {
@@ -425,90 +368,149 @@ var javascriptEcosystem = {
425
368
  }
426
369
  };
427
370
 
428
- // src/ecosystems/deno/parsers/deno-lock.ts
429
- function parseDenoLock(content) {
430
- const data = JSON.parse(content);
431
- const result = {};
432
- for (const [key, registry2] of [
433
- ["npm", data.packages?.npm],
434
- ["jsr", data.packages?.jsr]
435
- ]) {
436
- if (!registry2) continue;
437
- for (const specifier of Object.keys(registry2)) {
438
- const { name, version } = splitSpecifier(specifier);
439
- const resultKey = key === "jsr" ? `jsr:${name}` : name;
440
- if (name && version && !result[resultKey]) {
441
- result[resultKey] = version;
371
+ // src/ecosystems/python/parsers/toml.ts
372
+ import { parse } from "smol-toml";
373
+ function parseTomlPackages(content) {
374
+ try {
375
+ const data = parse(content);
376
+ const packages = {};
377
+ for (const pkg of [...data.package ?? [], ...data.packages ?? []]) {
378
+ if (typeof pkg.name === "string" && typeof pkg.version === "string") {
379
+ packages[pkg.name] = pkg.version;
442
380
  }
443
381
  }
382
+ return packages;
383
+ } catch {
384
+ return parseTomlPackagesRegex(content);
444
385
  }
445
- return result;
446
386
  }
447
- function splitSpecifier(specifier) {
448
- if (specifier.startsWith("@")) {
449
- const atIdx2 = specifier.indexOf("@", 1);
450
- if (atIdx2 < 0) return { name: specifier, version: "" };
451
- return { name: specifier.slice(0, atIdx2), version: specifier.slice(atIdx2 + 1) };
387
+ function parseTomlPackagesRegex(content) {
388
+ const packages = {};
389
+ const blocks = content.split(/\[\[packages?\]\]/);
390
+ for (const block of blocks) {
391
+ const nameMatch = block.match(/\nname\s*=\s*"([^"]+)"/);
392
+ const versionMatch = block.match(/\nversion\s*=\s*"([^"]+)"/);
393
+ if (nameMatch && versionMatch) {
394
+ packages[nameMatch[1]] = versionMatch[1];
395
+ }
452
396
  }
453
- const atIdx = specifier.indexOf("@");
454
- if (atIdx < 0) return { name: specifier, version: "" };
455
- return { name: specifier.slice(0, atIdx), version: specifier.slice(atIdx + 1) };
397
+ return packages;
456
398
  }
457
399
 
458
- // src/ecosystems/deno/deno-json.ts
459
- function normalizeDenoName(name) {
460
- return name.toLowerCase();
400
+ // src/ecosystems/python/pyproject.ts
401
+ import { parse as parse2 } from "smol-toml";
402
+ function normalizePythonName(name) {
403
+ return name.toLowerCase().replace(/[-_.]+/g, "_");
404
+ }
405
+ function extractPkgName(dep) {
406
+ const match = String(dep).match(/^([\w][\w.-]*)/);
407
+ return match ? normalizePythonName(match[1]) : null;
461
408
  }
462
409
  function parseDirectDeps3(content) {
463
410
  const prod = /* @__PURE__ */ new Set();
411
+ const dev = /* @__PURE__ */ new Set();
464
412
  let data;
465
413
  try {
466
- data = JSON.parse(content);
414
+ data = parse2(content);
467
415
  } catch {
468
- return { prod, dev: /* @__PURE__ */ new Set() };
416
+ return { prod, dev };
469
417
  }
470
- const imports = data["imports"];
471
- if (imports) {
472
- for (const specifier of Object.values(imports)) {
473
- const name = extractPackageName(specifier);
474
- if (name) prod.add(normalizeDenoName(name));
418
+ const project = data.project;
419
+ const pep517Deps = project?.dependencies;
420
+ if (Array.isArray(pep517Deps)) {
421
+ for (const dep of pep517Deps) {
422
+ const name = extractPkgName(dep);
423
+ if (name) prod.add(name);
475
424
  }
476
425
  }
477
- const workspace = data["workspace"];
478
- for (const specifier of workspace?.dependencies ?? []) {
479
- const name = extractPackageName(specifier);
480
- if (name) prod.add(normalizeDenoName(name));
426
+ const optDeps = project?.["optional-dependencies"];
427
+ if (optDeps && typeof optDeps === "object") {
428
+ for (const group of Object.values(optDeps)) {
429
+ if (Array.isArray(group)) {
430
+ for (const dep of group) {
431
+ const name = extractPkgName(dep);
432
+ if (name && !prod.has(name)) dev.add(name);
433
+ }
434
+ }
435
+ }
481
436
  }
482
- return { prod, dev: /* @__PURE__ */ new Set() };
483
- }
484
- function extractPackageName(specifier) {
485
- const withoutProtocol = specifier.replace(/^(?:npm|jsr|node):/, "");
486
- if (specifier.startsWith("node:")) return null;
487
- if (withoutProtocol.startsWith("@")) {
488
- const atIdx2 = withoutProtocol.indexOf("@", 1);
489
- return atIdx2 > 0 ? withoutProtocol.slice(0, atIdx2) : withoutProtocol;
437
+ const tool = data.tool;
438
+ const poetry = tool?.poetry;
439
+ if (poetry) {
440
+ const poetryDeps = poetry.dependencies;
441
+ if (poetryDeps) {
442
+ for (const key of Object.keys(poetryDeps)) {
443
+ if (key.toLowerCase() !== "python") prod.add(normalizePythonName(key));
444
+ }
445
+ }
446
+ const devDeps = poetry["dev-dependencies"];
447
+ if (devDeps) {
448
+ for (const key of Object.keys(devDeps)) {
449
+ const normalized = normalizePythonName(key);
450
+ if (!prod.has(normalized)) dev.add(normalized);
451
+ }
452
+ }
453
+ const groups = poetry.group;
454
+ if (groups) {
455
+ for (const group of Object.values(groups)) {
456
+ const groupDeps = group.dependencies;
457
+ if (groupDeps) {
458
+ for (const key of Object.keys(groupDeps)) {
459
+ const normalized = normalizePythonName(key);
460
+ if (!prod.has(normalized)) dev.add(normalized);
461
+ }
462
+ }
463
+ }
464
+ }
490
465
  }
491
- const atIdx = withoutProtocol.indexOf("@");
492
- return atIdx > 0 ? withoutProtocol.slice(0, atIdx) : withoutProtocol || null;
466
+ const uv = tool?.uv;
467
+ const uvDevDeps = uv?.["dev-dependencies"];
468
+ if (Array.isArray(uvDevDeps)) {
469
+ for (const dep of uvDevDeps) {
470
+ const name = extractPkgName(dep);
471
+ if (name && !prod.has(name)) dev.add(name);
472
+ }
473
+ }
474
+ const depGroups = data["dependency-groups"];
475
+ if (depGroups && typeof depGroups === "object") {
476
+ for (const group of Object.values(depGroups)) {
477
+ if (Array.isArray(group)) {
478
+ for (const entry of group) {
479
+ if (typeof entry === "string") {
480
+ const name = extractPkgName(entry);
481
+ if (name && !prod.has(name)) dev.add(name);
482
+ }
483
+ }
484
+ }
485
+ }
486
+ }
487
+ return { prod, dev };
493
488
  }
494
489
 
495
- // src/ecosystems/deno/index.ts
496
- var SUPPORTED_LOCKFILES3 = [{ filename: "deno.lock", type: "deno" }];
497
- var denoEcosystem = {
498
- name: "deno",
490
+ // src/ecosystems/python/index.ts
491
+ var SUPPORTED_LOCKFILES3 = [
492
+ { filename: "uv.lock", type: "uv" },
493
+ { filename: "poetry.lock", type: "poetry" },
494
+ { filename: "pdm.lock", type: "pdm" },
495
+ { filename: "pylock.toml", type: "pylock" }
496
+ // PEP 751
497
+ ];
498
+ var lockfileTypeMap2 = new Map(SUPPORTED_LOCKFILES3.map((l) => [l.filename, l.type]));
499
+ var pythonEcosystem = {
500
+ name: "python",
499
501
  supportedLockfiles: SUPPORTED_LOCKFILES3,
500
- manifestName: "deno.json",
502
+ manifestName: "pyproject.toml",
501
503
  getLockfileType(filename) {
502
- return filename === "deno.lock" ? "deno" : void 0;
504
+ return lockfileTypeMap2.get(filename);
503
505
  },
504
506
  parseLockfile(content, _lockfileType) {
505
- return parseDenoLock(content);
507
+ return parseTomlPackages(content);
506
508
  },
507
509
  parseDirectDeps(manifestContent) {
508
510
  return parseDirectDeps3(manifestContent);
509
511
  },
510
512
  normalizeName(name) {
511
- return normalizeDenoName(name);
513
+ return normalizePythonName(name);
512
514
  }
513
515
  };
514
516
 
@@ -533,6 +535,29 @@ registerEcosystem(pythonEcosystem);
533
535
  registerEcosystem(javascriptEcosystem);
534
536
  registerEcosystem(denoEcosystem);
535
537
 
538
+ // src/core/diff.ts
539
+ function diffPackages(oldPkgs, newPkgs, directDeps, normalizeName) {
540
+ const allNames = /* @__PURE__ */ new Set([...Object.keys(oldPkgs), ...Object.keys(newPkgs)]);
541
+ const changes = [];
542
+ for (const name of [...allNames].sort()) {
543
+ const inOld = name in oldPkgs;
544
+ const inNew = name in newPkgs;
545
+ if (inOld && inNew && oldPkgs[name] === newPkgs[name]) continue;
546
+ const normalized = normalizeName(name);
547
+ const isProd = directDeps.prod.has(normalized);
548
+ const isDev = directDeps.dev.has(normalized) && !isProd;
549
+ changes.push({
550
+ name,
551
+ change_type: !inOld ? "added" : !inNew ? "removed" : "updated",
552
+ old_version: inOld ? oldPkgs[name] : null,
553
+ new_version: inNew ? newPkgs[name] : null,
554
+ is_direct: isProd || isDev,
555
+ is_dev: isDev
556
+ });
557
+ }
558
+ return changes;
559
+ }
560
+
536
561
  // src/core/discovery.ts
537
562
  import { posix } from "path";
538
563
  function workspaceFromPath(filePath) {
@@ -596,6 +621,7 @@ function resolveLockfilePair(baseFiles, headFiles) {
596
621
  basePath: chosen.path,
597
622
  baseType: chosen.type,
598
623
  headPath: chosen.path,
624
+ // biome-ignore lint/style/noNonNullAssertion: path is guaranteed present (comes from common set)
599
625
  headType: headByPath.get(chosen.path).type,
600
626
  migrationNote: null,
601
627
  ecosystemName: chosen.ecosystemName
@@ -638,29 +664,6 @@ function resolveLockfilePair(baseFiles, headFiles) {
638
664
  return null;
639
665
  }
640
666
 
641
- // src/core/diff.ts
642
- function diffPackages(oldPkgs, newPkgs, directDeps, normalizeName) {
643
- const allNames = /* @__PURE__ */ new Set([...Object.keys(oldPkgs), ...Object.keys(newPkgs)]);
644
- const changes = [];
645
- for (const name of [...allNames].sort()) {
646
- const inOld = name in oldPkgs;
647
- const inNew = name in newPkgs;
648
- if (inOld && inNew && oldPkgs[name] === newPkgs[name]) continue;
649
- const normalized = normalizeName(name);
650
- const isProd = directDeps.prod.has(normalized);
651
- const isDev = directDeps.dev.has(normalized) && !isProd;
652
- changes.push({
653
- name,
654
- change_type: !inOld ? "added" : !inNew ? "removed" : "updated",
655
- old_version: inOld ? oldPkgs[name] : null,
656
- new_version: inNew ? newPkgs[name] : null,
657
- is_direct: isProd || isDev,
658
- is_dev: isDev
659
- });
660
- }
661
- return changes;
662
- }
663
-
664
667
  // src/core/report.ts
665
668
  async function buildLockfileEntry(pair, workspace, getBase, getHead) {
666
669
  const ecosystem = getEcosystemByName(pair.ecosystemName);
@@ -794,7 +797,7 @@ function gitLsTree(ref) {
794
797
  import { execFileSync as execFileSync2 } from "child_process";
795
798
  var API_BASE = "https://api.github.com";
796
799
  function token() {
797
- const t = process.env["GITHUB_TOKEN"];
800
+ const t = process.env.GITHUB_TOKEN;
798
801
  if (!t) throw new Error("GITHUB_TOKEN is required for GitHub API access");
799
802
  return t;
800
803
  }
@@ -830,7 +833,7 @@ async function getPrShas(prNumber, repo) {
830
833
  return { baseRefOid: data.base.sha, headRefOid: data.head.sha };
831
834
  }
832
835
  function detectRepo() {
833
- const fromEnv = process.env["GITHUB_REPOSITORY"];
836
+ const fromEnv = process.env.GITHUB_REPOSITORY;
834
837
  if (fromEnv) return fromEnv;
835
838
  try {
836
839
  const remote = execFileSync2("git", ["remote", "get-url", "origin"], {
@@ -841,15 +844,17 @@ function detectRepo() {
841
844
  if (match) return match[1];
842
845
  } catch {
843
846
  }
844
- throw new Error(
845
- "Could not detect GitHub repo \u2014 set GITHUB_REPOSITORY or pass --repo"
846
- );
847
+ throw new Error("Could not detect GitHub repo \u2014 set GITHUB_REPOSITORY or pass --repo");
847
848
  }
848
849
 
849
850
  // src/index.ts
850
851
  async function resolveApiShas(options) {
851
852
  if (options.baseSha && options.headSha) {
852
- return { baseSha: options.baseSha, headSha: options.headSha, repo: options.repo ?? detectRepo() };
853
+ return {
854
+ baseSha: options.baseSha,
855
+ headSha: options.headSha,
856
+ repo: options.repo ?? detectRepo()
857
+ };
853
858
  }
854
859
  if (options.prNumber) {
855
860
  const repo = options.repo ?? detectRepo();
@@ -924,22 +929,22 @@ async function run(options = {}) {
924
929
 
925
930
  // src/cli.ts
926
931
  var program = new Command();
927
- program.name("depdiff").description("Diff dependency lockfiles between git refs, PRs, or local files").version("0.1.0").option(
932
+ program.name("lockdelta").description("Diff dependency lockfiles between git refs, PRs, or local files").version("0.1.0").option(
928
933
  "--base <ref>",
929
934
  'Base git ref (default: HEAD~1). In CI, reads GITHUB_BASE_REF \u2014 may need "origin/" prefix.',
930
- process.env["GITHUB_BASE_REF"]
935
+ process.env.GITHUB_BASE_REF
931
936
  ).option(
932
937
  "--head <ref>",
933
938
  "Head git ref (default: HEAD). In CI, reads GITHUB_HEAD_REF.",
934
- process.env["GITHUB_HEAD_REF"]
939
+ process.env.GITHUB_HEAD_REF
935
940
  ).option(
936
941
  "--pr <number>",
937
942
  "GitHub PR number. Fetches exact SHAs via gh CLI.",
938
- process.env["GITHUB_PR_NUMBER"]
943
+ process.env.GITHUB_PR_NUMBER
939
944
  ).option(
940
945
  "--repo <owner/name>",
941
946
  "GitHub repo in OWNER/NAME format. Auto-detected if omitted.",
942
- process.env["GITHUB_REPOSITORY"]
947
+ process.env.GITHUB_REPOSITORY
943
948
  ).option("--lockfile <path>", "Specific lockfile path. Auto-discovers all lockfiles if omitted.").option("--type <type>", "Force lockfile type: uv, poetry, pdm. Only used with --lockfile.").option("--old <path>", "Old lockfile path (local file comparison mode).").option("--new <path>", "New lockfile path (local file comparison mode).").option("--output <path>", "Write JSON report to file instead of stdout.").action(async (opts) => {
944
949
  try {
945
950
  const report = await run({
@@ -954,7 +959,8 @@ program.name("depdiff").description("Diff dependency lockfiles between git refs,
954
959
  onNote: (msg) => process.stderr.write(`Note: ${msg}
955
960
  `)
956
961
  });
957
- const json = JSON.stringify(report, null, 2) + "\n";
962
+ const json = `${JSON.stringify(report, null, 2)}
963
+ `;
958
964
  if (opts.output) {
959
965
  writeFileSync(opts.output, json, "utf-8");
960
966
  process.stderr.write(`Report written to ${opts.output}