lockdelta 0.1.0 → 0.1.2

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