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/action.yml +1 -1
- package/dist/action.js +8575 -458
- package/dist/action.js.map +1 -1
- package/dist/cli.js +327 -321
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +320 -315
- package/dist/index.js.map +1 -1
- package/package.json +5 -1
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/
|
|
14
|
-
|
|
15
|
-
|
|
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 =
|
|
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
|
|
69
|
-
if (
|
|
70
|
-
for (const
|
|
71
|
-
|
|
72
|
-
|
|
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
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
|
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/
|
|
133
|
-
var SUPPORTED_LOCKFILES = [
|
|
134
|
-
|
|
135
|
-
|
|
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: "
|
|
85
|
+
manifestName: "deno.json",
|
|
145
86
|
getLockfileType(filename) {
|
|
146
|
-
return
|
|
87
|
+
return filename === "deno.lock" ? "deno" : void 0;
|
|
147
88
|
},
|
|
148
89
|
parseLockfile(content, _lockfileType) {
|
|
149
|
-
return
|
|
90
|
+
return parseDenoLock(content);
|
|
150
91
|
},
|
|
151
92
|
parseDirectDeps(manifestContent) {
|
|
152
93
|
return parseDirectDeps(manifestContent);
|
|
153
94
|
},
|
|
154
95
|
normalizeName(name) {
|
|
155
|
-
return
|
|
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
|
|
195
|
+
import { parse as parseYaml } from "yaml";
|
|
259
196
|
function parsePnpmLock(content) {
|
|
260
|
-
const data =
|
|
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
|
|
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
|
|
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/
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
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
|
-
|
|
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
|
|
365
|
-
const
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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
|
|
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
|
|
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
|
|
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/
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
[
|
|
435
|
-
|
|
436
|
-
|
|
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
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
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
|
-
|
|
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/
|
|
459
|
-
|
|
460
|
-
|
|
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 =
|
|
414
|
+
data = parse2(content);
|
|
467
415
|
} catch {
|
|
468
|
-
return { prod, dev
|
|
416
|
+
return { prod, dev };
|
|
469
417
|
}
|
|
470
|
-
const
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
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
|
|
478
|
-
|
|
479
|
-
const
|
|
480
|
-
|
|
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
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
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
|
|
492
|
-
|
|
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/
|
|
496
|
-
var SUPPORTED_LOCKFILES3 = [
|
|
497
|
-
|
|
498
|
-
|
|
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: "
|
|
502
|
+
manifestName: "pyproject.toml",
|
|
501
503
|
getLockfileType(filename) {
|
|
502
|
-
return filename
|
|
504
|
+
return lockfileTypeMap2.get(filename);
|
|
503
505
|
},
|
|
504
506
|
parseLockfile(content, _lockfileType) {
|
|
505
|
-
return
|
|
507
|
+
return parseTomlPackages(content);
|
|
506
508
|
},
|
|
507
509
|
parseDirectDeps(manifestContent) {
|
|
508
510
|
return parseDirectDeps3(manifestContent);
|
|
509
511
|
},
|
|
510
512
|
normalizeName(name) {
|
|
511
|
-
return
|
|
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
|
|
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
|
|
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 {
|
|
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("
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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)
|
|
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}
|