@agentxm/extension-discovery 0.28.4-bootstrap.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/LICENSE +110 -0
- package/dist/src/discover.d.ts +34 -0
- package/dist/src/discover.js +133 -0
- package/dist/src/index.d.ts +13 -0
- package/dist/src/index.js +12 -0
- package/dist/src/internal/environment.d.ts +14 -0
- package/dist/src/internal/environment.js +15 -0
- package/dist/src/packaging/bazel.d.ts +27 -0
- package/dist/src/packaging/bazel.js +125 -0
- package/dist/src/packaging/cargo.d.ts +32 -0
- package/dist/src/packaging/cargo.js +270 -0
- package/dist/src/packaging/cocoapods.d.ts +26 -0
- package/dist/src/packaging/cocoapods.js +187 -0
- package/dist/src/packaging/composer.d.ts +26 -0
- package/dist/src/packaging/composer.js +144 -0
- package/dist/src/packaging/conan.d.ts +30 -0
- package/dist/src/packaging/conan.js +238 -0
- package/dist/src/packaging/conda.d.ts +26 -0
- package/dist/src/packaging/conda.js +320 -0
- package/dist/src/packaging/cpan.d.ts +29 -0
- package/dist/src/packaging/cpan.js +189 -0
- package/dist/src/packaging/cran.d.ts +29 -0
- package/dist/src/packaging/cran.js +176 -0
- package/dist/src/packaging/detect.d.ts +16 -0
- package/dist/src/packaging/detect.js +25 -0
- package/dist/src/packaging/detected-package.d.ts +14 -0
- package/dist/src/packaging/detected-package.js +20 -0
- package/dist/src/packaging/docker.d.ts +27 -0
- package/dist/src/packaging/docker.js +256 -0
- package/dist/src/packaging/gem.d.ts +26 -0
- package/dist/src/packaging/gem.js +234 -0
- package/dist/src/packaging/golang.d.ts +26 -0
- package/dist/src/packaging/golang.js +169 -0
- package/dist/src/packaging/hackage.d.ts +26 -0
- package/dist/src/packaging/hackage.js +305 -0
- package/dist/src/packaging/hex.d.ts +28 -0
- package/dist/src/packaging/hex.js +186 -0
- package/dist/src/packaging/huggingface.d.ts +20 -0
- package/dist/src/packaging/huggingface.js +112 -0
- package/dist/src/packaging/index.d.ts +44 -0
- package/dist/src/packaging/index.js +120 -0
- package/dist/src/packaging/jsr.d.ts +30 -0
- package/dist/src/packaging/jsr.js +227 -0
- package/dist/src/packaging/julia.d.ts +27 -0
- package/dist/src/packaging/julia.js +165 -0
- package/dist/src/packaging/luarocks.d.ts +28 -0
- package/dist/src/packaging/luarocks.js +159 -0
- package/dist/src/packaging/maven.d.ts +27 -0
- package/dist/src/packaging/maven.js +471 -0
- package/dist/src/packaging/mojo.d.ts +29 -0
- package/dist/src/packaging/mojo.js +148 -0
- package/dist/src/packaging/npm.d.ts +26 -0
- package/dist/src/packaging/npm.js +190 -0
- package/dist/src/packaging/nuget.d.ts +26 -0
- package/dist/src/packaging/nuget.js +240 -0
- package/dist/src/packaging/opam.d.ts +27 -0
- package/dist/src/packaging/opam.js +287 -0
- package/dist/src/packaging/pub.d.ts +26 -0
- package/dist/src/packaging/pub.js +357 -0
- package/dist/src/packaging/pypi.d.ts +23 -0
- package/dist/src/packaging/pypi.js +448 -0
- package/dist/src/packaging/read.d.ts +20 -0
- package/dist/src/packaging/read.js +31 -0
- package/dist/src/packaging/reader-io.d.ts +29 -0
- package/dist/src/packaging/reader-io.js +29 -0
- package/dist/src/packaging/swift.d.ts +26 -0
- package/dist/src/packaging/swift.js +141 -0
- package/dist/src/packaging/types.d.ts +43 -0
- package/dist/src/packaging/types.js +8 -0
- package/dist/src/packaging/zig.d.ts +32 -0
- package/dist/src/packaging/zig.js +151 -0
- package/package.json +54 -0
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* opam (OCaml) package detector and reader for package-compatibility discovery.
|
|
3
|
+
*
|
|
4
|
+
* @experimental This API is unstable and may change without notice.
|
|
5
|
+
* @packageDocumentation
|
|
6
|
+
*/
|
|
7
|
+
// Intentional escape hatch: node:os homedir() has no @effect/platform equivalent.
|
|
8
|
+
import * as os from "node:os";
|
|
9
|
+
import * as Effect from "effect/Effect";
|
|
10
|
+
import * as FileSystem from "effect/FileSystem";
|
|
11
|
+
import * as Option from "effect/Option";
|
|
12
|
+
import * as Path from "effect/Path";
|
|
13
|
+
import * as Result from "effect/Result";
|
|
14
|
+
import * as Schema from "effect/Schema";
|
|
15
|
+
import { PackageURL } from "packageurl-js";
|
|
16
|
+
import { PackageTypeSchema } from "@agentxm/extension-model/unstable/packaging/package-type";
|
|
17
|
+
import { decodeAxmMeta, decodePurl, readFileOptional } from "./reader-io.js";
|
|
18
|
+
const opamType = Schema.decodeUnknownSync(PackageTypeSchema)("opam");
|
|
19
|
+
/** Build-tooling dependencies to skip. */
|
|
20
|
+
const SKIP_DEPS = new Set(["ocaml", "dune"]);
|
|
21
|
+
/**
|
|
22
|
+
* Parse opam depends field content.
|
|
23
|
+
* Format: depends: [ "pkg1" {>= "ver"} "pkg2" ... ]
|
|
24
|
+
* The constraint syntax uses { } for version constraints.
|
|
25
|
+
*/
|
|
26
|
+
const parseOpamDepends = (content, source) => {
|
|
27
|
+
const results = [];
|
|
28
|
+
// Find the depends field - it starts with "depends:" and contains a [ ... ] block
|
|
29
|
+
const dependsRegex = /depends:\s*\[([^\]]*)\]/s;
|
|
30
|
+
const match = dependsRegex.exec(content);
|
|
31
|
+
if (match === null || match[1] === undefined)
|
|
32
|
+
return [];
|
|
33
|
+
// Extract quoted package names and their optional version constraints
|
|
34
|
+
// Pattern: "name" {constraint} or just "name"
|
|
35
|
+
const entryRegex = /"([^"]+)"(?:\s*\{([^}]*)\})?/g;
|
|
36
|
+
let entryMatch;
|
|
37
|
+
while ((entryMatch = entryRegex.exec(match[1])) !== null) {
|
|
38
|
+
const name = entryMatch[1];
|
|
39
|
+
const constraint = entryMatch[2]?.trim();
|
|
40
|
+
if (name === undefined)
|
|
41
|
+
continue;
|
|
42
|
+
if (SKIP_DEPS.has(name))
|
|
43
|
+
continue;
|
|
44
|
+
// Check for exact version: = "version"
|
|
45
|
+
let version;
|
|
46
|
+
if (constraint !== undefined) {
|
|
47
|
+
const exactMatch = /^=\s*"([^"]+)"$/.exec(constraint);
|
|
48
|
+
if (exactMatch?.[1]) {
|
|
49
|
+
version = exactMatch[1];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const purl = new PackageURL("opam", null, name, version ?? null, null, null);
|
|
53
|
+
const purlParts = decodePurl(purl.toString());
|
|
54
|
+
results.push({ purl: purlParts, type: opamType, source });
|
|
55
|
+
}
|
|
56
|
+
return results;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Extract the contents of a top-level `(depends ...)` s-expression
|
|
60
|
+
* by finding the opening `(depends` and then walking to the matching
|
|
61
|
+
* closing `)` while counting nested parentheses.
|
|
62
|
+
*/
|
|
63
|
+
const extractDependsBlock = (content) => {
|
|
64
|
+
const idx = content.indexOf("(depends");
|
|
65
|
+
if (idx === -1)
|
|
66
|
+
return undefined;
|
|
67
|
+
// Move past "(depends"
|
|
68
|
+
let i = idx + "(depends".length;
|
|
69
|
+
let depth = 1;
|
|
70
|
+
const start = i;
|
|
71
|
+
while (i < content.length && depth > 0) {
|
|
72
|
+
if (content[i] === "(")
|
|
73
|
+
depth++;
|
|
74
|
+
else if (content[i] === ")")
|
|
75
|
+
depth--;
|
|
76
|
+
i++;
|
|
77
|
+
}
|
|
78
|
+
// depth === 0 means we found the matching close paren
|
|
79
|
+
if (depth !== 0)
|
|
80
|
+
return undefined;
|
|
81
|
+
// Return everything between "(depends" and the matching ")"
|
|
82
|
+
return content.slice(start, i - 1);
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Parse dune-project (depends ...) s-expression.
|
|
86
|
+
* Format: (depends (ocaml (>= 5.0)) (lwt (>= 5.0)) yojson)
|
|
87
|
+
*/
|
|
88
|
+
const parseDuneProjectDepends = (content, source) => {
|
|
89
|
+
const results = [];
|
|
90
|
+
const depsBlock = extractDependsBlock(content);
|
|
91
|
+
if (depsBlock === undefined)
|
|
92
|
+
return [];
|
|
93
|
+
const depsContent = depsBlock;
|
|
94
|
+
// Extract dependency entries: (name constraint) or bare name
|
|
95
|
+
// A dependency can be: bare_name, (name constraint_expr)
|
|
96
|
+
const entries = [];
|
|
97
|
+
// Parse the s-expression content to extract dependency names
|
|
98
|
+
let i = 0;
|
|
99
|
+
while (i < depsContent.length) {
|
|
100
|
+
// Skip whitespace
|
|
101
|
+
while (i < depsContent.length && /\s/.test(depsContent[i] ?? ""))
|
|
102
|
+
i++;
|
|
103
|
+
if (i >= depsContent.length)
|
|
104
|
+
break;
|
|
105
|
+
if (depsContent[i] === "(") {
|
|
106
|
+
// Parenthesized entry - extract the name (first atom)
|
|
107
|
+
i++; // skip (
|
|
108
|
+
// Skip whitespace
|
|
109
|
+
while (i < depsContent.length && /\s/.test(depsContent[i] ?? ""))
|
|
110
|
+
i++;
|
|
111
|
+
// Read the name
|
|
112
|
+
let name = "";
|
|
113
|
+
while (i < depsContent.length && !/[\s)]/.test(depsContent[i] ?? "")) {
|
|
114
|
+
name += depsContent[i];
|
|
115
|
+
i++;
|
|
116
|
+
}
|
|
117
|
+
// Check for exact version constraint: (= version)
|
|
118
|
+
let version;
|
|
119
|
+
const remaining = depsContent.slice(i);
|
|
120
|
+
const exactMatch = /^\s*\(\s*=\s*(\S+)\s*\)/.exec(remaining);
|
|
121
|
+
if (exactMatch?.[1]) {
|
|
122
|
+
version = exactMatch[1];
|
|
123
|
+
}
|
|
124
|
+
if (name !== "") {
|
|
125
|
+
entries.push(name);
|
|
126
|
+
if (version !== undefined && !SKIP_DEPS.has(name)) {
|
|
127
|
+
const purl = new PackageURL("opam", null, name, version, null, null);
|
|
128
|
+
const purlParts = decodePurl(purl.toString());
|
|
129
|
+
results.push({ purl: purlParts, type: opamType, source });
|
|
130
|
+
// Skip to closing paren
|
|
131
|
+
let depth = 1;
|
|
132
|
+
while (i < depsContent.length && depth > 0) {
|
|
133
|
+
if (depsContent[i] === "(")
|
|
134
|
+
depth++;
|
|
135
|
+
else if (depsContent[i] === ")")
|
|
136
|
+
depth--;
|
|
137
|
+
i++;
|
|
138
|
+
}
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// Skip to closing paren
|
|
143
|
+
let depth = 1;
|
|
144
|
+
while (i < depsContent.length && depth > 0) {
|
|
145
|
+
if (depsContent[i] === "(")
|
|
146
|
+
depth++;
|
|
147
|
+
else if (depsContent[i] === ")")
|
|
148
|
+
depth--;
|
|
149
|
+
i++;
|
|
150
|
+
}
|
|
151
|
+
if (name !== "" && !SKIP_DEPS.has(name) && version === undefined) {
|
|
152
|
+
const purl = new PackageURL("opam", null, name, null, null, null);
|
|
153
|
+
const purlParts = decodePurl(purl.toString());
|
|
154
|
+
results.push({ purl: purlParts, type: opamType, source });
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
// Bare name
|
|
159
|
+
let name = "";
|
|
160
|
+
while (i < depsContent.length && !/[\s)]/.test(depsContent[i] ?? "")) {
|
|
161
|
+
name += depsContent[i];
|
|
162
|
+
i++;
|
|
163
|
+
}
|
|
164
|
+
if (name !== "" && !SKIP_DEPS.has(name)) {
|
|
165
|
+
const purl = new PackageURL("opam", null, name, null, null, null);
|
|
166
|
+
const purlParts = decodePurl(purl.toString());
|
|
167
|
+
results.push({ purl: purlParts, type: opamType, source });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return results;
|
|
172
|
+
};
|
|
173
|
+
/**
|
|
174
|
+
* opam package detector.
|
|
175
|
+
*
|
|
176
|
+
* Scans `*.opam` files for `depends` fields and `dune-project` for
|
|
177
|
+
* `(depends ...)` s-expressions. Skips `ocaml` and `dune` build tooling.
|
|
178
|
+
* Dependencies are deduplicated by name.
|
|
179
|
+
*
|
|
180
|
+
* @experimental This API is unstable and may change without notice.
|
|
181
|
+
*/
|
|
182
|
+
export const opamDetector = {
|
|
183
|
+
type: opamType,
|
|
184
|
+
detect: Effect.fn("detect.opam")(function* (projectDir) {
|
|
185
|
+
const path = yield* Path.Path;
|
|
186
|
+
const fs = yield* FileSystem.FileSystem;
|
|
187
|
+
const allDeps = [];
|
|
188
|
+
const seenNames = new Set();
|
|
189
|
+
const addDeps = (deps) => {
|
|
190
|
+
for (const dep of deps) {
|
|
191
|
+
if (!seenNames.has(dep.purl.name)) {
|
|
192
|
+
seenNames.add(dep.purl.name);
|
|
193
|
+
allDeps.push(dep);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
// Scan for *.opam files
|
|
198
|
+
const entries = yield* fs.readDirectory(projectDir).pipe(Effect.option);
|
|
199
|
+
if (Option.isSome(entries)) {
|
|
200
|
+
const opamFiles = entries.value.filter((e) => e.endsWith(".opam"));
|
|
201
|
+
for (const opamFile of opamFiles) {
|
|
202
|
+
const filePath = path.join(projectDir, opamFile);
|
|
203
|
+
const content = yield* readFileOptional(filePath);
|
|
204
|
+
if (Option.isSome(content)) {
|
|
205
|
+
const deps = parseOpamDepends(content.value, filePath);
|
|
206
|
+
if (deps.length === 0 && content.value.trim().length > 0) {
|
|
207
|
+
if (!content.value.includes("opam-version:") && !content.value.includes("name:")) {
|
|
208
|
+
yield* Effect.logWarning(`Malformed opam file: ${opamFile}, skipping`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
addDeps(deps);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
// Parse dune-project
|
|
216
|
+
const dunePath = path.join(projectDir, "dune-project");
|
|
217
|
+
const duneContent = yield* readFileOptional(dunePath);
|
|
218
|
+
if (Option.isSome(duneContent)) {
|
|
219
|
+
const duneDeps = parseDuneProjectDepends(duneContent.value, dunePath);
|
|
220
|
+
addDeps(duneDeps);
|
|
221
|
+
}
|
|
222
|
+
return allDeps;
|
|
223
|
+
}, Effect.annotateLogs({ detector: "opam" }), Effect.withSpan("detect.opam")),
|
|
224
|
+
};
|
|
225
|
+
/**
|
|
226
|
+
* Parse x-axm custom fields from opam file content.
|
|
227
|
+
* Returns a record mapping field names (without x-axm- prefix) to values.
|
|
228
|
+
*/
|
|
229
|
+
const parseXAxmFields = (content) => {
|
|
230
|
+
const fields = {};
|
|
231
|
+
const regex = /^x-axm-(\S+)\s*:\s*(.+)$/gim;
|
|
232
|
+
let match;
|
|
233
|
+
while ((match = regex.exec(content)) !== null) {
|
|
234
|
+
const fieldName = match[1];
|
|
235
|
+
const rawValue = match[2]?.trim();
|
|
236
|
+
if (fieldName === undefined || rawValue === undefined)
|
|
237
|
+
continue;
|
|
238
|
+
// Try to parse as JSON value (for arrays, etc.)
|
|
239
|
+
try {
|
|
240
|
+
fields[fieldName] = JSON.parse(rawValue);
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
fields[fieldName] = rawValue;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return fields;
|
|
247
|
+
};
|
|
248
|
+
/**
|
|
249
|
+
* opam package reader.
|
|
250
|
+
*
|
|
251
|
+
* Reads `x-axm` prefixed custom fields from `.opam` files in the
|
|
252
|
+
* opam switch at `~/.opam/<switch>/lib/<pkg>/opam`.
|
|
253
|
+
*
|
|
254
|
+
* @experimental This API is unstable and may change without notice.
|
|
255
|
+
*/
|
|
256
|
+
export const opamReader = {
|
|
257
|
+
type: opamType,
|
|
258
|
+
read: Effect.fn("read.opam")(function* (pkg) {
|
|
259
|
+
const path = yield* Path.Path;
|
|
260
|
+
const fs = yield* FileSystem.FileSystem;
|
|
261
|
+
const pkgName = pkg.purl.name;
|
|
262
|
+
const home = os.homedir();
|
|
263
|
+
const opamDir = path.join(home, ".opam");
|
|
264
|
+
// List available switches
|
|
265
|
+
const switches = yield* fs.readDirectory(opamDir).pipe(Effect.option);
|
|
266
|
+
if (Option.isNone(switches))
|
|
267
|
+
return Option.none();
|
|
268
|
+
// Check each switch for the package
|
|
269
|
+
for (const switchName of switches.value) {
|
|
270
|
+
const opamFilePath = path.join(opamDir, switchName, "lib", pkgName, "opam");
|
|
271
|
+
const content = yield* readFileOptional(opamFilePath);
|
|
272
|
+
if (Option.isNone(content))
|
|
273
|
+
continue;
|
|
274
|
+
const xAxmFields = parseXAxmFields(content.value);
|
|
275
|
+
if (Object.keys(xAxmFields).length === 0)
|
|
276
|
+
continue;
|
|
277
|
+
const metaResult = decodeAxmMeta(xAxmFields);
|
|
278
|
+
if (Result.isFailure(metaResult)) {
|
|
279
|
+
yield* Effect.logWarning(`Invalid axm metadata in ${pkgName}: schema validation failed`);
|
|
280
|
+
return Option.none();
|
|
281
|
+
}
|
|
282
|
+
return Option.some(metaResult.success.extensions);
|
|
283
|
+
}
|
|
284
|
+
return Option.none();
|
|
285
|
+
}, Effect.annotateLogs({ reader: "opam" }), Effect.withSpan("read.opam")),
|
|
286
|
+
};
|
|
287
|
+
//# sourceMappingURL=opam.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dart/Flutter pub package detector and reader for package-compatibility discovery.
|
|
3
|
+
*
|
|
4
|
+
* @experimental This API is unstable and may change without notice.
|
|
5
|
+
* @packageDocumentation
|
|
6
|
+
*/
|
|
7
|
+
import type { PackageDetector, PackageReader } from "./types.js";
|
|
8
|
+
/**
|
|
9
|
+
* Pub package detector.
|
|
10
|
+
*
|
|
11
|
+
* Scans `pubspec.yaml` in the project directory and extracts dependencies
|
|
12
|
+
* from `dependencies` and `dev_dependencies`.
|
|
13
|
+
*
|
|
14
|
+
* @experimental This API is unstable and may change without notice.
|
|
15
|
+
*/
|
|
16
|
+
export declare const pubDetector: PackageDetector;
|
|
17
|
+
/**
|
|
18
|
+
* Pub package reader.
|
|
19
|
+
*
|
|
20
|
+
* Reads `.dart_tool/package_config.json` to locate the package root,
|
|
21
|
+
* then reads `pubspec.yaml` from that root and extracts the `axm` field.
|
|
22
|
+
*
|
|
23
|
+
* @experimental This API is unstable and may change without notice.
|
|
24
|
+
*/
|
|
25
|
+
export declare const pubReader: PackageReader;
|
|
26
|
+
//# sourceMappingURL=pub.d.ts.map
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dart/Flutter pub package detector and reader for package-compatibility discovery.
|
|
3
|
+
*
|
|
4
|
+
* @experimental This API is unstable and may change without notice.
|
|
5
|
+
* @packageDocumentation
|
|
6
|
+
*/
|
|
7
|
+
// Intentional escape hatch: node:url URL <-> path conversion has no @effect/platform equivalent.
|
|
8
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
9
|
+
import * as Effect from "effect/Effect";
|
|
10
|
+
import * as Option from "effect/Option";
|
|
11
|
+
import * as Path from "effect/Path";
|
|
12
|
+
import * as Result from "effect/Result";
|
|
13
|
+
import * as Schema from "effect/Schema";
|
|
14
|
+
import { PackageURL } from "packageurl-js";
|
|
15
|
+
import { PackageTypeSchema } from "@agentxm/extension-model/unstable/packaging/package-type";
|
|
16
|
+
import { decodeAxmMeta, decodePurl, parseJsonOptional, readFileOptional } from "./reader-io.js";
|
|
17
|
+
const pubType = Schema.decodeUnknownSync(PackageTypeSchema)("pub");
|
|
18
|
+
/** Pub package names must be lowercase `[a-z0-9_]` only. */
|
|
19
|
+
const VALID_PUB_NAME = /^[a-z0-9_]+$/;
|
|
20
|
+
/**
|
|
21
|
+
* Returns true if the specifier is an exact semver version (no range operators).
|
|
22
|
+
* Exact versions match: digits and dots only, e.g. "1.2.0", "1.0.0+1".
|
|
23
|
+
*/
|
|
24
|
+
const isExactVersion = (specifier) => /^\d+\.\d+\.\d+(?:[+][a-zA-Z0-9._+-]*)?$/.test(specifier);
|
|
25
|
+
/**
|
|
26
|
+
* Minimal line-based YAML parser for pubspec.yaml dependency sections.
|
|
27
|
+
* Extracts `dependencies:` and `dev_dependencies:` maps.
|
|
28
|
+
*
|
|
29
|
+
* Returns a record of dependency name -> value string (version or structured).
|
|
30
|
+
* Only handles flat key-value pairs and detects structured entries (path/git/sdk).
|
|
31
|
+
*/
|
|
32
|
+
const parsePubspecDeps = (content) => {
|
|
33
|
+
const lines = content.split("\n");
|
|
34
|
+
const deps = [];
|
|
35
|
+
let inDepSection = false;
|
|
36
|
+
let currentDepName;
|
|
37
|
+
let currentDepIsStructured = false;
|
|
38
|
+
for (const line of lines) {
|
|
39
|
+
// Skip comments
|
|
40
|
+
if (line.trimStart().startsWith("#"))
|
|
41
|
+
continue;
|
|
42
|
+
// Detect top-level section headers (no leading whitespace)
|
|
43
|
+
if (!line.startsWith(" ") && !line.startsWith("\t") && line.trim().length > 0) {
|
|
44
|
+
const trimmed = line.trim();
|
|
45
|
+
if (trimmed === "dependencies:" || trimmed === "dev_dependencies:") {
|
|
46
|
+
inDepSection = true;
|
|
47
|
+
currentDepName = undefined;
|
|
48
|
+
currentDepIsStructured = false;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
// Any other top-level key ends the section
|
|
52
|
+
if (trimmed.endsWith(":") || trimmed.includes(":")) {
|
|
53
|
+
inDepSection = false;
|
|
54
|
+
currentDepName = undefined;
|
|
55
|
+
currentDepIsStructured = false;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (!inDepSection)
|
|
60
|
+
continue;
|
|
61
|
+
const trimmed = line.trim();
|
|
62
|
+
if (trimmed.length === 0)
|
|
63
|
+
continue;
|
|
64
|
+
// Determine indentation level
|
|
65
|
+
const indent = line.length - line.trimStart().length;
|
|
66
|
+
// dep-level entry (typically 2 spaces or 1 tab)
|
|
67
|
+
if (indent > 0 && indent <= 4 && !trimmed.startsWith("-")) {
|
|
68
|
+
const colonIdx = trimmed.indexOf(":");
|
|
69
|
+
if (colonIdx === -1)
|
|
70
|
+
continue;
|
|
71
|
+
const name = trimmed.slice(0, colonIdx).trim();
|
|
72
|
+
const value = trimmed.slice(colonIdx + 1).trim();
|
|
73
|
+
currentDepName = name;
|
|
74
|
+
currentDepIsStructured = false;
|
|
75
|
+
if (value.length === 0) {
|
|
76
|
+
// Structured dependency follows on next lines
|
|
77
|
+
currentDepIsStructured = true;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
// Simple version string: `http: ^1.0.0` or `http: 1.2.0` or `http: any`
|
|
81
|
+
deps.push({ name, version: value, isSkipped: false });
|
|
82
|
+
currentDepName = undefined;
|
|
83
|
+
}
|
|
84
|
+
else if (indent > 4 && currentDepName !== undefined && currentDepIsStructured) {
|
|
85
|
+
// Sub-key of a structured dependency
|
|
86
|
+
const colonIdx = trimmed.indexOf(":");
|
|
87
|
+
if (colonIdx === -1)
|
|
88
|
+
continue;
|
|
89
|
+
const subKey = trimmed.slice(0, colonIdx).trim();
|
|
90
|
+
if (subKey === "path" || subKey === "git" || subKey === "sdk") {
|
|
91
|
+
deps.push({ name: currentDepName, version: undefined, isSkipped: true });
|
|
92
|
+
currentDepName = undefined;
|
|
93
|
+
currentDepIsStructured = false;
|
|
94
|
+
}
|
|
95
|
+
else if (subKey === "version") {
|
|
96
|
+
const subValue = trimmed.slice(colonIdx + 1).trim();
|
|
97
|
+
// Hosted dependency with explicit version
|
|
98
|
+
deps.push({ name: currentDepName, version: subValue, isSkipped: false });
|
|
99
|
+
currentDepName = undefined;
|
|
100
|
+
currentDepIsStructured = false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return { deps };
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* Convert a parsed pub dependency to a DetectedPackage, or undefined if skipped.
|
|
108
|
+
*/
|
|
109
|
+
const depToPurl = (name, version, isSkipped, source) => {
|
|
110
|
+
if (isSkipped)
|
|
111
|
+
return undefined;
|
|
112
|
+
if (!VALID_PUB_NAME.test(name))
|
|
113
|
+
return undefined;
|
|
114
|
+
const resolvedVersion = version !== undefined && isExactVersion(version) ? version : undefined;
|
|
115
|
+
const purl = new PackageURL("pub", null, name, resolvedVersion ?? null, null, null);
|
|
116
|
+
const purlParts = decodePurl(purl.toString());
|
|
117
|
+
return { purl: purlParts, type: pubType, source };
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* Pub package detector.
|
|
121
|
+
*
|
|
122
|
+
* Scans `pubspec.yaml` in the project directory and extracts dependencies
|
|
123
|
+
* from `dependencies` and `dev_dependencies`.
|
|
124
|
+
*
|
|
125
|
+
* @experimental This API is unstable and may change without notice.
|
|
126
|
+
*/
|
|
127
|
+
export const pubDetector = {
|
|
128
|
+
type: pubType,
|
|
129
|
+
detect: Effect.fn("detect.pub")(function* (projectDir) {
|
|
130
|
+
const path = yield* Path.Path;
|
|
131
|
+
const manifestPath = path.join(projectDir, "pubspec.yaml");
|
|
132
|
+
const content = yield* readFileOptional(manifestPath);
|
|
133
|
+
if (Option.isNone(content))
|
|
134
|
+
return [];
|
|
135
|
+
// Basic validation: check if it looks like YAML
|
|
136
|
+
const trimmed = content.value.trim();
|
|
137
|
+
if (trimmed.length === 0)
|
|
138
|
+
return [];
|
|
139
|
+
// Check for obviously malformed YAML
|
|
140
|
+
if (trimmed.startsWith("{") && !trimmed.includes(":")) {
|
|
141
|
+
yield* Effect.logWarning("Malformed pubspec.yaml, skipping");
|
|
142
|
+
return [];
|
|
143
|
+
}
|
|
144
|
+
const { deps } = parsePubspecDeps(content.value);
|
|
145
|
+
const results = [];
|
|
146
|
+
for (const dep of deps) {
|
|
147
|
+
const detected = depToPurl(dep.name, dep.version, dep.isSkipped, manifestPath);
|
|
148
|
+
if (detected !== undefined)
|
|
149
|
+
results.push(detected);
|
|
150
|
+
}
|
|
151
|
+
return results;
|
|
152
|
+
}, Effect.annotateLogs({ detector: "pub" }), Effect.withSpan("detect.pub")),
|
|
153
|
+
};
|
|
154
|
+
/**
|
|
155
|
+
* Schema for package_config.json package entries.
|
|
156
|
+
*/
|
|
157
|
+
const PackageConfigEntrySchema = Schema.Struct({
|
|
158
|
+
name: Schema.String,
|
|
159
|
+
rootUri: Schema.String,
|
|
160
|
+
});
|
|
161
|
+
const PackageConfigSchema = Schema.Struct({
|
|
162
|
+
packages: Schema.optional(Schema.Array(PackageConfigEntrySchema)),
|
|
163
|
+
});
|
|
164
|
+
const decodePackageConfig = Schema.decodeUnknownResult(PackageConfigSchema);
|
|
165
|
+
/**
|
|
166
|
+
* Resolve a `package_config.json` `rootUri` to an absolute filesystem path.
|
|
167
|
+
*
|
|
168
|
+
* `rootUri` is a URI reference resolved against the location of
|
|
169
|
+
* `package_config.json` (the `.dart_tool/` directory). A real `dart pub get`
|
|
170
|
+
* writes absolute `file://` URIs for hosted packages, while path dependencies
|
|
171
|
+
* and hand-written configs may use relative paths; resolving as a URL handles
|
|
172
|
+
* both and decodes any percent-encoding. `path.resolve` cannot be used here —
|
|
173
|
+
* it has no notion of URI schemes and mangles `file://` values into garbage
|
|
174
|
+
* paths. Returns `Option.none` for a `rootUri` that is not a resolvable
|
|
175
|
+
* `file:` location.
|
|
176
|
+
*/
|
|
177
|
+
const resolvePackageRoot = (dartToolDir, rootUri) => {
|
|
178
|
+
try {
|
|
179
|
+
const baseUrl = new URL(`${pathToFileURL(dartToolDir).href}/`);
|
|
180
|
+
return Option.some(fileURLToPath(new URL(rootUri, baseUrl)));
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
return Option.none();
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
const parseYamlInlineObject = (value) => {
|
|
187
|
+
const trimmed = value.trim();
|
|
188
|
+
if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) {
|
|
189
|
+
return trimmed.replace(/^["']|["']$/g, "");
|
|
190
|
+
}
|
|
191
|
+
const body = trimmed.slice(1, -1);
|
|
192
|
+
const entry = {};
|
|
193
|
+
for (const part of body.split(",")) {
|
|
194
|
+
const pair = /^\s*([A-Za-z0-9_-]+)\s*:\s*["']([^"']+)["']\s*$/.exec(part);
|
|
195
|
+
const key = pair?.[1];
|
|
196
|
+
const rawValue = pair?.[2];
|
|
197
|
+
if (key === undefined || rawValue === undefined)
|
|
198
|
+
continue;
|
|
199
|
+
entry[key] = rawValue;
|
|
200
|
+
}
|
|
201
|
+
return Object.keys(entry).length > 0 ? entry : value;
|
|
202
|
+
};
|
|
203
|
+
const parseYamlInlineArray = (value) => {
|
|
204
|
+
const inner = value.slice(1, value.lastIndexOf("]")).trim();
|
|
205
|
+
if (inner === "")
|
|
206
|
+
return [];
|
|
207
|
+
const objectMatches = Array.from(inner.matchAll(/\{[^{}]*\}/g), (match) => match[0]);
|
|
208
|
+
if (objectMatches.length > 0)
|
|
209
|
+
return objectMatches.map(parseYamlInlineObject);
|
|
210
|
+
return inner
|
|
211
|
+
.split(",")
|
|
212
|
+
.map((item) => item.trim())
|
|
213
|
+
.filter((item) => item.length > 0)
|
|
214
|
+
.map((item) => item.replace(/^["']|["']$/g, ""));
|
|
215
|
+
};
|
|
216
|
+
/**
|
|
217
|
+
* Extract the `axm` field from a pubspec.yaml content string using line-based parsing.
|
|
218
|
+
* Returns the axm field value as a simple JSON-like structure, or undefined if not found.
|
|
219
|
+
*/
|
|
220
|
+
const extractAxmFromPubspec = (content) => {
|
|
221
|
+
const lines = content.split("\n");
|
|
222
|
+
let inAxmSection = false;
|
|
223
|
+
const axmLines = [];
|
|
224
|
+
let baseIndent = 0;
|
|
225
|
+
for (const line of lines) {
|
|
226
|
+
if (line.trimStart().startsWith("#"))
|
|
227
|
+
continue;
|
|
228
|
+
// Detect "axm:" at the top level
|
|
229
|
+
if (!line.startsWith(" ") && !line.startsWith("\t") && line.trim() === "axm:") {
|
|
230
|
+
inAxmSection = true;
|
|
231
|
+
baseIndent = 0;
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
if (inAxmSection) {
|
|
235
|
+
const trimmed = line.trim();
|
|
236
|
+
if (trimmed.length === 0)
|
|
237
|
+
continue;
|
|
238
|
+
const indent = line.length - line.trimStart().length;
|
|
239
|
+
// If we hit another top-level key, stop
|
|
240
|
+
if (indent === 0 && trimmed.length > 0) {
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
if (baseIndent === 0)
|
|
244
|
+
baseIndent = indent;
|
|
245
|
+
if (indent >= baseIndent) {
|
|
246
|
+
axmLines.push(line);
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (axmLines.length === 0)
|
|
254
|
+
return undefined;
|
|
255
|
+
// Parse the axm section: extract extensions list
|
|
256
|
+
const result = {};
|
|
257
|
+
let currentKey;
|
|
258
|
+
const currentList = [];
|
|
259
|
+
let inList = false;
|
|
260
|
+
for (const line of axmLines) {
|
|
261
|
+
const trimmed = line.trim();
|
|
262
|
+
if (trimmed.startsWith("#"))
|
|
263
|
+
continue;
|
|
264
|
+
// Check for key: value or key:
|
|
265
|
+
const colonIdx = trimmed.indexOf(":");
|
|
266
|
+
if (colonIdx > 0 && !trimmed.startsWith("-")) {
|
|
267
|
+
// Save previous list if any
|
|
268
|
+
if (inList && currentKey !== undefined) {
|
|
269
|
+
result[currentKey] = currentList.slice();
|
|
270
|
+
currentList.length = 0;
|
|
271
|
+
}
|
|
272
|
+
const key = trimmed.slice(0, colonIdx).trim();
|
|
273
|
+
const value = trimmed.slice(colonIdx + 1).trim();
|
|
274
|
+
currentKey = key;
|
|
275
|
+
if (value.length > 0 && !value.startsWith("[")) {
|
|
276
|
+
// Simple scalar value
|
|
277
|
+
result[key] = value;
|
|
278
|
+
inList = false;
|
|
279
|
+
}
|
|
280
|
+
else if (value.startsWith("[")) {
|
|
281
|
+
result[key] = parseYamlInlineArray(value);
|
|
282
|
+
inList = false;
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
// List follows on next lines
|
|
286
|
+
inList = true;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
else if (trimmed.startsWith("- ") && inList && currentKey !== undefined) {
|
|
290
|
+
const item = parseYamlInlineObject(trimmed.slice(2));
|
|
291
|
+
currentList.push(item);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
// Save final list
|
|
295
|
+
if (inList && currentKey !== undefined) {
|
|
296
|
+
result[currentKey] = currentList.slice();
|
|
297
|
+
}
|
|
298
|
+
return result;
|
|
299
|
+
};
|
|
300
|
+
/**
|
|
301
|
+
* Pub package reader.
|
|
302
|
+
*
|
|
303
|
+
* Reads `.dart_tool/package_config.json` to locate the package root,
|
|
304
|
+
* then reads `pubspec.yaml` from that root and extracts the `axm` field.
|
|
305
|
+
*
|
|
306
|
+
* @experimental This API is unstable and may change without notice.
|
|
307
|
+
*/
|
|
308
|
+
export const pubReader = {
|
|
309
|
+
type: pubType,
|
|
310
|
+
read: Effect.fn("read.pub")(function* (pkg) {
|
|
311
|
+
const path = yield* Path.Path;
|
|
312
|
+
// Derive project directory from the source manifest path
|
|
313
|
+
const projectDir = path.dirname(pkg.source);
|
|
314
|
+
// Read .dart_tool/package_config.json
|
|
315
|
+
const packageConfigPath = path.join(projectDir, ".dart_tool", "package_config.json");
|
|
316
|
+
const configContent = yield* readFileOptional(packageConfigPath);
|
|
317
|
+
if (Option.isNone(configContent))
|
|
318
|
+
return Option.none();
|
|
319
|
+
const configParsed = yield* parseJsonOptional(configContent.value, "package_config.json");
|
|
320
|
+
if (Option.isNone(configParsed))
|
|
321
|
+
return Option.none();
|
|
322
|
+
const configResult = decodePackageConfig(configParsed.value);
|
|
323
|
+
if (Result.isFailure(configResult))
|
|
324
|
+
return Option.none();
|
|
325
|
+
const packages = configResult.success.packages;
|
|
326
|
+
if (packages === undefined)
|
|
327
|
+
return Option.none();
|
|
328
|
+
// Find the package entry matching our detected package name
|
|
329
|
+
const pkgEntry = packages.find((p) => p.name === pkg.purl.name);
|
|
330
|
+
if (pkgEntry === undefined)
|
|
331
|
+
return Option.none();
|
|
332
|
+
// Resolve rootUri against the .dart_tool/ directory. package_config.json
|
|
333
|
+
// entries are URI references — a real `dart pub get` writes absolute
|
|
334
|
+
// `file://` URIs for hosted packages — so resolve as a URL, not a path.
|
|
335
|
+
const dartToolDir = path.resolve(projectDir, ".dart_tool");
|
|
336
|
+
const packageRoot = resolvePackageRoot(dartToolDir, pkgEntry.rootUri);
|
|
337
|
+
if (Option.isNone(packageRoot))
|
|
338
|
+
return Option.none();
|
|
339
|
+
// Read pubspec.yaml from package root
|
|
340
|
+
const pubspecPath = path.join(packageRoot.value, "pubspec.yaml");
|
|
341
|
+
const pubspecContent = yield* readFileOptional(pubspecPath);
|
|
342
|
+
if (Option.isNone(pubspecContent))
|
|
343
|
+
return Option.none();
|
|
344
|
+
// Extract axm field from pubspec
|
|
345
|
+
const axmRaw = extractAxmFromPubspec(pubspecContent.value);
|
|
346
|
+
if (axmRaw === undefined)
|
|
347
|
+
return Option.none();
|
|
348
|
+
// Validate axm metadata structure
|
|
349
|
+
const metaResult = decodeAxmMeta(axmRaw);
|
|
350
|
+
if (Result.isFailure(metaResult)) {
|
|
351
|
+
yield* Effect.logWarning(`Invalid axm metadata in ${pkg.purl.name}: schema validation failed`);
|
|
352
|
+
return Option.none();
|
|
353
|
+
}
|
|
354
|
+
return Option.some(metaResult.success.extensions);
|
|
355
|
+
}, Effect.annotateLogs({ reader: "pub" }), Effect.withSpan("read.pub")),
|
|
356
|
+
};
|
|
357
|
+
//# sourceMappingURL=pub.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pypi package detector and reader for package-compatibility discovery.
|
|
3
|
+
*
|
|
4
|
+
* Parses Python dependency files (pyproject.toml, requirements.txt,
|
|
5
|
+
* setup.cfg, Pipfile) and reads axm metadata from installed packages.
|
|
6
|
+
*
|
|
7
|
+
* @experimental This API is unstable and may change without notice.
|
|
8
|
+
* @packageDocumentation
|
|
9
|
+
*/
|
|
10
|
+
import type { PackageDetector, PackageReader } from "./types.js";
|
|
11
|
+
/**
|
|
12
|
+
* pypi package detector. Parses Python dependency files in priority order.
|
|
13
|
+
*
|
|
14
|
+
* @experimental This API is unstable and may change without notice.
|
|
15
|
+
*/
|
|
16
|
+
export declare const pypiDetector: PackageDetector;
|
|
17
|
+
/**
|
|
18
|
+
* pypi package reader. Reads axm metadata from installed Python packages.
|
|
19
|
+
*
|
|
20
|
+
* @experimental This API is unstable and may change without notice.
|
|
21
|
+
*/
|
|
22
|
+
export declare const pypiReader: PackageReader;
|
|
23
|
+
//# sourceMappingURL=pypi.d.ts.map
|