@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,256 @@
1
+ /**
2
+ * Docker image 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 { PackageURL } from "packageurl-js";
13
+ import { PackageTypeSchema } from "@agentxm/extension-model/unstable/packaging/package-type";
14
+ import { decodeAxmMeta, decodePurl, parseJsonOptional, readFileOptional } from "./reader-io.js";
15
+ const dockerType = Schema.decodeUnknownSync(PackageTypeSchema)("docker");
16
+ /**
17
+ * Parse a Docker image reference into namespace, name, and version components.
18
+ *
19
+ * Image references can be:
20
+ * - `name` (e.g. `nginx`)
21
+ * - `name:tag` (e.g. `nginx:alpine`)
22
+ * - `org/name:tag` (e.g. `library/nginx:1.25`)
23
+ * - `registry/org/name:tag` (e.g. `ghcr.io/org/myapp:latest`)
24
+ */
25
+ const parseImageRef = (ref) => {
26
+ const trimmed = ref.trim();
27
+ if (trimmed.length === 0)
28
+ return undefined;
29
+ // Split on : to get name part and tag part
30
+ const colonIdx = trimmed.lastIndexOf(":");
31
+ let namePart;
32
+ let tag;
33
+ if (colonIdx > 0 && !trimmed.slice(colonIdx).includes("/")) {
34
+ namePart = trimmed.slice(0, colonIdx);
35
+ tag = trimmed.slice(colonIdx + 1);
36
+ }
37
+ else {
38
+ namePart = trimmed;
39
+ tag = undefined;
40
+ }
41
+ // Remove @sha256 digest if present
42
+ const digestIdx = namePart.indexOf("@");
43
+ if (digestIdx > 0) {
44
+ namePart = namePart.slice(0, digestIdx);
45
+ }
46
+ // Split namespace and name
47
+ const lastSlash = namePart.lastIndexOf("/");
48
+ let namespace;
49
+ let name;
50
+ if (lastSlash > 0) {
51
+ namespace = namePart.slice(0, lastSlash);
52
+ name = namePart.slice(lastSlash + 1);
53
+ }
54
+ else {
55
+ namespace = undefined;
56
+ name = namePart;
57
+ }
58
+ if (name.length === 0)
59
+ return undefined;
60
+ // `latest` tag or no tag → versionless
61
+ const version = tag !== undefined && tag !== "latest" && tag.length > 0 ? tag : undefined;
62
+ return { namespace, name, version };
63
+ };
64
+ /**
65
+ * Convert a parsed image reference to a DetectedPackage.
66
+ */
67
+ const imageToPurl = (ref, source) => {
68
+ const parsed = parseImageRef(ref);
69
+ if (parsed === undefined)
70
+ return undefined;
71
+ const purl = new PackageURL("docker", parsed.namespace ?? null, parsed.name, parsed.version ?? null, null, null);
72
+ const purlParts = decodePurl(purl.toString());
73
+ return { purl: purlParts, type: dockerType, source };
74
+ };
75
+ /**
76
+ * Parse ARG directives from Dockerfile content.
77
+ * Returns a map of variable name to default value (or undefined if no default).
78
+ */
79
+ const parseDockerArgs = (content) => {
80
+ const args = new Map();
81
+ const lines = content.split("\n");
82
+ for (const line of lines) {
83
+ const trimmed = line.trim();
84
+ if (!trimmed.toUpperCase().startsWith("ARG "))
85
+ continue;
86
+ const rest = trimmed.slice(4).trim();
87
+ const eqIdx = rest.indexOf("=");
88
+ if (eqIdx > 0) {
89
+ const name = rest.slice(0, eqIdx).trim();
90
+ const value = rest.slice(eqIdx + 1).trim();
91
+ args.set(name, value);
92
+ }
93
+ else {
94
+ args.set(rest, undefined);
95
+ }
96
+ }
97
+ return args;
98
+ };
99
+ /**
100
+ * Resolve a variable reference like `${VARIABLE}` using ARG defaults.
101
+ * Returns the resolved string, or undefined if unresolvable.
102
+ */
103
+ const resolveVariable = (imageRef, args) => {
104
+ // Check if the entire reference or parts contain variables
105
+ const varPattern = /\$\{([^}]+)\}/g;
106
+ let resolved = imageRef;
107
+ let match = varPattern.exec(imageRef);
108
+ while (match !== null) {
109
+ const varName = match[1];
110
+ if (varName === undefined)
111
+ return undefined;
112
+ // Check for ${VAR:-default} syntax
113
+ const colonIdx = varName.indexOf(":-");
114
+ const defaultValue = colonIdx > 0 ? varName.slice(colonIdx + 2) : args.get(varName);
115
+ if (defaultValue === undefined)
116
+ return undefined;
117
+ resolved = resolved.replace(match[0], defaultValue);
118
+ match = varPattern.exec(imageRef);
119
+ }
120
+ return resolved;
121
+ };
122
+ /**
123
+ * Parse FROM directives from Dockerfile content.
124
+ */
125
+ const parseDockerfile = (content, source) => {
126
+ const args = parseDockerArgs(content);
127
+ const lines = content.split("\n");
128
+ const results = [];
129
+ for (const line of lines) {
130
+ const trimmed = line.trim();
131
+ if (!trimmed.toUpperCase().startsWith("FROM "))
132
+ continue;
133
+ const rest = trimmed.slice(5).trim();
134
+ // Remove AS alias
135
+ const parts = rest.split(/\s+/);
136
+ let imageRef = parts[0];
137
+ if (imageRef === undefined || imageRef.length === 0)
138
+ continue;
139
+ // Resolve variables
140
+ if (imageRef.includes("${")) {
141
+ const resolved = resolveVariable(imageRef, args);
142
+ if (resolved === undefined)
143
+ continue;
144
+ imageRef = resolved;
145
+ }
146
+ // Skip scratch
147
+ if (imageRef === "scratch")
148
+ continue;
149
+ const detected = imageToPurl(imageRef, source);
150
+ if (detected !== undefined)
151
+ results.push(detected);
152
+ }
153
+ return results;
154
+ };
155
+ /**
156
+ * Parse docker-compose YAML using line-based parsing.
157
+ * Extracts `image:` values from service definitions.
158
+ */
159
+ const parseDockerCompose = (content, source) => {
160
+ const lines = content.split("\n");
161
+ const results = [];
162
+ for (const line of lines) {
163
+ const trimmed = line.trim();
164
+ if (trimmed.startsWith("#"))
165
+ continue;
166
+ // Match `image: value` lines
167
+ if (trimmed.startsWith("image:")) {
168
+ const value = trimmed.slice(6).trim();
169
+ // Remove quotes if present
170
+ const cleaned = value.replace(/^["']|["']$/g, "");
171
+ if (cleaned.length > 0) {
172
+ const detected = imageToPurl(cleaned, source);
173
+ if (detected !== undefined)
174
+ results.push(detected);
175
+ }
176
+ }
177
+ }
178
+ return results;
179
+ };
180
+ /**
181
+ * Docker package detector.
182
+ *
183
+ * Scans `Dockerfile`, `docker-compose.yml`, and `docker-compose.yaml`
184
+ * for image dependencies.
185
+ *
186
+ * @experimental This API is unstable and may change without notice.
187
+ */
188
+ export const dockerDetector = {
189
+ type: dockerType,
190
+ detect: Effect.fn("detect.docker")(function* (projectDir) {
191
+ const path = yield* Path.Path;
192
+ const results = [];
193
+ // Parse Dockerfile
194
+ const dockerfilePath = path.join(projectDir, "Dockerfile");
195
+ const dockerfileContent = yield* readFileOptional(dockerfilePath);
196
+ if (Option.isSome(dockerfileContent)) {
197
+ const deps = parseDockerfile(dockerfileContent.value, dockerfilePath);
198
+ results.push(...deps);
199
+ }
200
+ // Parse docker-compose.yml
201
+ const composeYmlPath = path.join(projectDir, "docker-compose.yml");
202
+ const composeYmlContent = yield* readFileOptional(composeYmlPath);
203
+ if (Option.isSome(composeYmlContent)) {
204
+ const deps = parseDockerCompose(composeYmlContent.value, composeYmlPath);
205
+ results.push(...deps);
206
+ }
207
+ // Parse docker-compose.yaml
208
+ const composeYamlPath = path.join(projectDir, "docker-compose.yaml");
209
+ const composeYamlContent = yield* readFileOptional(composeYamlPath);
210
+ if (Option.isSome(composeYamlContent)) {
211
+ const deps = parseDockerCompose(composeYamlContent.value, composeYamlPath);
212
+ results.push(...deps);
213
+ }
214
+ return results;
215
+ }, Effect.annotateLogs({ detector: "docker" }), Effect.withSpan("detect.docker")),
216
+ };
217
+ /**
218
+ * Docker package reader.
219
+ *
220
+ * Inspects local Docker image metadata for `sh.axm.recommended-extensions`
221
+ * annotation. Since inspecting Docker images requires the Docker CLI,
222
+ * this reader returns Option.none when Docker is not available.
223
+ *
224
+ * @experimental This API is unstable and may change without notice.
225
+ */
226
+ export const dockerReader = {
227
+ type: dockerType,
228
+ read: Effect.fn("read.docker")(function* (pkg) {
229
+ const path = yield* Path.Path;
230
+ // Reconstruct the image name from purl parts
231
+ const imageName = pkg.purl.namespace
232
+ ? `${pkg.purl.namespace}/${pkg.purl.name}`
233
+ : pkg.purl.name;
234
+ const imageRef = pkg.purl.version ? `${imageName}:${pkg.purl.version}` : imageName;
235
+ // Derive project directory from source
236
+ const projectDir = path.dirname(pkg.source);
237
+ // Check for a local .axm-docker-annotations/<image>.json file
238
+ // This is a simplified approach that checks for sidecar annotation files
239
+ const safeImageName = imageRef.replace(/[/:]/g, "_");
240
+ const annotationPath = path.join(projectDir, ".axm-docker-annotations", `${safeImageName}.json`);
241
+ const content = yield* readFileOptional(annotationPath);
242
+ if (Option.isNone(content))
243
+ return Option.none();
244
+ const parsed = yield* parseJsonOptional(content.value, `docker annotations for ${imageRef}`);
245
+ if (Option.isNone(parsed))
246
+ return Option.none();
247
+ // Validate axm metadata structure
248
+ const metaResult = decodeAxmMeta(parsed.value);
249
+ if (Result.isFailure(metaResult)) {
250
+ yield* Effect.logWarning(`Invalid axm metadata in docker image ${imageRef}: schema validation failed`);
251
+ return Option.none();
252
+ }
253
+ return Option.some(metaResult.success.extensions);
254
+ }, Effect.annotateLogs({ reader: "docker" }), Effect.withSpan("read.docker")),
255
+ };
256
+ //# sourceMappingURL=docker.js.map
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Ruby gem 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
+ * Gem package detector.
10
+ *
11
+ * Scans `Gemfile` and `*.gemspec` files in the project directory and extracts
12
+ * gem dependencies. Deduplicates gems that appear in both sources.
13
+ *
14
+ * @experimental This API is unstable and may change without notice.
15
+ */
16
+ export declare const gemDetector: PackageDetector;
17
+ /**
18
+ * Gem package reader.
19
+ *
20
+ * Reads gemspec metadata from `<gem-dir>/specifications/<gem>.gemspec`
21
+ * and extracts `axm_`-prefixed metadata keys.
22
+ *
23
+ * @experimental This API is unstable and may change without notice.
24
+ */
25
+ export declare const gemReader: PackageReader;
26
+ //# sourceMappingURL=gem.d.ts.map
@@ -0,0 +1,234 @@
1
+ /**
2
+ * Ruby gem 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 { envWithDefault } from "../internal/environment.js";
17
+ import { PackageTypeSchema } from "@agentxm/extension-model/unstable/packaging/package-type";
18
+ import { decodeAxmMeta, decodePurl, readFileOptional } from "./reader-io.js";
19
+ const gemType = Schema.decodeUnknownSync(PackageTypeSchema)("gem");
20
+ /** Range operators that indicate non-exact version constraints. */
21
+ const RANGE_OPERATORS = ["~>", ">=", "<=", ">", "<", "!="];
22
+ /**
23
+ * Returns true if the version specifier is an exact version (no range operators).
24
+ * Exact versions are bare numeric strings like "5.6.7".
25
+ */
26
+ const isExactVersion = (specifier) => {
27
+ const trimmed = specifier.trim();
28
+ if (RANGE_OPERATORS.some((op) => trimmed.startsWith(op)))
29
+ return false;
30
+ return /^\d+(\.\d+)*$/.test(trimmed);
31
+ };
32
+ /**
33
+ * Regex to match `gem 'name'` or `gem "name"` directives in a Gemfile.
34
+ * Captures: gem name, optional version string, optional options.
35
+ */
36
+ const GEMFILE_GEM_RE = /^\s*gem\s+['"]([^'"]+)['"]\s*(?:,\s*['"]([^'"]*)['"]\s*)?(?:,\s*(.*))?$/;
37
+ /** Patterns for path/git/github options that indicate non-registry sources. */
38
+ const SKIP_OPTIONS_RE = /\b(?:path|git|github)\s*:/;
39
+ /**
40
+ * Parse a Gemfile line into a gem name and optional version, or undefined if skipped.
41
+ */
42
+ const parseGemfileLine = (line, source) => {
43
+ const match = GEMFILE_GEM_RE.exec(line);
44
+ if (match === null)
45
+ return undefined;
46
+ const name = match[1];
47
+ const versionStr = match[2];
48
+ const options = match[3];
49
+ if (name === undefined)
50
+ return undefined;
51
+ // Check for path/git/github options - skip non-registry sources
52
+ if (options !== undefined && SKIP_OPTIONS_RE.test(options))
53
+ return undefined;
54
+ // Also check the full line for options that might come after the version
55
+ const afterName = line.slice(line.indexOf(name) + name.length);
56
+ if (SKIP_OPTIONS_RE.test(afterName))
57
+ return undefined;
58
+ const version = versionStr !== undefined && isExactVersion(versionStr) ? versionStr : undefined;
59
+ const purl = new PackageURL("gem", null, name, version ?? null, null, null);
60
+ const purlParts = decodePurl(purl.toString());
61
+ return { purl: purlParts, type: gemType, source };
62
+ };
63
+ /**
64
+ * Parse Gemfile content and extract gem dependencies.
65
+ */
66
+ const parseGemfile = (content, source) => {
67
+ const lines = content.split("\n");
68
+ const results = [];
69
+ for (const line of lines) {
70
+ const trimmed = line.trim();
71
+ if (trimmed === "" || trimmed.startsWith("#"))
72
+ continue;
73
+ const detected = parseGemfileLine(trimmed, source);
74
+ if (detected !== undefined)
75
+ results.push(detected);
76
+ }
77
+ return results;
78
+ };
79
+ /**
80
+ * Regex to match add_dependency, add_runtime_dependency, add_development_dependency
81
+ * in gemspec files.
82
+ */
83
+ const GEMSPEC_DEP_RE = /\.\s*add_(?:runtime_|development_)?dependency\s*\(?\s*['"]([^'"]+)['"]\s*(?:,\s*['"]([^'"]*)['"]\s*)?/;
84
+ /**
85
+ * Parse a gemspec file and extract dependency declarations.
86
+ */
87
+ const parseGemspec = (content, source) => {
88
+ const lines = content.split("\n");
89
+ const results = [];
90
+ for (const line of lines) {
91
+ const match = GEMSPEC_DEP_RE.exec(line);
92
+ if (match === null)
93
+ continue;
94
+ const name = match[1];
95
+ if (name === undefined)
96
+ continue;
97
+ const versionStr = match[2];
98
+ const version = versionStr !== undefined && isExactVersion(versionStr) ? versionStr : undefined;
99
+ const purl = new PackageURL("gem", null, name, version ?? null, null, null);
100
+ const purlParts = decodePurl(purl.toString());
101
+ results.push({ purl: purlParts, type: gemType, source });
102
+ }
103
+ return results;
104
+ };
105
+ /**
106
+ * Gem package detector.
107
+ *
108
+ * Scans `Gemfile` and `*.gemspec` files in the project directory and extracts
109
+ * gem dependencies. Deduplicates gems that appear in both sources.
110
+ *
111
+ * @experimental This API is unstable and may change without notice.
112
+ */
113
+ export const gemDetector = {
114
+ type: gemType,
115
+ detect: Effect.fn("detect.gem")(function* (projectDir) {
116
+ const fs = yield* FileSystem.FileSystem;
117
+ const path = yield* Path.Path;
118
+ const results = [];
119
+ const seen = new Set();
120
+ const addUnique = (deps) => {
121
+ for (const dep of deps) {
122
+ const key = dep.purl.name;
123
+ if (!seen.has(key)) {
124
+ seen.add(key);
125
+ results.push(dep);
126
+ }
127
+ }
128
+ };
129
+ // Parse Gemfile
130
+ const gemfilePath = path.join(projectDir, "Gemfile");
131
+ const gemfileContent = yield* readFileOptional(gemfilePath);
132
+ if (Option.isSome(gemfileContent)) {
133
+ const deps = parseGemfile(gemfileContent.value, gemfilePath);
134
+ addUnique(deps);
135
+ }
136
+ // Parse *.gemspec files
137
+ const dirEntries = yield* fs.readDirectory(projectDir).pipe(Effect.option);
138
+ if (Option.isSome(dirEntries)) {
139
+ for (const entry of dirEntries.value) {
140
+ if (entry.endsWith(".gemspec")) {
141
+ const gemspecPath = path.join(projectDir, entry);
142
+ const gemspecContent = yield* readFileOptional(gemspecPath);
143
+ if (Option.isSome(gemspecContent)) {
144
+ const deps = parseGemspec(gemspecContent.value, gemspecPath);
145
+ addUnique(deps);
146
+ }
147
+ }
148
+ }
149
+ }
150
+ return results;
151
+ }, Effect.annotateLogs({ detector: "gem" }), Effect.withSpan("detect.gem")),
152
+ };
153
+ /**
154
+ * Resolve the GEM_HOME or default gem installation directory.
155
+ * Uses GEM_HOME env var, falling back to the default Ruby gems directory.
156
+ */
157
+ const resolveGemDir = () => envWithDefault("GEM_HOME", `${os.homedir()}/.gem/ruby`);
158
+ /**
159
+ * Parse the `axm_extensions` value from a gemspec metadata string.
160
+ * The value format is a JSON-stringified array of extension declaration objects.
161
+ */
162
+ const parseAxmMetadataFromGemspec = (content) => {
163
+ const match = /"axm_extensions"\s*=>\s*"((?:\\.|[^"\\])*)"/.exec(content);
164
+ if (match === null || match[1] === undefined)
165
+ return undefined;
166
+ const rawValue = match[1];
167
+ try {
168
+ const decodedValue = JSON.parse(`"${rawValue}"`);
169
+ if (typeof decodedValue !== "string")
170
+ return undefined;
171
+ return { extensions: JSON.parse(decodedValue) };
172
+ }
173
+ catch {
174
+ return undefined;
175
+ }
176
+ };
177
+ /**
178
+ * Gem package reader.
179
+ *
180
+ * Reads gemspec metadata from `<gem-dir>/specifications/<gem>.gemspec`
181
+ * and extracts `axm_`-prefixed metadata keys.
182
+ *
183
+ * @experimental This API is unstable and may change without notice.
184
+ */
185
+ export const gemReader = {
186
+ type: gemType,
187
+ read: Effect.fn("read.gem")(function* (pkg) {
188
+ const fs = yield* FileSystem.FileSystem;
189
+ const path = yield* Path.Path;
190
+ const gemDir = yield* resolveGemDir();
191
+ const gemName = pkg.purl.name;
192
+ const version = pkg.purl.version;
193
+ // Try to find the gemspec in the specifications directory.
194
+ // Gemspec filename format: <name>-<version>.gemspec
195
+ const specsDir = path.join(gemDir, "specifications");
196
+ // If no version, try to find any matching gemspec
197
+ if (version !== undefined) {
198
+ const gemspecPath = path.join(specsDir, `${gemName}-${version}.gemspec`);
199
+ const content = yield* readFileOptional(gemspecPath);
200
+ if (Option.isNone(content))
201
+ return Option.none();
202
+ const axmMeta = parseAxmMetadataFromGemspec(content.value);
203
+ if (axmMeta === undefined)
204
+ return Option.none();
205
+ const metaResult = decodeAxmMeta(axmMeta);
206
+ if (Result.isFailure(metaResult)) {
207
+ yield* Effect.logWarning(`Invalid axm metadata in ${gemName}-${version}: schema validation failed`);
208
+ return Option.none();
209
+ }
210
+ return Option.some(metaResult.success.extensions);
211
+ }
212
+ // No version - scan specs directory for matching gemspec
213
+ const dirEntries = yield* fs.readDirectory(specsDir).pipe(Effect.option);
214
+ if (Option.isNone(dirEntries))
215
+ return Option.none();
216
+ const matchingSpec = dirEntries.value.find((entry) => entry.startsWith(`${gemName}-`) && entry.endsWith(".gemspec"));
217
+ if (matchingSpec === undefined)
218
+ return Option.none();
219
+ const gemspecPath = path.join(specsDir, matchingSpec);
220
+ const content = yield* readFileOptional(gemspecPath);
221
+ if (Option.isNone(content))
222
+ return Option.none();
223
+ const axmMeta = parseAxmMetadataFromGemspec(content.value);
224
+ if (axmMeta === undefined)
225
+ return Option.none();
226
+ const metaResult = decodeAxmMeta(axmMeta);
227
+ if (Result.isFailure(metaResult)) {
228
+ yield* Effect.logWarning(`Invalid axm metadata in ${gemName}: schema validation failed`);
229
+ return Option.none();
230
+ }
231
+ return Option.some(metaResult.success.extensions);
232
+ }, Effect.annotateLogs({ reader: "gem" }), Effect.withSpan("read.gem")),
233
+ };
234
+ //# sourceMappingURL=gem.js.map
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Go module 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
+ * Go module package detector.
10
+ *
11
+ * Scans `go.mod` in the project directory and extracts direct dependencies
12
+ * from `require` directives, filtering out `// indirect` entries.
13
+ *
14
+ * @experimental This API is unstable and may change without notice.
15
+ */
16
+ export declare const golangDetector: PackageDetector;
17
+ /**
18
+ * Go module package reader.
19
+ *
20
+ * Reads `axm.json` from `$GOPATH/pkg/mod/<module>@<version>/` for each
21
+ * detected Go module and extracts recommendation metadata.
22
+ *
23
+ * @experimental This API is unstable and may change without notice.
24
+ */
25
+ export declare const golangReader: PackageReader;
26
+ //# sourceMappingURL=golang.d.ts.map