lockdelta 0.1.1 → 0.1.3

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
@@ -4,9 +4,6 @@
4
4
  import { writeFileSync } from "fs";
5
5
  import { Command } from "commander";
6
6
 
7
- // src/index.ts
8
- import { readFileSync } from "fs";
9
-
10
7
  // src/core/report.ts
11
8
  import { posix as posix2 } from "path";
12
9
 
@@ -60,7 +57,7 @@ function parseDenoLock(content) {
60
57
  const { name, version } = splitSpecifier(specifier);
61
58
  const resultKey = key === "jsr" ? `jsr:${name}` : name;
62
59
  if (name && version && !result[resultKey]) {
63
- result[resultKey] = version;
60
+ result[resultKey] = { version };
64
61
  }
65
62
  }
66
63
  }
@@ -139,7 +136,7 @@ function parseBunLock(content) {
139
136
  if (typeof nameAtVersion !== "string") continue;
140
137
  const version = extractVersion(nameAtVersion);
141
138
  if (!version || version.startsWith("workspace:")) continue;
142
- result[name] = version;
139
+ result[name] = { version };
143
140
  }
144
141
  return result;
145
142
  }
@@ -174,7 +171,10 @@ function parseV2Packages(packages) {
174
171
  const name = key.slice("node_modules/".length);
175
172
  const pkgVersion = pkg.version;
176
173
  if (pkgVersion && !result[name]) {
177
- result[name] = pkgVersion;
174
+ const entry = { version: pkgVersion };
175
+ const registryUrl = resolvedToOrigin(pkg.resolved);
176
+ if (registryUrl !== void 0) entry.registryUrl = registryUrl;
177
+ result[name] = entry;
178
178
  }
179
179
  }
180
180
  return result;
@@ -182,7 +182,10 @@ function parseV2Packages(packages) {
182
182
  function parseV1Dependencies(deps, result = {}) {
183
183
  for (const [name, pkg] of Object.entries(deps)) {
184
184
  if (pkg.version && !result[name]) {
185
- result[name] = pkg.version;
185
+ const entry = { version: pkg.version };
186
+ const registryUrl = resolvedToOrigin(pkg.resolved);
187
+ if (registryUrl !== void 0) entry.registryUrl = registryUrl;
188
+ result[name] = entry;
186
189
  }
187
190
  if (pkg.dependencies) {
188
191
  parseV1Dependencies(pkg.dependencies, result);
@@ -190,6 +193,14 @@ function parseV1Dependencies(deps, result = {}) {
190
193
  }
191
194
  return result;
192
195
  }
196
+ function resolvedToOrigin(resolved) {
197
+ if (!resolved) return void 0;
198
+ try {
199
+ return new URL(resolved).origin;
200
+ } catch {
201
+ return void 0;
202
+ }
203
+ }
193
204
 
194
205
  // src/ecosystems/javascript/parsers/pnpm.ts
195
206
  import { parse as parseYaml } from "yaml";
@@ -209,7 +220,7 @@ function parseLockfileVersion(v) {
209
220
  }
210
221
  function parsePnpmV9(packages) {
211
222
  const result = {};
212
- for (const key of Object.keys(packages)) {
223
+ for (const [key, value] of Object.entries(packages)) {
213
224
  let name;
214
225
  let version;
215
226
  if (key.startsWith("@")) {
@@ -225,14 +236,18 @@ function parsePnpmV9(packages) {
225
236
  }
226
237
  version = stripVersionSuffix(version);
227
238
  if (name && version && !result[name]) {
228
- result[name] = version;
239
+ const entry = { version };
240
+ const pkg = value;
241
+ const registryUrl = pkg?.resolution?.tarball ? resolvedToOrigin2(pkg.resolution.tarball) : void 0;
242
+ if (registryUrl !== void 0) entry.registryUrl = registryUrl;
243
+ result[name] = entry;
229
244
  }
230
245
  }
231
246
  return result;
232
247
  }
233
248
  function parsePnpmLegacy(packages) {
234
249
  const result = {};
235
- for (const key of Object.keys(packages)) {
250
+ for (const [key, value] of Object.entries(packages)) {
236
251
  const cleaned = key.startsWith("/") ? key.slice(1) : key;
237
252
  let name;
238
253
  let version;
@@ -263,7 +278,11 @@ function parsePnpmLegacy(packages) {
263
278
  }
264
279
  version = stripVersionSuffix(version);
265
280
  if (name && version && !result[name]) {
266
- result[name] = version;
281
+ const entry = { version };
282
+ const pkg = value;
283
+ const registryUrl = pkg?.resolution?.tarball ? resolvedToOrigin2(pkg.resolution.tarball) : void 0;
284
+ if (registryUrl !== void 0) entry.registryUrl = registryUrl;
285
+ result[name] = entry;
267
286
  }
268
287
  }
269
288
  return result;
@@ -271,6 +290,13 @@ function parsePnpmLegacy(packages) {
271
290
  function stripVersionSuffix(version) {
272
291
  return version.split("(")[0].split("_")[0].trim();
273
292
  }
293
+ function resolvedToOrigin2(url) {
294
+ try {
295
+ return new URL(url).origin;
296
+ } catch {
297
+ return void 0;
298
+ }
299
+ }
274
300
 
275
301
  // src/ecosystems/javascript/parsers/yarn.ts
276
302
  import { parse as parseYaml2 } from "yaml";
@@ -301,7 +327,13 @@ function parseYarnV1(content) {
301
327
  const firstSpecifier = headerLine.split(",")[0].trim().replace(/^"|"$/g, "");
302
328
  const name = extractNameFromSpecifier(firstSpecifier);
303
329
  if (name && !packages[name]) {
304
- packages[name] = versionMatch[1];
330
+ const entry = { version: versionMatch[1] };
331
+ const resolvedMatch = trimmed.match(/^[ \t]+resolved "([^"]+)"/m);
332
+ if (resolvedMatch) {
333
+ const registryUrl = resolvedToOrigin3(resolvedMatch[1]);
334
+ if (registryUrl !== void 0) entry.registryUrl = registryUrl;
335
+ }
336
+ packages[name] = entry;
305
337
  }
306
338
  }
307
339
  return packages;
@@ -312,13 +344,16 @@ function parseYarnBerry(content) {
312
344
  for (const [key, value] of Object.entries(data)) {
313
345
  if (key === "__metadata") continue;
314
346
  if (typeof value !== "object" || !value) continue;
315
- const entry = value;
316
- if (entry.linkType === "soft") continue;
317
- if (!entry.version) continue;
347
+ const berryEntry = value;
348
+ if (berryEntry.linkType === "soft") continue;
349
+ if (!berryEntry.version) continue;
318
350
  const cleanKey = key.replace(/^"|"$/g, "");
319
351
  const name = extractNameFromBerryKey(cleanKey);
320
352
  if (name && !packages[name]) {
321
- packages[name] = entry.version;
353
+ const entry = { version: berryEntry.version };
354
+ const registryUrl = extractBerryRegistryOrigin(berryEntry.resolution);
355
+ if (registryUrl !== void 0) entry.registryUrl = registryUrl;
356
+ packages[name] = entry;
322
357
  }
323
358
  }
324
359
  return packages;
@@ -330,6 +365,23 @@ function extractNameFromBerryKey(key) {
330
365
  }
331
366
  return key.split("@")[0];
332
367
  }
368
+ function extractBerryRegistryOrigin(resolution) {
369
+ if (!resolution) return void 0;
370
+ const atIdx = resolution.startsWith("@") ? resolution.indexOf("@", 1) : resolution.indexOf("@");
371
+ if (atIdx < 0) return void 0;
372
+ const spec = resolution.slice(atIdx + 1);
373
+ if (spec.startsWith("http://") || spec.startsWith("https://")) {
374
+ return resolvedToOrigin3(spec.split("#")[0]);
375
+ }
376
+ return void 0;
377
+ }
378
+ function resolvedToOrigin3(url) {
379
+ try {
380
+ return new URL(url).origin;
381
+ } catch {
382
+ return void 0;
383
+ }
384
+ }
333
385
 
334
386
  // src/ecosystems/javascript/index.ts
335
387
  var SUPPORTED_LOCKFILES2 = [
@@ -376,7 +428,15 @@ function parseTomlPackages(content) {
376
428
  const packages = {};
377
429
  for (const pkg of [...data.package ?? [], ...data.packages ?? []]) {
378
430
  if (typeof pkg.name === "string" && typeof pkg.version === "string") {
379
- packages[pkg.name] = pkg.version;
431
+ const entry = { version: pkg.version };
432
+ const sourceUrl = typeof pkg.source?.registry === "string" ? pkg.source.registry : typeof pkg.source?.url === "string" ? pkg.source.url : void 0;
433
+ if (sourceUrl !== void 0) {
434
+ try {
435
+ entry.registryUrl = new URL(sourceUrl).origin;
436
+ } catch {
437
+ }
438
+ }
439
+ packages[pkg.name] = entry;
380
440
  }
381
441
  }
382
442
  return packages;
@@ -391,7 +451,17 @@ function parseTomlPackagesRegex(content) {
391
451
  const nameMatch = block.match(/\nname\s*=\s*"([^"]+)"/);
392
452
  const versionMatch = block.match(/\nversion\s*=\s*"([^"]+)"/);
393
453
  if (nameMatch && versionMatch) {
394
- packages[nameMatch[1]] = versionMatch[1];
454
+ const entry = { version: versionMatch[1] };
455
+ const sourceRegistryMatch = block.match(/source\s*=\s*\{[^}]*registry\s*=\s*"([^"]+)"/);
456
+ const sourceUrlMatch = block.match(/\nurl\s*=\s*"([^"]+)"/);
457
+ const sourceUrl = sourceRegistryMatch?.[1] ?? sourceUrlMatch?.[1];
458
+ if (sourceUrl !== void 0) {
459
+ try {
460
+ entry.registryUrl = new URL(sourceUrl).origin;
461
+ } catch {
462
+ }
463
+ }
464
+ packages[nameMatch[1]] = entry;
395
465
  }
396
466
  }
397
467
  return packages;
@@ -542,18 +612,23 @@ function diffPackages(oldPkgs, newPkgs, directDeps, normalizeName) {
542
612
  for (const name of [...allNames].sort()) {
543
613
  const inOld = name in oldPkgs;
544
614
  const inNew = name in newPkgs;
545
- if (inOld && inNew && oldPkgs[name] === newPkgs[name]) continue;
615
+ if (inOld && inNew && oldPkgs[name].version === newPkgs[name].version) continue;
546
616
  const normalized = normalizeName(name);
547
617
  const isProd = directDeps.prod.has(normalized);
548
618
  const isDev = directDeps.dev.has(normalized) && !isProd;
549
- changes.push({
619
+ const change = {
550
620
  name,
551
621
  change_type: !inOld ? "added" : !inNew ? "removed" : "updated",
552
- old_version: inOld ? oldPkgs[name] : null,
553
- new_version: inNew ? newPkgs[name] : null,
622
+ old_version: inOld ? oldPkgs[name].version : null,
623
+ new_version: inNew ? newPkgs[name].version : null,
554
624
  is_direct: isProd || isDev,
555
625
  is_dev: isDev
556
- });
626
+ };
627
+ const oldRegistryUrl = inOld ? oldPkgs[name].registryUrl : void 0;
628
+ const newRegistryUrl = inNew ? newPkgs[name].registryUrl : void 0;
629
+ if (oldRegistryUrl !== void 0) change.old_registry_url = oldRegistryUrl;
630
+ if (newRegistryUrl !== void 0) change.new_registry_url = newRegistryUrl;
631
+ changes.push(change);
557
632
  }
558
633
  return changes;
559
634
  }
@@ -796,14 +871,29 @@ function gitLsTree(ref) {
796
871
  // src/sources/github.ts
797
872
  import { execFileSync as execFileSync2 } from "child_process";
798
873
  var API_BASE = "https://api.github.com";
799
- function token() {
800
- const t = process.env.GITHUB_TOKEN;
801
- if (!t) throw new Error("GITHUB_TOKEN is required for GitHub API access");
802
- return t;
874
+ var cachedToken;
875
+ function resolveToken() {
876
+ if (cachedToken) return cachedToken;
877
+ if (process.env.GITHUB_TOKEN) {
878
+ cachedToken = process.env.GITHUB_TOKEN;
879
+ return cachedToken;
880
+ }
881
+ try {
882
+ const t = execFileSync2("gh", ["auth", "token"], {
883
+ encoding: "utf-8",
884
+ stdio: ["pipe", "pipe", "pipe"]
885
+ }).trim();
886
+ if (t) {
887
+ cachedToken = t;
888
+ return cachedToken;
889
+ }
890
+ } catch {
891
+ }
892
+ throw new Error("No GitHub token found. Set GITHUB_TOKEN or run `gh auth login`.");
803
893
  }
804
894
  function headers(accept = "application/vnd.github+json") {
805
895
  return {
806
- Authorization: `Bearer ${token()}`,
896
+ Authorization: `Bearer ${resolveToken()}`,
807
897
  Accept: accept,
808
898
  "X-GitHub-Api-Version": "2022-11-28"
809
899
  };
@@ -847,6 +937,16 @@ function detectRepo() {
847
937
  throw new Error("Could not detect GitHub repo \u2014 set GITHUB_REPOSITORY or pass --repo");
848
938
  }
849
939
 
940
+ // src/sources/local.ts
941
+ import { readFileSync } from "fs";
942
+ function readLocalFile(path) {
943
+ try {
944
+ return readFileSync(path, "utf-8");
945
+ } catch {
946
+ return null;
947
+ }
948
+ }
949
+
850
950
  // src/index.ts
851
951
  async function resolveApiShas(options) {
852
952
  if (options.baseSha && options.headSha) {
@@ -868,15 +968,8 @@ async function run(options = {}) {
868
968
  if (options.oldFile && options.newFile) {
869
969
  const oldPath = options.oldFile;
870
970
  const newPath = options.newFile;
871
- const readLocal = (filePath) => {
872
- try {
873
- return readFileSync(filePath, "utf-8");
874
- } catch {
875
- return null;
876
- }
877
- };
878
- const getBase2 = (path) => Promise.resolve(path === oldPath ? readLocal(oldPath) : null);
879
- const getHead2 = (path) => Promise.resolve(path === newPath ? readLocal(newPath) : null);
971
+ const getBase2 = (path) => Promise.resolve(path === oldPath ? readLocalFile(oldPath) : null);
972
+ const getHead2 = (path) => Promise.resolve(path === newPath ? readLocalFile(newPath) : null);
880
973
  const lockfiles2 = await collectLockfileEntries({
881
974
  getBase: getBase2,
882
975
  getHead: getHead2,