lockdelta 0.1.2 → 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/README.md CHANGED
@@ -72,7 +72,7 @@ No inputs are required on `pull_request` or `push` events. The action reads the
72
72
 
73
73
  ### Markdown summary
74
74
 
75
- When `markdown: 'true'` or `post-comment` is not `'false'`, lockdelta generates a three-section markdown summary. Direct production dependencies are **bold**, dev dependencies are *italic*, and transitive deps are plain. Package names link to their registry (PyPI, npmjs, jsr.io).
75
+ When `markdown: 'true'` or `post-comment` is not `'false'`, lockdelta generates a three-section markdown summary. Direct production dependencies are **bold**, dev dependencies are *italic*, and transitive deps are plain. Package names link to their public registry (PyPI, npmjs, jsr.io). Packages sourced from a private registry are shown without a link; GitHub Packages scoped packages link to their GitHub repository page instead.
76
76
 
77
77
  ```yaml
78
78
  - name: Diff dependencies
@@ -234,14 +234,17 @@ console.log(report.summary);
234
234
 
235
235
  ```ts
236
236
  import { registerEcosystem } from 'lockdelta';
237
- import type { Ecosystem, DirectDeps } from 'lockdelta';
237
+ import type { Ecosystem, DirectDeps, PackageEntry } from 'lockdelta';
238
238
 
239
239
  const rubyEcosystem: Ecosystem = {
240
240
  name: 'ruby',
241
241
  supportedLockfiles: [{ filename: 'Gemfile.lock', type: 'bundler' }],
242
242
  manifestName: 'Gemfile',
243
243
  getLockfileType: (filename) => filename === 'Gemfile.lock' ? 'bundler' : undefined,
244
- parseLockfile: (content, _type) => { /* parse and return { name: version } */ return {}; },
244
+ parseLockfile: (content, _type): Record<string, PackageEntry> => {
245
+ // parse and return { packageName: { version, registryUrl? } }
246
+ return {};
247
+ },
245
248
  parseDirectDeps: (content): DirectDeps => ({ prod: new Set(), dev: new Set() }),
246
249
  normalizeName: (name) => name.toLowerCase(),
247
250
  };
@@ -259,8 +262,11 @@ interface PackageChange {
259
262
  change_type: 'added' | 'removed' | 'updated';
260
263
  old_version: string | null;
261
264
  new_version: string | null;
262
- is_direct: boolean; // declared in the project manifest
263
- is_dev: boolean; // declared in a dev/optional dependency section
265
+ is_direct: boolean; // declared in the project manifest
266
+ is_dev: boolean; // declared in a dev/optional dependency section
267
+ old_registry_url?: string; // registry origin of the old version (e.g. 'https://npm.pkg.github.com')
268
+ new_registry_url?: string; // registry origin of the new version
269
+ // Both fields present on 'updated' changes: a mismatch signals a potential registry switch
264
270
  }
265
271
 
266
272
  interface DiffReport {
package/dist/action.cjs CHANGED
@@ -7511,7 +7511,31 @@ function applyFiltersConfig(config, changes) {
7511
7511
  }
7512
7512
 
7513
7513
  // src/action/markdown.ts
7514
- function packageUrl(ecosystem, name) {
7514
+ var PUBLIC_NPM_ORIGINS = /* @__PURE__ */ new Set(["https://registry.npmjs.org", "https://registry.yarnpkg.com"]);
7515
+ var PUBLIC_PYPI_ORIGIN = "https://pypi.org";
7516
+ function packageUrl(ecosystem, name, registryUrl) {
7517
+ if (registryUrl !== void 0) {
7518
+ let origin;
7519
+ try {
7520
+ origin = new URL(registryUrl).origin;
7521
+ } catch {
7522
+ return null;
7523
+ }
7524
+ const isNpmLike = ecosystem === "javascript" || ecosystem === "deno" && !name.startsWith("jsr:");
7525
+ if (isNpmLike) {
7526
+ if (!PUBLIC_NPM_ORIGINS.has(origin)) {
7527
+ if (origin === "https://npm.pkg.github.com" && name.startsWith("@")) {
7528
+ const parts = name.slice(1).split("/");
7529
+ if (parts.length === 2) return `https://github.com/${parts[0]}/${parts[1]}`;
7530
+ }
7531
+ return null;
7532
+ }
7533
+ } else if (ecosystem === "python") {
7534
+ if (!origin.startsWith(PUBLIC_PYPI_ORIGIN)) {
7535
+ return null;
7536
+ }
7537
+ }
7538
+ }
7515
7539
  switch (ecosystem) {
7516
7540
  case "python":
7517
7541
  return `https://pypi.org/project/${name}/`;
@@ -7525,7 +7549,11 @@ function packageUrl(ecosystem, name) {
7525
7549
  }
7526
7550
  }
7527
7551
  function formatName(change, ecosystem) {
7528
- const url = packageUrl(ecosystem, change.name);
7552
+ const url = packageUrl(
7553
+ ecosystem,
7554
+ change.name,
7555
+ change.new_registry_url ?? change.old_registry_url
7556
+ );
7529
7557
  const linked = url ? `[${change.name}](${url})` : change.name;
7530
7558
  if (change.is_direct && !change.is_dev) return `**${linked}**`;
7531
7559
  if (change.is_dev) return `*${linked}*`;
@@ -7620,7 +7648,7 @@ function parseDenoLock(content) {
7620
7648
  const { name, version } = splitSpecifier(specifier);
7621
7649
  const resultKey = key === "jsr" ? `jsr:${name}` : name;
7622
7650
  if (name && version && !result[resultKey]) {
7623
- result[resultKey] = version;
7651
+ result[resultKey] = { version };
7624
7652
  }
7625
7653
  }
7626
7654
  }
@@ -7699,7 +7727,7 @@ function parseBunLock(content) {
7699
7727
  if (typeof nameAtVersion !== "string") continue;
7700
7728
  const version = extractVersion(nameAtVersion);
7701
7729
  if (!version || version.startsWith("workspace:")) continue;
7702
- result[name] = version;
7730
+ result[name] = { version };
7703
7731
  }
7704
7732
  return result;
7705
7733
  }
@@ -7734,7 +7762,10 @@ function parseV2Packages(packages) {
7734
7762
  const name = key.slice("node_modules/".length);
7735
7763
  const pkgVersion = pkg.version;
7736
7764
  if (pkgVersion && !result[name]) {
7737
- result[name] = pkgVersion;
7765
+ const entry = { version: pkgVersion };
7766
+ const registryUrl = resolvedToOrigin(pkg.resolved);
7767
+ if (registryUrl !== void 0) entry.registryUrl = registryUrl;
7768
+ result[name] = entry;
7738
7769
  }
7739
7770
  }
7740
7771
  return result;
@@ -7742,7 +7773,10 @@ function parseV2Packages(packages) {
7742
7773
  function parseV1Dependencies(deps, result = {}) {
7743
7774
  for (const [name, pkg] of Object.entries(deps)) {
7744
7775
  if (pkg.version && !result[name]) {
7745
- result[name] = pkg.version;
7776
+ const entry = { version: pkg.version };
7777
+ const registryUrl = resolvedToOrigin(pkg.resolved);
7778
+ if (registryUrl !== void 0) entry.registryUrl = registryUrl;
7779
+ result[name] = entry;
7746
7780
  }
7747
7781
  if (pkg.dependencies) {
7748
7782
  parseV1Dependencies(pkg.dependencies, result);
@@ -7750,6 +7784,14 @@ function parseV1Dependencies(deps, result = {}) {
7750
7784
  }
7751
7785
  return result;
7752
7786
  }
7787
+ function resolvedToOrigin(resolved) {
7788
+ if (!resolved) return void 0;
7789
+ try {
7790
+ return new URL(resolved).origin;
7791
+ } catch {
7792
+ return void 0;
7793
+ }
7794
+ }
7753
7795
 
7754
7796
  // src/ecosystems/javascript/parsers/pnpm.ts
7755
7797
  var import_yaml2 = __toESM(require_dist(), 1);
@@ -7769,7 +7811,7 @@ function parseLockfileVersion(v) {
7769
7811
  }
7770
7812
  function parsePnpmV9(packages) {
7771
7813
  const result = {};
7772
- for (const key of Object.keys(packages)) {
7814
+ for (const [key, value] of Object.entries(packages)) {
7773
7815
  let name;
7774
7816
  let version;
7775
7817
  if (key.startsWith("@")) {
@@ -7785,14 +7827,18 @@ function parsePnpmV9(packages) {
7785
7827
  }
7786
7828
  version = stripVersionSuffix(version);
7787
7829
  if (name && version && !result[name]) {
7788
- result[name] = version;
7830
+ const entry = { version };
7831
+ const pkg = value;
7832
+ const registryUrl = pkg?.resolution?.tarball ? resolvedToOrigin2(pkg.resolution.tarball) : void 0;
7833
+ if (registryUrl !== void 0) entry.registryUrl = registryUrl;
7834
+ result[name] = entry;
7789
7835
  }
7790
7836
  }
7791
7837
  return result;
7792
7838
  }
7793
7839
  function parsePnpmLegacy(packages) {
7794
7840
  const result = {};
7795
- for (const key of Object.keys(packages)) {
7841
+ for (const [key, value] of Object.entries(packages)) {
7796
7842
  const cleaned = key.startsWith("/") ? key.slice(1) : key;
7797
7843
  let name;
7798
7844
  let version;
@@ -7823,7 +7869,11 @@ function parsePnpmLegacy(packages) {
7823
7869
  }
7824
7870
  version = stripVersionSuffix(version);
7825
7871
  if (name && version && !result[name]) {
7826
- result[name] = version;
7872
+ const entry = { version };
7873
+ const pkg = value;
7874
+ const registryUrl = pkg?.resolution?.tarball ? resolvedToOrigin2(pkg.resolution.tarball) : void 0;
7875
+ if (registryUrl !== void 0) entry.registryUrl = registryUrl;
7876
+ result[name] = entry;
7827
7877
  }
7828
7878
  }
7829
7879
  return result;
@@ -7831,6 +7881,13 @@ function parsePnpmLegacy(packages) {
7831
7881
  function stripVersionSuffix(version) {
7832
7882
  return version.split("(")[0].split("_")[0].trim();
7833
7883
  }
7884
+ function resolvedToOrigin2(url) {
7885
+ try {
7886
+ return new URL(url).origin;
7887
+ } catch {
7888
+ return void 0;
7889
+ }
7890
+ }
7834
7891
 
7835
7892
  // src/ecosystems/javascript/parsers/yarn.ts
7836
7893
  var import_yaml3 = __toESM(require_dist(), 1);
@@ -7861,7 +7918,13 @@ function parseYarnV1(content) {
7861
7918
  const firstSpecifier = headerLine.split(",")[0].trim().replace(/^"|"$/g, "");
7862
7919
  const name = extractNameFromSpecifier(firstSpecifier);
7863
7920
  if (name && !packages[name]) {
7864
- packages[name] = versionMatch[1];
7921
+ const entry = { version: versionMatch[1] };
7922
+ const resolvedMatch = trimmed.match(/^[ \t]+resolved "([^"]+)"/m);
7923
+ if (resolvedMatch) {
7924
+ const registryUrl = resolvedToOrigin3(resolvedMatch[1]);
7925
+ if (registryUrl !== void 0) entry.registryUrl = registryUrl;
7926
+ }
7927
+ packages[name] = entry;
7865
7928
  }
7866
7929
  }
7867
7930
  return packages;
@@ -7872,13 +7935,16 @@ function parseYarnBerry(content) {
7872
7935
  for (const [key, value] of Object.entries(data)) {
7873
7936
  if (key === "__metadata") continue;
7874
7937
  if (typeof value !== "object" || !value) continue;
7875
- const entry = value;
7876
- if (entry.linkType === "soft") continue;
7877
- if (!entry.version) continue;
7938
+ const berryEntry = value;
7939
+ if (berryEntry.linkType === "soft") continue;
7940
+ if (!berryEntry.version) continue;
7878
7941
  const cleanKey = key.replace(/^"|"$/g, "");
7879
7942
  const name = extractNameFromBerryKey(cleanKey);
7880
7943
  if (name && !packages[name]) {
7881
- packages[name] = entry.version;
7944
+ const entry = { version: berryEntry.version };
7945
+ const registryUrl = extractBerryRegistryOrigin(berryEntry.resolution);
7946
+ if (registryUrl !== void 0) entry.registryUrl = registryUrl;
7947
+ packages[name] = entry;
7882
7948
  }
7883
7949
  }
7884
7950
  return packages;
@@ -7890,6 +7956,23 @@ function extractNameFromBerryKey(key) {
7890
7956
  }
7891
7957
  return key.split("@")[0];
7892
7958
  }
7959
+ function extractBerryRegistryOrigin(resolution) {
7960
+ if (!resolution) return void 0;
7961
+ const atIdx = resolution.startsWith("@") ? resolution.indexOf("@", 1) : resolution.indexOf("@");
7962
+ if (atIdx < 0) return void 0;
7963
+ const spec = resolution.slice(atIdx + 1);
7964
+ if (spec.startsWith("http://") || spec.startsWith("https://")) {
7965
+ return resolvedToOrigin3(spec.split("#")[0]);
7966
+ }
7967
+ return void 0;
7968
+ }
7969
+ function resolvedToOrigin3(url) {
7970
+ try {
7971
+ return new URL(url).origin;
7972
+ } catch {
7973
+ return void 0;
7974
+ }
7975
+ }
7893
7976
 
7894
7977
  // src/ecosystems/javascript/index.ts
7895
7978
  var SUPPORTED_LOCKFILES2 = [
@@ -8625,7 +8708,15 @@ function parseTomlPackages(content) {
8625
8708
  const packages = {};
8626
8709
  for (const pkg of [...data.package ?? [], ...data.packages ?? []]) {
8627
8710
  if (typeof pkg.name === "string" && typeof pkg.version === "string") {
8628
- packages[pkg.name] = pkg.version;
8711
+ const entry = { version: pkg.version };
8712
+ const sourceUrl = typeof pkg.source?.registry === "string" ? pkg.source.registry : typeof pkg.source?.url === "string" ? pkg.source.url : void 0;
8713
+ if (sourceUrl !== void 0) {
8714
+ try {
8715
+ entry.registryUrl = new URL(sourceUrl).origin;
8716
+ } catch {
8717
+ }
8718
+ }
8719
+ packages[pkg.name] = entry;
8629
8720
  }
8630
8721
  }
8631
8722
  return packages;
@@ -8640,7 +8731,17 @@ function parseTomlPackagesRegex(content) {
8640
8731
  const nameMatch = block.match(/\nname\s*=\s*"([^"]+)"/);
8641
8732
  const versionMatch = block.match(/\nversion\s*=\s*"([^"]+)"/);
8642
8733
  if (nameMatch && versionMatch) {
8643
- packages[nameMatch[1]] = versionMatch[1];
8734
+ const entry = { version: versionMatch[1] };
8735
+ const sourceRegistryMatch = block.match(/source\s*=\s*\{[^}]*registry\s*=\s*"([^"]+)"/);
8736
+ const sourceUrlMatch = block.match(/\nurl\s*=\s*"([^"]+)"/);
8737
+ const sourceUrl = sourceRegistryMatch?.[1] ?? sourceUrlMatch?.[1];
8738
+ if (sourceUrl !== void 0) {
8739
+ try {
8740
+ entry.registryUrl = new URL(sourceUrl).origin;
8741
+ } catch {
8742
+ }
8743
+ }
8744
+ packages[nameMatch[1]] = entry;
8644
8745
  }
8645
8746
  }
8646
8747
  return packages;
@@ -8790,18 +8891,23 @@ function diffPackages(oldPkgs, newPkgs, directDeps, normalizeName) {
8790
8891
  for (const name of [...allNames].sort()) {
8791
8892
  const inOld = name in oldPkgs;
8792
8893
  const inNew = name in newPkgs;
8793
- if (inOld && inNew && oldPkgs[name] === newPkgs[name]) continue;
8894
+ if (inOld && inNew && oldPkgs[name].version === newPkgs[name].version) continue;
8794
8895
  const normalized = normalizeName(name);
8795
8896
  const isProd = directDeps.prod.has(normalized);
8796
8897
  const isDev = directDeps.dev.has(normalized) && !isProd;
8797
- changes.push({
8898
+ const change = {
8798
8899
  name,
8799
8900
  change_type: !inOld ? "added" : !inNew ? "removed" : "updated",
8800
- old_version: inOld ? oldPkgs[name] : null,
8801
- new_version: inNew ? newPkgs[name] : null,
8901
+ old_version: inOld ? oldPkgs[name].version : null,
8902
+ new_version: inNew ? newPkgs[name].version : null,
8802
8903
  is_direct: isProd || isDev,
8803
8904
  is_dev: isDev
8804
- });
8905
+ };
8906
+ const oldRegistryUrl = inOld ? oldPkgs[name].registryUrl : void 0;
8907
+ const newRegistryUrl = inNew ? newPkgs[name].registryUrl : void 0;
8908
+ if (oldRegistryUrl !== void 0) change.old_registry_url = oldRegistryUrl;
8909
+ if (newRegistryUrl !== void 0) change.new_registry_url = newRegistryUrl;
8910
+ changes.push(change);
8805
8911
  }
8806
8912
  return changes;
8807
8913
  }