@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,270 @@
1
+ /**
2
+ * Cargo (Rust) 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 { parseTomlValue } from "@agentxm/extension-workspace";
17
+ import { envWithDefault } from "../internal/environment.js";
18
+ import { PackageTypeSchema } from "@agentxm/extension-model/unstable/packaging/package-type";
19
+ import { decodeAxmMeta, decodePurl, readFileOptional } from "./reader-io.js";
20
+ const cargoType = Schema.decodeUnknownSync(PackageTypeSchema)("cargo");
21
+ /**
22
+ * Returns true if the version specifier is an exact pin (starts with `=`).
23
+ * In Cargo, `=1.0.193` means exact; bare `1.0` is a caret range.
24
+ */
25
+ const isExactVersion = (specifier) => specifier.startsWith("=");
26
+ /**
27
+ * Strip leading `=` from an exact version pin.
28
+ */
29
+ const stripExactPrefix = (specifier) => specifier.startsWith("=") ? specifier.slice(1) : specifier;
30
+ /** Regex to detect section headers like [dependencies] or [build-dependencies]. */
31
+ const SECTION_RE = /^\[([^\]]+)\]/;
32
+ /** Dependency section names we care about. */
33
+ const DEP_SECTIONS = new Set(["dependencies", "dev-dependencies", "build-dependencies"]);
34
+ /**
35
+ * Parse a dependency value (the right-hand side of `name = ...`).
36
+ * Handles:
37
+ * - Shorthand string: `"1.0"` or `"=1.0.193"`
38
+ * - Inline table: `{ version = "1.0", features = ["derive"] }`
39
+ */
40
+ const parseDependencyValue = (name, value) => {
41
+ const trimmed = value.trim();
42
+ // Shorthand string syntax: name = "1.0"
43
+ if (trimmed.startsWith('"') || trimmed.startsWith("'")) {
44
+ const inner = trimmed.slice(1, -1);
45
+ return { name, version: inner, isPathOrGit: false, packageName: undefined };
46
+ }
47
+ // Inline table syntax: name = { version = "1.0", ... }
48
+ if (trimmed.startsWith("{")) {
49
+ const hasPath = /\bpath\s*=/.test(trimmed);
50
+ const hasGit = /\bgit\s*=/.test(trimmed);
51
+ if (hasPath || hasGit) {
52
+ return { name, version: undefined, isPathOrGit: true, packageName: undefined };
53
+ }
54
+ // Extract version
55
+ const versionMatch = /\bversion\s*=\s*"([^"]*)"/.exec(trimmed);
56
+ const version = versionMatch?.[1];
57
+ // Extract package rename
58
+ const packageMatch = /\bpackage\s*=\s*"([^"]*)"/.exec(trimmed);
59
+ const packageName = packageMatch?.[1];
60
+ return { name, version, isPathOrGit: false, packageName };
61
+ }
62
+ // Unknown format, skip
63
+ return { name, version: undefined, isPathOrGit: false, packageName: undefined };
64
+ };
65
+ /**
66
+ * Parse Cargo.toml content and extract dependencies.
67
+ */
68
+ const parseCargoToml = (content, source) => {
69
+ const lines = content.split("\n");
70
+ const results = [];
71
+ let currentSection = "";
72
+ for (const line of lines) {
73
+ const trimmed = line.trim();
74
+ // Skip empty lines and comments
75
+ if (trimmed === "" || trimmed.startsWith("#"))
76
+ continue;
77
+ // Check for section header
78
+ const sectionMatch = SECTION_RE.exec(trimmed);
79
+ if (sectionMatch !== undefined && sectionMatch !== null && sectionMatch[1] !== undefined) {
80
+ currentSection = sectionMatch[1];
81
+ continue;
82
+ }
83
+ // Only process lines in dependency sections
84
+ if (!DEP_SECTIONS.has(currentSection))
85
+ continue;
86
+ // Parse key = value
87
+ const eqIdx = trimmed.indexOf("=");
88
+ if (eqIdx === -1)
89
+ continue;
90
+ const depName = trimmed.slice(0, eqIdx).trim();
91
+ const depValue = trimmed.slice(eqIdx + 1).trim();
92
+ if (depName === "" || depValue === "")
93
+ continue;
94
+ const dep = parseDependencyValue(depName, depValue);
95
+ // Skip path and git dependencies
96
+ if (dep.isPathOrGit)
97
+ continue;
98
+ // Use the real package name if renamed
99
+ const resolvedName = dep.packageName ?? dep.name;
100
+ // Determine version: only exact pins get a version in the purl
101
+ const version = dep.version !== undefined && isExactVersion(dep.version)
102
+ ? stripExactPrefix(dep.version)
103
+ : undefined;
104
+ const purl = new PackageURL("cargo", null, resolvedName, version ?? null, null, null);
105
+ const purlParts = decodePurl(purl.toString());
106
+ results.push({ purl: purlParts, type: cargoType, source });
107
+ }
108
+ return results;
109
+ };
110
+ /**
111
+ * Cargo package detector.
112
+ *
113
+ * Scans `Cargo.toml` in the project directory and extracts dependencies
114
+ * from `[dependencies]`, `[dev-dependencies]`, and `[build-dependencies]`.
115
+ *
116
+ * @experimental This API is unstable and may change without notice.
117
+ */
118
+ export const cargoDetector = {
119
+ type: cargoType,
120
+ detect: Effect.fn("detect.cargo")(function* (projectDir) {
121
+ const path = yield* Path.Path;
122
+ const cargoTomlPath = path.join(projectDir, "Cargo.toml");
123
+ const content = yield* readFileOptional(cargoTomlPath);
124
+ if (Option.isNone(content))
125
+ return [];
126
+ // Validate that it looks like TOML (basic check)
127
+ const trimmed = content.value.trim();
128
+ if (trimmed.length > 0 && !trimmed.includes("=") && !trimmed.includes("[")) {
129
+ yield* Effect.logWarning("Malformed Cargo.toml, skipping");
130
+ return [];
131
+ }
132
+ return parseCargoToml(content.value, cargoTomlPath);
133
+ }, Effect.annotateLogs({ detector: "cargo" }), Effect.withSpan("detect.cargo")),
134
+ };
135
+ /**
136
+ * Resolve the CARGO_HOME, defaulting to ~/.cargo when not set.
137
+ */
138
+ const resolveCargoHome = () => envWithDefault("CARGO_HOME", `${os.homedir()}/.cargo`);
139
+ /**
140
+ * Parse the `[package.metadata.axm]` table from a Cargo.toml string.
141
+ *
142
+ * Returns `undefined` when the section is absent. `Cargo.toml` is TOML; we
143
+ * scan section headers line-by-line rather than depending on a full TOML
144
+ * parser, mirroring `julia.ts`/`parseAxmSection`. Supported forms:
145
+ *
146
+ * [package.metadata.axm]
147
+ * extensions = [{ ref = "@owner/packs/example", versionRange = "^1.0.0" }]
148
+ *
149
+ * [[package.metadata.axm.extensions]]
150
+ * ref = "@owner/packs/example"
151
+ * versionRange = "^1.0.0"
152
+ */
153
+ const parsePackageMetadataAxm = (content) => {
154
+ const lines = content.split("\n");
155
+ let inAxmSection = false;
156
+ let found = false;
157
+ const fields = {};
158
+ const extensionEntries = [];
159
+ let currentTable;
160
+ for (const line of lines) {
161
+ const trimmed = line.trim();
162
+ // Skip empty lines and comments
163
+ if (trimmed === "" || trimmed.startsWith("#"))
164
+ continue;
165
+ // Section headers (`[section.path]` or `[[array.of.tables]]`)
166
+ if (trimmed.startsWith("[")) {
167
+ const arrayTableMatch = /^\[\[package\.metadata\.axm\.([A-Za-z0-9_-]+)\]\]$/.exec(trimmed);
168
+ const arrayTableKey = arrayTableMatch?.[1];
169
+ if (arrayTableKey !== undefined) {
170
+ found = true;
171
+ inAxmSection = false;
172
+ if (arrayTableKey === "extensions") {
173
+ const entry = {};
174
+ extensionEntries.push(entry);
175
+ fields["extensions"] = extensionEntries;
176
+ currentTable = entry;
177
+ }
178
+ else {
179
+ currentTable = undefined;
180
+ }
181
+ continue;
182
+ }
183
+ const axmTableMatch = /^\[package\.metadata\.axm(?:\.([A-Za-z0-9_-]+))?\]$/.exec(trimmed);
184
+ if (axmTableMatch !== null) {
185
+ found = true;
186
+ inAxmSection = axmTableMatch[1] === undefined;
187
+ currentTable = undefined;
188
+ continue;
189
+ }
190
+ inAxmSection = false;
191
+ currentTable = undefined;
192
+ continue;
193
+ }
194
+ const target = currentTable ?? (inAxmSection ? fields : undefined);
195
+ if (target === undefined)
196
+ continue;
197
+ const match = /^([^=\s]+)\s*=\s*(.+)$/.exec(trimmed);
198
+ if (match === null)
199
+ continue;
200
+ const key = match[1];
201
+ const rawValue = match[2]?.trim();
202
+ if (key === undefined || rawValue === undefined)
203
+ continue;
204
+ target[key] = parseTomlValue(rawValue);
205
+ }
206
+ return found ? fields : undefined;
207
+ };
208
+ /**
209
+ * Cargo package reader.
210
+ *
211
+ * Reads `[package.metadata.axm]` from
212
+ * `$CARGO_HOME/registry/src/<index>/<crate>-<version>/Cargo.toml` for each
213
+ * detected cargo crate. `[package.metadata.*]` is Cargo's standard
214
+ * extensibility mechanism for third-party tools (used by docs.rs, cargo-deb,
215
+ * cargo-bundle, etc.).
216
+ *
217
+ * When the crate version is unknown, scans the registry source directory
218
+ * for any matching crate directory.
219
+ *
220
+ * @experimental This API is unstable and may change without notice.
221
+ */
222
+ export const cargoReader = {
223
+ type: cargoType,
224
+ read: Effect.fn("read.cargo")(function* (pkg) {
225
+ const fs = yield* FileSystem.FileSystem;
226
+ const path = yield* Path.Path;
227
+ const cargoHome = yield* resolveCargoHome();
228
+ const crateName = pkg.purl.name;
229
+ const version = pkg.purl.version;
230
+ const registrySrcDir = path.join(cargoHome, "registry", "src");
231
+ const indexDirs = yield* fs.readDirectory(registrySrcDir).pipe(Effect.option);
232
+ if (Option.isNone(indexDirs))
233
+ return Option.none();
234
+ for (const indexDir of indexDirs.value) {
235
+ const indexPath = path.join(registrySrcDir, indexDir);
236
+ const crateDir = version !== undefined
237
+ ? `${crateName}-${version}`
238
+ : yield* findMatchingCrateDir(fs, indexPath, crateName);
239
+ if (crateDir === undefined)
240
+ continue;
241
+ const cargoTomlPath = path.join(indexPath, crateDir, "Cargo.toml");
242
+ const content = yield* readFileOptional(cargoTomlPath);
243
+ if (Option.isNone(content)) {
244
+ if (version !== undefined)
245
+ return Option.none();
246
+ continue;
247
+ }
248
+ const axmFields = parsePackageMetadataAxm(content.value);
249
+ if (axmFields === undefined) {
250
+ if (version !== undefined)
251
+ return Option.none();
252
+ continue;
253
+ }
254
+ const metaResult = decodeAxmMeta(axmFields);
255
+ if (Result.isFailure(metaResult)) {
256
+ yield* Effect.logWarning(`Invalid axm metadata in ${crateDir}/Cargo.toml: schema validation failed`);
257
+ return Option.none();
258
+ }
259
+ return Option.some(metaResult.success.extensions);
260
+ }
261
+ return Option.none();
262
+ }, Effect.annotateLogs({ reader: "cargo" }), Effect.withSpan("read.cargo")),
263
+ };
264
+ const findMatchingCrateDir = (fs, indexPath, crateName) => Effect.gen(function* () {
265
+ const entries = yield* fs.readDirectory(indexPath).pipe(Effect.option);
266
+ if (Option.isNone(entries))
267
+ return undefined;
268
+ return entries.value.find((entry) => entry === crateName || entry.startsWith(`${crateName}-`));
269
+ });
270
+ //# sourceMappingURL=cargo.js.map
@@ -0,0 +1,26 @@
1
+ /**
2
+ * CocoaPods 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
+ * CocoaPods package detector.
10
+ *
11
+ * Scans `Podfile` and `*.podspec` files in the project directory
12
+ * for pod dependencies.
13
+ *
14
+ * @experimental This API is unstable and may change without notice.
15
+ */
16
+ export declare const cocoapodsDetector: PackageDetector;
17
+ /**
18
+ * CocoaPods package reader.
19
+ *
20
+ * Reads `Pods/<pod-name>/axm.json` for each detected pod
21
+ * and extracts recommendation metadata.
22
+ *
23
+ * @experimental This API is unstable and may change without notice.
24
+ */
25
+ export declare const cocoapodsReader: PackageReader;
26
+ //# sourceMappingURL=cocoapods.d.ts.map
@@ -0,0 +1,187 @@
1
+ /**
2
+ * CocoaPods 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 { PackageTypeSchema } from "@agentxm/extension-model/unstable/packaging/package-type";
15
+ import { decodeAxmMeta, decodePurl, parseJsonOptional, readFileOptional } from "./reader-io.js";
16
+ const cocoapodsType = Schema.decodeUnknownSync(PackageTypeSchema)("cocoapods");
17
+ /**
18
+ * Returns true if the specifier is an exact semver version (no range operators).
19
+ */
20
+ const isExactVersion = (specifier) => /^\d+\.\d+\.\d+(?:[-+][a-zA-Z0-9._+-]*)?$/.test(specifier);
21
+ /**
22
+ * Convert a pod name (possibly with subspec) and version to a DetectedPackage.
23
+ */
24
+ const podToPurl = (podFullName, version, source) => {
25
+ // Split on '/' to separate name from subspecs
26
+ const parts = podFullName.split("/");
27
+ const name = parts[0];
28
+ if (name === undefined || name.length === 0)
29
+ return undefined;
30
+ const subpath = parts.length > 1 ? parts.slice(1).join("/") : null;
31
+ const resolvedVersion = version !== undefined && isExactVersion(version) ? version : undefined;
32
+ const purl = new PackageURL("cocoapods", null, name, resolvedVersion ?? null, null, subpath);
33
+ const purlParts = decodePurl(purl.toString());
34
+ return { purl: purlParts, type: cocoapodsType, source };
35
+ };
36
+ /**
37
+ * Parse a Podfile and extract pod directives.
38
+ *
39
+ * Matches lines like:
40
+ * - pod 'Alamofire', '~> 5.0'
41
+ * - pod 'Alamofire', '5.6.2'
42
+ * - pod 'Alamofire'
43
+ * - pod 'ShareKit/Twitter'
44
+ * - pod 'MyPod', :path => '../MyPod' (skipped)
45
+ * - pod 'MyPod', :git => 'https://...' (skipped)
46
+ */
47
+ const parsePodfile = (content, source) => {
48
+ const lines = content.split("\n");
49
+ const results = [];
50
+ // Match: pod 'Name' or pod "Name" with optional version and options
51
+ const podPattern = /^\s*pod\s+['"]([^'"]+)['"]\s*(?:,\s*(.*))?$/;
52
+ for (const line of lines) {
53
+ const trimmed = line.trim();
54
+ if (trimmed.startsWith("#"))
55
+ continue;
56
+ const match = podPattern.exec(trimmed);
57
+ if (match === null)
58
+ continue;
59
+ const podName = match[1];
60
+ const rest = match[2];
61
+ if (podName === undefined)
62
+ continue;
63
+ // Skip :path and :git dependencies
64
+ if (rest !== undefined) {
65
+ if (rest.includes(":path") || rest.includes(":git"))
66
+ continue;
67
+ }
68
+ // Extract version from rest
69
+ let version;
70
+ if (rest !== undefined) {
71
+ // Match version string: '5.6.2' or '~> 5.0' or '>= 5.0'
72
+ const versionMatch = /^['"]([^'"]+)['"]/.exec(rest.trim());
73
+ if (versionMatch !== null && versionMatch[1] !== undefined) {
74
+ const versionStr = versionMatch[1];
75
+ // Only use exact versions, not ranges
76
+ if (isExactVersion(versionStr)) {
77
+ version = versionStr;
78
+ }
79
+ }
80
+ }
81
+ const detected = podToPurl(podName, version, source);
82
+ if (detected !== undefined)
83
+ results.push(detected);
84
+ }
85
+ return results;
86
+ };
87
+ /**
88
+ * Parse a .podspec file and extract dependency directives.
89
+ *
90
+ * Matches lines like:
91
+ * - s.dependency 'Alamofire', '~> 5.0'
92
+ * - spec.dependency 'SwiftyJSON'
93
+ * - ss.dependency 'Something'
94
+ */
95
+ const parsePodspec = (content, source) => {
96
+ const lines = content.split("\n");
97
+ const results = [];
98
+ // Match: <var>.dependency 'Name', 'version' or <var>.dependency 'Name'
99
+ const depPattern = /^\s*\w+\.dependency\s+['"]([^'"]+)['"]\s*(?:,\s*['"]([^'"]+)['"])?/;
100
+ for (const line of lines) {
101
+ const trimmed = line.trim();
102
+ if (trimmed.startsWith("#"))
103
+ continue;
104
+ const match = depPattern.exec(trimmed);
105
+ if (match === null)
106
+ continue;
107
+ const depName = match[1];
108
+ const versionStr = match[2];
109
+ if (depName === undefined)
110
+ continue;
111
+ const version = versionStr !== undefined && isExactVersion(versionStr) ? versionStr : undefined;
112
+ const detected = podToPurl(depName, version, source);
113
+ if (detected !== undefined)
114
+ results.push(detected);
115
+ }
116
+ return results;
117
+ };
118
+ /**
119
+ * CocoaPods package detector.
120
+ *
121
+ * Scans `Podfile` and `*.podspec` files in the project directory
122
+ * for pod dependencies.
123
+ *
124
+ * @experimental This API is unstable and may change without notice.
125
+ */
126
+ export const cocoapodsDetector = {
127
+ type: cocoapodsType,
128
+ detect: Effect.fn("detect.cocoapods")(function* (projectDir) {
129
+ const fs = yield* FileSystem.FileSystem;
130
+ const path = yield* Path.Path;
131
+ const results = [];
132
+ // Parse Podfile
133
+ const podfilePath = path.join(projectDir, "Podfile");
134
+ const podfileContent = yield* readFileOptional(podfilePath);
135
+ if (Option.isSome(podfileContent)) {
136
+ const deps = parsePodfile(podfileContent.value, podfilePath);
137
+ results.push(...deps);
138
+ }
139
+ // Parse *.podspec files
140
+ const dirEntries = yield* fs.readDirectory(projectDir).pipe(Effect.option);
141
+ if (Option.isSome(dirEntries)) {
142
+ const podspecFiles = dirEntries.value.filter((f) => f.endsWith(".podspec"));
143
+ for (const podspecFile of podspecFiles) {
144
+ const podspecPath = path.join(projectDir, podspecFile);
145
+ const podspecContent = yield* readFileOptional(podspecPath);
146
+ if (Option.isSome(podspecContent)) {
147
+ const deps = parsePodspec(podspecContent.value, podspecPath);
148
+ results.push(...deps);
149
+ }
150
+ }
151
+ }
152
+ return results;
153
+ }, Effect.annotateLogs({ detector: "cocoapods" }), Effect.withSpan("detect.cocoapods")),
154
+ };
155
+ /**
156
+ * CocoaPods package reader.
157
+ *
158
+ * Reads `Pods/<pod-name>/axm.json` for each detected pod
159
+ * and extracts recommendation metadata.
160
+ *
161
+ * @experimental This API is unstable and may change without notice.
162
+ */
163
+ export const cocoapodsReader = {
164
+ type: cocoapodsType,
165
+ read: Effect.fn("read.cocoapods")(function* (pkg) {
166
+ const path = yield* Path.Path;
167
+ // Derive project directory from the source manifest path
168
+ const projectDir = path.dirname(pkg.source);
169
+ // Pod name from purl
170
+ const podName = pkg.purl.name;
171
+ const axmJsonPath = path.join(projectDir, "Pods", podName, "axm.json");
172
+ const content = yield* readFileOptional(axmJsonPath);
173
+ if (Option.isNone(content))
174
+ return Option.none();
175
+ const parsed = yield* parseJsonOptional(content.value, `${podName}/axm.json`);
176
+ if (Option.isNone(parsed))
177
+ return Option.none();
178
+ // Validate axm metadata structure
179
+ const metaResult = decodeAxmMeta(parsed.value);
180
+ if (Result.isFailure(metaResult)) {
181
+ yield* Effect.logWarning(`Invalid axm metadata in ${podName}: schema validation failed`);
182
+ return Option.none();
183
+ }
184
+ return Option.some(metaResult.success.extensions);
185
+ }, Effect.annotateLogs({ reader: "cocoapods" }), Effect.withSpan("read.cocoapods")),
186
+ };
187
+ //# sourceMappingURL=cocoapods.js.map
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Composer 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
+ * Composer package detector.
10
+ *
11
+ * Scans `composer.json` in the project directory and extracts dependencies
12
+ * from `require` and `require-dev`.
13
+ *
14
+ * @experimental This API is unstable and may change without notice.
15
+ */
16
+ export declare const composerDetector: PackageDetector;
17
+ /**
18
+ * Composer package reader.
19
+ *
20
+ * Reads `vendor/<namespace>/<name>/composer.json` for each detected Composer
21
+ * package and extracts the `"extra"."axm"` field containing recommendation metadata.
22
+ *
23
+ * @experimental This API is unstable and may change without notice.
24
+ */
25
+ export declare const composerReader: PackageReader;
26
+ //# sourceMappingURL=composer.d.ts.map
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Composer 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 Option from "effect/Option";
9
+ import * as Path from "effect/Path";
10
+ import * as Result from "effect/Result";
11
+ import * as Schema from "effect/Schema";
12
+ import { makeDetectedPackage } from "./detected-package.js";
13
+ import { PackageTypeSchema } from "@agentxm/extension-model/unstable/packaging/package-type";
14
+ import { decodeAxmMeta, parseJsonOptional, readFileOptional } from "./reader-io.js";
15
+ const composerType = Schema.decodeUnknownSync(PackageTypeSchema)("composer");
16
+ /** Schema to extract the optional "extra" field from a composer.json object. */
17
+ const ExtraContainerSchema = Schema.Struct({
18
+ extra: Schema.optional(Schema.Struct({
19
+ axm: Schema.optional(Schema.Unknown),
20
+ })),
21
+ });
22
+ const decodeExtraContainer = Schema.decodeUnknownResult(ExtraContainerSchema);
23
+ /**
24
+ * Returns true if the specifier is an exact semver version (no range operators).
25
+ * Exact versions match: digits and dots only, e.g. "7.5.0", "1.0.0-beta.1".
26
+ */
27
+ const isExactVersion = (specifier) => /^\d+\.\d+\.\d+(?:[-+][a-zA-Z0-9._+-]*)?$/.test(specifier);
28
+ /** Returns true if the dependency key is a platform requirement. */
29
+ const isPlatformRequirement = (name) => name === "php" || name.startsWith("ext-");
30
+ /**
31
+ * Convert a single composer dependency entry to a DetectedPackage, or undefined if skipped.
32
+ */
33
+ const depToPurl = (depName, specifier, source) => {
34
+ if (isPlatformRequirement(depName))
35
+ return undefined;
36
+ // Composer packages must be vendor/name format
37
+ const slashIdx = depName.indexOf("/");
38
+ if (slashIdx <= 0)
39
+ return undefined;
40
+ const namespace = depName.slice(0, slashIdx).toLowerCase();
41
+ const name = depName.slice(slashIdx + 1).toLowerCase();
42
+ const version = isExactVersion(specifier) ? specifier : undefined;
43
+ return Option.getOrUndefined(makeDetectedPackage({
44
+ type: composerType,
45
+ namespace,
46
+ name,
47
+ ...(version === undefined ? {} : { version }),
48
+ source,
49
+ }));
50
+ };
51
+ /**
52
+ * Schema to loosely decode the dependency-relevant shape of composer.json.
53
+ */
54
+ const DependencySectionsSchema = Schema.Struct({
55
+ require: Schema.optional(Schema.Record(Schema.String, Schema.String)),
56
+ "require-dev": Schema.optional(Schema.Record(Schema.String, Schema.String)),
57
+ });
58
+ const decodeDependencySections = Schema.decodeUnknownResult(DependencySectionsSchema);
59
+ /**
60
+ * Extract dependencies from a parsed composer.json value.
61
+ */
62
+ const extractDeps = (manifest, source) => {
63
+ const decoded = decodeDependencySections(manifest);
64
+ if (Result.isFailure(decoded))
65
+ return [];
66
+ const sections = [decoded.success.require, decoded.success["require-dev"]];
67
+ const results = [];
68
+ for (const deps of sections) {
69
+ if (deps === undefined)
70
+ continue;
71
+ for (const [name, specifier] of Object.entries(deps)) {
72
+ const detected = depToPurl(name, specifier, source);
73
+ if (detected !== undefined)
74
+ results.push(detected);
75
+ }
76
+ }
77
+ return results;
78
+ };
79
+ /**
80
+ * Composer package detector.
81
+ *
82
+ * Scans `composer.json` in the project directory and extracts dependencies
83
+ * from `require` and `require-dev`.
84
+ *
85
+ * @experimental This API is unstable and may change without notice.
86
+ */
87
+ export const composerDetector = {
88
+ type: composerType,
89
+ detect: Effect.fn("detect.composer")(function* (projectDir) {
90
+ const path = yield* Path.Path;
91
+ const manifestPath = path.join(projectDir, "composer.json");
92
+ const content = yield* readFileOptional(manifestPath);
93
+ if (Option.isNone(content))
94
+ return [];
95
+ const parsed = yield* parseJsonOptional(content.value, "composer.json");
96
+ if (Option.isNone(parsed))
97
+ return [];
98
+ return extractDeps(parsed.value, manifestPath);
99
+ }, Effect.annotateLogs({ detector: "composer" }), Effect.withSpan("detect.composer")),
100
+ };
101
+ /**
102
+ * Composer package reader.
103
+ *
104
+ * Reads `vendor/<namespace>/<name>/composer.json` for each detected Composer
105
+ * package and extracts the `"extra"."axm"` field containing recommendation metadata.
106
+ *
107
+ * @experimental This API is unstable and may change without notice.
108
+ */
109
+ export const composerReader = {
110
+ type: composerType,
111
+ read: Effect.fn("read.composer")(function* (pkg) {
112
+ const path = yield* Path.Path;
113
+ // Derive project directory from the source manifest path
114
+ const projectDir = path.dirname(pkg.source);
115
+ // Reconstruct the package path from purl parts
116
+ const pkgPath = pkg.purl.namespace ? `${pkg.purl.namespace}/${pkg.purl.name}` : pkg.purl.name;
117
+ const composerJsonPath = path.join(projectDir, "vendor", pkgPath, "composer.json");
118
+ const content = yield* readFileOptional(composerJsonPath);
119
+ if (Option.isNone(content))
120
+ return Option.none();
121
+ const parsed = yield* parseJsonOptional(content.value, pkgPath);
122
+ if (Option.isNone(parsed))
123
+ return Option.none();
124
+ // Extract and validate the "extra"."axm" field using schema
125
+ const extraContainerResult = decodeExtraContainer(parsed.value);
126
+ if (Result.isFailure(extraContainerResult)) {
127
+ return Option.none();
128
+ }
129
+ const extra = extraContainerResult.success.extra;
130
+ if (extra === undefined)
131
+ return Option.none();
132
+ const axmRaw = extra.axm;
133
+ if (axmRaw === undefined)
134
+ return Option.none();
135
+ // Validate axm metadata structure
136
+ const metaResult = decodeAxmMeta(axmRaw);
137
+ if (Result.isFailure(metaResult)) {
138
+ yield* Effect.logWarning(`Invalid axm metadata in ${pkgPath}: schema validation failed`);
139
+ return Option.none();
140
+ }
141
+ return Option.some(metaResult.success.extensions);
142
+ }, Effect.annotateLogs({ reader: "composer" }), Effect.withSpan("read.composer")),
143
+ };
144
+ //# sourceMappingURL=composer.js.map
@@ -0,0 +1,30 @@
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
+ import type { PackageDetector, PackageReader } from "./types.js";
12
+ /**
13
+ * Conan package detector.
14
+ *
15
+ * Scans `conanfile.txt` `[requires]` section and `conanfile.py` `requires`
16
+ * attribute in the project directory.
17
+ *
18
+ * @experimental This API is unstable and may change without notice.
19
+ */
20
+ export declare const conanDetector: PackageDetector;
21
+ /**
22
+ * Conan package reader.
23
+ *
24
+ * Reads axm metadata from `conandata.yml` in the Conan cache or
25
+ * `extension_properties` for each detected Conan package.
26
+ *
27
+ * @experimental This API is unstable and may change without notice.
28
+ */
29
+ export declare const conanReader: PackageReader;
30
+ //# sourceMappingURL=conan.d.ts.map