@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.
Files changed (72) hide show
  1. package/LICENSE +110 -0
  2. package/dist/src/discover.d.ts +34 -0
  3. package/dist/src/discover.js +133 -0
  4. package/dist/src/index.d.ts +13 -0
  5. package/dist/src/index.js +12 -0
  6. package/dist/src/internal/environment.d.ts +14 -0
  7. package/dist/src/internal/environment.js +15 -0
  8. package/dist/src/packaging/bazel.d.ts +27 -0
  9. package/dist/src/packaging/bazel.js +125 -0
  10. package/dist/src/packaging/cargo.d.ts +32 -0
  11. package/dist/src/packaging/cargo.js +270 -0
  12. package/dist/src/packaging/cocoapods.d.ts +26 -0
  13. package/dist/src/packaging/cocoapods.js +187 -0
  14. package/dist/src/packaging/composer.d.ts +26 -0
  15. package/dist/src/packaging/composer.js +144 -0
  16. package/dist/src/packaging/conan.d.ts +30 -0
  17. package/dist/src/packaging/conan.js +238 -0
  18. package/dist/src/packaging/conda.d.ts +26 -0
  19. package/dist/src/packaging/conda.js +320 -0
  20. package/dist/src/packaging/cpan.d.ts +29 -0
  21. package/dist/src/packaging/cpan.js +189 -0
  22. package/dist/src/packaging/cran.d.ts +29 -0
  23. package/dist/src/packaging/cran.js +176 -0
  24. package/dist/src/packaging/detect.d.ts +16 -0
  25. package/dist/src/packaging/detect.js +25 -0
  26. package/dist/src/packaging/detected-package.d.ts +14 -0
  27. package/dist/src/packaging/detected-package.js +20 -0
  28. package/dist/src/packaging/docker.d.ts +27 -0
  29. package/dist/src/packaging/docker.js +256 -0
  30. package/dist/src/packaging/gem.d.ts +26 -0
  31. package/dist/src/packaging/gem.js +234 -0
  32. package/dist/src/packaging/golang.d.ts +26 -0
  33. package/dist/src/packaging/golang.js +169 -0
  34. package/dist/src/packaging/hackage.d.ts +26 -0
  35. package/dist/src/packaging/hackage.js +305 -0
  36. package/dist/src/packaging/hex.d.ts +28 -0
  37. package/dist/src/packaging/hex.js +186 -0
  38. package/dist/src/packaging/huggingface.d.ts +20 -0
  39. package/dist/src/packaging/huggingface.js +112 -0
  40. package/dist/src/packaging/index.d.ts +44 -0
  41. package/dist/src/packaging/index.js +120 -0
  42. package/dist/src/packaging/jsr.d.ts +30 -0
  43. package/dist/src/packaging/jsr.js +227 -0
  44. package/dist/src/packaging/julia.d.ts +27 -0
  45. package/dist/src/packaging/julia.js +165 -0
  46. package/dist/src/packaging/luarocks.d.ts +28 -0
  47. package/dist/src/packaging/luarocks.js +159 -0
  48. package/dist/src/packaging/maven.d.ts +27 -0
  49. package/dist/src/packaging/maven.js +471 -0
  50. package/dist/src/packaging/mojo.d.ts +29 -0
  51. package/dist/src/packaging/mojo.js +148 -0
  52. package/dist/src/packaging/npm.d.ts +26 -0
  53. package/dist/src/packaging/npm.js +190 -0
  54. package/dist/src/packaging/nuget.d.ts +26 -0
  55. package/dist/src/packaging/nuget.js +240 -0
  56. package/dist/src/packaging/opam.d.ts +27 -0
  57. package/dist/src/packaging/opam.js +287 -0
  58. package/dist/src/packaging/pub.d.ts +26 -0
  59. package/dist/src/packaging/pub.js +357 -0
  60. package/dist/src/packaging/pypi.d.ts +23 -0
  61. package/dist/src/packaging/pypi.js +448 -0
  62. package/dist/src/packaging/read.d.ts +20 -0
  63. package/dist/src/packaging/read.js +31 -0
  64. package/dist/src/packaging/reader-io.d.ts +29 -0
  65. package/dist/src/packaging/reader-io.js +29 -0
  66. package/dist/src/packaging/swift.d.ts +26 -0
  67. package/dist/src/packaging/swift.js +141 -0
  68. package/dist/src/packaging/types.d.ts +43 -0
  69. package/dist/src/packaging/types.js +8 -0
  70. package/dist/src/packaging/zig.d.ts +32 -0
  71. package/dist/src/packaging/zig.js +151 -0
  72. 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