patchdrill 0.1.0
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/.patchdrill.yml +33 -0
- package/CHANGELOG.md +150 -0
- package/CONTRIBUTING.md +59 -0
- package/LICENSE +21 -0
- package/README.md +601 -0
- package/SECURITY.md +28 -0
- package/action.yml +338 -0
- package/dist/baseline.d.ts +9 -0
- package/dist/baseline.js +38 -0
- package/dist/baseline.js.map +1 -0
- package/dist/cli.d.ts +19 -0
- package/dist/cli.js +662 -0
- package/dist/cli.js.map +1 -0
- package/dist/codeowners.d.ts +14 -0
- package/dist/codeowners.js +104 -0
- package/dist/codeowners.js.map +1 -0
- package/dist/command-plan.d.ts +3 -0
- package/dist/command-plan.js +26 -0
- package/dist/command-plan.js.map +1 -0
- package/dist/demo.d.ts +5 -0
- package/dist/demo.js +525 -0
- package/dist/demo.js.map +1 -0
- package/dist/dependency.d.ts +4 -0
- package/dist/dependency.js +1424 -0
- package/dist/dependency.js.map +1 -0
- package/dist/doctor.d.ts +26 -0
- package/dist/doctor.js +183 -0
- package/dist/doctor.js.map +1 -0
- package/dist/evidence.d.ts +64 -0
- package/dist/evidence.js +352 -0
- package/dist/evidence.js.map +1 -0
- package/dist/git.d.ts +16 -0
- package/dist/git.js +349 -0
- package/dist/git.js.map +1 -0
- package/dist/i18n-catalog.d.ts +8 -0
- package/dist/i18n-catalog.js +446 -0
- package/dist/i18n-catalog.js.map +1 -0
- package/dist/i18n.d.ts +20 -0
- package/dist/i18n.js +67 -0
- package/dist/i18n.js.map +1 -0
- package/dist/init.d.ts +13 -0
- package/dist/init.js +312 -0
- package/dist/init.js.map +1 -0
- package/dist/markdown-links.d.ts +18 -0
- package/dist/markdown-links.js +180 -0
- package/dist/markdown-links.js.map +1 -0
- package/dist/package-scripts.d.ts +3 -0
- package/dist/package-scripts.js +55 -0
- package/dist/package-scripts.js.map +1 -0
- package/dist/planner.d.ts +8 -0
- package/dist/planner.js +2351 -0
- package/dist/planner.js.map +1 -0
- package/dist/policy.d.ts +12 -0
- package/dist/policy.js +255 -0
- package/dist/policy.js.map +1 -0
- package/dist/project.d.ts +2 -0
- package/dist/project.js +1085 -0
- package/dist/project.js.map +1 -0
- package/dist/release-readiness.d.ts +25 -0
- package/dist/release-readiness.js +426 -0
- package/dist/release-readiness.js.map +1 -0
- package/dist/report-annotations.d.ts +3 -0
- package/dist/report-annotations.js +28 -0
- package/dist/report-annotations.js.map +1 -0
- package/dist/report-contract.d.ts +2 -0
- package/dist/report-contract.js +82 -0
- package/dist/report-contract.js.map +1 -0
- package/dist/report-html.d.ts +7 -0
- package/dist/report-html.js +706 -0
- package/dist/report-html.js.map +1 -0
- package/dist/report-sarif.d.ts +2 -0
- package/dist/report-sarif.js +90 -0
- package/dist/report-sarif.js.map +1 -0
- package/dist/report.d.ts +14 -0
- package/dist/report.js +310 -0
- package/dist/report.js.map +1 -0
- package/dist/risk.d.ts +19 -0
- package/dist/risk.js +1226 -0
- package/dist/risk.js.map +1 -0
- package/dist/runner.d.ts +8 -0
- package/dist/runner.js +113 -0
- package/dist/runner.js.map +1 -0
- package/dist/scan.d.ts +2 -0
- package/dist/scan.js +195 -0
- package/dist/scan.js.map +1 -0
- package/dist/schema.d.ts +12 -0
- package/dist/schema.js +30 -0
- package/dist/schema.js.map +1 -0
- package/dist/stack-coverage.d.ts +8 -0
- package/dist/stack-coverage.js +94 -0
- package/dist/stack-coverage.js.map +1 -0
- package/dist/types.d.ts +206 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/verification.d.ts +11 -0
- package/dist/verification.js +108 -0
- package/dist/verification.js.map +1 -0
- package/docs/ANNOTATIONS.md +34 -0
- package/docs/ARCHITECTURE.md +79 -0
- package/docs/BASELINES.md +32 -0
- package/docs/CASE_STUDIES.md +106 -0
- package/docs/CODEOWNERS.md +23 -0
- package/docs/DASHBOARD.md +87 -0
- package/docs/EVIDENCE.md +55 -0
- package/docs/LAUNCH_PLAYBOOK.md +103 -0
- package/docs/MONOREPOS.md +74 -0
- package/docs/POLICY.md +98 -0
- package/docs/PROOF_PACKS.md +57 -0
- package/docs/PR_COMMENTS.md +56 -0
- package/docs/RELEASE.md +35 -0
- package/docs/ROADMAP.md +152 -0
- package/docs/RULE_CATALOG.md +90 -0
- package/docs/SARIF.md +74 -0
- package/docs/SCHEMAS.md +49 -0
- package/docs/SECURITY_POSTURE.md +32 -0
- package/docs/STACK_COVERAGE.md +20 -0
- package/docs/assets/patchdrill-demo.svg +21 -0
- package/docs/media/patchdrill-dashboard.png +0 -0
- package/docs/media/patchdrill-demo.gif +0 -0
- package/examples/case-studies/README.md +20 -0
- package/examples/demo/README.md +21 -0
- package/examples/demo/patchdrill-demo-summary.md +35 -0
- package/examples/demo/patchdrill-demo.html +623 -0
- package/examples/demo/patchdrill-demo.json +355 -0
- package/examples/demo/patchdrill-demo.md +120 -0
- package/examples/demo/patchdrill-demo.sarif +195 -0
- package/examples/report.md +128 -0
- package/examples/risky-agent-pr/README.md +15 -0
- package/examples/risky-agent-pr/patchdrill-demo-summary.md +41 -0
- package/examples/risky-agent-pr/patchdrill-demo.html +681 -0
- package/examples/risky-agent-pr/patchdrill-demo.json +483 -0
- package/examples/risky-agent-pr/patchdrill-demo.md +140 -0
- package/examples/risky-agent-pr/patchdrill-demo.sarif +398 -0
- package/fixtures/stacks/README.md +4 -0
- package/fixtures/stacks/android-gradle/fixture.json +33 -0
- package/fixtures/stacks/aspnet-core-service/fixture.json +36 -0
- package/fixtures/stacks/bazel-workspace/fixture.json +30 -0
- package/fixtures/stacks/buck2-workspace/fixture.json +30 -0
- package/fixtures/stacks/cargo-workspace/fixture.json +48 -0
- package/fixtures/stacks/django-app/fixture.json +25 -0
- package/fixtures/stacks/docker-compose/fixture.json +17 -0
- package/fixtures/stacks/dockerfile-service/fixture.json +17 -0
- package/fixtures/stacks/dotnet-service/fixture.json +36 -0
- package/fixtures/stacks/dotnet-solution-filter/fixture.json +62 -0
- package/fixtures/stacks/fastapi-app/fixture.json +29 -0
- package/fixtures/stacks/go-workspace/fixture.json +48 -0
- package/fixtures/stacks/java-gradle/fixture.json +29 -0
- package/fixtures/stacks/java-maven/fixture.json +32 -0
- package/fixtures/stacks/kubernetes-helm/fixture.json +25 -0
- package/fixtures/stacks/kubernetes-kustomize/fixture.json +21 -0
- package/fixtures/stacks/nested-go-workspace/fixture.json +51 -0
- package/fixtures/stacks/nextjs-app/fixture.json +34 -0
- package/fixtures/stacks/node-turbo-workspace/fixture.json +39 -0
- package/fixtures/stacks/pants-python/fixture.json +33 -0
- package/fixtures/stacks/php-composer/fixture.json +31 -0
- package/fixtures/stacks/python-service/fixture.json +21 -0
- package/fixtures/stacks/rails-app/fixture.json +25 -0
- package/fixtures/stacks/spring-boot-gradle/fixture.json +29 -0
- package/fixtures/stacks/spring-boot-maven/fixture.json +43 -0
- package/fixtures/stacks/swift-package/fixture.json +21 -0
- package/fixtures/stacks/terraform-module/fixture.json +17 -0
- package/fixtures/stacks/uv-python-service/fixture.json +47 -0
- package/fixtures/stacks/xcode-app/fixture.json +72 -0
- package/package.json +80 -0
- package/schemas/patchdrill-doctor.schema.json +171 -0
- package/schemas/patchdrill-evidence.schema.json +239 -0
- package/schemas/patchdrill-policy.schema.json +170 -0
- package/schemas/patchdrill-release-check.schema.json +78 -0
- package/schemas/patchdrill-report.schema.json +647 -0
|
@@ -0,0 +1,1424 @@
|
|
|
1
|
+
import { readFilePair } from "./git.js";
|
|
2
|
+
import { parse as parseYaml } from "yaml";
|
|
3
|
+
const dependencyFields = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"];
|
|
4
|
+
const dependencyAnalyzers = [
|
|
5
|
+
createDependencyAnalyzer("package.json", (path) => baseName(path) === "package.json", parsePackageJson, diffPackageJson, () => ({})),
|
|
6
|
+
createDependencyAnalyzer("requirements.txt", isRequirementsFile, parseRequirements, diffRequirementPackages, () => new Map()),
|
|
7
|
+
createDependencyAnalyzer("pyproject.toml", (path) => baseName(path) === "pyproject.toml", parsePyprojectDependencies, diffManifestDependencies, () => new Map()),
|
|
8
|
+
createDependencyAnalyzer("NuGet PackageReference/PackageVersion", isDotnetDependencyManifest, parseDotnetDependencyManifest, diffManifestDependencies, () => new Map()),
|
|
9
|
+
createDependencyAnalyzer("Maven pom.xml", (path) => baseName(path) === "pom.xml", parseMavenPomDependencies, diffManifestDependencies, () => new Map()),
|
|
10
|
+
createDependencyAnalyzer("Gradle build file", isGradleBuildFile, parseGradleDependencies, diffManifestDependencies, () => new Map()),
|
|
11
|
+
createDependencyAnalyzer("Gradle version catalog", isGradleVersionCatalog, parseGradleVersionCatalog, diffManifestDependencies, () => new Map()),
|
|
12
|
+
createDependencyAnalyzer("composer.json", (path) => baseName(path) === "composer.json", parseComposerJson, diffPackageJson, () => ({})),
|
|
13
|
+
createDependencyAnalyzer("Gemfile", (path) => baseName(path) === "Gemfile", parseGemfile, diffManifestDependencies, () => new Map()),
|
|
14
|
+
createDependencyAnalyzer("go.mod", (path) => baseName(path) === "go.mod", parseGoMod, diffManifestDependencies, () => new Map()),
|
|
15
|
+
createDependencyAnalyzer("Cargo.toml", (path) => baseName(path) === "Cargo.toml", parseCargoToml, diffManifestDependencies, () => new Map()),
|
|
16
|
+
createDependencyAnalyzer("package-lock.json", (path) => baseName(path) === "package-lock.json", parsePackageLock, diffLockPackages, () => new Map()),
|
|
17
|
+
createDependencyAnalyzer("pnpm-lock.yaml", (path) => baseName(path) === "pnpm-lock.yaml", parsePnpmLock, diffNameVersionLockPackages, () => new Map()),
|
|
18
|
+
createDependencyAnalyzer("yarn.lock", (path) => baseName(path) === "yarn.lock", parseYarnLock, diffNameVersionLockPackages, () => new Map()),
|
|
19
|
+
createDependencyAnalyzer("bun.lock", (path) => baseName(path) === "bun.lock", parseBunLock, diffLockPackages, () => new Map()),
|
|
20
|
+
createDependencyAnalyzer("go.sum", (path) => baseName(path) === "go.sum", parseGoSum, diffNameVersionLockPackages, () => new Map()),
|
|
21
|
+
createDependencyAnalyzer("Cargo.lock", (path) => baseName(path) === "Cargo.lock", parseTomlPackageLock, diffNameVersionLockPackages, () => new Map()),
|
|
22
|
+
createDependencyAnalyzer("poetry.lock", (path) => baseName(path) === "poetry.lock", parseTomlPackageLock, diffNameVersionLockPackages, () => new Map()),
|
|
23
|
+
createDependencyAnalyzer("uv.lock", (path) => baseName(path) === "uv.lock", parseUvLock, diffNameVersionLockPackages, () => new Map()),
|
|
24
|
+
createDependencyAnalyzer("Pipfile.lock", (path) => baseName(path) === "Pipfile.lock", parsePipfileLock, diffLockPackages, () => new Map()),
|
|
25
|
+
createDependencyAnalyzer("Gemfile.lock", (path) => baseName(path) === "Gemfile.lock", parseGemfileLock, diffNameVersionLockPackages, () => new Map()),
|
|
26
|
+
createDependencyAnalyzer("composer.lock", (path) => baseName(path) === "composer.lock", parseComposerLock, diffLockPackages, () => new Map())
|
|
27
|
+
];
|
|
28
|
+
// Match by exact basename, not suffix, so "my-package.json" or "x.go.mod" are not
|
|
29
|
+
// misclassified as manifests — keeping dependency.ts consistent with the proof-gap
|
|
30
|
+
// rules in risk.ts, which also compare basenames.
|
|
31
|
+
function baseName(path) {
|
|
32
|
+
return path.split("/").at(-1) ?? path;
|
|
33
|
+
}
|
|
34
|
+
export function analyzeDependencyChanges(options, changedFiles) {
|
|
35
|
+
const changes = [];
|
|
36
|
+
for (const file of changedFiles) {
|
|
37
|
+
const analyzer = dependencyAnalyzers.find((candidate) => candidate.matches(file.path));
|
|
38
|
+
if (!analyzer)
|
|
39
|
+
continue;
|
|
40
|
+
const pair = readFilePair(options, file.path);
|
|
41
|
+
changes.push(...analyzer.analyze(file.path, pair.before, pair.after));
|
|
42
|
+
}
|
|
43
|
+
return changes.sort((a, b) => `${a.file}:${a.dependencyType}:${a.packageName}:${a.packagePath ?? ""}`.localeCompare(`${b.file}:${b.dependencyType}:${b.packageName}:${b.packagePath ?? ""}`));
|
|
44
|
+
}
|
|
45
|
+
export function supportedDependencyFormats() {
|
|
46
|
+
return dependencyAnalyzers.map((analyzer) => analyzer.name);
|
|
47
|
+
}
|
|
48
|
+
function createDependencyAnalyzer(name, matches, parse, diff, empty) {
|
|
49
|
+
return {
|
|
50
|
+
name,
|
|
51
|
+
matches,
|
|
52
|
+
analyze: (file, beforeContent, afterContent) => {
|
|
53
|
+
const before = parse(beforeContent);
|
|
54
|
+
const after = parse(afterContent);
|
|
55
|
+
if (!before && !after)
|
|
56
|
+
return [];
|
|
57
|
+
return diff(file, before ?? empty(), after ?? empty());
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function diffPackageJson(file, before, after) {
|
|
62
|
+
const changes = [];
|
|
63
|
+
for (const dependencyType of dependencyFields) {
|
|
64
|
+
const beforeDeps = before[dependencyType] ?? {};
|
|
65
|
+
const afterDeps = after[dependencyType] ?? {};
|
|
66
|
+
const names = new Set([...Object.keys(beforeDeps), ...Object.keys(afterDeps)]);
|
|
67
|
+
for (const packageName of names) {
|
|
68
|
+
const beforeVersion = beforeDeps[packageName];
|
|
69
|
+
const afterVersion = afterDeps[packageName];
|
|
70
|
+
if (beforeVersion === afterVersion)
|
|
71
|
+
continue;
|
|
72
|
+
if (beforeVersion === undefined && afterVersion !== undefined) {
|
|
73
|
+
changes.push({ file, packageName, dependencyType, changeType: "added", after: afterVersion });
|
|
74
|
+
}
|
|
75
|
+
else if (beforeVersion !== undefined && afterVersion === undefined) {
|
|
76
|
+
changes.push({ file, packageName, dependencyType, changeType: "removed", before: beforeVersion });
|
|
77
|
+
}
|
|
78
|
+
else if (beforeVersion !== undefined && afterVersion !== undefined) {
|
|
79
|
+
changes.push({ file, packageName, dependencyType, changeType: "updated", before: beforeVersion, after: afterVersion });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return changes;
|
|
84
|
+
}
|
|
85
|
+
function diffRequirementPackages(file, before, after) {
|
|
86
|
+
const changes = [];
|
|
87
|
+
const keys = new Set([...before.keys(), ...after.keys()]);
|
|
88
|
+
for (const key of keys) {
|
|
89
|
+
const beforePackage = before.get(key);
|
|
90
|
+
const afterPackage = after.get(key);
|
|
91
|
+
if (beforePackage?.spec === afterPackage?.spec)
|
|
92
|
+
continue;
|
|
93
|
+
const packageName = afterPackage?.name ?? beforePackage?.name ?? key;
|
|
94
|
+
const packagePath = requirementPackagePath(afterPackage ?? beforePackage);
|
|
95
|
+
if (!beforePackage && afterPackage) {
|
|
96
|
+
changes.push({
|
|
97
|
+
file,
|
|
98
|
+
packageName,
|
|
99
|
+
...(packagePath ? { packagePath } : {}),
|
|
100
|
+
dependencyType: "dependencies",
|
|
101
|
+
changeType: "added",
|
|
102
|
+
after: afterPackage.spec
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
else if (beforePackage && !afterPackage) {
|
|
106
|
+
changes.push({
|
|
107
|
+
file,
|
|
108
|
+
packageName,
|
|
109
|
+
...(packagePath ? { packagePath } : {}),
|
|
110
|
+
dependencyType: "dependencies",
|
|
111
|
+
changeType: "removed",
|
|
112
|
+
before: beforePackage.spec
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
else if (beforePackage && afterPackage) {
|
|
116
|
+
changes.push({
|
|
117
|
+
file,
|
|
118
|
+
packageName,
|
|
119
|
+
...(packagePath ? { packagePath } : {}),
|
|
120
|
+
dependencyType: "dependencies",
|
|
121
|
+
changeType: "updated",
|
|
122
|
+
before: beforePackage.spec,
|
|
123
|
+
after: afterPackage.spec
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return changes;
|
|
128
|
+
}
|
|
129
|
+
function diffManifestDependencies(file, before, after) {
|
|
130
|
+
const changes = [];
|
|
131
|
+
const keys = new Set([...before.keys(), ...after.keys()]);
|
|
132
|
+
for (const key of keys) {
|
|
133
|
+
const beforePackage = before.get(key);
|
|
134
|
+
const afterPackage = after.get(key);
|
|
135
|
+
if (beforePackage?.spec === afterPackage?.spec)
|
|
136
|
+
continue;
|
|
137
|
+
const packageName = afterPackage?.name ?? beforePackage?.name ?? key;
|
|
138
|
+
const packagePath = afterPackage?.packagePath ?? beforePackage?.packagePath;
|
|
139
|
+
const dependencyType = afterPackage?.dependencyType ?? beforePackage?.dependencyType ?? "dependencies";
|
|
140
|
+
if (!beforePackage && afterPackage) {
|
|
141
|
+
changes.push({
|
|
142
|
+
file,
|
|
143
|
+
packageName,
|
|
144
|
+
...(packagePath ? { packagePath } : {}),
|
|
145
|
+
dependencyType,
|
|
146
|
+
changeType: "added",
|
|
147
|
+
after: afterPackage.spec
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
else if (beforePackage && !afterPackage) {
|
|
151
|
+
changes.push({
|
|
152
|
+
file,
|
|
153
|
+
packageName,
|
|
154
|
+
...(packagePath ? { packagePath } : {}),
|
|
155
|
+
dependencyType,
|
|
156
|
+
changeType: "removed",
|
|
157
|
+
before: beforePackage.spec
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
else if (beforePackage && afterPackage) {
|
|
161
|
+
changes.push({
|
|
162
|
+
file,
|
|
163
|
+
packageName,
|
|
164
|
+
...(packagePath ? { packagePath } : {}),
|
|
165
|
+
dependencyType,
|
|
166
|
+
changeType: "updated",
|
|
167
|
+
before: beforePackage.spec,
|
|
168
|
+
after: afterPackage.spec
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return changes;
|
|
173
|
+
}
|
|
174
|
+
function parsePackageJson(value) {
|
|
175
|
+
if (!value)
|
|
176
|
+
return undefined;
|
|
177
|
+
try {
|
|
178
|
+
const parsed = JSON.parse(value);
|
|
179
|
+
return parsed && typeof parsed === "object" ? parsed : undefined;
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function parseComposerJson(value) {
|
|
186
|
+
if (!value)
|
|
187
|
+
return undefined;
|
|
188
|
+
try {
|
|
189
|
+
const parsed = JSON.parse(value);
|
|
190
|
+
if (!parsed || typeof parsed !== "object")
|
|
191
|
+
return undefined;
|
|
192
|
+
const record = parsed;
|
|
193
|
+
return {
|
|
194
|
+
dependencies: readComposerJsonSection(record.require),
|
|
195
|
+
devDependencies: readComposerJsonSection(record["require-dev"])
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
return undefined;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
function parseGemfile(value) {
|
|
203
|
+
if (!value)
|
|
204
|
+
return undefined;
|
|
205
|
+
const packages = new Map();
|
|
206
|
+
const blockStack = [];
|
|
207
|
+
for (const rawLine of value.split(/\r?\n/)) {
|
|
208
|
+
const line = stripRubyComment(rawLine).trim();
|
|
209
|
+
if (!line)
|
|
210
|
+
continue;
|
|
211
|
+
const groupMatch = /^group\s+(.+?)\s+do\b/.exec(line);
|
|
212
|
+
if (groupMatch?.[1]) {
|
|
213
|
+
blockStack.push({ kind: "group", groups: readGemfileGroups(groupMatch[1]) });
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
if (/^end\b/.test(line)) {
|
|
217
|
+
blockStack.pop();
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (/\bdo\b/.test(line) && !/^gem\s/.test(line)) {
|
|
221
|
+
blockStack.push({ kind: "other", groups: [] });
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
const gem = parseGemfileGemLine(line);
|
|
225
|
+
if (!gem)
|
|
226
|
+
continue;
|
|
227
|
+
const groups = blockStack.flatMap((block) => (block.kind === "group" ? block.groups : []));
|
|
228
|
+
const dependencyType = gemfileDependencyType(groups);
|
|
229
|
+
const packagePath = groups.length > 0 ? `group:${groups.join(",")}` : "gem";
|
|
230
|
+
const key = `${dependencyType}:${packagePath}:${gem.name.toLowerCase()}`;
|
|
231
|
+
packages.set(key, {
|
|
232
|
+
name: gem.name,
|
|
233
|
+
key,
|
|
234
|
+
spec: gem.spec,
|
|
235
|
+
packagePath,
|
|
236
|
+
dependencyType
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
return packages.size > 0 ? packages : undefined;
|
|
240
|
+
}
|
|
241
|
+
function parseGoMod(value) {
|
|
242
|
+
if (!value)
|
|
243
|
+
return undefined;
|
|
244
|
+
const packages = new Map();
|
|
245
|
+
let inRequireBlock = false;
|
|
246
|
+
for (const rawLine of value.split(/\r?\n/)) {
|
|
247
|
+
const line = rawLine.trim();
|
|
248
|
+
if (!line || line.startsWith("//"))
|
|
249
|
+
continue;
|
|
250
|
+
if (/^require\s*\($/.test(line)) {
|
|
251
|
+
inRequireBlock = true;
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
if (inRequireBlock && line === ")") {
|
|
255
|
+
inRequireBlock = false;
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
const requireLine = inRequireBlock ? line : /^require\s+(.+)$/.exec(line)?.[1];
|
|
259
|
+
if (!requireLine)
|
|
260
|
+
continue;
|
|
261
|
+
const parsed = parseGoModRequireLine(requireLine);
|
|
262
|
+
if (!parsed)
|
|
263
|
+
continue;
|
|
264
|
+
const packagePath = parsed.indirect ? "require.indirect" : "require";
|
|
265
|
+
const key = `${packagePath}:${parsed.name.toLowerCase()}`;
|
|
266
|
+
packages.set(key, {
|
|
267
|
+
name: parsed.name,
|
|
268
|
+
key,
|
|
269
|
+
spec: parsed.version,
|
|
270
|
+
packagePath,
|
|
271
|
+
dependencyType: "dependencies"
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
return packages.size > 0 ? packages : undefined;
|
|
275
|
+
}
|
|
276
|
+
function parseCargoToml(value) {
|
|
277
|
+
if (!value)
|
|
278
|
+
return undefined;
|
|
279
|
+
const packages = new Map();
|
|
280
|
+
const lines = value.split(/\r?\n/);
|
|
281
|
+
let section = "";
|
|
282
|
+
let tableDependency;
|
|
283
|
+
const flushTableDependency = () => {
|
|
284
|
+
if (!tableDependency)
|
|
285
|
+
return;
|
|
286
|
+
const spec = readCargoTableDependencySpec(tableDependency.values);
|
|
287
|
+
if (spec) {
|
|
288
|
+
const dependencyType = cargoEffectiveDependencyType(tableDependency.dependencyType, tableDependency.values.join(", "));
|
|
289
|
+
const key = `${dependencyType}:${tableDependency.packagePath}:${tableDependency.name.toLowerCase()}`;
|
|
290
|
+
packages.set(key, {
|
|
291
|
+
name: tableDependency.name,
|
|
292
|
+
key,
|
|
293
|
+
spec,
|
|
294
|
+
packagePath: tableDependency.packagePath,
|
|
295
|
+
dependencyType
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
tableDependency = undefined;
|
|
299
|
+
};
|
|
300
|
+
for (const rawLine of lines) {
|
|
301
|
+
const trimmed = stripTomlComment(rawLine).trim();
|
|
302
|
+
if (!trimmed)
|
|
303
|
+
continue;
|
|
304
|
+
const sectionMatch = /^\[([^\]]+)\]$/.exec(trimmed);
|
|
305
|
+
if (sectionMatch?.[1]) {
|
|
306
|
+
flushTableDependency();
|
|
307
|
+
section = sectionMatch[1];
|
|
308
|
+
tableDependency = cargoDependencyTable(section);
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
if (tableDependency) {
|
|
312
|
+
if (readTomlKeyValue(trimmed))
|
|
313
|
+
tableDependency.values.push(trimmed);
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
const dependencyType = cargoDependencySectionType(section);
|
|
317
|
+
if (!dependencyType)
|
|
318
|
+
continue;
|
|
319
|
+
const item = readTomlKeyValue(trimmed);
|
|
320
|
+
if (!item)
|
|
321
|
+
continue;
|
|
322
|
+
addCargoDependency(packages, section, dependencyType, item.key, item.value);
|
|
323
|
+
}
|
|
324
|
+
flushTableDependency();
|
|
325
|
+
return packages.size > 0 ? packages : undefined;
|
|
326
|
+
}
|
|
327
|
+
function parseRequirements(value) {
|
|
328
|
+
if (!value)
|
|
329
|
+
return undefined;
|
|
330
|
+
const packages = new Map();
|
|
331
|
+
for (const rawLine of value.split(/\r?\n/)) {
|
|
332
|
+
const line = cleanRequirementLine(rawLine);
|
|
333
|
+
if (!line)
|
|
334
|
+
continue;
|
|
335
|
+
const parsed = parseRequirementLine(line);
|
|
336
|
+
if (!parsed)
|
|
337
|
+
continue;
|
|
338
|
+
packages.set(parsed.key, parsed);
|
|
339
|
+
}
|
|
340
|
+
return packages.size > 0 ? packages : undefined;
|
|
341
|
+
}
|
|
342
|
+
function parsePyprojectDependencies(value) {
|
|
343
|
+
if (!value)
|
|
344
|
+
return undefined;
|
|
345
|
+
const packages = new Map();
|
|
346
|
+
const lines = value.split(/\r?\n/);
|
|
347
|
+
let section = "";
|
|
348
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
349
|
+
const trimmed = stripTomlComment(lines[index] ?? "").trim();
|
|
350
|
+
if (!trimmed)
|
|
351
|
+
continue;
|
|
352
|
+
const sectionMatch = /^\[([A-Za-z0-9_.-]+)\]$/.exec(trimmed);
|
|
353
|
+
if (sectionMatch?.[1]) {
|
|
354
|
+
section = sectionMatch[1];
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
if (section === "project" && /^dependencies\s*=/.test(trimmed)) {
|
|
358
|
+
const result = readTomlStringArray(lines, index);
|
|
359
|
+
index = result.endIndex;
|
|
360
|
+
for (const spec of result.items)
|
|
361
|
+
addPyprojectDependency(packages, "project.dependencies", "dependencies", spec);
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
if (section === "project.optional-dependencies") {
|
|
365
|
+
const match = /^([A-Za-z0-9_.-]+)\s*=/.exec(trimmed);
|
|
366
|
+
if (!match?.[1])
|
|
367
|
+
continue;
|
|
368
|
+
const result = readTomlStringArray(lines, index);
|
|
369
|
+
index = result.endIndex;
|
|
370
|
+
for (const spec of result.items) {
|
|
371
|
+
addPyprojectDependency(packages, `project.optional-dependencies.${match[1]}`, "optionalDependencies", spec);
|
|
372
|
+
}
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
const poetryDependencyType = poetryDependencySectionType(section);
|
|
376
|
+
if (poetryDependencyType) {
|
|
377
|
+
const item = readTomlKeyValue(trimmed);
|
|
378
|
+
if (!item)
|
|
379
|
+
continue;
|
|
380
|
+
addPoetryDependency(packages, section, poetryDependencyType, item.key, item.value);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
return packages.size > 0 ? packages : undefined;
|
|
384
|
+
}
|
|
385
|
+
function parseDotnetDependencyManifest(value) {
|
|
386
|
+
if (!value)
|
|
387
|
+
return undefined;
|
|
388
|
+
const packages = new Map();
|
|
389
|
+
const packagePattern = /<(PackageReference|PackageVersion)\b([^>]*?)(?:\/>|>([\s\S]*?)<\/\1>)/gi;
|
|
390
|
+
for (const match of value.matchAll(packagePattern)) {
|
|
391
|
+
const kind = match[1];
|
|
392
|
+
const attributes = match[2] ?? "";
|
|
393
|
+
const inner = match[3] ?? "";
|
|
394
|
+
if (!kind)
|
|
395
|
+
continue;
|
|
396
|
+
const name = readXmlAttribute(attributes, "Include") ?? readXmlAttribute(attributes, "Update");
|
|
397
|
+
if (!name)
|
|
398
|
+
continue;
|
|
399
|
+
const version = readXmlAttribute(attributes, "Version") ?? readXmlElement(inner, "Version") ?? "*";
|
|
400
|
+
const packagePath = kind;
|
|
401
|
+
packages.set(`${kind}:${name.toLowerCase()}`, {
|
|
402
|
+
name,
|
|
403
|
+
key: `${kind}:${name.toLowerCase()}`,
|
|
404
|
+
spec: version,
|
|
405
|
+
packagePath
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
return packages.size > 0 ? packages : undefined;
|
|
409
|
+
}
|
|
410
|
+
function parseMavenPomDependencies(value) {
|
|
411
|
+
if (!value)
|
|
412
|
+
return undefined;
|
|
413
|
+
const packages = new Map();
|
|
414
|
+
const withoutBuild = value.replace(/<build\b[\s\S]*?<\/build>/gi, "");
|
|
415
|
+
const dependencyManagement = [...withoutBuild.matchAll(/<dependencyManagement\b[^>]*>([\s\S]*?)<\/dependencyManagement>/gi)];
|
|
416
|
+
for (const match of dependencyManagement) {
|
|
417
|
+
if (match[1])
|
|
418
|
+
readMavenDependencyBlocks(packages, match[1], "dependencyManagement.dependencies");
|
|
419
|
+
}
|
|
420
|
+
const directDependencies = withoutBuild.replace(/<dependencyManagement\b[\s\S]*?<\/dependencyManagement>/gi, "");
|
|
421
|
+
readMavenDependencyBlocks(packages, directDependencies, "dependencies");
|
|
422
|
+
return packages.size > 0 ? packages : undefined;
|
|
423
|
+
}
|
|
424
|
+
function parseGradleDependencies(value) {
|
|
425
|
+
if (!value)
|
|
426
|
+
return undefined;
|
|
427
|
+
const packages = new Map();
|
|
428
|
+
for (const block of readGradleBlocks(value, "dependencies")) {
|
|
429
|
+
for (const rawLine of block.split(/\r?\n/)) {
|
|
430
|
+
const line = stripGradleComment(rawLine).trim();
|
|
431
|
+
if (!line)
|
|
432
|
+
continue;
|
|
433
|
+
const parsed = parseGradleDependencyLine(line);
|
|
434
|
+
if (!parsed)
|
|
435
|
+
continue;
|
|
436
|
+
const dependencyType = gradleDependencyType(parsed.configuration);
|
|
437
|
+
const key = `${dependencyType}:${parsed.configuration}:${parsed.name.toLowerCase()}`;
|
|
438
|
+
packages.set(key, {
|
|
439
|
+
name: parsed.name,
|
|
440
|
+
key,
|
|
441
|
+
spec: parsed.version,
|
|
442
|
+
packagePath: parsed.configuration,
|
|
443
|
+
dependencyType
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
return packages.size > 0 ? packages : undefined;
|
|
448
|
+
}
|
|
449
|
+
function parseGradleVersionCatalog(value) {
|
|
450
|
+
if (!value)
|
|
451
|
+
return undefined;
|
|
452
|
+
const packages = new Map();
|
|
453
|
+
const versions = new Map();
|
|
454
|
+
const entries = [];
|
|
455
|
+
let section = "";
|
|
456
|
+
for (const rawLine of value.split(/\r?\n/)) {
|
|
457
|
+
const trimmed = stripTomlComment(rawLine).trim();
|
|
458
|
+
if (!trimmed)
|
|
459
|
+
continue;
|
|
460
|
+
const sectionMatch = /^\[([A-Za-z0-9_.-]+)\]$/.exec(trimmed);
|
|
461
|
+
if (sectionMatch?.[1]) {
|
|
462
|
+
section = sectionMatch[1];
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
const item = readTomlKeyValue(trimmed);
|
|
466
|
+
if (!item)
|
|
467
|
+
continue;
|
|
468
|
+
const alias = unquoteTomlScalar(item.key);
|
|
469
|
+
if (section === "versions") {
|
|
470
|
+
versions.set(alias, unquoteTomlScalar(item.value));
|
|
471
|
+
}
|
|
472
|
+
else if (section === "libraries" || section === "plugins") {
|
|
473
|
+
entries.push({ section, alias, value: item.value });
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
for (const entry of entries)
|
|
477
|
+
addGradleVersionCatalogEntry(packages, versions, entry.section, entry.alias, entry.value);
|
|
478
|
+
return packages.size > 0 ? packages : undefined;
|
|
479
|
+
}
|
|
480
|
+
function diffLockPackages(file, before, after) {
|
|
481
|
+
const changes = [];
|
|
482
|
+
const paths = new Set([...before.keys(), ...after.keys()]);
|
|
483
|
+
for (const packagePath of paths) {
|
|
484
|
+
const beforePackage = before.get(packagePath);
|
|
485
|
+
const afterPackage = after.get(packagePath);
|
|
486
|
+
if (beforePackage?.version === afterPackage?.version)
|
|
487
|
+
continue;
|
|
488
|
+
const packageName = afterPackage?.name ?? beforePackage?.name ?? packagePath;
|
|
489
|
+
if (!beforePackage && afterPackage) {
|
|
490
|
+
changes.push({
|
|
491
|
+
file,
|
|
492
|
+
packageName,
|
|
493
|
+
packagePath,
|
|
494
|
+
dependencyType: "lockfile",
|
|
495
|
+
changeType: "added",
|
|
496
|
+
after: afterPackage.version
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
else if (beforePackage && !afterPackage) {
|
|
500
|
+
changes.push({
|
|
501
|
+
file,
|
|
502
|
+
packageName,
|
|
503
|
+
packagePath,
|
|
504
|
+
dependencyType: "lockfile",
|
|
505
|
+
changeType: "removed",
|
|
506
|
+
before: beforePackage.version
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
else if (beforePackage && afterPackage) {
|
|
510
|
+
changes.push({
|
|
511
|
+
file,
|
|
512
|
+
packageName,
|
|
513
|
+
packagePath,
|
|
514
|
+
dependencyType: "lockfile",
|
|
515
|
+
changeType: "updated",
|
|
516
|
+
before: beforePackage.version,
|
|
517
|
+
after: afterPackage.version
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return changes;
|
|
522
|
+
}
|
|
523
|
+
function diffNameVersionLockPackages(file, before, after) {
|
|
524
|
+
const changes = [];
|
|
525
|
+
const beforeByName = groupLockPackagesByName(before);
|
|
526
|
+
const afterByName = groupLockPackagesByName(after);
|
|
527
|
+
const names = new Set([...beforeByName.keys(), ...afterByName.keys()]);
|
|
528
|
+
const fallbackBefore = new Map();
|
|
529
|
+
const fallbackAfter = new Map();
|
|
530
|
+
for (const name of names) {
|
|
531
|
+
const beforePackages = beforeByName.get(name) ?? [];
|
|
532
|
+
const afterPackages = afterByName.get(name) ?? [];
|
|
533
|
+
if (beforePackages.length === 1 && afterPackages.length === 1) {
|
|
534
|
+
const beforePackage = beforePackages[0];
|
|
535
|
+
const afterPackage = afterPackages[0];
|
|
536
|
+
if (!beforePackage || !afterPackage || beforePackage.version === afterPackage.version)
|
|
537
|
+
continue;
|
|
538
|
+
changes.push({
|
|
539
|
+
file,
|
|
540
|
+
packageName: name,
|
|
541
|
+
packagePath: `${beforePackage.path} -> ${afterPackage.path}`,
|
|
542
|
+
dependencyType: "lockfile",
|
|
543
|
+
changeType: "updated",
|
|
544
|
+
before: beforePackage.version,
|
|
545
|
+
after: afterPackage.version
|
|
546
|
+
});
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
for (const item of beforePackages)
|
|
550
|
+
fallbackBefore.set(item.path, item);
|
|
551
|
+
for (const item of afterPackages)
|
|
552
|
+
fallbackAfter.set(item.path, item);
|
|
553
|
+
}
|
|
554
|
+
changes.push(...diffLockPackages(file, fallbackBefore, fallbackAfter));
|
|
555
|
+
return changes;
|
|
556
|
+
}
|
|
557
|
+
function groupLockPackagesByName(packages) {
|
|
558
|
+
const grouped = new Map();
|
|
559
|
+
for (const item of packages.values()) {
|
|
560
|
+
const group = grouped.get(item.name) ?? [];
|
|
561
|
+
group.push(item);
|
|
562
|
+
grouped.set(item.name, group);
|
|
563
|
+
}
|
|
564
|
+
return grouped;
|
|
565
|
+
}
|
|
566
|
+
function parsePackageLock(value) {
|
|
567
|
+
if (!value)
|
|
568
|
+
return undefined;
|
|
569
|
+
try {
|
|
570
|
+
const root = JSON.parse(value);
|
|
571
|
+
if (!root || typeof root !== "object")
|
|
572
|
+
return undefined;
|
|
573
|
+
const parsed = root;
|
|
574
|
+
if (parsed.packages && typeof parsed.packages === "object") {
|
|
575
|
+
return readPackageLockPackages(parsed.packages);
|
|
576
|
+
}
|
|
577
|
+
if (parsed.dependencies && typeof parsed.dependencies === "object") {
|
|
578
|
+
const packages = new Map();
|
|
579
|
+
collectLockDependencies(parsed.dependencies, packages);
|
|
580
|
+
return packages;
|
|
581
|
+
}
|
|
582
|
+
return undefined;
|
|
583
|
+
}
|
|
584
|
+
catch {
|
|
585
|
+
return undefined;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
function parsePnpmLock(value) {
|
|
589
|
+
if (!value)
|
|
590
|
+
return undefined;
|
|
591
|
+
try {
|
|
592
|
+
const parsed = parseYaml(value);
|
|
593
|
+
if (!isRecord(parsed.packages))
|
|
594
|
+
return undefined;
|
|
595
|
+
const packages = new Map();
|
|
596
|
+
for (const [packagePath, entry] of Object.entries(parsed.packages)) {
|
|
597
|
+
if (!isRecord(entry))
|
|
598
|
+
continue;
|
|
599
|
+
const parsedKey = parsePnpmPackageKey(packagePath);
|
|
600
|
+
if (!parsedKey)
|
|
601
|
+
continue;
|
|
602
|
+
packages.set(packagePath, {
|
|
603
|
+
name: parsedKey.name,
|
|
604
|
+
path: packagePath,
|
|
605
|
+
version: parsedKey.version
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
return packages;
|
|
609
|
+
}
|
|
610
|
+
catch {
|
|
611
|
+
return undefined;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
function parseYarnLock(value) {
|
|
615
|
+
if (!value)
|
|
616
|
+
return undefined;
|
|
617
|
+
const packages = new Map();
|
|
618
|
+
const lines = value.split(/\r?\n/);
|
|
619
|
+
let descriptor;
|
|
620
|
+
for (const rawLine of lines) {
|
|
621
|
+
if (!rawLine.trim() || rawLine.startsWith("#"))
|
|
622
|
+
continue;
|
|
623
|
+
if (!/^\s/.test(rawLine) && rawLine.trim().endsWith(":")) {
|
|
624
|
+
descriptor = normalizeYarnDescriptor(rawLine.trim().slice(0, -1));
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
if (!descriptor)
|
|
628
|
+
continue;
|
|
629
|
+
const version = readYarnVersion(rawLine);
|
|
630
|
+
if (!version)
|
|
631
|
+
continue;
|
|
632
|
+
const parsedDescriptor = parseYarnDescriptor(descriptor);
|
|
633
|
+
if (!parsedDescriptor)
|
|
634
|
+
continue;
|
|
635
|
+
packages.set(descriptor, {
|
|
636
|
+
name: parsedDescriptor.name,
|
|
637
|
+
path: descriptor,
|
|
638
|
+
version
|
|
639
|
+
});
|
|
640
|
+
descriptor = undefined;
|
|
641
|
+
}
|
|
642
|
+
return packages.size > 0 ? packages : undefined;
|
|
643
|
+
}
|
|
644
|
+
function parseBunLock(value) {
|
|
645
|
+
if (!value)
|
|
646
|
+
return undefined;
|
|
647
|
+
try {
|
|
648
|
+
const parsed = parseYaml(value);
|
|
649
|
+
if (!isRecord(parsed.packages))
|
|
650
|
+
return undefined;
|
|
651
|
+
const packages = new Map();
|
|
652
|
+
for (const [packagePath, entry] of Object.entries(parsed.packages)) {
|
|
653
|
+
const version = readBunPackageVersion(entry);
|
|
654
|
+
if (!packagePath || !version)
|
|
655
|
+
continue;
|
|
656
|
+
packages.set(packagePath, { name: packagePath, path: packagePath, version });
|
|
657
|
+
}
|
|
658
|
+
return packages.size > 0 ? packages : undefined;
|
|
659
|
+
}
|
|
660
|
+
catch {
|
|
661
|
+
return undefined;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
function parseGoSum(value) {
|
|
665
|
+
if (!value)
|
|
666
|
+
return undefined;
|
|
667
|
+
const packages = new Map();
|
|
668
|
+
for (const rawLine of value.split(/\r?\n/)) {
|
|
669
|
+
const [name, rawVersion] = rawLine.trim().split(/\s+/, 3);
|
|
670
|
+
if (!name || !rawVersion)
|
|
671
|
+
continue;
|
|
672
|
+
const version = rawVersion.replace(/\/go\.mod$/, "");
|
|
673
|
+
const path = `${name}@${version}`;
|
|
674
|
+
packages.set(path, { name, path, version });
|
|
675
|
+
}
|
|
676
|
+
return packages.size > 0 ? packages : undefined;
|
|
677
|
+
}
|
|
678
|
+
function parseTomlPackageLock(value) {
|
|
679
|
+
if (!value)
|
|
680
|
+
return undefined;
|
|
681
|
+
const packages = new Map();
|
|
682
|
+
let current;
|
|
683
|
+
const flush = () => {
|
|
684
|
+
if (!current?.name || !current.version)
|
|
685
|
+
return;
|
|
686
|
+
const path = `${current.name}@${current.version}`;
|
|
687
|
+
packages.set(path, { name: current.name, path, version: current.version });
|
|
688
|
+
};
|
|
689
|
+
for (const rawLine of value.split(/\r?\n/)) {
|
|
690
|
+
const trimmed = rawLine.trim();
|
|
691
|
+
if (/^\[\[.*\]\]$/.test(trimmed)) {
|
|
692
|
+
flush();
|
|
693
|
+
current = trimmed === "[[package]]" ? {} : undefined;
|
|
694
|
+
continue;
|
|
695
|
+
}
|
|
696
|
+
if (!current)
|
|
697
|
+
continue;
|
|
698
|
+
const match = /^(name|version)\s*=\s*"([^"]+)"/.exec(trimmed);
|
|
699
|
+
if (!match?.[1] || !match[2])
|
|
700
|
+
continue;
|
|
701
|
+
if (match[1] === "name") {
|
|
702
|
+
current.name = match[2];
|
|
703
|
+
}
|
|
704
|
+
else {
|
|
705
|
+
current.version = match[2];
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
flush();
|
|
709
|
+
return packages.size > 0 ? packages : undefined;
|
|
710
|
+
}
|
|
711
|
+
function parseUvLock(value) {
|
|
712
|
+
if (!value)
|
|
713
|
+
return undefined;
|
|
714
|
+
const packages = new Map();
|
|
715
|
+
let current;
|
|
716
|
+
const flush = () => {
|
|
717
|
+
if (!current?.name || !current.version)
|
|
718
|
+
return;
|
|
719
|
+
const source = current.source ? ` ${current.source}` : "";
|
|
720
|
+
const path = `${current.name}@${current.version}${source}`;
|
|
721
|
+
packages.set(path, { name: current.name, path, version: current.version });
|
|
722
|
+
};
|
|
723
|
+
for (const rawLine of value.split(/\r?\n/)) {
|
|
724
|
+
const trimmed = rawLine.trim();
|
|
725
|
+
if (/^\[\[.*\]\]$/.test(trimmed)) {
|
|
726
|
+
flush();
|
|
727
|
+
current = trimmed === "[[package]]" ? {} : undefined;
|
|
728
|
+
continue;
|
|
729
|
+
}
|
|
730
|
+
if (!current)
|
|
731
|
+
continue;
|
|
732
|
+
const scalar = /^([A-Za-z0-9_-]+)\s*=\s*(.+)$/.exec(trimmed);
|
|
733
|
+
if (!scalar?.[1] || !scalar[2])
|
|
734
|
+
continue;
|
|
735
|
+
const value = unquoteTomlScalar(scalar[2]);
|
|
736
|
+
if (scalar[1] === "name")
|
|
737
|
+
current.name = value;
|
|
738
|
+
if (scalar[1] === "version")
|
|
739
|
+
current.version = value;
|
|
740
|
+
if (scalar[1] === "source")
|
|
741
|
+
current.source = normalizeInlineToml(value);
|
|
742
|
+
}
|
|
743
|
+
flush();
|
|
744
|
+
return packages.size > 0 ? packages : undefined;
|
|
745
|
+
}
|
|
746
|
+
function parsePipfileLock(value) {
|
|
747
|
+
if (!value)
|
|
748
|
+
return undefined;
|
|
749
|
+
try {
|
|
750
|
+
const parsed = JSON.parse(value);
|
|
751
|
+
const packages = new Map();
|
|
752
|
+
readPipfileSection(packages, "default", parsed.default);
|
|
753
|
+
readPipfileSection(packages, "develop", parsed.develop);
|
|
754
|
+
return packages.size > 0 ? packages : undefined;
|
|
755
|
+
}
|
|
756
|
+
catch {
|
|
757
|
+
return undefined;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
function parseGemfileLock(value) {
|
|
761
|
+
if (!value)
|
|
762
|
+
return undefined;
|
|
763
|
+
const packages = new Map();
|
|
764
|
+
let inSpecs = false;
|
|
765
|
+
for (const rawLine of value.split(/\r?\n/)) {
|
|
766
|
+
if (/^[A-Z][A-Z ]+$/.test(rawLine.trim())) {
|
|
767
|
+
inSpecs = rawLine.trim() === "GEM";
|
|
768
|
+
continue;
|
|
769
|
+
}
|
|
770
|
+
if (!inSpecs)
|
|
771
|
+
continue;
|
|
772
|
+
if (rawLine.trim() === "specs:")
|
|
773
|
+
continue;
|
|
774
|
+
const match = /^ {4}([A-Za-z0-9_.-]+)\s+\(([^)]+)\)/.exec(rawLine);
|
|
775
|
+
if (!match?.[1] || !match[2])
|
|
776
|
+
continue;
|
|
777
|
+
const version = match[2].split(",")[0]?.trim();
|
|
778
|
+
if (!version)
|
|
779
|
+
continue;
|
|
780
|
+
const path = `${match[1]}@${version}`;
|
|
781
|
+
packages.set(path, { name: match[1], path, version });
|
|
782
|
+
}
|
|
783
|
+
return packages.size > 0 ? packages : undefined;
|
|
784
|
+
}
|
|
785
|
+
function parseComposerLock(value) {
|
|
786
|
+
if (!value)
|
|
787
|
+
return undefined;
|
|
788
|
+
try {
|
|
789
|
+
const parsed = JSON.parse(value);
|
|
790
|
+
const packages = new Map();
|
|
791
|
+
readComposerSection(packages, "packages", parsed.packages);
|
|
792
|
+
readComposerSection(packages, "packages-dev", parsed["packages-dev"]);
|
|
793
|
+
return packages.size > 0 ? packages : undefined;
|
|
794
|
+
}
|
|
795
|
+
catch {
|
|
796
|
+
return undefined;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
function isRequirementsFile(path) {
|
|
800
|
+
const fileName = path.split("/").at(-1) ?? path;
|
|
801
|
+
return /^requirements([-.].*)?\.txt$/i.test(fileName) || /^.*[-.]requirements\.txt$/i.test(fileName);
|
|
802
|
+
}
|
|
803
|
+
function isDotnetDependencyManifest(path) {
|
|
804
|
+
const fileName = path.split("/").at(-1) ?? path;
|
|
805
|
+
return /\.(csproj|fsproj|vbproj)$/i.test(fileName) || fileName === "Directory.Packages.props";
|
|
806
|
+
}
|
|
807
|
+
function stripRubyComment(line) {
|
|
808
|
+
let quote;
|
|
809
|
+
for (let index = 0; index < line.length; index += 1) {
|
|
810
|
+
const char = line[index];
|
|
811
|
+
const previous = line[index - 1];
|
|
812
|
+
if ((char === '"' || char === "'") && previous !== "\\") {
|
|
813
|
+
quote = quote === char ? undefined : quote ?? char;
|
|
814
|
+
continue;
|
|
815
|
+
}
|
|
816
|
+
if (char === "#" && !quote)
|
|
817
|
+
return line.slice(0, index);
|
|
818
|
+
}
|
|
819
|
+
return line;
|
|
820
|
+
}
|
|
821
|
+
function readGemfileGroups(value) {
|
|
822
|
+
const groups = [];
|
|
823
|
+
for (const match of value.matchAll(/(?::([A-Za-z0-9_]+)|["']([^"']+)["'])/g)) {
|
|
824
|
+
const group = match[1] ?? match[2];
|
|
825
|
+
if (group)
|
|
826
|
+
groups.push(group);
|
|
827
|
+
}
|
|
828
|
+
return groups;
|
|
829
|
+
}
|
|
830
|
+
const GEMFILE_SOURCE_KEYS = ["git", "github", "gitlab", "bitbucket", "path", "ref", "branch", "tag"];
|
|
831
|
+
function parseGemfileGemLine(line) {
|
|
832
|
+
const match = /^gem\s+(['"])([^'"]+)\1\s*(?:,\s*(.*))?$/.exec(line);
|
|
833
|
+
if (!match?.[2])
|
|
834
|
+
return undefined;
|
|
835
|
+
const rest = match[3] ?? "";
|
|
836
|
+
const constraints = readGemfileStringArguments(rest);
|
|
837
|
+
if (constraints.length > 0) {
|
|
838
|
+
return { name: match[2], spec: constraints.join(", ") };
|
|
839
|
+
}
|
|
840
|
+
// No version constraint: fold the source/ref options into the spec so git/path
|
|
841
|
+
// and branch/tag/ref drift is surfaced instead of collapsing to "*".
|
|
842
|
+
const source = readGemfileSourceOptions(rest);
|
|
843
|
+
return {
|
|
844
|
+
name: match[2],
|
|
845
|
+
spec: source.length > 0 ? source.join(", ") : "*"
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
function readGemfileSourceOptions(value) {
|
|
849
|
+
const options = [];
|
|
850
|
+
for (const key of GEMFILE_SOURCE_KEYS) {
|
|
851
|
+
const match = new RegExp(`(?:^|[\\s,])${key}\\s*:\\s*(['"])([^'"]+)\\1`).exec(value);
|
|
852
|
+
if (match?.[2])
|
|
853
|
+
options.push(`${key}:${match[2]}`);
|
|
854
|
+
}
|
|
855
|
+
return options;
|
|
856
|
+
}
|
|
857
|
+
function readGemfileStringArguments(value) {
|
|
858
|
+
const constraints = [];
|
|
859
|
+
let index = 0;
|
|
860
|
+
while (index < value.length) {
|
|
861
|
+
while (/[\s,]/.test(value[index] ?? ""))
|
|
862
|
+
index += 1;
|
|
863
|
+
const quote = value[index];
|
|
864
|
+
if (quote !== '"' && quote !== "'")
|
|
865
|
+
break;
|
|
866
|
+
index += 1;
|
|
867
|
+
let current = "";
|
|
868
|
+
while (index < value.length) {
|
|
869
|
+
const char = value[index];
|
|
870
|
+
if (char === quote && value[index - 1] !== "\\")
|
|
871
|
+
break;
|
|
872
|
+
current += char;
|
|
873
|
+
index += 1;
|
|
874
|
+
}
|
|
875
|
+
if (current)
|
|
876
|
+
constraints.push(current);
|
|
877
|
+
if (value[index] === quote)
|
|
878
|
+
index += 1;
|
|
879
|
+
}
|
|
880
|
+
return constraints;
|
|
881
|
+
}
|
|
882
|
+
function gemfileDependencyType(groups) {
|
|
883
|
+
return groups.some((group) => group === "development" || group === "test") ? "devDependencies" : "dependencies";
|
|
884
|
+
}
|
|
885
|
+
function parseGoModRequireLine(line) {
|
|
886
|
+
const indirect = /\s+\/\/\s*indirect\b/.test(line);
|
|
887
|
+
const cleaned = line.replace(/\s+\/\/.*$/, "").trim();
|
|
888
|
+
const [name, version] = cleaned.split(/\s+/);
|
|
889
|
+
if (!name || !version || name === ")")
|
|
890
|
+
return undefined;
|
|
891
|
+
return { name, version, indirect };
|
|
892
|
+
}
|
|
893
|
+
function cargoDependencySectionType(section) {
|
|
894
|
+
// Tables nested under a metadata segment (e.g. [package.metadata.docs.rs.dependencies],
|
|
895
|
+
// [workspace.metadata.release.dependencies]) are tool config, not real dependencies.
|
|
896
|
+
if (/(^|\.)metadata\./.test(section))
|
|
897
|
+
return undefined;
|
|
898
|
+
if (section === "dependencies" || section === "workspace.dependencies" || section.endsWith(".dependencies"))
|
|
899
|
+
return "dependencies";
|
|
900
|
+
if (section === "dev-dependencies" || section === "workspace.dev-dependencies" || section.endsWith(".dev-dependencies"))
|
|
901
|
+
return "devDependencies";
|
|
902
|
+
if (section === "build-dependencies" || section === "workspace.build-dependencies" || section.endsWith(".build-dependencies"))
|
|
903
|
+
return "dependencies";
|
|
904
|
+
return undefined;
|
|
905
|
+
}
|
|
906
|
+
function cargoDependencyTable(section) {
|
|
907
|
+
const match = /^((?:dependencies|dev-dependencies|build-dependencies)|workspace\.(?:dependencies|dev-dependencies|build-dependencies)|.+\.(?:dependencies|dev-dependencies|build-dependencies))\.("[^"]+"|'[^']+'|[A-Za-z0-9_-]+)$/.exec(section);
|
|
908
|
+
if (!match?.[1] || !match[2])
|
|
909
|
+
return undefined;
|
|
910
|
+
const dependencyType = cargoDependencySectionType(match[1]);
|
|
911
|
+
if (!dependencyType)
|
|
912
|
+
return undefined;
|
|
913
|
+
return {
|
|
914
|
+
name: unquoteTomlScalar(match[2]),
|
|
915
|
+
packagePath: section,
|
|
916
|
+
dependencyType,
|
|
917
|
+
values: []
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
function addCargoDependency(packages, packagePath, dependencyType, rawName, rawValue) {
|
|
921
|
+
const name = unquoteTomlScalar(rawName);
|
|
922
|
+
const spec = readCargoDependencySpec(rawValue);
|
|
923
|
+
if (!name || !spec)
|
|
924
|
+
return;
|
|
925
|
+
const effectiveDependencyType = cargoEffectiveDependencyType(dependencyType, rawValue);
|
|
926
|
+
const key = `${effectiveDependencyType}:${packagePath}:${name.toLowerCase()}`;
|
|
927
|
+
packages.set(key, {
|
|
928
|
+
name,
|
|
929
|
+
key,
|
|
930
|
+
spec,
|
|
931
|
+
packagePath,
|
|
932
|
+
dependencyType: effectiveDependencyType
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
function readCargoDependencySpec(value) {
|
|
936
|
+
const trimmed = stripTomlComment(value).trim();
|
|
937
|
+
if (!trimmed)
|
|
938
|
+
return undefined;
|
|
939
|
+
if (trimmed.startsWith("{")) {
|
|
940
|
+
const version = /\bversion\s*=\s*("[^"]*"|'[^']*'|[^,}]+)/.exec(trimmed)?.[1];
|
|
941
|
+
return version ? unquoteTomlScalar(version.trim()) : normalizeInlineToml(trimmed);
|
|
942
|
+
}
|
|
943
|
+
return unquoteTomlScalar(trimmed);
|
|
944
|
+
}
|
|
945
|
+
function readCargoTableDependencySpec(values) {
|
|
946
|
+
for (const value of values) {
|
|
947
|
+
const item = readTomlKeyValue(value);
|
|
948
|
+
if (!item || unquoteTomlScalar(item.key) !== "version")
|
|
949
|
+
continue;
|
|
950
|
+
return unquoteTomlScalar(item.value);
|
|
951
|
+
}
|
|
952
|
+
return values.length > 0 ? normalizeInlineToml(`{ ${values.join(", ")} }`) : undefined;
|
|
953
|
+
}
|
|
954
|
+
function cargoEffectiveDependencyType(dependencyType, rawValue) {
|
|
955
|
+
return dependencyType === "dependencies" && /\boptional\s*=\s*true\b/i.test(rawValue) ? "optionalDependencies" : dependencyType;
|
|
956
|
+
}
|
|
957
|
+
function readMavenDependencyBlocks(packages, content, packagePath) {
|
|
958
|
+
for (const dependencyBlock of content.matchAll(/<dependencies\b[^>]*>([\s\S]*?)<\/dependencies>/gi)) {
|
|
959
|
+
if (!dependencyBlock[1])
|
|
960
|
+
continue;
|
|
961
|
+
for (const dependency of dependencyBlock[1].matchAll(/<dependency\b[^>]*>([\s\S]*?)<\/dependency>/gi)) {
|
|
962
|
+
if (!dependency[1])
|
|
963
|
+
continue;
|
|
964
|
+
addMavenDependency(packages, dependency[1], packagePath);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
function addMavenDependency(packages, content, packagePath) {
|
|
969
|
+
const groupId = readXmlElement(content, "groupId");
|
|
970
|
+
const artifactId = readXmlElement(content, "artifactId");
|
|
971
|
+
if (!groupId || !artifactId)
|
|
972
|
+
return;
|
|
973
|
+
const version = readXmlElement(content, "version") ?? "*";
|
|
974
|
+
const scope = readXmlElement(content, "scope") ?? "";
|
|
975
|
+
const optional = readXmlElement(content, "optional") ?? "";
|
|
976
|
+
const dependencyType = mavenDependencyType(scope, optional);
|
|
977
|
+
const name = `${groupId}:${artifactId}`;
|
|
978
|
+
const key = `${dependencyType}:${packagePath}:${name.toLowerCase()}`;
|
|
979
|
+
packages.set(key, {
|
|
980
|
+
name,
|
|
981
|
+
key,
|
|
982
|
+
spec: version,
|
|
983
|
+
packagePath,
|
|
984
|
+
dependencyType
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
function mavenDependencyType(scope, optional) {
|
|
988
|
+
if (optional.trim().toLowerCase() === "true")
|
|
989
|
+
return "optionalDependencies";
|
|
990
|
+
if (scope.trim().toLowerCase() === "test")
|
|
991
|
+
return "devDependencies";
|
|
992
|
+
return "dependencies";
|
|
993
|
+
}
|
|
994
|
+
function isGradleBuildFile(path) {
|
|
995
|
+
const fileName = path.split("/").at(-1) ?? path;
|
|
996
|
+
return fileName === "build.gradle" || fileName === "build.gradle.kts";
|
|
997
|
+
}
|
|
998
|
+
function isGradleVersionCatalog(path) {
|
|
999
|
+
const fileName = path.split("/").at(-1) ?? path;
|
|
1000
|
+
return fileName === "libs.versions.toml";
|
|
1001
|
+
}
|
|
1002
|
+
function readGradleBlocks(value, blockName) {
|
|
1003
|
+
const blocks = [];
|
|
1004
|
+
const pattern = new RegExp(`\\b${blockName}\\s*\\{`, "g");
|
|
1005
|
+
for (const match of value.matchAll(pattern)) {
|
|
1006
|
+
const openBrace = match.index + match[0].lastIndexOf("{");
|
|
1007
|
+
let depth = 0;
|
|
1008
|
+
let quote;
|
|
1009
|
+
for (let index = openBrace; index < value.length; index += 1) {
|
|
1010
|
+
const char = value[index];
|
|
1011
|
+
const previous = value[index - 1];
|
|
1012
|
+
if ((char === '"' || char === "'") && previous !== "\\") {
|
|
1013
|
+
quote = quote === char ? undefined : quote ?? char;
|
|
1014
|
+
continue;
|
|
1015
|
+
}
|
|
1016
|
+
if (quote)
|
|
1017
|
+
continue;
|
|
1018
|
+
if (char === "{")
|
|
1019
|
+
depth += 1;
|
|
1020
|
+
if (char === "}") {
|
|
1021
|
+
depth -= 1;
|
|
1022
|
+
if (depth === 0) {
|
|
1023
|
+
blocks.push(value.slice(openBrace + 1, index));
|
|
1024
|
+
break;
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
return blocks;
|
|
1030
|
+
}
|
|
1031
|
+
function stripGradleComment(line) {
|
|
1032
|
+
let quote;
|
|
1033
|
+
for (let index = 0; index < line.length - 1; index += 1) {
|
|
1034
|
+
const char = line[index];
|
|
1035
|
+
const previous = line[index - 1];
|
|
1036
|
+
if ((char === '"' || char === "'") && previous !== "\\") {
|
|
1037
|
+
quote = quote === char ? undefined : quote ?? char;
|
|
1038
|
+
continue;
|
|
1039
|
+
}
|
|
1040
|
+
if (!quote && char === "/" && line[index + 1] === "/")
|
|
1041
|
+
return line.slice(0, index);
|
|
1042
|
+
}
|
|
1043
|
+
return line;
|
|
1044
|
+
}
|
|
1045
|
+
function parseGradleDependencyLine(line) {
|
|
1046
|
+
const stringMatch = /^([A-Za-z][A-Za-z0-9_]*)\s*(?:\(\s*)?(?:(?:platform|enforcedPlatform)\s*)?(?:\(\s*)?["']([^"']+)["']/.exec(line);
|
|
1047
|
+
if (stringMatch?.[1] && stringMatch[2]) {
|
|
1048
|
+
const coordinate = parseGradleCoordinate(stringMatch[2]);
|
|
1049
|
+
return coordinate ? { configuration: stringMatch[1], ...coordinate } : undefined;
|
|
1050
|
+
}
|
|
1051
|
+
const mapMatch = /^([A-Za-z][A-Za-z0-9_]*)\s*(?:\(\s*)?(.+)$/.exec(line);
|
|
1052
|
+
if (!mapMatch?.[1] || !mapMatch[2])
|
|
1053
|
+
return undefined;
|
|
1054
|
+
const group = readGradleMapAttribute(mapMatch[2], "group");
|
|
1055
|
+
const name = readGradleMapAttribute(mapMatch[2], "name");
|
|
1056
|
+
const version = readGradleMapAttribute(mapMatch[2], "version");
|
|
1057
|
+
if (!group || !name || !version)
|
|
1058
|
+
return undefined;
|
|
1059
|
+
return { configuration: mapMatch[1], name: `${group}:${name}`, version };
|
|
1060
|
+
}
|
|
1061
|
+
function parseGradleCoordinate(value) {
|
|
1062
|
+
const parts = value.split(":");
|
|
1063
|
+
if (parts.length < 3)
|
|
1064
|
+
return undefined;
|
|
1065
|
+
const group = parts[0];
|
|
1066
|
+
const artifact = parts[1];
|
|
1067
|
+
const version = parts.slice(2).join(":");
|
|
1068
|
+
if (!group || !artifact || !version)
|
|
1069
|
+
return undefined;
|
|
1070
|
+
return { name: `${group}:${artifact}`, version };
|
|
1071
|
+
}
|
|
1072
|
+
function readGradleMapAttribute(value, name) {
|
|
1073
|
+
const match = new RegExp(`\\b${name}\\s*:\\s*["']([^"']+)["']`).exec(value);
|
|
1074
|
+
return match?.[1];
|
|
1075
|
+
}
|
|
1076
|
+
function gradleDependencyType(configuration) {
|
|
1077
|
+
return configuration.toLowerCase().includes("test") ? "devDependencies" : "dependencies";
|
|
1078
|
+
}
|
|
1079
|
+
function addGradleVersionCatalogEntry(packages, versions, section, alias, rawValue) {
|
|
1080
|
+
const parsed = section === "libraries" ? parseGradleCatalogLibrary(rawValue, versions) : parseGradleCatalogPlugin(rawValue, versions);
|
|
1081
|
+
if (!parsed)
|
|
1082
|
+
return;
|
|
1083
|
+
const packagePath = `${section}.${alias}`;
|
|
1084
|
+
const key = `dependencies:${packagePath}:${parsed.name.toLowerCase()}`;
|
|
1085
|
+
packages.set(key, {
|
|
1086
|
+
name: parsed.name,
|
|
1087
|
+
key,
|
|
1088
|
+
spec: parsed.version,
|
|
1089
|
+
packagePath,
|
|
1090
|
+
dependencyType: "dependencies"
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
function parseGradleCatalogLibrary(value, versions) {
|
|
1094
|
+
const trimmed = stripTomlComment(value).trim();
|
|
1095
|
+
const stringCoordinate = /^["']([^"']+)["']$/.exec(trimmed)?.[1];
|
|
1096
|
+
if (stringCoordinate)
|
|
1097
|
+
return parseGradleCoordinate(stringCoordinate);
|
|
1098
|
+
if (!trimmed.startsWith("{"))
|
|
1099
|
+
return undefined;
|
|
1100
|
+
const module = readTomlInlineStringAttribute(trimmed, "module");
|
|
1101
|
+
const group = readTomlInlineStringAttribute(trimmed, "group");
|
|
1102
|
+
const name = readTomlInlineStringAttribute(trimmed, "name");
|
|
1103
|
+
const version = readTomlInlineVersion(trimmed, versions);
|
|
1104
|
+
if (module && version)
|
|
1105
|
+
return { name: module, version };
|
|
1106
|
+
if (group && name && version)
|
|
1107
|
+
return { name: `${group}:${name}`, version };
|
|
1108
|
+
return undefined;
|
|
1109
|
+
}
|
|
1110
|
+
function parseGradleCatalogPlugin(value, versions) {
|
|
1111
|
+
const trimmed = stripTomlComment(value).trim();
|
|
1112
|
+
if (!trimmed.startsWith("{"))
|
|
1113
|
+
return undefined;
|
|
1114
|
+
const id = readTomlInlineStringAttribute(trimmed, "id");
|
|
1115
|
+
const version = readTomlInlineVersion(trimmed, versions);
|
|
1116
|
+
return id && version ? { name: id, version } : undefined;
|
|
1117
|
+
}
|
|
1118
|
+
function readTomlInlineVersion(value, versions) {
|
|
1119
|
+
const version = readTomlInlineStringAttribute(value, "version");
|
|
1120
|
+
if (version)
|
|
1121
|
+
return version;
|
|
1122
|
+
const versionRef = readTomlInlineStringAttribute(value, "version.ref");
|
|
1123
|
+
if (versionRef === undefined)
|
|
1124
|
+
return undefined;
|
|
1125
|
+
// A still-declared library whose version.ref points at a deleted [versions] alias
|
|
1126
|
+
// must not vanish (reported as "removed"); surface the dangling ref instead.
|
|
1127
|
+
return versions.get(versionRef) ?? `version.ref:${versionRef} (unresolved)`;
|
|
1128
|
+
}
|
|
1129
|
+
function readTomlInlineStringAttribute(value, name) {
|
|
1130
|
+
const escapedName = name.replace(/\./g, "\\.");
|
|
1131
|
+
const match = new RegExp(`\\b${escapedName}\\s*=\\s*("[^"]*"|'[^']*'|[^,}]+)`).exec(value);
|
|
1132
|
+
return match?.[1] ? unquoteTomlScalar(match[1].trim()) : undefined;
|
|
1133
|
+
}
|
|
1134
|
+
function readXmlAttribute(attributes, name) {
|
|
1135
|
+
const value = new RegExp(`\\b${name}\\s*=\\s*["']([^"']+)["']`, "i").exec(attributes)?.[1]?.trim();
|
|
1136
|
+
// Treat an empty (whitespace-only) match as absent, not as "".
|
|
1137
|
+
if (!value)
|
|
1138
|
+
return undefined;
|
|
1139
|
+
return value;
|
|
1140
|
+
}
|
|
1141
|
+
function readXmlElement(content, name) {
|
|
1142
|
+
const value = new RegExp(`<${name}>\\s*([^<]+?)\\s*</${name}>`, "i").exec(content)?.[1]?.trim();
|
|
1143
|
+
if (!value)
|
|
1144
|
+
return undefined;
|
|
1145
|
+
return value;
|
|
1146
|
+
}
|
|
1147
|
+
function cleanRequirementLine(rawLine) {
|
|
1148
|
+
const line = rawLine.trim();
|
|
1149
|
+
if (!line || line.startsWith("#") || line.startsWith("--hash"))
|
|
1150
|
+
return undefined;
|
|
1151
|
+
const cleaned = line
|
|
1152
|
+
.replace(/\s+#.*$/, "")
|
|
1153
|
+
.replace(/\s+\\$/, "")
|
|
1154
|
+
.replace(/\s+--hash=\S+.*$/, "")
|
|
1155
|
+
.trim();
|
|
1156
|
+
if (!cleaned || cleaned.startsWith("-r ") || cleaned.startsWith("--"))
|
|
1157
|
+
return undefined;
|
|
1158
|
+
return cleaned;
|
|
1159
|
+
}
|
|
1160
|
+
function parseRequirementLine(line) {
|
|
1161
|
+
const editable = /^-e\s+(.+)$/.exec(line);
|
|
1162
|
+
if (editable?.[1]) {
|
|
1163
|
+
const egg = /[#&]egg=([^&\s]+)/.exec(editable[1]);
|
|
1164
|
+
if (!egg?.[1])
|
|
1165
|
+
return undefined;
|
|
1166
|
+
return buildRequirementPackage(decodeURIComponent(egg[1]), `@ ${editable[1]}`);
|
|
1167
|
+
}
|
|
1168
|
+
const direct = /^([A-Za-z0-9_.-]+(?:\[[^\]]+\])?)\s*@\s*(.+)$/.exec(line);
|
|
1169
|
+
if (direct?.[1] && direct[2]) {
|
|
1170
|
+
return buildRequirementPackage(direct[1], `@ ${direct[2].trim()}`);
|
|
1171
|
+
}
|
|
1172
|
+
const constrained = /^([A-Za-z0-9_.-]+(?:\[[^\]]+\])?)\s*(===|==|~=|!=|<=|>=|<|>)\s*(.+)$/.exec(line);
|
|
1173
|
+
if (constrained?.[1] && constrained[2] && constrained[3]) {
|
|
1174
|
+
return buildRequirementPackage(constrained[1], `${constrained[2]}${constrained[3].trim()}`);
|
|
1175
|
+
}
|
|
1176
|
+
const bare = /^([A-Za-z0-9_.-]+(?:\[[^\]]+\])?)$/.exec(line);
|
|
1177
|
+
if (bare?.[1])
|
|
1178
|
+
return buildRequirementPackage(bare[1], "*");
|
|
1179
|
+
return undefined;
|
|
1180
|
+
}
|
|
1181
|
+
function buildRequirementPackage(displayName, spec) {
|
|
1182
|
+
const name = normalizePythonPackageName(displayName);
|
|
1183
|
+
if (!name)
|
|
1184
|
+
return undefined;
|
|
1185
|
+
return {
|
|
1186
|
+
name,
|
|
1187
|
+
displayName,
|
|
1188
|
+
key: requirementKey(name, spec),
|
|
1189
|
+
spec
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
1192
|
+
function normalizePythonPackageName(value) {
|
|
1193
|
+
return value.replace(/\[.*$/, "").toLowerCase().replace(/[-_.]+/g, "-");
|
|
1194
|
+
}
|
|
1195
|
+
function requirementKey(name, spec) {
|
|
1196
|
+
const marker = spec.split(";", 2)[1]?.trim();
|
|
1197
|
+
return marker ? `${name};${marker}` : name;
|
|
1198
|
+
}
|
|
1199
|
+
function requirementPackagePath(item) {
|
|
1200
|
+
if (!item)
|
|
1201
|
+
return undefined;
|
|
1202
|
+
const marker = item.key.split(";", 2)[1];
|
|
1203
|
+
if (marker)
|
|
1204
|
+
return `${item.displayName}; ${marker}`;
|
|
1205
|
+
return normalizePythonPackageName(item.displayName) === item.displayName ? undefined : item.displayName;
|
|
1206
|
+
}
|
|
1207
|
+
function addPyprojectDependency(packages, packagePath, dependencyType, spec) {
|
|
1208
|
+
const parsed = parseRequirementLine(spec.trim());
|
|
1209
|
+
if (!parsed)
|
|
1210
|
+
return;
|
|
1211
|
+
packages.set(`${dependencyType}:${packagePath}:${parsed.key}`, {
|
|
1212
|
+
name: parsed.name,
|
|
1213
|
+
key: `${dependencyType}:${packagePath}:${parsed.key}`,
|
|
1214
|
+
spec: parsed.spec,
|
|
1215
|
+
packagePath,
|
|
1216
|
+
dependencyType
|
|
1217
|
+
});
|
|
1218
|
+
}
|
|
1219
|
+
function addPoetryDependency(packages, packagePath, dependencyType, rawName, rawValue) {
|
|
1220
|
+
const displayName = unquoteTomlScalar(rawName);
|
|
1221
|
+
const name = normalizePythonPackageName(displayName);
|
|
1222
|
+
if (!name || name === "python")
|
|
1223
|
+
return;
|
|
1224
|
+
const spec = readPoetryDependencySpec(rawValue);
|
|
1225
|
+
if (!spec)
|
|
1226
|
+
return;
|
|
1227
|
+
const effectiveDependencyType = dependencyType === "dependencies" && /\boptional\s*=\s*true\b/i.test(rawValue) ? "optionalDependencies" : dependencyType;
|
|
1228
|
+
packages.set(`${effectiveDependencyType}:${packagePath}:${name}`, {
|
|
1229
|
+
name,
|
|
1230
|
+
key: `${effectiveDependencyType}:${packagePath}:${name}`,
|
|
1231
|
+
spec,
|
|
1232
|
+
packagePath,
|
|
1233
|
+
dependencyType: effectiveDependencyType
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
1236
|
+
function poetryDependencySectionType(section) {
|
|
1237
|
+
if (section === "tool.poetry.dependencies")
|
|
1238
|
+
return "dependencies";
|
|
1239
|
+
if (section === "tool.poetry.dev-dependencies")
|
|
1240
|
+
return "devDependencies";
|
|
1241
|
+
if (/^tool\.poetry\.group\.[A-Za-z0-9_.-]+\.dependencies$/.test(section))
|
|
1242
|
+
return "devDependencies";
|
|
1243
|
+
return undefined;
|
|
1244
|
+
}
|
|
1245
|
+
function readPoetryDependencySpec(value) {
|
|
1246
|
+
const trimmed = stripTomlComment(value).trim();
|
|
1247
|
+
if (!trimmed)
|
|
1248
|
+
return undefined;
|
|
1249
|
+
if (trimmed.startsWith("{")) {
|
|
1250
|
+
const version = /\bversion\s*=\s*("[^"]*"|'[^']*'|[^,}]+)/.exec(trimmed)?.[1];
|
|
1251
|
+
return version ? unquoteTomlScalar(version.trim()) : normalizeInlineToml(trimmed);
|
|
1252
|
+
}
|
|
1253
|
+
return unquoteTomlScalar(trimmed);
|
|
1254
|
+
}
|
|
1255
|
+
function readTomlStringArray(lines, startIndex) {
|
|
1256
|
+
let buffer = stripTomlComment(lines[startIndex] ?? "");
|
|
1257
|
+
let endIndex = startIndex;
|
|
1258
|
+
while (!buffer.includes("]") && endIndex + 1 < lines.length) {
|
|
1259
|
+
endIndex += 1;
|
|
1260
|
+
buffer += `\n${stripTomlComment(lines[endIndex] ?? "")}`;
|
|
1261
|
+
}
|
|
1262
|
+
return { items: readQuotedStrings(buffer), endIndex };
|
|
1263
|
+
}
|
|
1264
|
+
function readQuotedStrings(value) {
|
|
1265
|
+
const items = [];
|
|
1266
|
+
const pattern = /"((?:\\.|[^"\\])*)"|'([^']*)'/g;
|
|
1267
|
+
for (const match of value.matchAll(pattern)) {
|
|
1268
|
+
const raw = match[1] ?? match[2];
|
|
1269
|
+
if (!raw)
|
|
1270
|
+
continue;
|
|
1271
|
+
items.push(raw.replace(/\\"/g, '"').replace(/\\\\/g, "\\"));
|
|
1272
|
+
}
|
|
1273
|
+
return items;
|
|
1274
|
+
}
|
|
1275
|
+
function readTomlKeyValue(line) {
|
|
1276
|
+
const match = /^("[^"]+"|'[^']+'|[A-Za-z0-9_.-]+)\s*=\s*(.+)$/.exec(line);
|
|
1277
|
+
if (!match?.[1] || !match[2])
|
|
1278
|
+
return undefined;
|
|
1279
|
+
return { key: match[1], value: match[2] };
|
|
1280
|
+
}
|
|
1281
|
+
function stripTomlComment(value) {
|
|
1282
|
+
let quote;
|
|
1283
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
1284
|
+
const char = value[index];
|
|
1285
|
+
const previous = value[index - 1];
|
|
1286
|
+
if ((char === '"' || char === "'") && previous !== "\\") {
|
|
1287
|
+
quote = quote === char ? undefined : quote ?? char;
|
|
1288
|
+
}
|
|
1289
|
+
if (char === "#" && !quote)
|
|
1290
|
+
return value.slice(0, index);
|
|
1291
|
+
}
|
|
1292
|
+
return value;
|
|
1293
|
+
}
|
|
1294
|
+
function readPipfileSection(result, section, value) {
|
|
1295
|
+
if (!isRecord(value))
|
|
1296
|
+
return;
|
|
1297
|
+
for (const [rawName, entry] of Object.entries(value)) {
|
|
1298
|
+
const version = readPipfileVersion(entry);
|
|
1299
|
+
if (!version)
|
|
1300
|
+
continue;
|
|
1301
|
+
const name = normalizePythonPackageName(rawName);
|
|
1302
|
+
if (!name)
|
|
1303
|
+
continue;
|
|
1304
|
+
const path = `${section}.${name}`;
|
|
1305
|
+
result.set(path, { name, path, version });
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
function readPipfileVersion(value) {
|
|
1309
|
+
if (typeof value === "string")
|
|
1310
|
+
return value;
|
|
1311
|
+
if (isRecord(value) && typeof value.version === "string")
|
|
1312
|
+
return value.version;
|
|
1313
|
+
return undefined;
|
|
1314
|
+
}
|
|
1315
|
+
function readComposerSection(result, section, value) {
|
|
1316
|
+
if (!Array.isArray(value))
|
|
1317
|
+
return;
|
|
1318
|
+
for (const item of value) {
|
|
1319
|
+
if (!isRecord(item) || typeof item.name !== "string" || typeof item.version !== "string")
|
|
1320
|
+
continue;
|
|
1321
|
+
const path = `${section}.${item.name}`;
|
|
1322
|
+
result.set(path, { name: item.name, path, version: item.version });
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
function readComposerJsonSection(value) {
|
|
1326
|
+
if (!isRecord(value))
|
|
1327
|
+
return {};
|
|
1328
|
+
const dependencies = {};
|
|
1329
|
+
for (const [name, constraint] of Object.entries(value)) {
|
|
1330
|
+
if (typeof constraint !== "string")
|
|
1331
|
+
continue;
|
|
1332
|
+
if (name.toLowerCase() === "php")
|
|
1333
|
+
continue;
|
|
1334
|
+
dependencies[name] = constraint;
|
|
1335
|
+
}
|
|
1336
|
+
return dependencies;
|
|
1337
|
+
}
|
|
1338
|
+
function readPackageLockPackages(packages) {
|
|
1339
|
+
const result = new Map();
|
|
1340
|
+
for (const [packagePath, entry] of Object.entries(packages)) {
|
|
1341
|
+
if (!packagePath || typeof entry.version !== "string")
|
|
1342
|
+
continue;
|
|
1343
|
+
const name = packageNameFromLockPath(packagePath);
|
|
1344
|
+
if (!name)
|
|
1345
|
+
continue;
|
|
1346
|
+
result.set(packagePath, { name, path: packagePath, version: entry.version });
|
|
1347
|
+
}
|
|
1348
|
+
return result;
|
|
1349
|
+
}
|
|
1350
|
+
function collectLockDependencies(dependencies, result, parentPath = "") {
|
|
1351
|
+
for (const [name, entry] of Object.entries(dependencies)) {
|
|
1352
|
+
if (typeof entry.version !== "string")
|
|
1353
|
+
continue;
|
|
1354
|
+
const packagePath = parentPath ? `${parentPath}/node_modules/${name}` : `node_modules/${name}`;
|
|
1355
|
+
result.set(packagePath, { name, path: packagePath, version: entry.version });
|
|
1356
|
+
if (entry.dependencies)
|
|
1357
|
+
collectLockDependencies(entry.dependencies, result, packagePath);
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
function packageNameFromLockPath(packagePath) {
|
|
1361
|
+
const tail = packagePath.split("node_modules/").at(-1);
|
|
1362
|
+
if (!tail)
|
|
1363
|
+
return undefined;
|
|
1364
|
+
const parts = tail.split("/");
|
|
1365
|
+
if (parts[0]?.startsWith("@"))
|
|
1366
|
+
return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : undefined;
|
|
1367
|
+
return parts[0];
|
|
1368
|
+
}
|
|
1369
|
+
function parsePnpmPackageKey(rawKey) {
|
|
1370
|
+
const key = rawKey.replace(/^\//, "");
|
|
1371
|
+
const separator = key.startsWith("@") ? key.indexOf("@", key.indexOf("/") + 1) : key.indexOf("@");
|
|
1372
|
+
if (separator <= 0)
|
|
1373
|
+
return undefined;
|
|
1374
|
+
const name = key.slice(0, separator);
|
|
1375
|
+
const version = key.slice(separator + 1).split("(")[0]?.split("_")[0];
|
|
1376
|
+
if (!name || !version)
|
|
1377
|
+
return undefined;
|
|
1378
|
+
return { name, version };
|
|
1379
|
+
}
|
|
1380
|
+
function normalizeYarnDescriptor(value) {
|
|
1381
|
+
return value.replace(/^"|"$/g, "").split(",")[0]?.trim().replace(/^"|"$/g, "") ?? value;
|
|
1382
|
+
}
|
|
1383
|
+
function readYarnVersion(line) {
|
|
1384
|
+
const trimmed = line.trim();
|
|
1385
|
+
const classic = /^version\s+"([^"]+)"$/.exec(trimmed);
|
|
1386
|
+
if (classic?.[1])
|
|
1387
|
+
return classic[1];
|
|
1388
|
+
const modern = /^version:\s*"?([^"\s]+)"?$/.exec(trimmed);
|
|
1389
|
+
return modern?.[1];
|
|
1390
|
+
}
|
|
1391
|
+
function parseYarnDescriptor(descriptor) {
|
|
1392
|
+
const key = descriptor.replace(/^"|"$/g, "");
|
|
1393
|
+
const separator = key.startsWith("@") ? key.indexOf("@", key.indexOf("/") + 1) : key.indexOf("@");
|
|
1394
|
+
if (separator <= 0)
|
|
1395
|
+
return undefined;
|
|
1396
|
+
const name = key.slice(0, separator);
|
|
1397
|
+
return name ? { name } : undefined;
|
|
1398
|
+
}
|
|
1399
|
+
function unquoteTomlScalar(value) {
|
|
1400
|
+
const trimmed = value.trim();
|
|
1401
|
+
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) || (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
|
|
1402
|
+
return trimmed.slice(1, -1);
|
|
1403
|
+
}
|
|
1404
|
+
return trimmed;
|
|
1405
|
+
}
|
|
1406
|
+
function normalizeInlineToml(value) {
|
|
1407
|
+
return value.replace(/\s+/g, " ").trim();
|
|
1408
|
+
}
|
|
1409
|
+
function readBunPackageVersion(value) {
|
|
1410
|
+
const resolution = Array.isArray(value) && typeof value[0] === "string" ? value[0] : typeof value === "string" ? value : undefined;
|
|
1411
|
+
if (!resolution)
|
|
1412
|
+
return undefined;
|
|
1413
|
+
const npmVersion = /@npm:([^,\s]+)/.exec(resolution);
|
|
1414
|
+
if (npmVersion?.[1])
|
|
1415
|
+
return npmVersion[1];
|
|
1416
|
+
const separator = resolution.startsWith("@") ? resolution.indexOf("@", resolution.indexOf("/") + 1) : resolution.lastIndexOf("@");
|
|
1417
|
+
if (separator <= 0)
|
|
1418
|
+
return resolution;
|
|
1419
|
+
return resolution.slice(separator + 1) || resolution;
|
|
1420
|
+
}
|
|
1421
|
+
function isRecord(value) {
|
|
1422
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1423
|
+
}
|
|
1424
|
+
//# sourceMappingURL=dependency.js.map
|