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/action.yml +19 -3
- package/dist/action.cjs +9306 -0
- package/dist/action.cjs.map +1 -0
- package/dist/cli.js +357 -336
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +350 -330
- package/dist/index.js.map +1 -1
- package/package.json +21 -13
- package/dist/action.js +0 -1144
- package/dist/action.js.map +0 -1
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/
|
|
8
|
-
|
|
9
|
-
|
|
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 =
|
|
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
|
|
63
|
-
if (
|
|
64
|
-
for (const
|
|
65
|
-
|
|
66
|
-
|
|
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
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
|
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/
|
|
127
|
-
var SUPPORTED_LOCKFILES = [
|
|
128
|
-
|
|
129
|
-
|
|
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: "
|
|
76
|
+
manifestName: "deno.json",
|
|
139
77
|
getLockfileType(filename) {
|
|
140
|
-
return
|
|
78
|
+
return filename === "deno.lock" ? "deno" : void 0;
|
|
141
79
|
},
|
|
142
80
|
parseLockfile(content, _lockfileType) {
|
|
143
|
-
return
|
|
81
|
+
return parseDenoLock(content);
|
|
144
82
|
},
|
|
145
83
|
parseDirectDeps(manifestContent) {
|
|
146
84
|
return parseDirectDeps(manifestContent);
|
|
147
85
|
},
|
|
148
86
|
normalizeName(name) {
|
|
149
|
-
return
|
|
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
|
|
186
|
+
import { parse as parseYaml } from "yaml";
|
|
253
187
|
function parsePnpmLock(content) {
|
|
254
|
-
const data =
|
|
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
|
|
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
|
|
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/
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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
|
-
|
|
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
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
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
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
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
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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
|
|
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
|
|
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
|
|
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/
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
[
|
|
429
|
-
|
|
430
|
-
|
|
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
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
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
|
-
|
|
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/
|
|
453
|
-
|
|
454
|
-
|
|
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 =
|
|
405
|
+
data = parse2(content);
|
|
461
406
|
} catch {
|
|
462
|
-
return { prod, dev
|
|
407
|
+
return { prod, dev };
|
|
463
408
|
}
|
|
464
|
-
const
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
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
|
|
472
|
-
|
|
473
|
-
const
|
|
474
|
-
|
|
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
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
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
|
|
486
|
-
|
|
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/
|
|
490
|
-
var SUPPORTED_LOCKFILES3 = [
|
|
491
|
-
|
|
492
|
-
|
|
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: "
|
|
493
|
+
manifestName: "pyproject.toml",
|
|
495
494
|
getLockfileType(filename) {
|
|
496
|
-
return filename
|
|
495
|
+
return lockfileTypeMap2.get(filename);
|
|
497
496
|
},
|
|
498
497
|
parseLockfile(content, _lockfileType) {
|
|
499
|
-
return
|
|
498
|
+
return parseTomlPackages(content);
|
|
500
499
|
},
|
|
501
500
|
parseDirectDeps(manifestContent) {
|
|
502
501
|
return parseDirectDeps3(manifestContent);
|
|
503
502
|
},
|
|
504
503
|
normalizeName(name) {
|
|
505
|
-
return
|
|
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
|
-
|
|
791
|
-
|
|
792
|
-
if (
|
|
793
|
-
|
|
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 ${
|
|
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
|
|
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
|
-
|
|
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 {
|
|
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
|
|
861
|
-
|
|
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,
|