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/index.js CHANGED
@@ -4,152 +4,148 @@ import { readFileSync } from "fs";
4
4
  // src/core/report.ts
5
5
  import { posix as posix2 } from "path";
6
6
 
7
- // src/ecosystems/python/parsers/toml.ts
8
- import { parse } from "smol-toml";
9
- function parseTomlPackages(content) {
10
- try {
11
- const data = parse(content);
12
- const packages = {};
13
- for (const pkg of [...data.package ?? [], ...data.packages ?? []]) {
14
- if (typeof pkg.name === "string" && typeof pkg.version === "string") {
15
- packages[pkg.name] = pkg.version;
16
- }
17
- }
18
- return packages;
19
- } catch {
20
- return parseTomlPackagesRegex(content);
21
- }
22
- }
23
- function parseTomlPackagesRegex(content) {
24
- const packages = {};
25
- const blocks = content.split(/\[\[packages?\]\]/);
26
- for (const block of blocks) {
27
- const nameMatch = block.match(/\nname\s*=\s*"([^"]+)"/);
28
- const versionMatch = block.match(/\nversion\s*=\s*"([^"]+)"/);
29
- if (nameMatch && versionMatch) {
30
- packages[nameMatch[1]] = versionMatch[1];
31
- }
32
- }
33
- return packages;
34
- }
35
-
36
- // src/ecosystems/python/pyproject.ts
37
- import { parse as parse2 } from "smol-toml";
38
- function normalizePythonName(name) {
39
- return name.toLowerCase().replace(/[-_.]+/g, "_");
40
- }
41
- function extractPkgName(dep) {
42
- const match = String(dep).match(/^([\w][\w.-]*)/);
43
- return match ? normalizePythonName(match[1]) : null;
7
+ // src/ecosystems/deno/deno-json.ts
8
+ function normalizeDenoName(name) {
9
+ return name.toLowerCase();
44
10
  }
45
11
  function parseDirectDeps(content) {
46
12
  const prod = /* @__PURE__ */ new Set();
47
- const dev = /* @__PURE__ */ new Set();
48
13
  let data;
49
14
  try {
50
- data = parse2(content);
15
+ data = JSON.parse(content);
51
16
  } catch {
52
- return { prod, dev };
53
- }
54
- const project = data["project"];
55
- const pep517Deps = project?.["dependencies"];
56
- if (Array.isArray(pep517Deps)) {
57
- for (const dep of pep517Deps) {
58
- const name = extractPkgName(dep);
59
- if (name) prod.add(name);
60
- }
17
+ return { prod, dev: /* @__PURE__ */ new Set() };
61
18
  }
62
- const optDeps = project?.["optional-dependencies"];
63
- if (optDeps && typeof optDeps === "object") {
64
- for (const group of Object.values(optDeps)) {
65
- if (Array.isArray(group)) {
66
- for (const dep of group) {
67
- const name = extractPkgName(dep);
68
- if (name && !prod.has(name)) dev.add(name);
69
- }
70
- }
19
+ const imports = data.imports;
20
+ if (imports) {
21
+ for (const specifier of Object.values(imports)) {
22
+ const name = extractPackageName(specifier);
23
+ if (name) prod.add(normalizeDenoName(name));
71
24
  }
72
25
  }
73
- const tool = data["tool"];
74
- const poetry = tool?.["poetry"];
75
- if (poetry) {
76
- const poetryDeps = poetry["dependencies"];
77
- if (poetryDeps) {
78
- for (const key of Object.keys(poetryDeps)) {
79
- if (key.toLowerCase() !== "python") prod.add(normalizePythonName(key));
80
- }
81
- }
82
- const devDeps = poetry["dev-dependencies"];
83
- if (devDeps) {
84
- for (const key of Object.keys(devDeps)) {
85
- const normalized = normalizePythonName(key);
86
- if (!prod.has(normalized)) dev.add(normalized);
87
- }
88
- }
89
- const groups = poetry["group"];
90
- if (groups) {
91
- for (const group of Object.values(groups)) {
92
- const groupDeps = group["dependencies"];
93
- if (groupDeps) {
94
- for (const key of Object.keys(groupDeps)) {
95
- const normalized = normalizePythonName(key);
96
- if (!prod.has(normalized)) dev.add(normalized);
97
- }
98
- }
99
- }
100
- }
26
+ const workspace = data.workspace;
27
+ for (const specifier of workspace?.dependencies ?? []) {
28
+ const name = extractPackageName(specifier);
29
+ if (name) prod.add(normalizeDenoName(name));
101
30
  }
102
- const uv = tool?.["uv"];
103
- const uvDevDeps = uv?.["dev-dependencies"];
104
- if (Array.isArray(uvDevDeps)) {
105
- for (const dep of uvDevDeps) {
106
- const name = extractPkgName(dep);
107
- if (name && !prod.has(name)) dev.add(name);
108
- }
31
+ return { prod, dev: /* @__PURE__ */ new Set() };
32
+ }
33
+ function extractPackageName(specifier) {
34
+ const withoutProtocol = specifier.replace(/^(?:npm|jsr|node):/, "");
35
+ if (specifier.startsWith("node:")) return null;
36
+ if (withoutProtocol.startsWith("@")) {
37
+ const atIdx2 = withoutProtocol.indexOf("@", 1);
38
+ return atIdx2 > 0 ? withoutProtocol.slice(0, atIdx2) : withoutProtocol;
109
39
  }
110
- const depGroups = data["dependency-groups"];
111
- if (depGroups && typeof depGroups === "object") {
112
- for (const group of Object.values(depGroups)) {
113
- if (Array.isArray(group)) {
114
- for (const entry of group) {
115
- if (typeof entry === "string") {
116
- const name = extractPkgName(entry);
117
- if (name && !prod.has(name)) dev.add(name);
118
- }
119
- }
40
+ const atIdx = withoutProtocol.indexOf("@");
41
+ return atIdx > 0 ? withoutProtocol.slice(0, atIdx) : withoutProtocol || null;
42
+ }
43
+
44
+ // src/ecosystems/deno/parsers/deno-lock.ts
45
+ function parseDenoLock(content) {
46
+ const data = JSON.parse(content);
47
+ const result = {};
48
+ for (const [key, registry2] of [
49
+ ["npm", data.packages?.npm],
50
+ ["jsr", data.packages?.jsr]
51
+ ]) {
52
+ if (!registry2) continue;
53
+ for (const specifier of Object.keys(registry2)) {
54
+ const { name, version } = splitSpecifier(specifier);
55
+ const resultKey = key === "jsr" ? `jsr:${name}` : name;
56
+ if (name && version && !result[resultKey]) {
57
+ result[resultKey] = version;
120
58
  }
121
59
  }
122
60
  }
123
- return { prod, dev };
61
+ return result;
62
+ }
63
+ function splitSpecifier(specifier) {
64
+ if (specifier.startsWith("@")) {
65
+ const atIdx2 = specifier.indexOf("@", 1);
66
+ if (atIdx2 < 0) return { name: specifier, version: "" };
67
+ return { name: specifier.slice(0, atIdx2), version: specifier.slice(atIdx2 + 1) };
68
+ }
69
+ const atIdx = specifier.indexOf("@");
70
+ if (atIdx < 0) return { name: specifier, version: "" };
71
+ return { name: specifier.slice(0, atIdx), version: specifier.slice(atIdx + 1) };
124
72
  }
125
73
 
126
- // src/ecosystems/python/index.ts
127
- var SUPPORTED_LOCKFILES = [
128
- { filename: "uv.lock", type: "uv" },
129
- { filename: "poetry.lock", type: "poetry" },
130
- { filename: "pdm.lock", type: "pdm" },
131
- { filename: "pylock.toml", type: "pylock" }
132
- // PEP 751
133
- ];
134
- var lockfileTypeMap = new Map(SUPPORTED_LOCKFILES.map((l) => [l.filename, l.type]));
135
- var pythonEcosystem = {
136
- name: "python",
74
+ // src/ecosystems/deno/index.ts
75
+ var SUPPORTED_LOCKFILES = [{ filename: "deno.lock", type: "deno" }];
76
+ var denoEcosystem = {
77
+ name: "deno",
137
78
  supportedLockfiles: SUPPORTED_LOCKFILES,
138
- manifestName: "pyproject.toml",
79
+ manifestName: "deno.json",
139
80
  getLockfileType(filename) {
140
- return lockfileTypeMap.get(filename);
81
+ return filename === "deno.lock" ? "deno" : void 0;
141
82
  },
142
83
  parseLockfile(content, _lockfileType) {
143
- return parseTomlPackages(content);
84
+ return parseDenoLock(content);
144
85
  },
145
86
  parseDirectDeps(manifestContent) {
146
87
  return parseDirectDeps(manifestContent);
147
88
  },
148
89
  normalizeName(name) {
149
- return normalizePythonName(name);
90
+ return normalizeDenoName(name);
150
91
  }
151
92
  };
152
93
 
94
+ // src/ecosystems/javascript/package-json.ts
95
+ var PROD_SECTIONS = ["dependencies", "optionalDependencies", "peerDependencies"];
96
+ function normalizeJsName(name) {
97
+ return name.toLowerCase();
98
+ }
99
+ function parseDirectDeps2(content) {
100
+ const prod = /* @__PURE__ */ new Set();
101
+ const dev = /* @__PURE__ */ new Set();
102
+ let data;
103
+ try {
104
+ data = JSON.parse(content);
105
+ } catch {
106
+ return { prod, dev };
107
+ }
108
+ for (const section of PROD_SECTIONS) {
109
+ const deps = data[section];
110
+ if (deps && typeof deps === "object") {
111
+ for (const name of Object.keys(deps)) {
112
+ prod.add(normalizeJsName(name));
113
+ }
114
+ }
115
+ }
116
+ const devDeps = data.devDependencies;
117
+ if (devDeps && typeof devDeps === "object") {
118
+ for (const name of Object.keys(devDeps)) {
119
+ const normalized = normalizeJsName(name);
120
+ if (!prod.has(normalized)) dev.add(normalized);
121
+ }
122
+ }
123
+ return { prod, dev };
124
+ }
125
+
126
+ // src/ecosystems/javascript/parsers/bun.ts
127
+ function parseBunLock(content) {
128
+ const data = JSON.parse(content);
129
+ const result = {};
130
+ for (const [name, entry] of Object.entries(data.packages ?? {})) {
131
+ if (!Array.isArray(entry)) continue;
132
+ const nameAtVersion = entry[0];
133
+ if (typeof nameAtVersion !== "string") continue;
134
+ const version = extractVersion(nameAtVersion);
135
+ if (!version || version.startsWith("workspace:")) continue;
136
+ result[name] = version;
137
+ }
138
+ return result;
139
+ }
140
+ function extractVersion(nameAtVersion) {
141
+ if (nameAtVersion.startsWith("@")) {
142
+ const atIdx2 = nameAtVersion.indexOf("@", 1);
143
+ return atIdx2 > 0 ? nameAtVersion.slice(atIdx2 + 1) : "";
144
+ }
145
+ const atIdx = nameAtVersion.indexOf("@");
146
+ return atIdx > 0 ? nameAtVersion.slice(atIdx + 1) : "";
147
+ }
148
+
153
149
  // src/ecosystems/javascript/parsers/npm.ts
154
150
  function parseNpmLock(content) {
155
151
  const data = JSON.parse(content);
@@ -189,69 +185,10 @@ function parseV1Dependencies(deps, result = {}) {
189
185
  return result;
190
186
  }
191
187
 
192
- // src/ecosystems/javascript/parsers/yarn.ts
193
- import { parse as parseYaml } from "yaml";
194
- function parseYarnLock(content) {
195
- return isYarnBerry(content) ? parseYarnBerry(content) : parseYarnV1(content);
196
- }
197
- function isYarnBerry(content) {
198
- return content.includes("__metadata:");
199
- }
200
- function extractNameFromSpecifier(spec) {
201
- const trimmed = spec.trim().replace(/^"|"$/g, "");
202
- if (trimmed.startsWith("@")) {
203
- const idx = trimmed.indexOf("@", 1);
204
- return idx > 0 ? trimmed.slice(0, idx) : trimmed;
205
- }
206
- const atIdx = trimmed.indexOf("@");
207
- return atIdx > 0 ? trimmed.slice(0, atIdx) : trimmed;
208
- }
209
- function parseYarnV1(content) {
210
- const packages = {};
211
- const blocks = content.split(/\n\n+/);
212
- for (const block of blocks) {
213
- const trimmed = block.trim();
214
- if (!trimmed || trimmed.startsWith("#")) continue;
215
- const versionMatch = trimmed.match(/^[ \t]+version "([^"]+)"/m);
216
- if (!versionMatch) continue;
217
- const headerLine = trimmed.split("\n")[0].trim().replace(/:$/, "");
218
- const firstSpecifier = headerLine.split(",")[0].trim().replace(/^"|"$/g, "");
219
- const name = extractNameFromSpecifier(firstSpecifier);
220
- if (name && !packages[name]) {
221
- packages[name] = versionMatch[1];
222
- }
223
- }
224
- return packages;
225
- }
226
- function parseYarnBerry(content) {
227
- const data = parseYaml(content);
228
- const packages = {};
229
- for (const [key, value] of Object.entries(data)) {
230
- if (key === "__metadata") continue;
231
- if (typeof value !== "object" || !value) continue;
232
- const entry = value;
233
- if (entry.linkType === "soft") continue;
234
- if (!entry.version) continue;
235
- const cleanKey = key.replace(/^"|"$/g, "");
236
- const name = extractNameFromBerryKey(cleanKey);
237
- if (name && !packages[name]) {
238
- packages[name] = entry.version;
239
- }
240
- }
241
- return packages;
242
- }
243
- function extractNameFromBerryKey(key) {
244
- if (key.startsWith("@")) {
245
- const idx = key.indexOf("@", 1);
246
- return idx > 0 ? key.slice(0, idx) : key;
247
- }
248
- return key.split("@")[0];
249
- }
250
-
251
188
  // src/ecosystems/javascript/parsers/pnpm.ts
252
- import { parse as parseYaml2 } from "yaml";
189
+ import { parse as parseYaml } from "yaml";
253
190
  function parsePnpmLock(content) {
254
- const data = parseYaml2(content);
191
+ const data = parseYaml(content);
255
192
  if (!data?.packages) return {};
256
193
  const lockfileVersion = parseLockfileVersion(data.lockfileVersion);
257
194
  if (lockfileVersion >= 9) {
@@ -261,13 +198,14 @@ function parsePnpmLock(content) {
261
198
  }
262
199
  function parseLockfileVersion(v) {
263
200
  if (typeof v === "number") return v;
264
- if (typeof v === "string") return parseFloat(v);
201
+ if (typeof v === "string") return Number.parseFloat(v);
265
202
  return 0;
266
203
  }
267
204
  function parsePnpmV9(packages) {
268
205
  const result = {};
269
206
  for (const key of Object.keys(packages)) {
270
- let name, version;
207
+ let name;
208
+ let version;
271
209
  if (key.startsWith("@")) {
272
210
  const atIdx = key.indexOf("@", 1);
273
211
  if (atIdx < 0) continue;
@@ -290,7 +228,8 @@ function parsePnpmLegacy(packages) {
290
228
  const result = {};
291
229
  for (const key of Object.keys(packages)) {
292
230
  const cleaned = key.startsWith("/") ? key.slice(1) : key;
293
- let name, version;
231
+ let name;
232
+ let version;
294
233
  if (cleaned.startsWith("@")) {
295
234
  const secondSlash = cleaned.indexOf("/", cleaned.indexOf("/") + 1);
296
235
  const secondAt = cleaned.indexOf("@", 1);
@@ -327,59 +266,63 @@ function stripVersionSuffix(version) {
327
266
  return version.split("(")[0].split("_")[0].trim();
328
267
  }
329
268
 
330
- // src/ecosystems/javascript/parsers/bun.ts
331
- function parseBunLock(content) {
332
- const data = JSON.parse(content);
333
- const result = {};
334
- for (const [name, entry] of Object.entries(data.packages ?? {})) {
335
- if (!Array.isArray(entry)) continue;
336
- const nameAtVersion = entry[0];
337
- if (typeof nameAtVersion !== "string") continue;
338
- const version = extractVersion(nameAtVersion);
339
- if (!version || version.startsWith("workspace:")) continue;
340
- result[name] = version;
341
- }
342
- return result;
343
- }
344
- function extractVersion(nameAtVersion) {
345
- if (nameAtVersion.startsWith("@")) {
346
- const atIdx2 = nameAtVersion.indexOf("@", 1);
347
- return atIdx2 > 0 ? nameAtVersion.slice(atIdx2 + 1) : "";
348
- }
349
- const atIdx = nameAtVersion.indexOf("@");
350
- return atIdx > 0 ? nameAtVersion.slice(atIdx + 1) : "";
269
+ // src/ecosystems/javascript/parsers/yarn.ts
270
+ import { parse as parseYaml2 } from "yaml";
271
+ function parseYarnLock(content) {
272
+ return isYarnBerry(content) ? parseYarnBerry(content) : parseYarnV1(content);
351
273
  }
352
-
353
- // src/ecosystems/javascript/package-json.ts
354
- var PROD_SECTIONS = ["dependencies", "optionalDependencies", "peerDependencies"];
355
- function normalizeJsName(name) {
356
- return name.toLowerCase();
274
+ function isYarnBerry(content) {
275
+ return content.includes("__metadata:");
357
276
  }
358
- function parseDirectDeps2(content) {
359
- const prod = /* @__PURE__ */ new Set();
360
- const dev = /* @__PURE__ */ new Set();
361
- let data;
362
- try {
363
- data = JSON.parse(content);
364
- } catch {
365
- return { prod, dev };
366
- }
367
- for (const section of PROD_SECTIONS) {
368
- const deps = data[section];
369
- if (deps && typeof deps === "object") {
370
- for (const name of Object.keys(deps)) {
371
- prod.add(normalizeJsName(name));
372
- }
277
+ function extractNameFromSpecifier(spec) {
278
+ const trimmed = spec.trim().replace(/^"|"$/g, "");
279
+ if (trimmed.startsWith("@")) {
280
+ const idx = trimmed.indexOf("@", 1);
281
+ return idx > 0 ? trimmed.slice(0, idx) : trimmed;
282
+ }
283
+ const atIdx = trimmed.indexOf("@");
284
+ return atIdx > 0 ? trimmed.slice(0, atIdx) : trimmed;
285
+ }
286
+ function parseYarnV1(content) {
287
+ const packages = {};
288
+ const blocks = content.split(/\n\n+/);
289
+ for (const block of blocks) {
290
+ const trimmed = block.trim();
291
+ if (!trimmed || trimmed.startsWith("#")) continue;
292
+ const versionMatch = trimmed.match(/^[ \t]+version "([^"]+)"/m);
293
+ if (!versionMatch) continue;
294
+ const headerLine = trimmed.split("\n")[0].trim().replace(/:$/, "");
295
+ const firstSpecifier = headerLine.split(",")[0].trim().replace(/^"|"$/g, "");
296
+ const name = extractNameFromSpecifier(firstSpecifier);
297
+ if (name && !packages[name]) {
298
+ packages[name] = versionMatch[1];
373
299
  }
374
300
  }
375
- const devDeps = data["devDependencies"];
376
- if (devDeps && typeof devDeps === "object") {
377
- for (const name of Object.keys(devDeps)) {
378
- const normalized = normalizeJsName(name);
379
- if (!prod.has(normalized)) dev.add(normalized);
301
+ return packages;
302
+ }
303
+ function parseYarnBerry(content) {
304
+ const data = parseYaml2(content);
305
+ const packages = {};
306
+ for (const [key, value] of Object.entries(data)) {
307
+ if (key === "__metadata") continue;
308
+ if (typeof value !== "object" || !value) continue;
309
+ const entry = value;
310
+ if (entry.linkType === "soft") continue;
311
+ if (!entry.version) continue;
312
+ const cleanKey = key.replace(/^"|"$/g, "");
313
+ const name = extractNameFromBerryKey(cleanKey);
314
+ if (name && !packages[name]) {
315
+ packages[name] = entry.version;
380
316
  }
381
317
  }
382
- return { prod, dev };
318
+ return packages;
319
+ }
320
+ function extractNameFromBerryKey(key) {
321
+ if (key.startsWith("@")) {
322
+ const idx = key.indexOf("@", 1);
323
+ return idx > 0 ? key.slice(0, idx) : key;
324
+ }
325
+ return key.split("@")[0];
383
326
  }
384
327
 
385
328
  // src/ecosystems/javascript/index.ts
@@ -389,13 +332,13 @@ var SUPPORTED_LOCKFILES2 = [
389
332
  { filename: "pnpm-lock.yaml", type: "pnpm" },
390
333
  { filename: "bun.lock", type: "bun" }
391
334
  ];
392
- var lockfileTypeMap2 = new Map(SUPPORTED_LOCKFILES2.map((l) => [l.filename, l.type]));
335
+ var lockfileTypeMap = new Map(SUPPORTED_LOCKFILES2.map((l) => [l.filename, l.type]));
393
336
  var javascriptEcosystem = {
394
337
  name: "javascript",
395
338
  supportedLockfiles: SUPPORTED_LOCKFILES2,
396
339
  manifestName: "package.json",
397
340
  getLockfileType(filename) {
398
- return lockfileTypeMap2.get(filename);
341
+ return lockfileTypeMap.get(filename);
399
342
  },
400
343
  parseLockfile(content, lockfileType) {
401
344
  switch (lockfileType) {
@@ -419,90 +362,149 @@ var javascriptEcosystem = {
419
362
  }
420
363
  };
421
364
 
422
- // src/ecosystems/deno/parsers/deno-lock.ts
423
- function parseDenoLock(content) {
424
- const data = JSON.parse(content);
425
- const result = {};
426
- for (const [key, registry2] of [
427
- ["npm", data.packages?.npm],
428
- ["jsr", data.packages?.jsr]
429
- ]) {
430
- if (!registry2) continue;
431
- for (const specifier of Object.keys(registry2)) {
432
- const { name, version } = splitSpecifier(specifier);
433
- const resultKey = key === "jsr" ? `jsr:${name}` : name;
434
- if (name && version && !result[resultKey]) {
435
- result[resultKey] = version;
365
+ // src/ecosystems/python/parsers/toml.ts
366
+ import { parse } from "smol-toml";
367
+ function parseTomlPackages(content) {
368
+ try {
369
+ const data = parse(content);
370
+ const packages = {};
371
+ for (const pkg of [...data.package ?? [], ...data.packages ?? []]) {
372
+ if (typeof pkg.name === "string" && typeof pkg.version === "string") {
373
+ packages[pkg.name] = pkg.version;
436
374
  }
437
375
  }
376
+ return packages;
377
+ } catch {
378
+ return parseTomlPackagesRegex(content);
438
379
  }
439
- return result;
440
380
  }
441
- function splitSpecifier(specifier) {
442
- if (specifier.startsWith("@")) {
443
- const atIdx2 = specifier.indexOf("@", 1);
444
- if (atIdx2 < 0) return { name: specifier, version: "" };
445
- return { name: specifier.slice(0, atIdx2), version: specifier.slice(atIdx2 + 1) };
381
+ function parseTomlPackagesRegex(content) {
382
+ const packages = {};
383
+ const blocks = content.split(/\[\[packages?\]\]/);
384
+ for (const block of blocks) {
385
+ const nameMatch = block.match(/\nname\s*=\s*"([^"]+)"/);
386
+ const versionMatch = block.match(/\nversion\s*=\s*"([^"]+)"/);
387
+ if (nameMatch && versionMatch) {
388
+ packages[nameMatch[1]] = versionMatch[1];
389
+ }
446
390
  }
447
- const atIdx = specifier.indexOf("@");
448
- if (atIdx < 0) return { name: specifier, version: "" };
449
- return { name: specifier.slice(0, atIdx), version: specifier.slice(atIdx + 1) };
391
+ return packages;
450
392
  }
451
393
 
452
- // src/ecosystems/deno/deno-json.ts
453
- function normalizeDenoName(name) {
454
- return name.toLowerCase();
394
+ // src/ecosystems/python/pyproject.ts
395
+ import { parse as parse2 } from "smol-toml";
396
+ function normalizePythonName(name) {
397
+ return name.toLowerCase().replace(/[-_.]+/g, "_");
398
+ }
399
+ function extractPkgName(dep) {
400
+ const match = String(dep).match(/^([\w][\w.-]*)/);
401
+ return match ? normalizePythonName(match[1]) : null;
455
402
  }
456
403
  function parseDirectDeps3(content) {
457
404
  const prod = /* @__PURE__ */ new Set();
405
+ const dev = /* @__PURE__ */ new Set();
458
406
  let data;
459
407
  try {
460
- data = JSON.parse(content);
408
+ data = parse2(content);
461
409
  } catch {
462
- return { prod, dev: /* @__PURE__ */ new Set() };
410
+ return { prod, dev };
463
411
  }
464
- const imports = data["imports"];
465
- if (imports) {
466
- for (const specifier of Object.values(imports)) {
467
- const name = extractPackageName(specifier);
468
- if (name) prod.add(normalizeDenoName(name));
412
+ const project = data.project;
413
+ const pep517Deps = project?.dependencies;
414
+ if (Array.isArray(pep517Deps)) {
415
+ for (const dep of pep517Deps) {
416
+ const name = extractPkgName(dep);
417
+ if (name) prod.add(name);
469
418
  }
470
419
  }
471
- const workspace = data["workspace"];
472
- for (const specifier of workspace?.dependencies ?? []) {
473
- const name = extractPackageName(specifier);
474
- if (name) prod.add(normalizeDenoName(name));
420
+ const optDeps = project?.["optional-dependencies"];
421
+ if (optDeps && typeof optDeps === "object") {
422
+ for (const group of Object.values(optDeps)) {
423
+ if (Array.isArray(group)) {
424
+ for (const dep of group) {
425
+ const name = extractPkgName(dep);
426
+ if (name && !prod.has(name)) dev.add(name);
427
+ }
428
+ }
429
+ }
475
430
  }
476
- return { prod, dev: /* @__PURE__ */ new Set() };
477
- }
478
- function extractPackageName(specifier) {
479
- const withoutProtocol = specifier.replace(/^(?:npm|jsr|node):/, "");
480
- if (specifier.startsWith("node:")) return null;
481
- if (withoutProtocol.startsWith("@")) {
482
- const atIdx2 = withoutProtocol.indexOf("@", 1);
483
- return atIdx2 > 0 ? withoutProtocol.slice(0, atIdx2) : withoutProtocol;
431
+ const tool = data.tool;
432
+ const poetry = tool?.poetry;
433
+ if (poetry) {
434
+ const poetryDeps = poetry.dependencies;
435
+ if (poetryDeps) {
436
+ for (const key of Object.keys(poetryDeps)) {
437
+ if (key.toLowerCase() !== "python") prod.add(normalizePythonName(key));
438
+ }
439
+ }
440
+ const devDeps = poetry["dev-dependencies"];
441
+ if (devDeps) {
442
+ for (const key of Object.keys(devDeps)) {
443
+ const normalized = normalizePythonName(key);
444
+ if (!prod.has(normalized)) dev.add(normalized);
445
+ }
446
+ }
447
+ const groups = poetry.group;
448
+ if (groups) {
449
+ for (const group of Object.values(groups)) {
450
+ const groupDeps = group.dependencies;
451
+ if (groupDeps) {
452
+ for (const key of Object.keys(groupDeps)) {
453
+ const normalized = normalizePythonName(key);
454
+ if (!prod.has(normalized)) dev.add(normalized);
455
+ }
456
+ }
457
+ }
458
+ }
484
459
  }
485
- const atIdx = withoutProtocol.indexOf("@");
486
- return atIdx > 0 ? withoutProtocol.slice(0, atIdx) : withoutProtocol || null;
460
+ const uv = tool?.uv;
461
+ const uvDevDeps = uv?.["dev-dependencies"];
462
+ if (Array.isArray(uvDevDeps)) {
463
+ for (const dep of uvDevDeps) {
464
+ const name = extractPkgName(dep);
465
+ if (name && !prod.has(name)) dev.add(name);
466
+ }
467
+ }
468
+ const depGroups = data["dependency-groups"];
469
+ if (depGroups && typeof depGroups === "object") {
470
+ for (const group of Object.values(depGroups)) {
471
+ if (Array.isArray(group)) {
472
+ for (const entry of group) {
473
+ if (typeof entry === "string") {
474
+ const name = extractPkgName(entry);
475
+ if (name && !prod.has(name)) dev.add(name);
476
+ }
477
+ }
478
+ }
479
+ }
480
+ }
481
+ return { prod, dev };
487
482
  }
488
483
 
489
- // src/ecosystems/deno/index.ts
490
- var SUPPORTED_LOCKFILES3 = [{ filename: "deno.lock", type: "deno" }];
491
- var denoEcosystem = {
492
- name: "deno",
484
+ // src/ecosystems/python/index.ts
485
+ var SUPPORTED_LOCKFILES3 = [
486
+ { filename: "uv.lock", type: "uv" },
487
+ { filename: "poetry.lock", type: "poetry" },
488
+ { filename: "pdm.lock", type: "pdm" },
489
+ { filename: "pylock.toml", type: "pylock" }
490
+ // PEP 751
491
+ ];
492
+ var lockfileTypeMap2 = new Map(SUPPORTED_LOCKFILES3.map((l) => [l.filename, l.type]));
493
+ var pythonEcosystem = {
494
+ name: "python",
493
495
  supportedLockfiles: SUPPORTED_LOCKFILES3,
494
- manifestName: "deno.json",
496
+ manifestName: "pyproject.toml",
495
497
  getLockfileType(filename) {
496
- return filename === "deno.lock" ? "deno" : void 0;
498
+ return lockfileTypeMap2.get(filename);
497
499
  },
498
500
  parseLockfile(content, _lockfileType) {
499
- return parseDenoLock(content);
501
+ return parseTomlPackages(content);
500
502
  },
501
503
  parseDirectDeps(manifestContent) {
502
504
  return parseDirectDeps3(manifestContent);
503
505
  },
504
506
  normalizeName(name) {
505
- return normalizeDenoName(name);
507
+ return normalizePythonName(name);
506
508
  }
507
509
  };
508
510
 
@@ -527,6 +529,29 @@ registerEcosystem(pythonEcosystem);
527
529
  registerEcosystem(javascriptEcosystem);
528
530
  registerEcosystem(denoEcosystem);
529
531
 
532
+ // src/core/diff.ts
533
+ function diffPackages(oldPkgs, newPkgs, directDeps, normalizeName) {
534
+ const allNames = /* @__PURE__ */ new Set([...Object.keys(oldPkgs), ...Object.keys(newPkgs)]);
535
+ const changes = [];
536
+ for (const name of [...allNames].sort()) {
537
+ const inOld = name in oldPkgs;
538
+ const inNew = name in newPkgs;
539
+ if (inOld && inNew && oldPkgs[name] === newPkgs[name]) continue;
540
+ const normalized = normalizeName(name);
541
+ const isProd = directDeps.prod.has(normalized);
542
+ const isDev = directDeps.dev.has(normalized) && !isProd;
543
+ changes.push({
544
+ name,
545
+ change_type: !inOld ? "added" : !inNew ? "removed" : "updated",
546
+ old_version: inOld ? oldPkgs[name] : null,
547
+ new_version: inNew ? newPkgs[name] : null,
548
+ is_direct: isProd || isDev,
549
+ is_dev: isDev
550
+ });
551
+ }
552
+ return changes;
553
+ }
554
+
530
555
  // src/core/discovery.ts
531
556
  import { posix } from "path";
532
557
  function workspaceFromPath(filePath) {
@@ -590,6 +615,7 @@ function resolveLockfilePair(baseFiles, headFiles) {
590
615
  basePath: chosen.path,
591
616
  baseType: chosen.type,
592
617
  headPath: chosen.path,
618
+ // biome-ignore lint/style/noNonNullAssertion: path is guaranteed present (comes from common set)
593
619
  headType: headByPath.get(chosen.path).type,
594
620
  migrationNote: null,
595
621
  ecosystemName: chosen.ecosystemName
@@ -632,29 +658,6 @@ function resolveLockfilePair(baseFiles, headFiles) {
632
658
  return null;
633
659
  }
634
660
 
635
- // src/core/diff.ts
636
- function diffPackages(oldPkgs, newPkgs, directDeps, normalizeName) {
637
- const allNames = /* @__PURE__ */ new Set([...Object.keys(oldPkgs), ...Object.keys(newPkgs)]);
638
- const changes = [];
639
- for (const name of [...allNames].sort()) {
640
- const inOld = name in oldPkgs;
641
- const inNew = name in newPkgs;
642
- if (inOld && inNew && oldPkgs[name] === newPkgs[name]) continue;
643
- const normalized = normalizeName(name);
644
- const isProd = directDeps.prod.has(normalized);
645
- const isDev = directDeps.dev.has(normalized) && !isProd;
646
- changes.push({
647
- name,
648
- change_type: !inOld ? "added" : !inNew ? "removed" : "updated",
649
- old_version: inOld ? oldPkgs[name] : null,
650
- new_version: inNew ? newPkgs[name] : null,
651
- is_direct: isProd || isDev,
652
- is_dev: isDev
653
- });
654
- }
655
- return changes;
656
- }
657
-
658
661
  // src/core/report.ts
659
662
  async function buildLockfileEntry(pair, workspace, getBase, getHead) {
660
663
  const ecosystem = getEcosystemByName(pair.ecosystemName);
@@ -788,7 +791,7 @@ function gitLsTree(ref) {
788
791
  import { execFileSync as execFileSync2 } from "child_process";
789
792
  var API_BASE = "https://api.github.com";
790
793
  function token() {
791
- const t = process.env["GITHUB_TOKEN"];
794
+ const t = process.env.GITHUB_TOKEN;
792
795
  if (!t) throw new Error("GITHUB_TOKEN is required for GitHub API access");
793
796
  return t;
794
797
  }
@@ -824,7 +827,7 @@ async function getPrShas(prNumber, repo) {
824
827
  return { baseRefOid: data.base.sha, headRefOid: data.head.sha };
825
828
  }
826
829
  function detectRepo() {
827
- const fromEnv = process.env["GITHUB_REPOSITORY"];
830
+ const fromEnv = process.env.GITHUB_REPOSITORY;
828
831
  if (fromEnv) return fromEnv;
829
832
  try {
830
833
  const remote = execFileSync2("git", ["remote", "get-url", "origin"], {
@@ -835,15 +838,17 @@ function detectRepo() {
835
838
  if (match) return match[1];
836
839
  } catch {
837
840
  }
838
- throw new Error(
839
- "Could not detect GitHub repo \u2014 set GITHUB_REPOSITORY or pass --repo"
840
- );
841
+ throw new Error("Could not detect GitHub repo \u2014 set GITHUB_REPOSITORY or pass --repo");
841
842
  }
842
843
 
843
844
  // src/index.ts
844
845
  async function resolveApiShas(options) {
845
846
  if (options.baseSha && options.headSha) {
846
- return { baseSha: options.baseSha, headSha: options.headSha, repo: options.repo ?? detectRepo() };
847
+ return {
848
+ baseSha: options.baseSha,
849
+ headSha: options.headSha,
850
+ repo: options.repo ?? detectRepo()
851
+ };
847
852
  }
848
853
  if (options.prNumber) {
849
854
  const repo = options.repo ?? detectRepo();