@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,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conan (C++) package detector and reader for package-compatibility discovery.
|
|
3
|
+
*
|
|
4
|
+
* Parses `conanfile.txt` `[requires]` sections and `conanfile.py` `requires`
|
|
5
|
+
* attributes. Reads axm metadata from Conan cache `conandata.yml` or
|
|
6
|
+
* `extension_properties`.
|
|
7
|
+
*
|
|
8
|
+
* @experimental This API is unstable and may change without notice.
|
|
9
|
+
* @packageDocumentation
|
|
10
|
+
*/
|
|
11
|
+
// Intentional escape hatch: node:os homedir() has no @effect/platform equivalent.
|
|
12
|
+
import * as os from "node:os";
|
|
13
|
+
import * as Effect from "effect/Effect";
|
|
14
|
+
import * as Option from "effect/Option";
|
|
15
|
+
import * as Path from "effect/Path";
|
|
16
|
+
import * as Result from "effect/Result";
|
|
17
|
+
import * as Schema from "effect/Schema";
|
|
18
|
+
import YAML from "yaml";
|
|
19
|
+
import { readEnv } from "../internal/environment.js";
|
|
20
|
+
import { PackageTypeSchema } from "@agentxm/extension-model/unstable/packaging/package-type";
|
|
21
|
+
import { decodeAxmMeta, readFileOptional } from "./reader-io.js";
|
|
22
|
+
const conanType = Schema.decodeUnknownSync(PackageTypeSchema)("conan");
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Helpers
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
/**
|
|
27
|
+
* Parse a Conan reference string like "name/version" or "name/version@user/channel".
|
|
28
|
+
* Returns name and optional version.
|
|
29
|
+
*/
|
|
30
|
+
const parseConanRef = (ref) => {
|
|
31
|
+
const trimmed = ref.trim();
|
|
32
|
+
if (trimmed === "" || trimmed.startsWith("#"))
|
|
33
|
+
return undefined;
|
|
34
|
+
// Strip user/channel suffix if present: name/version@user/channel
|
|
35
|
+
const atIdx = trimmed.indexOf("@");
|
|
36
|
+
const refPart = atIdx >= 0 ? trimmed.slice(0, atIdx) : trimmed;
|
|
37
|
+
const slashIdx = refPart.indexOf("/");
|
|
38
|
+
if (slashIdx <= 0)
|
|
39
|
+
return undefined;
|
|
40
|
+
const name = refPart.slice(0, slashIdx).trim();
|
|
41
|
+
const version = refPart.slice(slashIdx + 1).trim();
|
|
42
|
+
if (name === "")
|
|
43
|
+
return undefined;
|
|
44
|
+
return { name, version: version !== "" ? version : undefined };
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Parse `conanfile.txt` content and extract dependencies from [requires] section.
|
|
48
|
+
*/
|
|
49
|
+
const parseConanfileTxt = (content, source) => {
|
|
50
|
+
const lines = content.split("\n");
|
|
51
|
+
const results = [];
|
|
52
|
+
let inRequiresSection = false;
|
|
53
|
+
for (const line of lines) {
|
|
54
|
+
const trimmed = line.trim();
|
|
55
|
+
// Section header
|
|
56
|
+
if (trimmed.startsWith("[")) {
|
|
57
|
+
inRequiresSection = trimmed === "[requires]";
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (!inRequiresSection)
|
|
61
|
+
continue;
|
|
62
|
+
if (trimmed === "" || trimmed.startsWith("#"))
|
|
63
|
+
continue;
|
|
64
|
+
const parsed = parseConanRef(trimmed);
|
|
65
|
+
if (parsed !== undefined) {
|
|
66
|
+
results.push({
|
|
67
|
+
purl: {
|
|
68
|
+
type: conanType,
|
|
69
|
+
name: parsed.name,
|
|
70
|
+
...(parsed.version !== undefined ? { version: parsed.version } : {}),
|
|
71
|
+
},
|
|
72
|
+
type: conanType,
|
|
73
|
+
source,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return results;
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* Parse `conanfile.py` content and extract dependencies from `requires` attribute.
|
|
81
|
+
* Looks for patterns like: requires = "name/version", "name2/version2"
|
|
82
|
+
* and self.requires("name/version")
|
|
83
|
+
*/
|
|
84
|
+
const parseConanfilePy = (content, source) => {
|
|
85
|
+
const results = [];
|
|
86
|
+
const seenNames = new Set();
|
|
87
|
+
// Match class-level `requires = "ref", "ref2"` or `requires = ("ref", "ref2")`
|
|
88
|
+
const classRequiresRegex = /requires\s*=\s*(?:\(([^)]*)\)|([^\n]*))/g;
|
|
89
|
+
let match;
|
|
90
|
+
while ((match = classRequiresRegex.exec(content)) !== null) {
|
|
91
|
+
const value = match[1] ?? match[2] ?? "";
|
|
92
|
+
const refs = value.match(/"([^"]+)"/g);
|
|
93
|
+
if (refs) {
|
|
94
|
+
for (const quotedRef of refs) {
|
|
95
|
+
const ref = quotedRef.slice(1, -1);
|
|
96
|
+
const parsed = parseConanRef(ref);
|
|
97
|
+
if (parsed !== undefined && !seenNames.has(parsed.name)) {
|
|
98
|
+
seenNames.add(parsed.name);
|
|
99
|
+
results.push({
|
|
100
|
+
purl: {
|
|
101
|
+
type: conanType,
|
|
102
|
+
name: parsed.name,
|
|
103
|
+
...(parsed.version !== undefined ? { version: parsed.version } : {}),
|
|
104
|
+
},
|
|
105
|
+
type: conanType,
|
|
106
|
+
source,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// Match self.requires("ref")
|
|
113
|
+
const selfRequiresRegex = /self\.requires\s*\(\s*"([^"]+)"/g;
|
|
114
|
+
while ((match = selfRequiresRegex.exec(content)) !== null) {
|
|
115
|
+
const ref = match[1];
|
|
116
|
+
if (ref === undefined)
|
|
117
|
+
continue;
|
|
118
|
+
const parsed = parseConanRef(ref);
|
|
119
|
+
if (parsed !== undefined && !seenNames.has(parsed.name)) {
|
|
120
|
+
seenNames.add(parsed.name);
|
|
121
|
+
results.push({
|
|
122
|
+
purl: {
|
|
123
|
+
type: conanType,
|
|
124
|
+
name: parsed.name,
|
|
125
|
+
...(parsed.version !== undefined ? { version: parsed.version } : {}),
|
|
126
|
+
},
|
|
127
|
+
type: conanType,
|
|
128
|
+
source,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return results;
|
|
133
|
+
};
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
// Detector
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
/**
|
|
138
|
+
* Conan package detector.
|
|
139
|
+
*
|
|
140
|
+
* Scans `conanfile.txt` `[requires]` section and `conanfile.py` `requires`
|
|
141
|
+
* attribute in the project directory.
|
|
142
|
+
*
|
|
143
|
+
* @experimental This API is unstable and may change without notice.
|
|
144
|
+
*/
|
|
145
|
+
export const conanDetector = {
|
|
146
|
+
type: conanType,
|
|
147
|
+
detect: Effect.fn("detect.conan")(function* (projectDir) {
|
|
148
|
+
const path = yield* Path.Path;
|
|
149
|
+
const allPackages = [];
|
|
150
|
+
const seenNames = new Set();
|
|
151
|
+
const addUnique = (pkgs) => {
|
|
152
|
+
for (const pkg of pkgs) {
|
|
153
|
+
if (!seenNames.has(pkg.purl.name)) {
|
|
154
|
+
seenNames.add(pkg.purl.name);
|
|
155
|
+
allPackages.push(pkg);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
// 1. conanfile.txt
|
|
160
|
+
const conanfileTxtPath = path.join(projectDir, "conanfile.txt");
|
|
161
|
+
const conanfileTxtContent = yield* readFileOptional(conanfileTxtPath);
|
|
162
|
+
if (Option.isSome(conanfileTxtContent)) {
|
|
163
|
+
addUnique(parseConanfileTxt(conanfileTxtContent.value, conanfileTxtPath));
|
|
164
|
+
}
|
|
165
|
+
// 2. conanfile.py
|
|
166
|
+
const conanfilePyPath = path.join(projectDir, "conanfile.py");
|
|
167
|
+
const conanfilePyContent = yield* readFileOptional(conanfilePyPath);
|
|
168
|
+
if (Option.isSome(conanfilePyContent)) {
|
|
169
|
+
addUnique(parseConanfilePy(conanfilePyContent.value, conanfilePyPath));
|
|
170
|
+
}
|
|
171
|
+
return allPackages;
|
|
172
|
+
}, Effect.annotateLogs({ detector: "conan" }), Effect.withSpan("detect.conan")),
|
|
173
|
+
};
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
175
|
+
// Reader
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
177
|
+
/**
|
|
178
|
+
* Parse YAML content, returning Option.none and logging a warning on failure.
|
|
179
|
+
*/
|
|
180
|
+
const parseYamlOptional = (content, context) => Effect.gen(function* () {
|
|
181
|
+
const result = yield* Effect.try({
|
|
182
|
+
try: () => YAML.parse(content),
|
|
183
|
+
catch: () => ({ _tag: "YamlParseError" }),
|
|
184
|
+
}).pipe(Effect.option);
|
|
185
|
+
if (Option.isNone(result)) {
|
|
186
|
+
yield* Effect.logWarning(`Malformed YAML in ${context}, skipping`);
|
|
187
|
+
return Option.none();
|
|
188
|
+
}
|
|
189
|
+
return Option.some(result.value);
|
|
190
|
+
});
|
|
191
|
+
/** Schema for extracting the axm field from conandata.yml */
|
|
192
|
+
const ConanDataAxmSchema = Schema.Struct({
|
|
193
|
+
axm: Schema.optional(Schema.Unknown),
|
|
194
|
+
});
|
|
195
|
+
const decodeConanDataAxm = Schema.decodeUnknownResult(ConanDataAxmSchema);
|
|
196
|
+
/**
|
|
197
|
+
* Resolve the Conan cache directory.
|
|
198
|
+
* Checks CONAN_USER_HOME, then defaults to ~/.conan2.
|
|
199
|
+
*/
|
|
200
|
+
const resolveConanCache = () => Effect.sync(() => readEnv("CONAN_USER_HOME") ?? `${os.homedir()}/.conan2`);
|
|
201
|
+
/**
|
|
202
|
+
* Conan package reader.
|
|
203
|
+
*
|
|
204
|
+
* Reads axm metadata from `conandata.yml` in the Conan cache or
|
|
205
|
+
* `extension_properties` for each detected Conan package.
|
|
206
|
+
*
|
|
207
|
+
* @experimental This API is unstable and may change without notice.
|
|
208
|
+
*/
|
|
209
|
+
export const conanReader = {
|
|
210
|
+
type: conanType,
|
|
211
|
+
read: Effect.fn("read.conan")(function* (pkg) {
|
|
212
|
+
const path = yield* Path.Path;
|
|
213
|
+
const conanCache = yield* resolveConanCache();
|
|
214
|
+
// Try conandata.yml in the package's cache directory
|
|
215
|
+
const version = pkg.purl.version ?? "0.0.0";
|
|
216
|
+
const conanDataPath = path.join(conanCache, "p", pkg.purl.name, version, "export", "conandata.yml");
|
|
217
|
+
const content = yield* readFileOptional(conanDataPath);
|
|
218
|
+
if (Option.isNone(content))
|
|
219
|
+
return Option.none();
|
|
220
|
+
const parsed = yield* parseYamlOptional(content.value, `${pkg.purl.name}/conandata.yml`);
|
|
221
|
+
if (Option.isNone(parsed))
|
|
222
|
+
return Option.none();
|
|
223
|
+
// Extract axm metadata from conandata.yml
|
|
224
|
+
const containerResult = decodeConanDataAxm(parsed.value);
|
|
225
|
+
if (Result.isFailure(containerResult))
|
|
226
|
+
return Option.none();
|
|
227
|
+
const axmRaw = containerResult.success.axm;
|
|
228
|
+
if (axmRaw === undefined)
|
|
229
|
+
return Option.none();
|
|
230
|
+
const metaResult = decodeAxmMeta(axmRaw);
|
|
231
|
+
if (Result.isFailure(metaResult)) {
|
|
232
|
+
yield* Effect.logWarning(`Invalid axm metadata in ${pkg.purl.name}: schema validation failed`);
|
|
233
|
+
return Option.none();
|
|
234
|
+
}
|
|
235
|
+
return Option.some(metaResult.success.extensions);
|
|
236
|
+
}, Effect.annotateLogs({ reader: "conan" }), Effect.withSpan("read.conan")),
|
|
237
|
+
};
|
|
238
|
+
//# sourceMappingURL=conan.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conda 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
|
+
* Conda package detector.
|
|
10
|
+
*
|
|
11
|
+
* Scans `environment.yml` and `meta.yaml` in the project directory
|
|
12
|
+
* for conda dependencies.
|
|
13
|
+
*
|
|
14
|
+
* @experimental This API is unstable and may change without notice.
|
|
15
|
+
*/
|
|
16
|
+
export declare const condaDetector: PackageDetector;
|
|
17
|
+
/**
|
|
18
|
+
* Conda package reader.
|
|
19
|
+
*
|
|
20
|
+
* Reads `$CONDA_PREFIX/share/axm/<package>/axm.json` as primary source,
|
|
21
|
+
* falling back to package cache `info/about.json` with `extra.axm` key.
|
|
22
|
+
*
|
|
23
|
+
* @experimental This API is unstable and may change without notice.
|
|
24
|
+
*/
|
|
25
|
+
export declare const condaReader: PackageReader;
|
|
26
|
+
//# sourceMappingURL=conda.d.ts.map
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conda 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 * as Effect from "effect/Effect";
|
|
8
|
+
import * as FileSystem from "effect/FileSystem";
|
|
9
|
+
import * as Option from "effect/Option";
|
|
10
|
+
import * as Path from "effect/Path";
|
|
11
|
+
import * as Result from "effect/Result";
|
|
12
|
+
import * as Schema from "effect/Schema";
|
|
13
|
+
import { PackageURL } from "packageurl-js";
|
|
14
|
+
import { envOption } from "../internal/environment.js";
|
|
15
|
+
import { PackageTypeSchema } from "@agentxm/extension-model/unstable/packaging/package-type";
|
|
16
|
+
import { decodeAxmMeta, decodePurl, parseJsonOptional, readFileOptional } from "./reader-io.js";
|
|
17
|
+
const condaType = Schema.decodeUnknownSync(PackageTypeSchema)("conda");
|
|
18
|
+
const pypiType = Schema.decodeUnknownSync(PackageTypeSchema)("pypi");
|
|
19
|
+
/**
|
|
20
|
+
* Parse a conda dependency string like "numpy=1.24.0=py311h..." or "numpy >=1.24".
|
|
21
|
+
*
|
|
22
|
+
* Returns name, version (exact only), and optional channel.
|
|
23
|
+
*/
|
|
24
|
+
const parseCondaDep = (dep) => {
|
|
25
|
+
const trimmed = dep.trim();
|
|
26
|
+
if (trimmed.length === 0)
|
|
27
|
+
return undefined;
|
|
28
|
+
// Check for channel prefix: conda-forge::numpy=1.24.0
|
|
29
|
+
let channel;
|
|
30
|
+
let rest;
|
|
31
|
+
const channelIdx = trimmed.indexOf("::");
|
|
32
|
+
if (channelIdx > 0) {
|
|
33
|
+
channel = trimmed.slice(0, channelIdx);
|
|
34
|
+
rest = trimmed.slice(channelIdx + 2);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
channel = undefined;
|
|
38
|
+
rest = trimmed;
|
|
39
|
+
}
|
|
40
|
+
// Check for range operators - produce versionless purl
|
|
41
|
+
if (rest.includes(">=") ||
|
|
42
|
+
rest.includes("<=") ||
|
|
43
|
+
rest.includes(">") ||
|
|
44
|
+
rest.includes("<") ||
|
|
45
|
+
rest.includes("!=")) {
|
|
46
|
+
// Extract just the name
|
|
47
|
+
const name = rest.split(/[><=!]/)[0]?.trim();
|
|
48
|
+
if (name === undefined || name.length === 0)
|
|
49
|
+
return undefined;
|
|
50
|
+
return { name, version: undefined, channel };
|
|
51
|
+
}
|
|
52
|
+
// Parse name=version=build or name=version or name
|
|
53
|
+
const parts = rest.split("=");
|
|
54
|
+
const name = parts[0]?.trim();
|
|
55
|
+
if (name === undefined || name.length === 0)
|
|
56
|
+
return undefined;
|
|
57
|
+
const version = parts[1]?.trim();
|
|
58
|
+
if (version === undefined || version.length === 0) {
|
|
59
|
+
return { name, version: undefined, channel };
|
|
60
|
+
}
|
|
61
|
+
return { name, version, channel };
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Parse a pip dependency string like "requests==2.31.0" or "flask>=2.0".
|
|
65
|
+
*
|
|
66
|
+
* Returns name and version (exact == only).
|
|
67
|
+
*/
|
|
68
|
+
const parsePipDep = (dep) => {
|
|
69
|
+
const trimmed = dep.trim();
|
|
70
|
+
if (trimmed.length === 0)
|
|
71
|
+
return undefined;
|
|
72
|
+
// Check for exact version pin: ==
|
|
73
|
+
const eqIdx = trimmed.indexOf("==");
|
|
74
|
+
if (eqIdx > 0) {
|
|
75
|
+
const name = trimmed.slice(0, eqIdx).trim();
|
|
76
|
+
const version = trimmed.slice(eqIdx + 2).trim();
|
|
77
|
+
if (name.length === 0)
|
|
78
|
+
return undefined;
|
|
79
|
+
// Normalize Python package name: lowercase, replace _ and . with -
|
|
80
|
+
const normalizedName = name.toLowerCase().replace(/[_.]/g, "-");
|
|
81
|
+
return { name: normalizedName, version: version.length > 0 ? version : undefined };
|
|
82
|
+
}
|
|
83
|
+
// Range specifiers: >=, <=, !=, ~=, >, <
|
|
84
|
+
const rangeMatch = /^([a-zA-Z0-9._-]+)/.exec(trimmed);
|
|
85
|
+
if (rangeMatch !== null && rangeMatch[1] !== undefined) {
|
|
86
|
+
const normalizedName = rangeMatch[1].toLowerCase().replace(/[_.]/g, "-");
|
|
87
|
+
return { name: normalizedName, version: undefined };
|
|
88
|
+
}
|
|
89
|
+
return undefined;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Parse environment.yml content using line-based parsing.
|
|
93
|
+
* Extracts conda dependencies and pip sub-list items.
|
|
94
|
+
*/
|
|
95
|
+
const parseEnvironmentYml = (content, source) => {
|
|
96
|
+
const lines = content.split("\n");
|
|
97
|
+
const results = [];
|
|
98
|
+
let inDependencies = false;
|
|
99
|
+
let inPipSection = false;
|
|
100
|
+
for (const line of lines) {
|
|
101
|
+
const trimmed = line.trim();
|
|
102
|
+
if (trimmed.startsWith("#"))
|
|
103
|
+
continue;
|
|
104
|
+
const indent = line.length - line.trimStart().length;
|
|
105
|
+
// Top-level section detection
|
|
106
|
+
if (indent === 0 && trimmed.length > 0) {
|
|
107
|
+
if (trimmed === "dependencies:") {
|
|
108
|
+
inDependencies = true;
|
|
109
|
+
inPipSection = false;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (trimmed.endsWith(":") || trimmed.includes(":")) {
|
|
113
|
+
inDependencies = false;
|
|
114
|
+
inPipSection = false;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (!inDependencies)
|
|
119
|
+
continue;
|
|
120
|
+
if (trimmed.length === 0)
|
|
121
|
+
continue;
|
|
122
|
+
// Detect pip: sub-section
|
|
123
|
+
if (trimmed === "- pip:" || trimmed === "pip:") {
|
|
124
|
+
inPipSection = true;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
// pip sub-list items are more deeply indented
|
|
128
|
+
if (inPipSection) {
|
|
129
|
+
if (trimmed.startsWith("- ")) {
|
|
130
|
+
const dep = trimmed.slice(2).trim();
|
|
131
|
+
const parsed = parsePipDep(dep);
|
|
132
|
+
if (parsed !== undefined) {
|
|
133
|
+
const purl = new PackageURL("pypi", null, parsed.name, parsed.version ?? null, null, null);
|
|
134
|
+
const purlParts = decodePurl(purl.toString());
|
|
135
|
+
results.push({ purl: purlParts, type: pypiType, source });
|
|
136
|
+
}
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
// Non-list item after pip section means pip section ended
|
|
140
|
+
if (!trimmed.startsWith("-")) {
|
|
141
|
+
inPipSection = false;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Regular conda dependency
|
|
145
|
+
if (!inPipSection && trimmed.startsWith("- ")) {
|
|
146
|
+
const dep = trimmed.slice(2).trim();
|
|
147
|
+
// Skip if it's a map item (pip: or other structured entries)
|
|
148
|
+
if (dep.endsWith(":") || dep.includes(": "))
|
|
149
|
+
continue;
|
|
150
|
+
const parsed = parseCondaDep(dep);
|
|
151
|
+
if (parsed !== undefined) {
|
|
152
|
+
const qualifiers = parsed.channel !== undefined ? { channel: parsed.channel } : null;
|
|
153
|
+
const purl = new PackageURL("conda", null, parsed.name, parsed.version ?? null, qualifiers, null);
|
|
154
|
+
const purlParts = decodePurl(purl.toString());
|
|
155
|
+
results.push({ purl: purlParts, type: condaType, source });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return results;
|
|
160
|
+
};
|
|
161
|
+
/**
|
|
162
|
+
* Parse meta.yaml content using line-based parsing.
|
|
163
|
+
* Extracts dependencies from `requirements.host` and `requirements.run` lists.
|
|
164
|
+
*/
|
|
165
|
+
const parseMetaYaml = (content, source) => {
|
|
166
|
+
const lines = content.split("\n");
|
|
167
|
+
const results = [];
|
|
168
|
+
let inRequirements = false;
|
|
169
|
+
let inSubSection = false;
|
|
170
|
+
for (const line of lines) {
|
|
171
|
+
const trimmed = line.trim();
|
|
172
|
+
if (trimmed.startsWith("#"))
|
|
173
|
+
continue;
|
|
174
|
+
const indent = line.length - line.trimStart().length;
|
|
175
|
+
// Top-level section detection
|
|
176
|
+
if (indent === 0 && trimmed.length > 0) {
|
|
177
|
+
if (trimmed === "requirements:") {
|
|
178
|
+
inRequirements = true;
|
|
179
|
+
inSubSection = false;
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (trimmed.endsWith(":") || trimmed.includes(":")) {
|
|
183
|
+
inRequirements = false;
|
|
184
|
+
inSubSection = false;
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (!inRequirements)
|
|
189
|
+
continue;
|
|
190
|
+
if (trimmed.length === 0)
|
|
191
|
+
continue;
|
|
192
|
+
// Detect host: or run: sub-sections
|
|
193
|
+
if ((trimmed === "host:" || trimmed === "run:") && indent > 0) {
|
|
194
|
+
inSubSection = true;
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
// Other sub-sections like build:
|
|
198
|
+
if (trimmed.endsWith(":") && indent > 0 && !trimmed.startsWith("-")) {
|
|
199
|
+
if (trimmed === "build:") {
|
|
200
|
+
inSubSection = false;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
inSubSection = true;
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
// List items in host: or run: sections
|
|
207
|
+
if (inSubSection && trimmed.startsWith("- ")) {
|
|
208
|
+
const dep = trimmed.slice(2).trim();
|
|
209
|
+
const parsed = parseCondaDep(dep);
|
|
210
|
+
if (parsed !== undefined) {
|
|
211
|
+
const purl = new PackageURL("conda", null, parsed.name, parsed.version ?? null, null, null);
|
|
212
|
+
const purlParts = decodePurl(purl.toString());
|
|
213
|
+
results.push({ purl: purlParts, type: condaType, source });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return results;
|
|
218
|
+
};
|
|
219
|
+
/**
|
|
220
|
+
* Conda package detector.
|
|
221
|
+
*
|
|
222
|
+
* Scans `environment.yml` and `meta.yaml` in the project directory
|
|
223
|
+
* for conda dependencies.
|
|
224
|
+
*
|
|
225
|
+
* @experimental This API is unstable and may change without notice.
|
|
226
|
+
*/
|
|
227
|
+
export const condaDetector = {
|
|
228
|
+
type: condaType,
|
|
229
|
+
detect: Effect.fn("detect.conda")(function* (projectDir) {
|
|
230
|
+
const path = yield* Path.Path;
|
|
231
|
+
const results = [];
|
|
232
|
+
// Parse environment.yml
|
|
233
|
+
const envYmlPath = path.join(projectDir, "environment.yml");
|
|
234
|
+
const envYmlContent = yield* readFileOptional(envYmlPath);
|
|
235
|
+
if (Option.isSome(envYmlContent)) {
|
|
236
|
+
const deps = parseEnvironmentYml(envYmlContent.value, envYmlPath);
|
|
237
|
+
results.push(...deps);
|
|
238
|
+
}
|
|
239
|
+
// Parse meta.yaml
|
|
240
|
+
const metaYamlPath = path.join(projectDir, "meta.yaml");
|
|
241
|
+
const metaYamlContent = yield* readFileOptional(metaYamlPath);
|
|
242
|
+
if (Option.isSome(metaYamlContent)) {
|
|
243
|
+
const deps = parseMetaYaml(metaYamlContent.value, metaYamlPath);
|
|
244
|
+
results.push(...deps);
|
|
245
|
+
}
|
|
246
|
+
return results;
|
|
247
|
+
}, Effect.annotateLogs({ detector: "conda" }), Effect.withSpan("detect.conda")),
|
|
248
|
+
};
|
|
249
|
+
/**
|
|
250
|
+
* Schema to extract the optional extra.axm field from about.json.
|
|
251
|
+
*/
|
|
252
|
+
const AboutJsonSchema = Schema.Struct({
|
|
253
|
+
extra: Schema.optional(Schema.Struct({
|
|
254
|
+
axm: Schema.optional(Schema.Unknown),
|
|
255
|
+
})),
|
|
256
|
+
});
|
|
257
|
+
const decodeAboutJson = Schema.decodeUnknownResult(AboutJsonSchema);
|
|
258
|
+
/**
|
|
259
|
+
* Conda package reader.
|
|
260
|
+
*
|
|
261
|
+
* Reads `$CONDA_PREFIX/share/axm/<package>/axm.json` as primary source,
|
|
262
|
+
* falling back to package cache `info/about.json` with `extra.axm` key.
|
|
263
|
+
*
|
|
264
|
+
* @experimental This API is unstable and may change without notice.
|
|
265
|
+
*/
|
|
266
|
+
export const condaReader = {
|
|
267
|
+
type: condaType,
|
|
268
|
+
read: Effect.fn("read.conda")(function* (pkg) {
|
|
269
|
+
const path = yield* Path.Path;
|
|
270
|
+
const condaPrefix = yield* envOption("CONDA_PREFIX");
|
|
271
|
+
if (Option.isNone(condaPrefix))
|
|
272
|
+
return Option.none();
|
|
273
|
+
const pkgName = pkg.purl.name;
|
|
274
|
+
// Primary: $CONDA_PREFIX/share/axm/<package>/axm.json
|
|
275
|
+
const axmJsonPath = path.join(condaPrefix.value, "share", "axm", pkgName, "axm.json");
|
|
276
|
+
const primaryContent = yield* readFileOptional(axmJsonPath);
|
|
277
|
+
if (Option.isSome(primaryContent)) {
|
|
278
|
+
const parsed = yield* parseJsonOptional(primaryContent.value, `conda share/axm/${pkgName}/axm.json`);
|
|
279
|
+
if (Option.isSome(parsed)) {
|
|
280
|
+
const metaResult = decodeAxmMeta(parsed.value);
|
|
281
|
+
if (Result.isFailure(metaResult)) {
|
|
282
|
+
yield* Effect.logWarning(`Invalid axm metadata in conda ${pkgName}: schema validation failed`);
|
|
283
|
+
return Option.none();
|
|
284
|
+
}
|
|
285
|
+
return Option.some(metaResult.success.extensions);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
// Fallback: $CONDA_PREFIX/pkgs/<package>*/info/about.json
|
|
289
|
+
const fs = yield* FileSystem.FileSystem;
|
|
290
|
+
const pkgsDir = path.join(condaPrefix.value, "pkgs");
|
|
291
|
+
const pkgsDirEntries = yield* fs.readDirectory(pkgsDir).pipe(Effect.option);
|
|
292
|
+
if (Option.isSome(pkgsDirEntries)) {
|
|
293
|
+
// Find the first matching package directory
|
|
294
|
+
const matchingDir = pkgsDirEntries.value.find((entry) => entry.startsWith(`${pkgName}-`));
|
|
295
|
+
if (matchingDir !== undefined) {
|
|
296
|
+
const aboutJsonPath = path.join(pkgsDir, matchingDir, "info", "about.json");
|
|
297
|
+
const aboutContent = yield* readFileOptional(aboutJsonPath);
|
|
298
|
+
if (Option.isSome(aboutContent)) {
|
|
299
|
+
const parsed = yield* parseJsonOptional(aboutContent.value, `conda ${matchingDir}/info/about.json`);
|
|
300
|
+
if (Option.isSome(parsed)) {
|
|
301
|
+
const aboutResult = decodeAboutJson(parsed.value);
|
|
302
|
+
if (Result.isSuccess(aboutResult)) {
|
|
303
|
+
const axmRaw = aboutResult.success.extra?.axm;
|
|
304
|
+
if (axmRaw !== undefined) {
|
|
305
|
+
const metaResult = decodeAxmMeta(axmRaw);
|
|
306
|
+
if (Result.isFailure(metaResult)) {
|
|
307
|
+
yield* Effect.logWarning(`Invalid axm metadata in conda cache ${pkgName}: schema validation failed`);
|
|
308
|
+
return Option.none();
|
|
309
|
+
}
|
|
310
|
+
return Option.some(metaResult.success.extensions);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return Option.none();
|
|
318
|
+
}, Effect.annotateLogs({ reader: "conda" }), Effect.withSpan("read.conda")),
|
|
319
|
+
};
|
|
320
|
+
//# sourceMappingURL=conda.js.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CPAN (Perl) package detector and reader for package-compatibility discovery.
|
|
3
|
+
*
|
|
4
|
+
* Parses `cpanfile` (`requires 'Name'` lines) and `Makefile.PL` (`PREREQ_PM`).
|
|
5
|
+
* Reads `x_axm` from `<lib-path>/.meta/<dist>/MYMETA.json`.
|
|
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
|
+
* CPAN package detector.
|
|
13
|
+
*
|
|
14
|
+
* Scans `cpanfile` and `Makefile.PL` in the project directory and extracts
|
|
15
|
+
* dependencies.
|
|
16
|
+
*
|
|
17
|
+
* @experimental This API is unstable and may change without notice.
|
|
18
|
+
*/
|
|
19
|
+
export declare const cpanDetector: PackageDetector;
|
|
20
|
+
/**
|
|
21
|
+
* CPAN package reader.
|
|
22
|
+
*
|
|
23
|
+
* Reads `x_axm` from `<lib-path>/.meta/<dist>/MYMETA.json` for each
|
|
24
|
+
* detected CPAN package and extracts recommendation metadata.
|
|
25
|
+
*
|
|
26
|
+
* @experimental This API is unstable and may change without notice.
|
|
27
|
+
*/
|
|
28
|
+
export declare const cpanReader: PackageReader;
|
|
29
|
+
//# sourceMappingURL=cpan.d.ts.map
|