kirograph 0.28.1 → 1.0.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/dist/architecture/index.js +4 -1
- package/dist/architecture/index.js.map +2 -2
- package/dist/architecture/manifest/index.js +13 -2
- package/dist/architecture/manifest/index.js.map +2 -2
- package/dist/bin/commands/install.js +2 -1
- package/dist/bin/commands/install.js.map +2 -2
- package/dist/bin/commands/status.js +2 -1
- package/dist/bin/commands/status.js.map +2 -2
- package/dist/bin/commands/uninit.js +2 -1
- package/dist/bin/commands/uninit.js.map +2 -2
- package/dist/bin/commands/vulns.js +10 -5
- package/dist/bin/commands/vulns.js.map +2 -2
- package/dist/bin/installer/common.js +7 -1
- package/dist/bin/installer/common.js.map +2 -2
- package/dist/bin/installer/detect.js +10 -0
- package/dist/bin/installer/detect.js.map +2 -2
- package/dist/bin/installer/hooks.js +13 -8
- package/dist/bin/installer/hooks.js.map +2 -2
- package/dist/bin/installer/targets/antigravity.js +1 -1
- package/dist/bin/installer/targets/antigravity.js.map +2 -2
- package/dist/bin/installer/targets/claude.js +1 -1
- package/dist/bin/installer/targets/claude.js.map +2 -2
- package/dist/bin/installer/targets/codex.js +1 -1
- package/dist/bin/installer/targets/codex.js.map +2 -2
- package/dist/bin/installer/targets/commandcode.js +84 -0
- package/dist/bin/installer/targets/commandcode.js.map +7 -0
- package/dist/bin/installer/targets/copilot.js +1 -1
- package/dist/bin/installer/targets/copilot.js.map +2 -2
- package/dist/bin/installer/targets/cursor.js +2 -2
- package/dist/bin/installer/targets/cursor.js.map +2 -2
- package/dist/bin/installer/targets/devin.js +1 -1
- package/dist/bin/installer/targets/devin.js.map +2 -2
- package/dist/bin/installer/targets/gemini-cli.js +1 -1
- package/dist/bin/installer/targets/gemini-cli.js.map +2 -2
- package/dist/bin/installer/targets/index.js +10 -0
- package/dist/bin/installer/targets/index.js.map +2 -2
- package/dist/bin/installer/targets/opencode.js +1 -1
- package/dist/bin/installer/targets/opencode.js.map +2 -2
- package/dist/bin/installer/targets/windsurf.js +1 -1
- package/dist/bin/installer/targets/windsurf.js.map +2 -2
- package/dist/bin/kirograph.js +1 -1
- package/dist/bin/kirograph.js.map +1 -1
- package/dist/config.js +21 -1
- package/dist/config.js.map +2 -2
- package/dist/core/pipeline.js +18 -6
- package/dist/core/pipeline.js.map +2 -2
- package/dist/extraction/extractor.js +58 -1
- package/dist/extraction/extractor.js.map +2 -2
- package/dist/extraction/grammars.js +38 -3
- package/dist/extraction/grammars.js.map +2 -2
- package/dist/extraction/wasm/tree-sitter-bash.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-c.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-c_sharp.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-cpp.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-csharp.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-css.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-dart.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-elisp.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-elixir.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-elm.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-embedded_template.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-embeddedtemplate.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-go.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-html.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-java.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-javascript.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-json.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-kotlin.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-lua.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-objc.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-ocaml.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-php.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-python.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-ql.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-rescript.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-ruby.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-rust.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-scala.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-solidity.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-swift.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-systemrdl.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-tlaplus.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-toml.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-tsx.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-typescript.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-vue.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-yaml.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-zig.wasm +0 -0
- package/dist/frameworks/index.js +68 -7
- package/dist/frameworks/index.js.map +2 -2
- package/dist/security/manifest/adapter.js +17 -2
- package/dist/security/manifest/adapter.js.map +2 -2
- package/dist/security/manifest/parser.js +2 -2
- package/dist/security/manifest/parser.js.map +2 -2
- package/dist/security/pipeline.js +4 -1
- package/dist/security/pipeline.js.map +2 -2
- package/dist/security/reachability.js +61 -45
- package/dist/security/reachability.js.map +3 -3
- package/dist/security/types.js.map +1 -1
- package/dist/security/vuln/epss-client.js +1 -1
- package/dist/security/vuln/epss-client.js.map +1 -1
- package/dist/security/vuln/osv-adapter.js +99 -31
- package/dist/security/vuln/osv-adapter.js.map +2 -2
- package/dist/sync/index.js +9 -2
- package/dist/sync/index.js.map +2 -2
- package/dist/vectors/libsql-index.js +287 -0
- package/dist/vectors/libsql-index.js.map +7 -0
- package/dist/vectors/turso-index.js +307 -0
- package/dist/vectors/turso-index.js.map +7 -0
- package/package.json +3 -2
|
Binary file
|
|
Binary file
|
package/dist/frameworks/index.js
CHANGED
|
@@ -188,6 +188,7 @@ function registerFrameworkResolver(resolver) {
|
|
|
188
188
|
function buildResolutionContext(projectRoot, db) {
|
|
189
189
|
const fileCache = /* @__PURE__ */ new Map();
|
|
190
190
|
const nodeCache = /* @__PURE__ */ new Map();
|
|
191
|
+
const allIndexedFiles = db.getAllFiles().map((f) => f.path);
|
|
191
192
|
return {
|
|
192
193
|
getNodesInFile(filePath) {
|
|
193
194
|
if (!nodeCache.has(filePath)) {
|
|
@@ -202,24 +203,84 @@ function buildResolutionContext(projectRoot, db) {
|
|
|
202
203
|
return db.getNodesByKind(kind);
|
|
203
204
|
},
|
|
204
205
|
fileExists(filePath) {
|
|
205
|
-
|
|
206
|
+
if (fs.existsSync(path.join(projectRoot, filePath))) return true;
|
|
207
|
+
const basename = path.basename(filePath);
|
|
208
|
+
if (allIndexedFiles.some((f) => f.endsWith("/" + basename) || f === basename)) return true;
|
|
209
|
+
try {
|
|
210
|
+
const entries = fs.readdirSync(projectRoot, { withFileTypes: true });
|
|
211
|
+
for (const entry of entries) {
|
|
212
|
+
if (!entry.isDirectory()) continue;
|
|
213
|
+
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
214
|
+
const candidate = path.join(projectRoot, entry.name, filePath);
|
|
215
|
+
if (fs.existsSync(candidate)) return true;
|
|
216
|
+
try {
|
|
217
|
+
const subEntries = fs.readdirSync(path.join(projectRoot, entry.name), { withFileTypes: true });
|
|
218
|
+
for (const sub of subEntries) {
|
|
219
|
+
if (!sub.isDirectory() || sub.name === "node_modules" || sub.name === ".git") continue;
|
|
220
|
+
if (fs.existsSync(path.join(projectRoot, entry.name, sub.name, filePath))) return true;
|
|
221
|
+
}
|
|
222
|
+
} catch {
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
} catch {
|
|
226
|
+
}
|
|
227
|
+
return false;
|
|
206
228
|
},
|
|
207
229
|
readFile(filePath) {
|
|
208
230
|
if (fileCache.has(filePath)) return fileCache.get(filePath);
|
|
231
|
+
const rootPath = path.join(projectRoot, filePath);
|
|
232
|
+
if (fs.existsSync(rootPath)) {
|
|
233
|
+
try {
|
|
234
|
+
const content = fs.readFileSync(rootPath, "utf8");
|
|
235
|
+
fileCache.set(filePath, content);
|
|
236
|
+
return content;
|
|
237
|
+
} catch {
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
const basename = path.basename(filePath);
|
|
241
|
+
const match = allIndexedFiles.find((f) => f.endsWith("/" + basename) || f === basename);
|
|
242
|
+
if (match) {
|
|
243
|
+
try {
|
|
244
|
+
const content = fs.readFileSync(path.join(projectRoot, match), "utf8");
|
|
245
|
+
fileCache.set(filePath, content);
|
|
246
|
+
return content;
|
|
247
|
+
} catch {
|
|
248
|
+
}
|
|
249
|
+
}
|
|
209
250
|
try {
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
251
|
+
const entries = fs.readdirSync(projectRoot, { withFileTypes: true });
|
|
252
|
+
for (const entry of entries) {
|
|
253
|
+
if (!entry.isDirectory() || entry.name === "node_modules" || entry.name === ".git") continue;
|
|
254
|
+
const candidate = path.join(projectRoot, entry.name, filePath);
|
|
255
|
+
if (fs.existsSync(candidate)) {
|
|
256
|
+
const content = fs.readFileSync(candidate, "utf8");
|
|
257
|
+
fileCache.set(filePath, content);
|
|
258
|
+
return content;
|
|
259
|
+
}
|
|
260
|
+
try {
|
|
261
|
+
const subEntries = fs.readdirSync(path.join(projectRoot, entry.name), { withFileTypes: true });
|
|
262
|
+
for (const sub of subEntries) {
|
|
263
|
+
if (!sub.isDirectory() || sub.name === "node_modules" || sub.name === ".git") continue;
|
|
264
|
+
const deepCandidate = path.join(projectRoot, entry.name, sub.name, filePath);
|
|
265
|
+
if (fs.existsSync(deepCandidate)) {
|
|
266
|
+
const content = fs.readFileSync(deepCandidate, "utf8");
|
|
267
|
+
fileCache.set(filePath, content);
|
|
268
|
+
return content;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
} catch {
|
|
272
|
+
}
|
|
273
|
+
}
|
|
213
274
|
} catch {
|
|
214
|
-
fileCache.set(filePath, null);
|
|
215
|
-
return null;
|
|
216
275
|
}
|
|
276
|
+
fileCache.set(filePath, null);
|
|
277
|
+
return null;
|
|
217
278
|
},
|
|
218
279
|
getProjectRoot() {
|
|
219
280
|
return projectRoot;
|
|
220
281
|
},
|
|
221
282
|
getAllFiles() {
|
|
222
|
-
return
|
|
283
|
+
return allIndexedFiles;
|
|
223
284
|
}
|
|
224
285
|
};
|
|
225
286
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/frameworks/index.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Framework Resolver Registry\n *\n * Mirrors CodeGraph src/resolution/frameworks/index.ts\n * Manages framework-specific resolvers and detection.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { logDebug, logWarn } from '../errors';\nimport { updateConfig } from '../config';\nimport type { Node } from '../types';\nimport type { GraphDatabase } from '../db/database';\nimport type { FrameworkResolver, ResolutionContext } from './types';\n\n// Re-export types\nexport type { FrameworkResolver, ResolutionContext, UnresolvedRef, ResolvedRef } from './types';\n\n// Re-export individual resolvers\nexport { reactResolver } from './react';\nexport { svelteResolver } from './svelte';\nexport { expressResolver } from './express';\nexport { djangoResolver, flaskResolver, fastapiResolver } from './python';\nexport { railsResolver } from './ruby';\nexport { springResolver } from './java';\nexport { goResolver } from './go';\nexport { rustResolver } from './rust';\nexport { aspnetResolver } from './csharp';\nexport { swiftUIResolver, uikitResolver, vaporResolver } from './swift';\nexport { laravelResolver, FACADE_MAPPINGS } from './laravel';\nexport { phoenixResolver } from './elixir';\nexport { playResolver } from './scala';\nexport { nuxtResolver, vueResolver } from './vue';\nexport { solidityResolver } from './solidity';\nexport { sstResolver, cdkResolver, serverlessResolver, samResolver } from './iac';\nexport { terraformResolver } from './terraform';\nexport { pulumiResolver } from './pulumi';\nexport { cloudformationResolver } from './cloudformation';\nexport { kubernetesResolver } from './kubernetes';\nexport { dockerComposeResolver } from './docker';\nexport { ansibleResolver } from './ansible';\nexport { angularResolver } from './angular';\nexport { amplifyResolver } from './amplify';\nexport { flutterResolver } from './flutter';\n\n// \u2500\u2500 Registry \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nimport { reactResolver } from './react';\nimport { svelteResolver } from './svelte';\nimport { expressResolver } from './express';\nimport { djangoResolver, flaskResolver, fastapiResolver } from './python';\nimport { railsResolver } from './ruby';\nimport { springResolver } from './java';\nimport { goResolver } from './go';\nimport { rustResolver } from './rust';\nimport { aspnetResolver } from './csharp';\nimport { swiftUIResolver, uikitResolver, vaporResolver } from './swift';\nimport { laravelResolver } from './laravel';\nimport { phoenixResolver } from './elixir';\nimport { playResolver } from './scala';\nimport { nuxtResolver, vueResolver } from './vue';\nimport { solidityResolver } from './solidity';\nimport { sstResolver, cdkResolver, serverlessResolver, samResolver } from './iac';\nimport { terraformResolver } from './terraform';\nimport { pulumiResolver } from './pulumi';\nimport { cloudformationResolver } from './cloudformation';\nimport { kubernetesResolver } from './kubernetes';\nimport { dockerComposeResolver } from './docker';\nimport { ansibleResolver } from './ansible';\nimport { angularResolver } from './angular';\nimport { amplifyResolver } from './amplify';\nimport { flutterResolver } from './flutter';\n\nconst FRAMEWORK_RESOLVERS: FrameworkResolver[] = [\n // PHP\n laravelResolver,\n // JavaScript / TypeScript\n expressResolver,\n reactResolver,\n svelteResolver,\n angularResolver,\n // Vue / Nuxt\n nuxtResolver,\n vueResolver,\n // Python\n djangoResolver,\n flaskResolver,\n fastapiResolver,\n // Ruby\n railsResolver,\n // Java\n springResolver,\n // Go\n goResolver,\n // Rust\n rustResolver,\n // C#\n aspnetResolver,\n // Swift\n swiftUIResolver,\n uikitResolver,\n vaporResolver,\n // Elixir\n phoenixResolver,\n // Scala\n playResolver,\n // Solidity\n solidityResolver,\n // Infrastructure as Code\n sstResolver,\n cdkResolver,\n serverlessResolver,\n samResolver,\n terraformResolver,\n pulumiResolver,\n cloudformationResolver,\n amplifyResolver,\n // Containers & Orchestration\n kubernetesResolver,\n dockerComposeResolver,\n // Configuration Management\n ansibleResolver,\n // Dart / Flutter\n flutterResolver,\n];\n\nexport function getAllFrameworkResolvers(): FrameworkResolver[] {\n return FRAMEWORK_RESOLVERS;\n}\n\nexport function getFrameworkResolver(name: string): FrameworkResolver | undefined {\n return FRAMEWORK_RESOLVERS.find(r => r.name === name);\n}\n\nexport function registerFrameworkResolver(resolver: FrameworkResolver): void {\n const index = FRAMEWORK_RESOLVERS.findIndex(r => r.name === resolver.name);\n if (index !== -1) FRAMEWORK_RESOLVERS.splice(index, 1);\n FRAMEWORK_RESOLVERS.push(resolver);\n}\n\n// \u2500\u2500 Context builder \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction buildResolutionContext(projectRoot: string, db: GraphDatabase): ResolutionContext {\n const fileCache = new Map<string, string | null>();\n const nodeCache = new Map<string, Node[]>();\n\n return {\n getNodesInFile(filePath: string): Node[] {\n if (!nodeCache.has(filePath)) {\n nodeCache.set(filePath, db.getNodesByFile(filePath));\n }\n return nodeCache.get(filePath)!;\n },\n getNodesByName(name: string): Node[] {\n return db.findNodesByExactName(name);\n },\n getNodesByKind(kind: Node['kind']): Node[] {\n return db.getNodesByKind(kind);\n },\n fileExists(filePath: string): boolean {\n return fs.existsSync(path.join(projectRoot, filePath));\n },\n readFile(filePath: string): string | null {\n if (fileCache.has(filePath)) return fileCache.get(filePath)!;\n try {\n const content = fs.readFileSync(path.join(projectRoot, filePath), 'utf8');\n fileCache.set(filePath, content);\n return content;\n } catch {\n fileCache.set(filePath, null);\n return null;\n }\n },\n getProjectRoot(): string {\n return projectRoot;\n },\n getAllFiles(): string[] {\n return db.getAllFiles().map(f => f.path);\n },\n };\n}\n\n// \u2500\u2500 detectFrameworks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Detect which frameworks are used in a project.\n * Mirrors CodeGraph detectFrameworks() \u2014 uses ResolutionContext for file access.\n * Records detected framework names in config via updateConfig().\n */\nexport async function detectFrameworks(projectRoot: string, db?: GraphDatabase): Promise<FrameworkResolver[]> {\n // If no db provided, fall back to package.json-only detection\n if (!db) {\n return detectFromPackageJson(projectRoot);\n }\n\n const context = buildResolutionContext(projectRoot, db);\n const detected: FrameworkResolver[] = [];\n\n for (const resolver of FRAMEWORK_RESOLVERS) {\n try {\n if (resolver.detect(context)) {\n logDebug(`detectFrameworks: detected ${resolver.name}`);\n detected.push(resolver);\n }\n } catch (err) {\n logWarn(`detectFrameworks: error detecting ${resolver.name}`, { error: String(err) });\n }\n }\n\n if (detected.length > 0) {\n await updateConfig(projectRoot, { frameworkHints: detected.map(f => f.name) });\n }\n\n return detected;\n}\n\n/**\n * Lightweight fallback: detect frameworks from package.json only (no DB needed).\n */\nasync function detectFromPackageJson(projectRoot: string): Promise<FrameworkResolver[]> {\n const pkgPath = path.join(projectRoot, 'package.json');\n if (!fs.existsSync(pkgPath)) return [];\n\n let pkg: Record<string, unknown>;\n try {\n pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')) as Record<string, unknown>;\n } catch {\n return [];\n }\n\n const deps: Record<string, string> = {\n ...((pkg.dependencies as Record<string, string>) ?? {}),\n ...((pkg.devDependencies as Record<string, string>) ?? {}),\n };\n\n const PACKAGE_TO_RESOLVER: Record<string, string> = {\n react: 'react', next: 'react', 'react-native': 'react',\n svelte: 'svelte', '@sveltejs/kit': 'svelte',\n express: 'express', fastify: 'express', koa: 'express',\n };\n\n const detectedNames = new Set<string>();\n const detected: FrameworkResolver[] = [];\n\n for (const [pkg, resolverName] of Object.entries(PACKAGE_TO_RESOLVER)) {\n if (Object.prototype.hasOwnProperty.call(deps, pkg) && !detectedNames.has(resolverName)) {\n const resolver = getFrameworkResolver(resolverName);\n if (resolver) {\n detectedNames.add(resolverName);\n detected.push(resolver);\n }\n }\n }\n\n if (detected.length > 0) {\n await updateConfig(projectRoot, { frameworkHints: detected.map(f => f.name) });\n }\n\n return detected;\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOA,SAAoB;AACpB,WAAsB;AACtB,oBAAkC;AAClC,oBAA6B;AAS7B,mBAA8B;AAC9B,oBAA+B;AAC/B,qBAAgC;AAChC,oBAA+D;AAC/D,kBAA8B;AAC9B,kBAA+B;AAC/B,gBAA2B;AAC3B,kBAA6B;AAC7B,oBAA+B;AAC/B,mBAA8D;AAC9D,qBAAiD;AACjD,oBAAgC;AAChC,mBAA6B;AAC7B,iBAA0C;AAC1C,sBAAiC;AACjC,iBAA0E;AAC1E,uBAAkC;AAClC,oBAA+B;AAC/B,4BAAuC;AACvC,wBAAmC;AACnC,oBAAsC;AACtC,qBAAgC;AAChC,qBAAgC;AAChC,qBAAgC;AAChC,qBAAgC;AAIhC,IAAAA,gBAA8B;AAC9B,IAAAC,iBAA+B;AAC/B,IAAAC,kBAAgC;AAChC,IAAAC,iBAA+D;AAC/D,IAAAC,eAA8B;AAC9B,IAAAC,eAA+B;AAC/B,IAAAC,aAA2B;AAC3B,IAAAC,eAA6B;AAC7B,IAAAC,iBAA+B;AAC/B,IAAAC,gBAA8D;AAC9D,IAAAC,kBAAgC;AAChC,IAAAC,iBAAgC;AAChC,IAAAC,gBAA6B;AAC7B,IAAAC,cAA0C;AAC1C,IAAAC,mBAAiC;AACjC,IAAAC,cAA0E;AAC1E,IAAAC,oBAAkC;AAClC,IAAAC,iBAA+B;AAC/B,IAAAC,yBAAuC;AACvC,IAAAC,qBAAmC;AACnC,IAAAC,iBAAsC;AACtC,IAAAC,kBAAgC;AAChC,IAAAC,kBAAgC;AAChC,IAAAC,kBAAgC;AAChC,IAAAC,kBAAgC;AAEhC,MAAM,sBAA2C;AAAA;AAAA,EAE/C;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AACF;AAEO,SAAS,2BAAgD;AAC9D,SAAO;AACT;AAEO,SAAS,qBAAqB,MAA6C;AAChF,SAAO,oBAAoB,KAAK,OAAK,EAAE,SAAS,IAAI;AACtD;AAEO,SAAS,0BAA0B,UAAmC;AAC3E,QAAM,QAAQ,oBAAoB,UAAU,OAAK,EAAE,SAAS,SAAS,IAAI;AACzE,MAAI,UAAU,GAAI,qBAAoB,OAAO,OAAO,CAAC;AACrD,sBAAoB,KAAK,QAAQ;AACnC;AAIA,SAAS,uBAAuB,aAAqB,IAAsC;AACzF,QAAM,YAAY,oBAAI,IAA2B;AACjD,QAAM,YAAY,oBAAI,IAAoB;
|
|
4
|
+
"sourcesContent": ["/**\n * Framework Resolver Registry\n *\n * Mirrors CodeGraph src/resolution/frameworks/index.ts\n * Manages framework-specific resolvers and detection.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { logDebug, logWarn } from '../errors';\nimport { updateConfig } from '../config';\nimport type { Node } from '../types';\nimport type { GraphDatabase } from '../db/database';\nimport type { FrameworkResolver, ResolutionContext } from './types';\n\n// Re-export types\nexport type { FrameworkResolver, ResolutionContext, UnresolvedRef, ResolvedRef } from './types';\n\n// Re-export individual resolvers\nexport { reactResolver } from './react';\nexport { svelteResolver } from './svelte';\nexport { expressResolver } from './express';\nexport { djangoResolver, flaskResolver, fastapiResolver } from './python';\nexport { railsResolver } from './ruby';\nexport { springResolver } from './java';\nexport { goResolver } from './go';\nexport { rustResolver } from './rust';\nexport { aspnetResolver } from './csharp';\nexport { swiftUIResolver, uikitResolver, vaporResolver } from './swift';\nexport { laravelResolver, FACADE_MAPPINGS } from './laravel';\nexport { phoenixResolver } from './elixir';\nexport { playResolver } from './scala';\nexport { nuxtResolver, vueResolver } from './vue';\nexport { solidityResolver } from './solidity';\nexport { sstResolver, cdkResolver, serverlessResolver, samResolver } from './iac';\nexport { terraformResolver } from './terraform';\nexport { pulumiResolver } from './pulumi';\nexport { cloudformationResolver } from './cloudformation';\nexport { kubernetesResolver } from './kubernetes';\nexport { dockerComposeResolver } from './docker';\nexport { ansibleResolver } from './ansible';\nexport { angularResolver } from './angular';\nexport { amplifyResolver } from './amplify';\nexport { flutterResolver } from './flutter';\n\n// \u2500\u2500 Registry \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nimport { reactResolver } from './react';\nimport { svelteResolver } from './svelte';\nimport { expressResolver } from './express';\nimport { djangoResolver, flaskResolver, fastapiResolver } from './python';\nimport { railsResolver } from './ruby';\nimport { springResolver } from './java';\nimport { goResolver } from './go';\nimport { rustResolver } from './rust';\nimport { aspnetResolver } from './csharp';\nimport { swiftUIResolver, uikitResolver, vaporResolver } from './swift';\nimport { laravelResolver } from './laravel';\nimport { phoenixResolver } from './elixir';\nimport { playResolver } from './scala';\nimport { nuxtResolver, vueResolver } from './vue';\nimport { solidityResolver } from './solidity';\nimport { sstResolver, cdkResolver, serverlessResolver, samResolver } from './iac';\nimport { terraformResolver } from './terraform';\nimport { pulumiResolver } from './pulumi';\nimport { cloudformationResolver } from './cloudformation';\nimport { kubernetesResolver } from './kubernetes';\nimport { dockerComposeResolver } from './docker';\nimport { ansibleResolver } from './ansible';\nimport { angularResolver } from './angular';\nimport { amplifyResolver } from './amplify';\nimport { flutterResolver } from './flutter';\n\nconst FRAMEWORK_RESOLVERS: FrameworkResolver[] = [\n // PHP\n laravelResolver,\n // JavaScript / TypeScript\n expressResolver,\n reactResolver,\n svelteResolver,\n angularResolver,\n // Vue / Nuxt\n nuxtResolver,\n vueResolver,\n // Python\n djangoResolver,\n flaskResolver,\n fastapiResolver,\n // Ruby\n railsResolver,\n // Java\n springResolver,\n // Go\n goResolver,\n // Rust\n rustResolver,\n // C#\n aspnetResolver,\n // Swift\n swiftUIResolver,\n uikitResolver,\n vaporResolver,\n // Elixir\n phoenixResolver,\n // Scala\n playResolver,\n // Solidity\n solidityResolver,\n // Infrastructure as Code\n sstResolver,\n cdkResolver,\n serverlessResolver,\n samResolver,\n terraformResolver,\n pulumiResolver,\n cloudformationResolver,\n amplifyResolver,\n // Containers & Orchestration\n kubernetesResolver,\n dockerComposeResolver,\n // Configuration Management\n ansibleResolver,\n // Dart / Flutter\n flutterResolver,\n];\n\nexport function getAllFrameworkResolvers(): FrameworkResolver[] {\n return FRAMEWORK_RESOLVERS;\n}\n\nexport function getFrameworkResolver(name: string): FrameworkResolver | undefined {\n return FRAMEWORK_RESOLVERS.find(r => r.name === name);\n}\n\nexport function registerFrameworkResolver(resolver: FrameworkResolver): void {\n const index = FRAMEWORK_RESOLVERS.findIndex(r => r.name === resolver.name);\n if (index !== -1) FRAMEWORK_RESOLVERS.splice(index, 1);\n FRAMEWORK_RESOLVERS.push(resolver);\n}\n\n// \u2500\u2500 Context builder \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction buildResolutionContext(projectRoot: string, db: GraphDatabase): ResolutionContext {\n const fileCache = new Map<string, string | null>();\n const nodeCache = new Map<string, Node[]>();\n const allIndexedFiles = db.getAllFiles().map(f => f.path);\n\n return {\n getNodesInFile(filePath: string): Node[] {\n if (!nodeCache.has(filePath)) {\n nodeCache.set(filePath, db.getNodesByFile(filePath));\n }\n return nodeCache.get(filePath)!;\n },\n getNodesByName(name: string): Node[] {\n return db.findNodesByExactName(name);\n },\n getNodesByKind(kind: Node['kind']): Node[] {\n return db.getNodesByKind(kind);\n },\n fileExists(filePath: string): boolean {\n // Check root first\n if (fs.existsSync(path.join(projectRoot, filePath))) return true;\n // Multi-root workspace: check if any indexed file matches the filename\n const basename = path.basename(filePath);\n if (allIndexedFiles.some(f => f.endsWith('/' + basename) || f === basename)) return true;\n // Note: basename matching is intentionally broad \u2014 config files like angular.json,\n // docker-compose.yaml are highly specific names unlikely to cause false positives.\n // Fallback: scan top-level subdirectories for non-indexed config files (e.g., angular.json)\n try {\n const entries = fs.readdirSync(projectRoot, { withFileTypes: true });\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n if (entry.name === 'node_modules' || entry.name === '.git') continue;\n const candidate = path.join(projectRoot, entry.name, filePath);\n if (fs.existsSync(candidate)) return true;\n // Check one level deeper (module/app/file pattern)\n try {\n const subEntries = fs.readdirSync(path.join(projectRoot, entry.name), { withFileTypes: true });\n for (const sub of subEntries) {\n if (!sub.isDirectory() || sub.name === 'node_modules' || sub.name === '.git') continue;\n if (fs.existsSync(path.join(projectRoot, entry.name, sub.name, filePath))) return true;\n }\n } catch { /* skip unreadable */ }\n }\n } catch { /* fall through */ }\n return false;\n },\n readFile(filePath: string): string | null {\n if (fileCache.has(filePath)) return fileCache.get(filePath)!;\n // Try root first\n const rootPath = path.join(projectRoot, filePath);\n if (fs.existsSync(rootPath)) {\n try {\n const content = fs.readFileSync(rootPath, 'utf8');\n fileCache.set(filePath, content);\n return content;\n } catch { /* fall through */ }\n }\n // Multi-root workspace: find first matching file in indexed paths\n const basename = path.basename(filePath);\n const match = allIndexedFiles.find(f => f.endsWith('/' + basename) || f === basename);\n if (match) {\n try {\n const content = fs.readFileSync(path.join(projectRoot, match), 'utf8');\n fileCache.set(filePath, content);\n return content;\n } catch { /* fall through */ }\n }\n // Fallback: find first matching file in subdirectories (for non-indexed config files)\n try {\n const entries = fs.readdirSync(projectRoot, { withFileTypes: true });\n for (const entry of entries) {\n if (!entry.isDirectory() || entry.name === 'node_modules' || entry.name === '.git') continue;\n const candidate = path.join(projectRoot, entry.name, filePath);\n if (fs.existsSync(candidate)) {\n const content = fs.readFileSync(candidate, 'utf8');\n fileCache.set(filePath, content);\n return content;\n }\n // Check one level deeper\n try {\n const subEntries = fs.readdirSync(path.join(projectRoot, entry.name), { withFileTypes: true });\n for (const sub of subEntries) {\n if (!sub.isDirectory() || sub.name === 'node_modules' || sub.name === '.git') continue;\n const deepCandidate = path.join(projectRoot, entry.name, sub.name, filePath);\n if (fs.existsSync(deepCandidate)) {\n const content = fs.readFileSync(deepCandidate, 'utf8');\n fileCache.set(filePath, content);\n return content;\n }\n }\n } catch { /* skip */ }\n }\n } catch { /* fall through */ }\n fileCache.set(filePath, null);\n return null;\n },\n getProjectRoot(): string {\n return projectRoot;\n },\n getAllFiles(): string[] {\n return allIndexedFiles;\n },\n };\n}\n\n// \u2500\u2500 detectFrameworks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Detect which frameworks are used in a project.\n * Mirrors CodeGraph detectFrameworks() \u2014 uses ResolutionContext for file access.\n * Records detected framework names in config via updateConfig().\n */\nexport async function detectFrameworks(projectRoot: string, db?: GraphDatabase): Promise<FrameworkResolver[]> {\n // If no db provided, fall back to package.json-only detection\n if (!db) {\n return detectFromPackageJson(projectRoot);\n }\n\n const context = buildResolutionContext(projectRoot, db);\n const detected: FrameworkResolver[] = [];\n\n for (const resolver of FRAMEWORK_RESOLVERS) {\n try {\n if (resolver.detect(context)) {\n logDebug(`detectFrameworks: detected ${resolver.name}`);\n detected.push(resolver);\n }\n } catch (err) {\n logWarn(`detectFrameworks: error detecting ${resolver.name}`, { error: String(err) });\n }\n }\n\n if (detected.length > 0) {\n await updateConfig(projectRoot, { frameworkHints: detected.map(f => f.name) });\n }\n\n return detected;\n}\n\n/**\n * Lightweight fallback: detect frameworks from package.json only (no DB needed).\n */\nasync function detectFromPackageJson(projectRoot: string): Promise<FrameworkResolver[]> {\n const pkgPath = path.join(projectRoot, 'package.json');\n if (!fs.existsSync(pkgPath)) return [];\n\n let pkg: Record<string, unknown>;\n try {\n pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')) as Record<string, unknown>;\n } catch {\n return [];\n }\n\n const deps: Record<string, string> = {\n ...((pkg.dependencies as Record<string, string>) ?? {}),\n ...((pkg.devDependencies as Record<string, string>) ?? {}),\n };\n\n const PACKAGE_TO_RESOLVER: Record<string, string> = {\n react: 'react', next: 'react', 'react-native': 'react',\n svelte: 'svelte', '@sveltejs/kit': 'svelte',\n express: 'express', fastify: 'express', koa: 'express',\n };\n\n const detectedNames = new Set<string>();\n const detected: FrameworkResolver[] = [];\n\n for (const [pkg, resolverName] of Object.entries(PACKAGE_TO_RESOLVER)) {\n if (Object.prototype.hasOwnProperty.call(deps, pkg) && !detectedNames.has(resolverName)) {\n const resolver = getFrameworkResolver(resolverName);\n if (resolver) {\n detectedNames.add(resolverName);\n detected.push(resolver);\n }\n }\n }\n\n if (detected.length > 0) {\n await updateConfig(projectRoot, { frameworkHints: detected.map(f => f.name) });\n }\n\n return detected;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["import_react", "import_svelte", "import_express", "import_python", "import_ruby", "import_java", "import_go", "import_rust", "import_csharp", "import_swift", "import_laravel", "import_elixir", "import_scala", "import_vue", "import_solidity", "import_iac", "import_terraform", "import_pulumi", "import_cloudformation", "import_kubernetes", "import_docker", "import_ansible", "import_angular", "import_amplify", "import_flutter", "pkg"]
|
|
7
7
|
}
|
|
@@ -54,11 +54,12 @@ const SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
|
54
54
|
"zig-out"
|
|
55
55
|
]);
|
|
56
56
|
class SecurityManifestAdapter {
|
|
57
|
-
constructor(projectRoot) {
|
|
57
|
+
constructor(projectRoot, scanOptions) {
|
|
58
58
|
this.plugins = /* @__PURE__ */ new Map();
|
|
59
59
|
/** Standalone plugins have no corresponding architecture parser — they own their own discovery. */
|
|
60
60
|
this.standalonePlugins = /* @__PURE__ */ new Map();
|
|
61
61
|
this.projectRoot = projectRoot;
|
|
62
|
+
this.scanOptions = scanOptions ?? {};
|
|
62
63
|
this.archParsers = (0, import_manifest.getAllManifestParsers)();
|
|
63
64
|
}
|
|
64
65
|
/**
|
|
@@ -93,10 +94,24 @@ class SecurityManifestAdapter {
|
|
|
93
94
|
/**
|
|
94
95
|
* Discover all manifest files in the project tree.
|
|
95
96
|
* Includes files known to architecture parsers AND files from standalone plugins.
|
|
97
|
+
* Respects config include/exclude patterns when provided.
|
|
96
98
|
* Returns absolute paths.
|
|
97
99
|
*/
|
|
98
100
|
discoverManifests() {
|
|
99
|
-
|
|
101
|
+
const allManifests = this._findManifests(this.projectRoot);
|
|
102
|
+
if (!this.scanOptions.include || this.scanOptions.include.length === 0) {
|
|
103
|
+
return allManifests;
|
|
104
|
+
}
|
|
105
|
+
const picomatch = require("picomatch");
|
|
106
|
+
const includeMatchers = this.scanOptions.include.map((p) => picomatch(p));
|
|
107
|
+
const excludeMatchers = (this.scanOptions.exclude ?? []).map((p) => picomatch(p));
|
|
108
|
+
return allManifests.filter((absPath) => {
|
|
109
|
+
const rel = path.relative(this.projectRoot, absPath).replace(/\\/g, "/");
|
|
110
|
+
const included = includeMatchers.some((m) => m(rel));
|
|
111
|
+
if (!included) return false;
|
|
112
|
+
const excluded = excludeMatchers.some((m) => m(rel));
|
|
113
|
+
return !excluded;
|
|
114
|
+
});
|
|
100
115
|
}
|
|
101
116
|
/**
|
|
102
117
|
* Run full discovery and extraction:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/security/manifest/adapter.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * SecurityManifestAdapter\n *\n * Wraps the existing architecture manifest parsers (11 parsers: npm, cargo, go,\n * gradle, maven, csproj, python, scala, elm, ocaml) to additionally extract\n * version constraints and dependency scopes that the architecture module does\n * not capture.\n *\n * The architecture parsers discover manifest files and extract package names into\n * `ArchPackage.externalDeps`. This adapter reuses that discovery infrastructure\n * and layers version/scope extraction on top via ecosystem-specific plugins.\n */\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { getAllManifestParsers } from '../../architecture/manifest/index';\nimport type { ManifestParser as ArchManifestParser, ArchPackage } from '../../architecture/types';\nimport type { ParsedDependency, ManifestParseResult } from '../types';\n\n// \u2500\u2500 Plugin Interface \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * A version extraction plugin provides ecosystem-specific logic to extract\n * version constraints and scopes from manifest files. Each plugin corresponds\n * to one or more architecture parsers and enhances their output with security-\n * relevant metadata.\n */\nexport interface VersionExtractionPlugin {\n /** Ecosystem identifier matching the architecture parser name (e.g. 'npm', 'maven', 'go') */\n ecosystem: string;\n /** Manifest filenames this plugin handles (should match the architecture parser's manifestFiles) */\n manifestFiles: string[];\n /** Returns true if this plugin can handle the given manifest file path */\n canExtract(manifestPath: string): boolean;\n /**\n * Extract version constraints and scopes from a manifest file.\n * This is called AFTER the architecture parser has already discovered the manifest.\n * The plugin receives the manifest path and project root, and returns detailed\n * dependency information including versions and scopes.\n */\n extract(manifestPath: string, projectRoot: string): Promise<ParsedDependency[]>;\n}\n\n// \u2500\u2500 Directories to skip (mirrors architecture module's SKIP_DIRS) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst SKIP_DIRS = new Set([\n 'node_modules', '.git', 'dist', 'build', 'target', '.gradle', '__pycache__',\n '.kirograph', 'vendor', '.cache', 'coverage', '.nyc_output', '_build', '_opam',\n 'elm-stuff', 'zig-cache', 'zig-out',\n]);\n\n// \u2500\u2500 SecurityManifestAdapter \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport class SecurityManifestAdapter {\n private readonly archParsers: ArchManifestParser[];\n private readonly plugins: Map<string, VersionExtractionPlugin> = new Map();\n /** Standalone plugins have no corresponding architecture parser \u2014 they own their own discovery. */\n private readonly standalonePlugins: Map<string, VersionExtractionPlugin> = new Map();\n private readonly projectRoot: string;\n\n constructor(projectRoot: string) {\n this.projectRoot = projectRoot;\n this.archParsers = getAllManifestParsers();\n }\n\n /**\n * Register a version extraction plugin for an ecosystem that has a\n * corresponding architecture parser. The plugin's `ecosystem` field\n * must equal the architecture parser's `name` for the lookup to work.\n */\n registerPlugin(plugin: VersionExtractionPlugin): void {\n this.plugins.set(plugin.ecosystem, plugin);\n }\n\n /**\n * Register a standalone version extraction plugin for an ecosystem that\n * has NO corresponding architecture parser. The plugin's `manifestFiles`\n * list drives discovery \u2014 the adapter will scan for those filenames and\n * invoke the plugin directly when found.\n */\n registerStandalonePlugin(plugin: VersionExtractionPlugin): void {\n this.standalonePlugins.set(plugin.ecosystem, plugin);\n }\n\n /**\n * Get all registered version extraction plugins (wrapped + standalone).\n */\n getPlugins(): VersionExtractionPlugin[] {\n return [...this.plugins.values(), ...this.standalonePlugins.values()];\n }\n\n /**\n * Get the architecture parsers being wrapped.\n */\n getArchParsers(): ArchManifestParser[] {\n return this.archParsers;\n }\n\n /**\n * Discover all manifest files in the project tree.\n * Includes files known to architecture parsers AND files from standalone plugins.\n * Returns absolute paths.\n */\n discoverManifests(): string[] {\n return this._findManifests(this.projectRoot);\n }\n\n /**\n * Run full discovery and extraction:\n * 1. Discover manifest files (reusing architecture's SKIP_DIRS logic)\n * 2. For each manifest, find the matching architecture parser\n * 3. If a version extraction plugin is registered for that ecosystem,\n * use it to extract detailed version/scope information\n * 4. Otherwise, fall back to basic extraction from the architecture parser's\n * ArchPackage.externalDeps (names only, no version info)\n */\n async extractAll(): Promise<ManifestParseResult> {\n const manifestPaths = this.discoverManifests();\n const allDeps: ParsedDependency[] = [];\n const warnings: Array<{ file: string; line?: number; message: string }> = [];\n const errors: Array<{ file: string; message: string }> = [];\n let manifestsParsed = 0;\n\n for (const manifestPath of manifestPaths) {\n const archParser = this.archParsers.find(p => p.canParse(manifestPath));\n if (!archParser) continue;\n\n const relManifest = path.relative(this.projectRoot, manifestPath).replace(/\\\\/g, '/');\n const plugin = this.plugins.get(archParser.name);\n\n if (plugin && plugin.canExtract(manifestPath)) {\n // Use the version extraction plugin for detailed parsing\n try {\n const deps = await plugin.extract(manifestPath, this.projectRoot);\n allDeps.push(...deps);\n manifestsParsed++;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n errors.push({ file: relManifest, message });\n }\n } else {\n // Fallback: use architecture parser output (names only, no versions)\n try {\n const packages = await archParser.parse(manifestPath, this.projectRoot);\n const deps = this._fallbackExtract(packages, archParser.name, relManifest);\n allDeps.push(...deps);\n manifestsParsed++;\n\n if (!plugin) {\n warnings.push({\n file: relManifest,\n message: `No version extraction plugin registered for ecosystem '${archParser.name}'; dependency names extracted without version constraints`,\n });\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n errors.push({ file: relManifest, message });\n }\n }\n }\n\n return {\n dependenciesCreated: allDeps.length,\n manifestsParsed,\n warnings,\n errors,\n };\n }\n\n /**\n * Extract dependencies for a single manifest file.\n * Tries standalone plugins first (when there is no arch parser), then\n * wrapped plugins, then falls back to arch parser output.\n */\n async extractFromManifest(manifestPath: string): Promise<ParsedDependency[]> {\n const archParser = this.archParsers.find(p => p.canParse(manifestPath));\n\n if (!archParser) {\n // No arch parser \u2014 try standalone plugins\n const standalonePlugin = [...this.standalonePlugins.values()]\n .find(p => p.canExtract(manifestPath));\n if (standalonePlugin) {\n return standalonePlugin.extract(manifestPath, this.projectRoot);\n }\n return [];\n }\n\n const plugin = this.plugins.get(archParser.name);\n\n if (plugin && plugin.canExtract(manifestPath)) {\n return plugin.extract(manifestPath, this.projectRoot);\n }\n\n // Fallback: architecture parser output (names only)\n const packages = await archParser.parse(manifestPath, this.projectRoot);\n const relManifest = path.relative(this.projectRoot, manifestPath).replace(/\\\\/g, '/');\n return this._fallbackExtract(packages, archParser.name, relManifest);\n }\n\n /**\n * Get the ecosystem name for a given manifest file path.\n * Returns undefined if no parser handles this file.\n */\n getEcosystem(manifestPath: string): string | undefined {\n const parser = this.archParsers.find(p => p.canParse(manifestPath));\n return parser?.name;\n }\n\n // \u2500\u2500 Private helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Fallback extraction: converts ArchPackage.externalDeps (names only) into\n * ParsedDependency records with empty version constraints and default scope.\n */\n private _fallbackExtract(\n packages: ArchPackage[],\n ecosystem: string,\n sourceManifest: string,\n ): ParsedDependency[] {\n const deps: ParsedDependency[] = [];\n for (const pkg of packages) {\n if (!pkg.externalDeps) continue;\n for (const depName of pkg.externalDeps) {\n deps.push({\n name: depName,\n declaredConstraint: '*',\n scope: 'production',\n ecosystem,\n sourceManifest,\n });\n }\n }\n return deps;\n }\n\n /**\n * Walk the project directory tree looking for manifest files.\n * Mirrors the architecture module's _findManifests() logic, respecting SKIP_DIRS.\n * Includes manifest filenames from standalone plugins in addition to arch parsers.\n */\n private _findManifests(dir: string, results: string[] = []): string[] {\n const manifestFilenames = new Set([\n ...this.archParsers.flatMap(p => p.manifestFiles),\n ...Array.from(this.standalonePlugins.values()).flatMap(p => p.manifestFiles),\n ]);\n\n return this._walkDir(dir, manifestFilenames, results);\n }\n\n private _walkDir(dir: string, manifestFilenames: Set<string>, results: string[]): string[] {\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(dir, { withFileTypes: true });\n } catch {\n return results;\n }\n\n for (const entry of entries) {\n if (entry.isDirectory()) {\n if (!SKIP_DIRS.has(entry.name)) {\n this._walkDir(path.join(dir, entry.name), manifestFilenames, results);\n }\n } else if (entry.isFile()) {\n if (manifestFilenames.has(entry.name) || entry.name.endsWith('.csproj')) {\n results.push(path.join(dir, entry.name));\n }\n }\n }\n\n return results;\n }\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAYA,SAAoB;AACpB,WAAsB;AACtB,sBAAsC;
|
|
4
|
+
"sourcesContent": ["/**\n * SecurityManifestAdapter\n *\n * Wraps the existing architecture manifest parsers (11 parsers: npm, cargo, go,\n * gradle, maven, csproj, python, scala, elm, ocaml) to additionally extract\n * version constraints and dependency scopes that the architecture module does\n * not capture.\n *\n * The architecture parsers discover manifest files and extract package names into\n * `ArchPackage.externalDeps`. This adapter reuses that discovery infrastructure\n * and layers version/scope extraction on top via ecosystem-specific plugins.\n */\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { getAllManifestParsers } from '../../architecture/manifest/index';\nimport type { ManifestParser as ArchManifestParser, ArchPackage } from '../../architecture/types';\nimport type { ParsedDependency, ManifestParseResult } from '../types';\n\n// \u2500\u2500 Include/Exclude filter support \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface ManifestScanOptions {\n /** Glob patterns for paths to include (relative to projectRoot). Empty = all. */\n include?: string[];\n /** Glob patterns for paths to exclude (relative to projectRoot). */\n exclude?: string[];\n}\n\n// \u2500\u2500 Plugin Interface \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * A version extraction plugin provides ecosystem-specific logic to extract\n * version constraints and scopes from manifest files. Each plugin corresponds\n * to one or more architecture parsers and enhances their output with security-\n * relevant metadata.\n */\nexport interface VersionExtractionPlugin {\n /** Ecosystem identifier matching the architecture parser name (e.g. 'npm', 'maven', 'go') */\n ecosystem: string;\n /** Manifest filenames this plugin handles (should match the architecture parser's manifestFiles) */\n manifestFiles: string[];\n /** Returns true if this plugin can handle the given manifest file path */\n canExtract(manifestPath: string): boolean;\n /**\n * Extract version constraints and scopes from a manifest file.\n * This is called AFTER the architecture parser has already discovered the manifest.\n * The plugin receives the manifest path and project root, and returns detailed\n * dependency information including versions and scopes.\n */\n extract(manifestPath: string, projectRoot: string): Promise<ParsedDependency[]>;\n}\n\n// \u2500\u2500 Directories to skip (mirrors architecture module's SKIP_DIRS) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst SKIP_DIRS = new Set([\n 'node_modules', '.git', 'dist', 'build', 'target', '.gradle', '__pycache__',\n '.kirograph', 'vendor', '.cache', 'coverage', '.nyc_output', '_build', '_opam',\n 'elm-stuff', 'zig-cache', 'zig-out',\n]);\n\n// \u2500\u2500 SecurityManifestAdapter \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport class SecurityManifestAdapter {\n private readonly archParsers: ArchManifestParser[];\n private readonly plugins: Map<string, VersionExtractionPlugin> = new Map();\n /** Standalone plugins have no corresponding architecture parser \u2014 they own their own discovery. */\n private readonly standalonePlugins: Map<string, VersionExtractionPlugin> = new Map();\n private readonly projectRoot: string;\n private readonly scanOptions: ManifestScanOptions;\n\n constructor(projectRoot: string, scanOptions?: ManifestScanOptions) {\n this.projectRoot = projectRoot;\n this.scanOptions = scanOptions ?? {};\n this.archParsers = getAllManifestParsers();\n }\n\n /**\n * Register a version extraction plugin for an ecosystem that has a\n * corresponding architecture parser. The plugin's `ecosystem` field\n * must equal the architecture parser's `name` for the lookup to work.\n */\n registerPlugin(plugin: VersionExtractionPlugin): void {\n this.plugins.set(plugin.ecosystem, plugin);\n }\n\n /**\n * Register a standalone version extraction plugin for an ecosystem that\n * has NO corresponding architecture parser. The plugin's `manifestFiles`\n * list drives discovery \u2014 the adapter will scan for those filenames and\n * invoke the plugin directly when found.\n */\n registerStandalonePlugin(plugin: VersionExtractionPlugin): void {\n this.standalonePlugins.set(plugin.ecosystem, plugin);\n }\n\n /**\n * Get all registered version extraction plugins (wrapped + standalone).\n */\n getPlugins(): VersionExtractionPlugin[] {\n return [...this.plugins.values(), ...this.standalonePlugins.values()];\n }\n\n /**\n * Get the architecture parsers being wrapped.\n */\n getArchParsers(): ArchManifestParser[] {\n return this.archParsers;\n }\n\n /**\n * Discover all manifest files in the project tree.\n * Includes files known to architecture parsers AND files from standalone plugins.\n * Respects config include/exclude patterns when provided.\n * Returns absolute paths.\n */\n discoverManifests(): string[] {\n const allManifests = this._findManifests(this.projectRoot);\n\n // If no include patterns, return all discovered manifests\n if (!this.scanOptions.include || this.scanOptions.include.length === 0) {\n return allManifests;\n }\n\n // Filter manifests to only those within included paths\n const picomatch = require('picomatch');\n const includeMatchers = this.scanOptions.include.map((p: string) => picomatch(p));\n const excludeMatchers = (this.scanOptions.exclude ?? []).map((p: string) => picomatch(p));\n\n return allManifests.filter(absPath => {\n const rel = path.relative(this.projectRoot, absPath).replace(/\\\\/g, '/');\n const included = includeMatchers.some((m: (s: string) => boolean) => m(rel));\n if (!included) return false;\n const excluded = excludeMatchers.some((m: (s: string) => boolean) => m(rel));\n return !excluded;\n });\n }\n\n /**\n * Run full discovery and extraction:\n * 1. Discover manifest files (reusing architecture's SKIP_DIRS logic)\n * 2. For each manifest, find the matching architecture parser\n * 3. If a version extraction plugin is registered for that ecosystem,\n * use it to extract detailed version/scope information\n * 4. Otherwise, fall back to basic extraction from the architecture parser's\n * ArchPackage.externalDeps (names only, no version info)\n */\n async extractAll(): Promise<ManifestParseResult> {\n const manifestPaths = this.discoverManifests();\n const allDeps: ParsedDependency[] = [];\n const warnings: Array<{ file: string; line?: number; message: string }> = [];\n const errors: Array<{ file: string; message: string }> = [];\n let manifestsParsed = 0;\n\n for (const manifestPath of manifestPaths) {\n const archParser = this.archParsers.find(p => p.canParse(manifestPath));\n if (!archParser) continue;\n\n const relManifest = path.relative(this.projectRoot, manifestPath).replace(/\\\\/g, '/');\n const plugin = this.plugins.get(archParser.name);\n\n if (plugin && plugin.canExtract(manifestPath)) {\n // Use the version extraction plugin for detailed parsing\n try {\n const deps = await plugin.extract(manifestPath, this.projectRoot);\n allDeps.push(...deps);\n manifestsParsed++;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n errors.push({ file: relManifest, message });\n }\n } else {\n // Fallback: use architecture parser output (names only, no versions)\n try {\n const packages = await archParser.parse(manifestPath, this.projectRoot);\n const deps = this._fallbackExtract(packages, archParser.name, relManifest);\n allDeps.push(...deps);\n manifestsParsed++;\n\n if (!plugin) {\n warnings.push({\n file: relManifest,\n message: `No version extraction plugin registered for ecosystem '${archParser.name}'; dependency names extracted without version constraints`,\n });\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n errors.push({ file: relManifest, message });\n }\n }\n }\n\n return {\n dependenciesCreated: allDeps.length,\n manifestsParsed,\n warnings,\n errors,\n };\n }\n\n /**\n * Extract dependencies for a single manifest file.\n * Tries standalone plugins first (when there is no arch parser), then\n * wrapped plugins, then falls back to arch parser output.\n */\n async extractFromManifest(manifestPath: string): Promise<ParsedDependency[]> {\n const archParser = this.archParsers.find(p => p.canParse(manifestPath));\n\n if (!archParser) {\n // No arch parser \u2014 try standalone plugins\n const standalonePlugin = [...this.standalonePlugins.values()]\n .find(p => p.canExtract(manifestPath));\n if (standalonePlugin) {\n return standalonePlugin.extract(manifestPath, this.projectRoot);\n }\n return [];\n }\n\n const plugin = this.plugins.get(archParser.name);\n\n if (plugin && plugin.canExtract(manifestPath)) {\n return plugin.extract(manifestPath, this.projectRoot);\n }\n\n // Fallback: architecture parser output (names only)\n const packages = await archParser.parse(manifestPath, this.projectRoot);\n const relManifest = path.relative(this.projectRoot, manifestPath).replace(/\\\\/g, '/');\n return this._fallbackExtract(packages, archParser.name, relManifest);\n }\n\n /**\n * Get the ecosystem name for a given manifest file path.\n * Returns undefined if no parser handles this file.\n */\n getEcosystem(manifestPath: string): string | undefined {\n const parser = this.archParsers.find(p => p.canParse(manifestPath));\n return parser?.name;\n }\n\n // \u2500\u2500 Private helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Fallback extraction: converts ArchPackage.externalDeps (names only) into\n * ParsedDependency records with empty version constraints and default scope.\n */\n private _fallbackExtract(\n packages: ArchPackage[],\n ecosystem: string,\n sourceManifest: string,\n ): ParsedDependency[] {\n const deps: ParsedDependency[] = [];\n for (const pkg of packages) {\n if (!pkg.externalDeps) continue;\n for (const depName of pkg.externalDeps) {\n deps.push({\n name: depName,\n declaredConstraint: '*',\n scope: 'production',\n ecosystem,\n sourceManifest,\n });\n }\n }\n return deps;\n }\n\n /**\n * Walk the project directory tree looking for manifest files.\n * Mirrors the architecture module's _findManifests() logic, respecting SKIP_DIRS.\n * Includes manifest filenames from standalone plugins in addition to arch parsers.\n */\n private _findManifests(dir: string, results: string[] = []): string[] {\n const manifestFilenames = new Set([\n ...this.archParsers.flatMap(p => p.manifestFiles),\n ...Array.from(this.standalonePlugins.values()).flatMap(p => p.manifestFiles),\n ]);\n\n return this._walkDir(dir, manifestFilenames, results);\n }\n\n private _walkDir(dir: string, manifestFilenames: Set<string>, results: string[]): string[] {\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(dir, { withFileTypes: true });\n } catch {\n return results;\n }\n\n for (const entry of entries) {\n if (entry.isDirectory()) {\n if (!SKIP_DIRS.has(entry.name)) {\n this._walkDir(path.join(dir, entry.name), manifestFilenames, results);\n }\n } else if (entry.isFile()) {\n if (manifestFilenames.has(entry.name) || entry.name.endsWith('.csproj')) {\n results.push(path.join(dir, entry.name));\n }\n }\n }\n\n return results;\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -237,10 +237,10 @@ function deduplicateDependencies(deps) {
|
|
|
237
237
|
return [...map.values()];
|
|
238
238
|
}
|
|
239
239
|
class ManifestParser {
|
|
240
|
-
constructor(db, projectRoot) {
|
|
240
|
+
constructor(db, projectRoot, scanOptions) {
|
|
241
241
|
this.db = db;
|
|
242
242
|
this.projectRoot = projectRoot;
|
|
243
|
-
this.adapter = new import_adapter.SecurityManifestAdapter(projectRoot);
|
|
243
|
+
this.adapter = new import_adapter.SecurityManifestAdapter(projectRoot, scanOptions);
|
|
244
244
|
this.adapter.registerPlugin(createNpmPlugin());
|
|
245
245
|
this.adapter.registerPlugin(createMavenPlugin());
|
|
246
246
|
this.adapter.registerPlugin(createGoPlugin());
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/security/manifest/parser.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * ManifestParser Orchestrator\n *\n * Orchestrates manifest discovery, version extraction, deduplication, and\n * database persistence. Uses the SecurityManifestAdapter for discovery and\n * extraction, registers all 12 version extraction plugins:\n * Wrapped (arch parser exists): npm, maven, go, pip, cargo, nuget, gradle\n * Standalone (no arch parser): rubygems, composer, swift, pubspec, hex\n *\n * Node ID format: `dep:<ecosystem>:<name>` (e.g. `dep:npm:express`)\n */\nimport { SecurityManifestAdapter, type VersionExtractionPlugin } from './adapter';\nimport { parseNpmManifest } from './plugins/npm';\nimport { parseMavenManifest } from './plugins/maven';\nimport { parseGoManifest } from './plugins/go';\nimport { parsePipManifest } from './plugins/pip';\nimport { parseCargoManifest } from './plugins/cargo';\nimport { parseNugetManifest } from './plugins/nuget';\nimport { parseGradleManifest } from './plugins/gradle';\nimport { parseRubygemsManifest } from './plugins/rubygems';\nimport { parseComposerManifest } from './plugins/composer';\nimport { parseSwiftManifest } from './plugins/swift';\nimport { parsePubspecManifest } from './plugins/pubspec';\nimport { parseHexManifest } from './plugins/hex';\nimport { parsePyprojectManifest } from './plugins/pyproject';\nimport type { GraphDatabase } from '../../db/database';\nimport type { ParsedDependency, ManifestParseResult } from '../types';\nimport type { Node, Edge } from '../../types';\nimport { logWarn, logError } from '../../errors';\n\n// \u2500\u2500 Plugin Definitions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Create the npm version extraction plugin wrapping parseNpmManifest.\n */\nfunction createNpmPlugin(): VersionExtractionPlugin {\n return {\n ecosystem: 'npm',\n manifestFiles: ['package.json'],\n canExtract(manifestPath: string): boolean {\n return manifestPath.endsWith('package.json');\n },\n extract: parseNpmManifest,\n };\n}\n\n/**\n * Create the Maven version extraction plugin wrapping parseMavenManifest.\n */\nfunction createMavenPlugin(): VersionExtractionPlugin {\n return {\n ecosystem: 'maven',\n manifestFiles: ['pom.xml'],\n canExtract(manifestPath: string): boolean {\n return manifestPath.endsWith('pom.xml');\n },\n extract: parseMavenManifest,\n };\n}\n\n/**\n * Create the Go version extraction plugin wrapping parseGoManifest.\n */\nfunction createGoPlugin(): VersionExtractionPlugin {\n return {\n ecosystem: 'go',\n manifestFiles: ['go.mod'],\n canExtract(manifestPath: string): boolean {\n return manifestPath.endsWith('go.mod');\n },\n extract: parseGoManifest,\n };\n}\n\n/**\n * Create the pip version extraction plugin wrapping parsePipManifest.\n */\nfunction createPipPlugin(): VersionExtractionPlugin {\n return {\n ecosystem: 'python',\n manifestFiles: ['requirements.txt'],\n canExtract(manifestPath: string): boolean {\n return manifestPath.endsWith('requirements.txt');\n },\n extract: parsePipManifest,\n };\n}\n\n/**\n * Create the Cargo version extraction plugin wrapping parseCargoManifest.\n */\nfunction createCargoPlugin(): VersionExtractionPlugin {\n return {\n ecosystem: 'cargo',\n manifestFiles: ['Cargo.toml'],\n canExtract(manifestPath: string): boolean {\n return manifestPath.endsWith('Cargo.toml');\n },\n extract: parseCargoManifest,\n };\n}\n\n/**\n * Create the NuGet version extraction plugin wrapping parseNugetManifest.\n * ecosystem = 'csproj' to match the architecture parser name for lookup;\n * ParsedDependency.ecosystem is set to 'nuget' inside the plugin function.\n */\nfunction createNugetPlugin(): VersionExtractionPlugin {\n return {\n ecosystem: 'csproj',\n manifestFiles: ['.csproj'],\n canExtract(manifestPath: string): boolean {\n return manifestPath.endsWith('.csproj');\n },\n extract: parseNugetManifest,\n };\n}\n\n/**\n * Create the Gradle version extraction plugin wrapping parseGradleManifest.\n */\nfunction createGradlePlugin(): VersionExtractionPlugin {\n return {\n ecosystem: 'gradle',\n manifestFiles: ['build.gradle', 'build.gradle.kts'],\n canExtract(manifestPath: string): boolean {\n const base = manifestPath.split('/').pop() ?? '';\n return base === 'build.gradle' || base === 'build.gradle.kts';\n },\n extract: parseGradleManifest,\n };\n}\n\nfunction createPyprojectPlugin(): VersionExtractionPlugin {\n return {\n ecosystem: 'pyproject',\n manifestFiles: ['pyproject.toml'],\n canExtract(manifestPath: string): boolean {\n return manifestPath.endsWith('pyproject.toml');\n },\n extract: parsePyprojectManifest,\n };\n}\n\n// \u2500\u2500 Standalone Plugin Factories \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction createRubygemsPlugin(): VersionExtractionPlugin {\n return {\n ecosystem: 'rubygems',\n manifestFiles: ['Gemfile'],\n canExtract(manifestPath: string): boolean {\n return manifestPath.endsWith('/Gemfile') || manifestPath === 'Gemfile';\n },\n extract: parseRubygemsManifest,\n };\n}\n\nfunction createComposerPlugin(): VersionExtractionPlugin {\n return {\n ecosystem: 'composer',\n manifestFiles: ['composer.json'],\n canExtract(manifestPath: string): boolean {\n return manifestPath.endsWith('composer.json');\n },\n extract: parseComposerManifest,\n };\n}\n\nfunction createSwiftPlugin(): VersionExtractionPlugin {\n return {\n ecosystem: 'swift',\n manifestFiles: ['Package.swift'],\n canExtract(manifestPath: string): boolean {\n return manifestPath.endsWith('Package.swift');\n },\n extract: parseSwiftManifest,\n };\n}\n\nfunction createPubspecPlugin(): VersionExtractionPlugin {\n return {\n ecosystem: 'pub',\n manifestFiles: ['pubspec.yaml'],\n canExtract(manifestPath: string): boolean {\n return manifestPath.endsWith('pubspec.yaml');\n },\n extract: parsePubspecManifest,\n };\n}\n\nfunction createHexPlugin(): VersionExtractionPlugin {\n return {\n ecosystem: 'hex',\n manifestFiles: ['mix.exs'],\n canExtract(manifestPath: string): boolean {\n return manifestPath.endsWith('mix.exs');\n },\n extract: parseHexManifest,\n };\n}\n\n// \u2500\u2500 Version Comparison \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Compare two version strings. Returns:\n * - positive if a > b\n * - negative if a < b\n * - 0 if equal\n *\n * Handles semver-like versions (1.2.3), Go pseudo-versions, and constraint\n * strings. Falls back to lexicographic comparison for non-numeric versions.\n */\nexport function compareVersions(a: string, b: string): number {\n // Strip leading 'v' (common in Go modules)\n const cleanA = a.startsWith('v') ? a.slice(1) : a;\n const cleanB = b.startsWith('v') ? b.slice(1) : b;\n\n // Strip common constraint prefixes (^, ~, >=, <=, =, >, <)\n const numericA = cleanA.replace(/^[~^>=<]+/, '');\n const numericB = cleanB.replace(/^[~^>=<]+/, '');\n\n const partsA = numericA.split('.');\n const partsB = numericB.split('.');\n\n const maxLen = Math.max(partsA.length, partsB.length);\n for (let i = 0; i < maxLen; i++) {\n const segA = partsA[i] ?? '0';\n const segB = partsB[i] ?? '0';\n\n // Try numeric comparison first\n const numA = parseInt(segA, 10);\n const numB = parseInt(segB, 10);\n\n if (!isNaN(numA) && !isNaN(numB)) {\n if (numA !== numB) return numA - numB;\n // If numeric parts are equal but segments differ (e.g. \"3-beta\" vs \"3\"),\n // compare the remaining suffix\n const suffixA = segA.slice(String(numA).length);\n const suffixB = segB.slice(String(numB).length);\n if (suffixA !== suffixB) {\n // Pre-release suffixes (starting with -) are lower than no suffix\n if (suffixA && !suffixB) return -1;\n if (!suffixA && suffixB) return 1;\n return suffixA.localeCompare(suffixB);\n }\n } else {\n // Lexicographic fallback for non-numeric segments\n const cmp = segA.localeCompare(segB);\n if (cmp !== 0) return cmp;\n }\n }\n\n return 0;\n}\n\n/**\n * Get the effective version string for comparison purposes.\n * Prefers resolvedVersion over declaredConstraint.\n */\nfunction getEffectiveVersion(dep: ParsedDependency): string {\n return dep.resolvedVersion ?? dep.declaredConstraint;\n}\n\n// \u2500\u2500 Deduplication \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * A deduplicated dependency entry combining data from multiple manifests.\n */\ninterface DeduplicatedDependency {\n name: string;\n ecosystem: string;\n /** The highest declared constraint across all manifests */\n declaredConstraint: string;\n /** The highest resolved version across all manifests (if any) */\n resolvedVersion?: string;\n /** Scope from the manifest with the highest version */\n scope: ParsedDependency['scope'];\n /** All source manifests that declared this dependency */\n sourceManifests: string[];\n /** License from manifest, if available */\n license?: string;\n}\n\n/**\n * Deduplicate dependencies by (name, ecosystem) pair, keeping the entry\n * with the highest version and collecting all source manifests.\n */\nexport function deduplicateDependencies(deps: ParsedDependency[]): DeduplicatedDependency[] {\n const map = new Map<string, DeduplicatedDependency>();\n\n for (const dep of deps) {\n const key = `${dep.ecosystem}:${dep.name}`;\n const existing = map.get(key);\n\n if (!existing) {\n map.set(key, {\n name: dep.name,\n ecosystem: dep.ecosystem,\n declaredConstraint: dep.declaredConstraint,\n resolvedVersion: dep.resolvedVersion,\n scope: dep.scope,\n sourceManifests: [dep.sourceManifest],\n license: dep.license,\n });\n } else {\n // Add source manifest if not already present\n if (!existing.sourceManifests.includes(dep.sourceManifest)) {\n existing.sourceManifests.push(dep.sourceManifest);\n }\n\n // Keep the highest version\n const existingVersion = existing.resolvedVersion ?? existing.declaredConstraint;\n const newVersion = getEffectiveVersion(dep);\n\n if (compareVersions(newVersion, existingVersion) > 0) {\n existing.declaredConstraint = dep.declaredConstraint;\n existing.resolvedVersion = dep.resolvedVersion;\n existing.scope = dep.scope;\n // Update license if the winning version entry has one\n if (dep.license !== undefined) {\n existing.license = dep.license;\n }\n } else if (existing.license === undefined && dep.license !== undefined) {\n // Even if version didn't win, adopt the license if we don't have one\n existing.license = dep.license;\n }\n }\n }\n\n return [...map.values()];\n}\n\n// \u2500\u2500 ManifestParser Class \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * ManifestParser orchestrates manifest discovery, parsing, deduplication,\n * and database persistence for the security module.\n */\nexport class ManifestParser {\n private readonly adapter: SecurityManifestAdapter;\n private readonly db: GraphDatabase;\n private readonly projectRoot: string;\n\n constructor(db: GraphDatabase, projectRoot: string) {\n this.db = db;\n this.projectRoot = projectRoot;\n this.adapter = new SecurityManifestAdapter(projectRoot);\n\n // Wrapped plugins (arch parser exists \u2014 ecosystem must match archParser.name)\n this.adapter.registerPlugin(createNpmPlugin());\n this.adapter.registerPlugin(createMavenPlugin());\n this.adapter.registerPlugin(createGoPlugin());\n this.adapter.registerPlugin(createPipPlugin());\n this.adapter.registerPlugin(createCargoPlugin());\n this.adapter.registerPlugin(createNugetPlugin());\n this.adapter.registerPlugin(createGradlePlugin());\n\n // Standalone plugins (no arch parser \u2014 own manifest discovery via manifestFiles)\n this.adapter.registerStandalonePlugin(createPyprojectPlugin());\n this.adapter.registerStandalonePlugin(createRubygemsPlugin());\n this.adapter.registerStandalonePlugin(createComposerPlugin());\n this.adapter.registerStandalonePlugin(createSwiftPlugin());\n this.adapter.registerStandalonePlugin(createPubspecPlugin());\n this.adapter.registerStandalonePlugin(createHexPlugin());\n }\n\n /**\n * Get the underlying adapter (useful for testing).\n */\n getAdapter(): SecurityManifestAdapter {\n return this.adapter;\n }\n\n /**\n * Run full manifest discovery, parsing, deduplication, and database persistence.\n *\n * Steps:\n * 1. Discover manifest files via the adapter (reuses architecture parsers)\n * 2. Extract dependencies with version/scope data via registered plugins\n * 3. Deduplicate: same (name, ecosystem) \u2192 single entry with highest version\n * 4. Create Dependency_Nodes in the database (nodes + sec_dependencies tables)\n * 5. Create `declared_in` edges linking dependencies to their source manifests\n */\n async parseAll(): Promise<ManifestParseResult> {\n const warnings: Array<{ file: string; line?: number; message: string }> = [];\n const errors: Array<{ file: string; message: string }> = [];\n\n // Step 1 & 2: Discover and extract all dependencies\n const allDeps: ParsedDependency[] = [];\n const manifestPaths = this.adapter.discoverManifests();\n let manifestsParsed = 0;\n\n for (const manifestPath of manifestPaths) {\n try {\n const deps = await this.adapter.extractFromManifest(manifestPath);\n if (deps.length > 0) {\n allDeps.push(...deps);\n manifestsParsed++;\n } else {\n // Manifest was parsed but had no dependencies \u2014 still counts\n manifestsParsed++;\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n const relPath = manifestPath.replace(this.projectRoot + '/', '').replace(/\\\\/g, '/');\n errors.push({ file: relPath, message });\n logError(`[sec:parser] Failed to parse manifest ${relPath}: ${message}`);\n }\n }\n\n if (allDeps.length === 0) {\n return {\n dependenciesCreated: 0,\n manifestsParsed,\n warnings,\n errors,\n };\n }\n\n // Step 3: Deduplicate by (name, ecosystem)\n const deduplicated = deduplicateDependencies(allDeps);\n\n // Step 4 & 5: Persist to database\n let dependenciesCreated = 0;\n const rawDb = this.db.getRawDb();\n\n for (const dep of deduplicated) {\n try {\n const nodeId = `dep:${dep.ecosystem}:${dep.name}`;\n\n // Create the node in the nodes table via upsertNode\n const node: Node = {\n id: nodeId,\n kind: 'dependency',\n name: dep.name,\n qualifiedName: `${dep.ecosystem}/${dep.name}`,\n filePath: dep.sourceManifests[0] ?? '',\n language: 'unknown',\n startLine: 0,\n endLine: 0,\n startColumn: 0,\n endColumn: 0,\n signature: dep.resolvedVersion ?? dep.declaredConstraint,\n isExported: false,\n isAsync: false,\n isStatic: false,\n isAbstract: false,\n updatedAt: Date.now(),\n };\n\n this.db.upsertNode(node);\n\n // Insert/update the sec_dependencies row\n rawDb.run(\n `INSERT OR REPLACE INTO sec_dependencies\n (node_id, ecosystem, package_name, declared_constraint, resolved_version, scope, source_manifests, license)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,\n [\n nodeId,\n dep.ecosystem,\n dep.name,\n dep.declaredConstraint,\n dep.resolvedVersion ?? null,\n dep.scope,\n JSON.stringify(dep.sourceManifests),\n dep.license ?? null,\n ],\n );\n\n // Create `declared_in` edges from the dependency to each source manifest\n for (const manifest of dep.sourceManifests) {\n const edge: Edge = {\n source: nodeId,\n target: manifest,\n kind: 'declared_in',\n };\n this.db.insertEdge(edge);\n }\n\n dependenciesCreated++;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n warnings.push({\n file: dep.sourceManifests[0] ?? 'unknown',\n message: `Failed to persist dependency ${dep.ecosystem}/${dep.name}: ${message}`,\n });\n logWarn(`[sec:parser] Failed to persist dependency ${dep.ecosystem}/${dep.name}: ${message}`);\n }\n }\n\n return {\n dependenciesCreated,\n manifestsParsed,\n warnings,\n errors,\n };\n }\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAWA,
|
|
4
|
+
"sourcesContent": ["/**\n * ManifestParser Orchestrator\n *\n * Orchestrates manifest discovery, version extraction, deduplication, and\n * database persistence. Uses the SecurityManifestAdapter for discovery and\n * extraction, registers all 12 version extraction plugins:\n * Wrapped (arch parser exists): npm, maven, go, pip, cargo, nuget, gradle\n * Standalone (no arch parser): rubygems, composer, swift, pubspec, hex\n *\n * Node ID format: `dep:<ecosystem>:<name>` (e.g. `dep:npm:express`)\n */\nimport { SecurityManifestAdapter, type VersionExtractionPlugin, type ManifestScanOptions } from './adapter';\nimport { parseNpmManifest } from './plugins/npm';\nimport { parseMavenManifest } from './plugins/maven';\nimport { parseGoManifest } from './plugins/go';\nimport { parsePipManifest } from './plugins/pip';\nimport { parseCargoManifest } from './plugins/cargo';\nimport { parseNugetManifest } from './plugins/nuget';\nimport { parseGradleManifest } from './plugins/gradle';\nimport { parseRubygemsManifest } from './plugins/rubygems';\nimport { parseComposerManifest } from './plugins/composer';\nimport { parseSwiftManifest } from './plugins/swift';\nimport { parsePubspecManifest } from './plugins/pubspec';\nimport { parseHexManifest } from './plugins/hex';\nimport { parsePyprojectManifest } from './plugins/pyproject';\nimport type { GraphDatabase } from '../../db/database';\nimport type { ParsedDependency, ManifestParseResult } from '../types';\nimport type { Node, Edge } from '../../types';\nimport { logWarn, logError } from '../../errors';\n\n// \u2500\u2500 Plugin Definitions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Create the npm version extraction plugin wrapping parseNpmManifest.\n */\nfunction createNpmPlugin(): VersionExtractionPlugin {\n return {\n ecosystem: 'npm',\n manifestFiles: ['package.json'],\n canExtract(manifestPath: string): boolean {\n return manifestPath.endsWith('package.json');\n },\n extract: parseNpmManifest,\n };\n}\n\n/**\n * Create the Maven version extraction plugin wrapping parseMavenManifest.\n */\nfunction createMavenPlugin(): VersionExtractionPlugin {\n return {\n ecosystem: 'maven',\n manifestFiles: ['pom.xml'],\n canExtract(manifestPath: string): boolean {\n return manifestPath.endsWith('pom.xml');\n },\n extract: parseMavenManifest,\n };\n}\n\n/**\n * Create the Go version extraction plugin wrapping parseGoManifest.\n */\nfunction createGoPlugin(): VersionExtractionPlugin {\n return {\n ecosystem: 'go',\n manifestFiles: ['go.mod'],\n canExtract(manifestPath: string): boolean {\n return manifestPath.endsWith('go.mod');\n },\n extract: parseGoManifest,\n };\n}\n\n/**\n * Create the pip version extraction plugin wrapping parsePipManifest.\n */\nfunction createPipPlugin(): VersionExtractionPlugin {\n return {\n ecosystem: 'python',\n manifestFiles: ['requirements.txt'],\n canExtract(manifestPath: string): boolean {\n return manifestPath.endsWith('requirements.txt');\n },\n extract: parsePipManifest,\n };\n}\n\n/**\n * Create the Cargo version extraction plugin wrapping parseCargoManifest.\n */\nfunction createCargoPlugin(): VersionExtractionPlugin {\n return {\n ecosystem: 'cargo',\n manifestFiles: ['Cargo.toml'],\n canExtract(manifestPath: string): boolean {\n return manifestPath.endsWith('Cargo.toml');\n },\n extract: parseCargoManifest,\n };\n}\n\n/**\n * Create the NuGet version extraction plugin wrapping parseNugetManifest.\n * ecosystem = 'csproj' to match the architecture parser name for lookup;\n * ParsedDependency.ecosystem is set to 'nuget' inside the plugin function.\n */\nfunction createNugetPlugin(): VersionExtractionPlugin {\n return {\n ecosystem: 'csproj',\n manifestFiles: ['.csproj'],\n canExtract(manifestPath: string): boolean {\n return manifestPath.endsWith('.csproj');\n },\n extract: parseNugetManifest,\n };\n}\n\n/**\n * Create the Gradle version extraction plugin wrapping parseGradleManifest.\n */\nfunction createGradlePlugin(): VersionExtractionPlugin {\n return {\n ecosystem: 'gradle',\n manifestFiles: ['build.gradle', 'build.gradle.kts'],\n canExtract(manifestPath: string): boolean {\n const base = manifestPath.split('/').pop() ?? '';\n return base === 'build.gradle' || base === 'build.gradle.kts';\n },\n extract: parseGradleManifest,\n };\n}\n\nfunction createPyprojectPlugin(): VersionExtractionPlugin {\n return {\n ecosystem: 'pyproject',\n manifestFiles: ['pyproject.toml'],\n canExtract(manifestPath: string): boolean {\n return manifestPath.endsWith('pyproject.toml');\n },\n extract: parsePyprojectManifest,\n };\n}\n\n// \u2500\u2500 Standalone Plugin Factories \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction createRubygemsPlugin(): VersionExtractionPlugin {\n return {\n ecosystem: 'rubygems',\n manifestFiles: ['Gemfile'],\n canExtract(manifestPath: string): boolean {\n return manifestPath.endsWith('/Gemfile') || manifestPath === 'Gemfile';\n },\n extract: parseRubygemsManifest,\n };\n}\n\nfunction createComposerPlugin(): VersionExtractionPlugin {\n return {\n ecosystem: 'composer',\n manifestFiles: ['composer.json'],\n canExtract(manifestPath: string): boolean {\n return manifestPath.endsWith('composer.json');\n },\n extract: parseComposerManifest,\n };\n}\n\nfunction createSwiftPlugin(): VersionExtractionPlugin {\n return {\n ecosystem: 'swift',\n manifestFiles: ['Package.swift'],\n canExtract(manifestPath: string): boolean {\n return manifestPath.endsWith('Package.swift');\n },\n extract: parseSwiftManifest,\n };\n}\n\nfunction createPubspecPlugin(): VersionExtractionPlugin {\n return {\n ecosystem: 'pub',\n manifestFiles: ['pubspec.yaml'],\n canExtract(manifestPath: string): boolean {\n return manifestPath.endsWith('pubspec.yaml');\n },\n extract: parsePubspecManifest,\n };\n}\n\nfunction createHexPlugin(): VersionExtractionPlugin {\n return {\n ecosystem: 'hex',\n manifestFiles: ['mix.exs'],\n canExtract(manifestPath: string): boolean {\n return manifestPath.endsWith('mix.exs');\n },\n extract: parseHexManifest,\n };\n}\n\n// \u2500\u2500 Version Comparison \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Compare two version strings. Returns:\n * - positive if a > b\n * - negative if a < b\n * - 0 if equal\n *\n * Handles semver-like versions (1.2.3), Go pseudo-versions, and constraint\n * strings. Falls back to lexicographic comparison for non-numeric versions.\n */\nexport function compareVersions(a: string, b: string): number {\n // Strip leading 'v' (common in Go modules)\n const cleanA = a.startsWith('v') ? a.slice(1) : a;\n const cleanB = b.startsWith('v') ? b.slice(1) : b;\n\n // Strip common constraint prefixes (^, ~, >=, <=, =, >, <)\n const numericA = cleanA.replace(/^[~^>=<]+/, '');\n const numericB = cleanB.replace(/^[~^>=<]+/, '');\n\n const partsA = numericA.split('.');\n const partsB = numericB.split('.');\n\n const maxLen = Math.max(partsA.length, partsB.length);\n for (let i = 0; i < maxLen; i++) {\n const segA = partsA[i] ?? '0';\n const segB = partsB[i] ?? '0';\n\n // Try numeric comparison first\n const numA = parseInt(segA, 10);\n const numB = parseInt(segB, 10);\n\n if (!isNaN(numA) && !isNaN(numB)) {\n if (numA !== numB) return numA - numB;\n // If numeric parts are equal but segments differ (e.g. \"3-beta\" vs \"3\"),\n // compare the remaining suffix\n const suffixA = segA.slice(String(numA).length);\n const suffixB = segB.slice(String(numB).length);\n if (suffixA !== suffixB) {\n // Pre-release suffixes (starting with -) are lower than no suffix\n if (suffixA && !suffixB) return -1;\n if (!suffixA && suffixB) return 1;\n return suffixA.localeCompare(suffixB);\n }\n } else {\n // Lexicographic fallback for non-numeric segments\n const cmp = segA.localeCompare(segB);\n if (cmp !== 0) return cmp;\n }\n }\n\n return 0;\n}\n\n/**\n * Get the effective version string for comparison purposes.\n * Prefers resolvedVersion over declaredConstraint.\n */\nfunction getEffectiveVersion(dep: ParsedDependency): string {\n return dep.resolvedVersion ?? dep.declaredConstraint;\n}\n\n// \u2500\u2500 Deduplication \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * A deduplicated dependency entry combining data from multiple manifests.\n */\ninterface DeduplicatedDependency {\n name: string;\n ecosystem: string;\n /** The highest declared constraint across all manifests */\n declaredConstraint: string;\n /** The highest resolved version across all manifests (if any) */\n resolvedVersion?: string;\n /** Scope from the manifest with the highest version */\n scope: ParsedDependency['scope'];\n /** All source manifests that declared this dependency */\n sourceManifests: string[];\n /** License from manifest, if available */\n license?: string;\n}\n\n/**\n * Deduplicate dependencies by (name, ecosystem) pair, keeping the entry\n * with the highest version and collecting all source manifests.\n */\nexport function deduplicateDependencies(deps: ParsedDependency[]): DeduplicatedDependency[] {\n const map = new Map<string, DeduplicatedDependency>();\n\n for (const dep of deps) {\n const key = `${dep.ecosystem}:${dep.name}`;\n const existing = map.get(key);\n\n if (!existing) {\n map.set(key, {\n name: dep.name,\n ecosystem: dep.ecosystem,\n declaredConstraint: dep.declaredConstraint,\n resolvedVersion: dep.resolvedVersion,\n scope: dep.scope,\n sourceManifests: [dep.sourceManifest],\n license: dep.license,\n });\n } else {\n // Add source manifest if not already present\n if (!existing.sourceManifests.includes(dep.sourceManifest)) {\n existing.sourceManifests.push(dep.sourceManifest);\n }\n\n // Keep the highest version\n const existingVersion = existing.resolvedVersion ?? existing.declaredConstraint;\n const newVersion = getEffectiveVersion(dep);\n\n if (compareVersions(newVersion, existingVersion) > 0) {\n existing.declaredConstraint = dep.declaredConstraint;\n existing.resolvedVersion = dep.resolvedVersion;\n existing.scope = dep.scope;\n // Update license if the winning version entry has one\n if (dep.license !== undefined) {\n existing.license = dep.license;\n }\n } else if (existing.license === undefined && dep.license !== undefined) {\n // Even if version didn't win, adopt the license if we don't have one\n existing.license = dep.license;\n }\n }\n }\n\n return [...map.values()];\n}\n\n// \u2500\u2500 ManifestParser Class \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * ManifestParser orchestrates manifest discovery, parsing, deduplication,\n * and database persistence for the security module.\n */\nexport class ManifestParser {\n private readonly adapter: SecurityManifestAdapter;\n private readonly db: GraphDatabase;\n private readonly projectRoot: string;\n\n constructor(db: GraphDatabase, projectRoot: string, scanOptions?: ManifestScanOptions) {\n this.db = db;\n this.projectRoot = projectRoot;\n this.adapter = new SecurityManifestAdapter(projectRoot, scanOptions);\n\n // Wrapped plugins (arch parser exists \u2014 ecosystem must match archParser.name)\n this.adapter.registerPlugin(createNpmPlugin());\n this.adapter.registerPlugin(createMavenPlugin());\n this.adapter.registerPlugin(createGoPlugin());\n this.adapter.registerPlugin(createPipPlugin());\n this.adapter.registerPlugin(createCargoPlugin());\n this.adapter.registerPlugin(createNugetPlugin());\n this.adapter.registerPlugin(createGradlePlugin());\n\n // Standalone plugins (no arch parser \u2014 own manifest discovery via manifestFiles)\n this.adapter.registerStandalonePlugin(createPyprojectPlugin());\n this.adapter.registerStandalonePlugin(createRubygemsPlugin());\n this.adapter.registerStandalonePlugin(createComposerPlugin());\n this.adapter.registerStandalonePlugin(createSwiftPlugin());\n this.adapter.registerStandalonePlugin(createPubspecPlugin());\n this.adapter.registerStandalonePlugin(createHexPlugin());\n }\n\n /**\n * Get the underlying adapter (useful for testing).\n */\n getAdapter(): SecurityManifestAdapter {\n return this.adapter;\n }\n\n /**\n * Run full manifest discovery, parsing, deduplication, and database persistence.\n *\n * Steps:\n * 1. Discover manifest files via the adapter (reuses architecture parsers)\n * 2. Extract dependencies with version/scope data via registered plugins\n * 3. Deduplicate: same (name, ecosystem) \u2192 single entry with highest version\n * 4. Create Dependency_Nodes in the database (nodes + sec_dependencies tables)\n * 5. Create `declared_in` edges linking dependencies to their source manifests\n */\n async parseAll(): Promise<ManifestParseResult> {\n const warnings: Array<{ file: string; line?: number; message: string }> = [];\n const errors: Array<{ file: string; message: string }> = [];\n\n // Step 1 & 2: Discover and extract all dependencies\n const allDeps: ParsedDependency[] = [];\n const manifestPaths = this.adapter.discoverManifests();\n let manifestsParsed = 0;\n\n for (const manifestPath of manifestPaths) {\n try {\n const deps = await this.adapter.extractFromManifest(manifestPath);\n if (deps.length > 0) {\n allDeps.push(...deps);\n manifestsParsed++;\n } else {\n // Manifest was parsed but had no dependencies \u2014 still counts\n manifestsParsed++;\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n const relPath = manifestPath.replace(this.projectRoot + '/', '').replace(/\\\\/g, '/');\n errors.push({ file: relPath, message });\n logError(`[sec:parser] Failed to parse manifest ${relPath}: ${message}`);\n }\n }\n\n if (allDeps.length === 0) {\n return {\n dependenciesCreated: 0,\n manifestsParsed,\n warnings,\n errors,\n };\n }\n\n // Step 3: Deduplicate by (name, ecosystem)\n const deduplicated = deduplicateDependencies(allDeps);\n\n // Step 4 & 5: Persist to database\n let dependenciesCreated = 0;\n const rawDb = this.db.getRawDb();\n\n for (const dep of deduplicated) {\n try {\n const nodeId = `dep:${dep.ecosystem}:${dep.name}`;\n\n // Create the node in the nodes table via upsertNode\n const node: Node = {\n id: nodeId,\n kind: 'dependency',\n name: dep.name,\n qualifiedName: `${dep.ecosystem}/${dep.name}`,\n filePath: dep.sourceManifests[0] ?? '',\n language: 'unknown',\n startLine: 0,\n endLine: 0,\n startColumn: 0,\n endColumn: 0,\n signature: dep.resolvedVersion ?? dep.declaredConstraint,\n isExported: false,\n isAsync: false,\n isStatic: false,\n isAbstract: false,\n updatedAt: Date.now(),\n };\n\n this.db.upsertNode(node);\n\n // Insert/update the sec_dependencies row\n rawDb.run(\n `INSERT OR REPLACE INTO sec_dependencies\n (node_id, ecosystem, package_name, declared_constraint, resolved_version, scope, source_manifests, license)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,\n [\n nodeId,\n dep.ecosystem,\n dep.name,\n dep.declaredConstraint,\n dep.resolvedVersion ?? null,\n dep.scope,\n JSON.stringify(dep.sourceManifests),\n dep.license ?? null,\n ],\n );\n\n // Create `declared_in` edges from the dependency to each source manifest\n for (const manifest of dep.sourceManifests) {\n const edge: Edge = {\n source: nodeId,\n target: manifest,\n kind: 'declared_in',\n };\n this.db.insertEdge(edge);\n }\n\n dependenciesCreated++;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n warnings.push({\n file: dep.sourceManifests[0] ?? 'unknown',\n message: `Failed to persist dependency ${dep.ecosystem}/${dep.name}: ${message}`,\n });\n logWarn(`[sec:parser] Failed to persist dependency ${dep.ecosystem}/${dep.name}: ${message}`);\n }\n }\n\n return {\n dependenciesCreated,\n manifestsParsed,\n warnings,\n errors,\n };\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -51,7 +51,10 @@ class SecurityPipeline {
|
|
|
51
51
|
duration: 0
|
|
52
52
|
};
|
|
53
53
|
onProgress?.("manifest-parsing", 0, 4);
|
|
54
|
-
const parser = new import_parser.ManifestParser(this.db, this.projectRoot
|
|
54
|
+
const parser = new import_parser.ManifestParser(this.db, this.projectRoot, {
|
|
55
|
+
include: this.config.include,
|
|
56
|
+
exclude: this.config.exclude
|
|
57
|
+
});
|
|
55
58
|
const parseResult = await parser.parseAll();
|
|
56
59
|
result.manifestsDiscovered = parseResult.manifestsParsed;
|
|
57
60
|
result.dependenciesCreated = parseResult.dependenciesCreated;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/security/pipeline.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * SecurityPipeline \u2014 Orchestrates the security analysis phases.\n *\n * Phases (in order):\n * 1. Manifest parsing \u2014 discover and parse dependency manifests\n * 2. Dependency integration \u2014 link dependencies to code symbols\n * 3. Vulnerability enrichment \u2014 query databases for CVEs (if securityAutoEnrich)\n * 4. Reachability analysis \u2014 determine if vulnerable code is reachable\n *\n * Reports progress via callback and returns a SecurityResult with counts and duration.\n *\n * Requirements: 9.3, 9.5\n */\n\nimport type { GraphDatabase } from '../db/database';\nimport type { KiroGraphConfig } from '../config';\nimport type { SecurityResult } from './types';\nimport { ManifestParser } from './manifest/parser';\nimport { DependencyGraphIntegrator } from './integrator';\nimport { VulnerabilityDatabaseClient } from './vuln/client';\nimport { OsvAdapter } from './vuln/osv-adapter';\nimport { ReachabilityAnalyzer } from './reachability';\nimport { logWarn } from '../errors';\n\n/**\n * SecurityPipeline orchestrates the full security analysis workflow.\n */\nexport class SecurityPipeline {\n private readonly db: GraphDatabase;\n private readonly config: KiroGraphConfig;\n private readonly projectRoot: string;\n\n constructor(db: GraphDatabase, config: KiroGraphConfig, projectRoot: string) {\n this.db = db;\n this.config = config;\n this.projectRoot = projectRoot;\n }\n\n /**\n * Run the full security analysis pipeline.\n *\n * @param onProgress - Optional callback reporting (phase, current, total)\n * @returns SecurityResult with counts and duration\n */\n async run(\n onProgress?: (phase: string, current: number, total: number) => void,\n ): Promise<SecurityResult> {\n const start = Date.now();\n\n const result: SecurityResult = {\n manifestsDiscovered: 0,\n dependenciesCreated: 0,\n vulnerabilitiesFound: 0,\n affectedCount: 0,\n notAffectedCount: 0,\n underInvestigationCount: 0,\n duration: 0,\n };\n\n // \u2500\u2500 Phase 1: Manifest Parsing \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n onProgress?.('manifest-parsing', 0, 4);\n\n const parser = new ManifestParser(this.db, this.projectRoot);\n const parseResult = await parser.parseAll();\n\n result.manifestsDiscovered = parseResult.manifestsParsed;\n result.dependenciesCreated = parseResult.dependenciesCreated;\n\n onProgress?.('manifest-parsing', 1, 4);\n\n // \u2500\u2500 Phase 2: Dependency Integration \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n onProgress?.('dependency-integration', 1, 4);\n\n const integrator = new DependencyGraphIntegrator(this.db, this.projectRoot);\n await integrator.integrate();\n await integrator.resolveTransitives(10);\n await integrator.cleanup();\n\n onProgress?.('dependency-integration', 2, 4);\n\n // \u2500\u2500 Phase 3: Vulnerability Enrichment (conditional) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (this.config.securityAutoEnrich) {\n onProgress?.('vulnerability-enrichment', 2, 4);\n\n const adapters = this.createVulnAdapters();\n const vulnClient = new VulnerabilityDatabaseClient(adapters, this.db);\n const enrichResult = await vulnClient.enrichAll();\n\n result.vulnerabilitiesFound = enrichResult.vulnerabilitiesFound;\n\n onProgress?.('vulnerability-enrichment', 3, 4);\n } else {\n onProgress?.('vulnerability-enrichment', 3, 4);\n }\n\n // \u2500\u2500 Phase 4: Reachability Analysis \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n onProgress?.('reachability-analysis', 3, 4);\n\n const reachability = new ReachabilityAnalyzer(this.db, this.config);\n const reachResults = await reachability.analyzeAll();\n\n // Count verdicts\n for (const [, reachResult] of reachResults) {\n switch (reachResult.verdict) {\n case 'affected':\n result.affectedCount++;\n break;\n case 'not_affected':\n result.notAffectedCount++;\n break;\n case 'under_investigation':\n result.underInvestigationCount++;\n break;\n }\n }\n\n // Generate impact summaries for affected vulnerabilities\n for (const [vulnNodeId, reachResult] of reachResults) {\n if (reachResult.verdict === 'affected') {\n await reachability.getImpactSummary(vulnNodeId);\n }\n }\n\n onProgress?.('reachability-analysis', 4, 4);\n\n result.duration = Date.now() - start;\n return result;\n }\n\n /**\n * Create vulnerability database adapters based on config.\n */\n private createVulnAdapters(): import('./vuln/types').VulnDatabaseAdapter[] {\n const adapters: import('./vuln/types').VulnDatabaseAdapter[] = [];\n\n for (const dbName of this.config.securityDatabases) {\n switch (dbName.toUpperCase()) {\n case 'OSV':\n adapters.push(new OsvAdapter());\n break;\n default:\n logWarn(`[sec:pipeline] Unknown vulnerability database \"${dbName}\", skipping`);\n break;\n }\n }\n\n // Fallback to OSV if no valid adapters configured\n if (adapters.length === 0) {\n adapters.push(new OsvAdapter());\n }\n\n return adapters;\n }\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAiBA,oBAA+B;AAC/B,wBAA0C;AAC1C,oBAA4C;AAC5C,yBAA2B;AAC3B,0BAAqC;AACrC,oBAAwB;AAKjB,MAAM,iBAAiB;AAAA,EAK5B,YAAY,IAAmB,QAAyB,aAAqB;AAC3E,SAAK,KAAK;AACV,SAAK,SAAS;AACd,SAAK,cAAc;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,IACJ,YACyB;AACzB,UAAM,QAAQ,KAAK,IAAI;AAEvB,UAAM,SAAyB;AAAA,MAC7B,qBAAqB;AAAA,MACrB,qBAAqB;AAAA,MACrB,sBAAsB;AAAA,MACtB,eAAe;AAAA,MACf,kBAAkB;AAAA,MAClB,yBAAyB;AAAA,MACzB,UAAU;AAAA,IACZ;AAGA,iBAAa,oBAAoB,GAAG,CAAC;AAErC,UAAM,SAAS,IAAI,6BAAe,KAAK,IAAI,KAAK,
|
|
4
|
+
"sourcesContent": ["/**\n * SecurityPipeline \u2014 Orchestrates the security analysis phases.\n *\n * Phases (in order):\n * 1. Manifest parsing \u2014 discover and parse dependency manifests\n * 2. Dependency integration \u2014 link dependencies to code symbols\n * 3. Vulnerability enrichment \u2014 query databases for CVEs (if securityAutoEnrich)\n * 4. Reachability analysis \u2014 determine if vulnerable code is reachable\n *\n * Reports progress via callback and returns a SecurityResult with counts and duration.\n *\n * Requirements: 9.3, 9.5\n */\n\nimport type { GraphDatabase } from '../db/database';\nimport type { KiroGraphConfig } from '../config';\nimport type { SecurityResult } from './types';\nimport { ManifestParser } from './manifest/parser';\nimport { DependencyGraphIntegrator } from './integrator';\nimport { VulnerabilityDatabaseClient } from './vuln/client';\nimport { OsvAdapter } from './vuln/osv-adapter';\nimport { ReachabilityAnalyzer } from './reachability';\nimport { logWarn } from '../errors';\n\n/**\n * SecurityPipeline orchestrates the full security analysis workflow.\n */\nexport class SecurityPipeline {\n private readonly db: GraphDatabase;\n private readonly config: KiroGraphConfig;\n private readonly projectRoot: string;\n\n constructor(db: GraphDatabase, config: KiroGraphConfig, projectRoot: string) {\n this.db = db;\n this.config = config;\n this.projectRoot = projectRoot;\n }\n\n /**\n * Run the full security analysis pipeline.\n *\n * @param onProgress - Optional callback reporting (phase, current, total)\n * @returns SecurityResult with counts and duration\n */\n async run(\n onProgress?: (phase: string, current: number, total: number) => void,\n ): Promise<SecurityResult> {\n const start = Date.now();\n\n const result: SecurityResult = {\n manifestsDiscovered: 0,\n dependenciesCreated: 0,\n vulnerabilitiesFound: 0,\n affectedCount: 0,\n notAffectedCount: 0,\n underInvestigationCount: 0,\n duration: 0,\n };\n\n // \u2500\u2500 Phase 1: Manifest Parsing \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n onProgress?.('manifest-parsing', 0, 4);\n\n const parser = new ManifestParser(this.db, this.projectRoot, {\n include: this.config.include,\n exclude: this.config.exclude,\n });\n const parseResult = await parser.parseAll();\n\n result.manifestsDiscovered = parseResult.manifestsParsed;\n result.dependenciesCreated = parseResult.dependenciesCreated;\n\n onProgress?.('manifest-parsing', 1, 4);\n\n // \u2500\u2500 Phase 2: Dependency Integration \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n onProgress?.('dependency-integration', 1, 4);\n\n const integrator = new DependencyGraphIntegrator(this.db, this.projectRoot);\n await integrator.integrate();\n await integrator.resolveTransitives(10);\n await integrator.cleanup();\n\n onProgress?.('dependency-integration', 2, 4);\n\n // \u2500\u2500 Phase 3: Vulnerability Enrichment (conditional) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (this.config.securityAutoEnrich) {\n onProgress?.('vulnerability-enrichment', 2, 4);\n\n const adapters = this.createVulnAdapters();\n const vulnClient = new VulnerabilityDatabaseClient(adapters, this.db);\n const enrichResult = await vulnClient.enrichAll();\n\n result.vulnerabilitiesFound = enrichResult.vulnerabilitiesFound;\n\n onProgress?.('vulnerability-enrichment', 3, 4);\n } else {\n onProgress?.('vulnerability-enrichment', 3, 4);\n }\n\n // \u2500\u2500 Phase 4: Reachability Analysis \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n onProgress?.('reachability-analysis', 3, 4);\n\n const reachability = new ReachabilityAnalyzer(this.db, this.config);\n const reachResults = await reachability.analyzeAll();\n\n // Count verdicts\n for (const [, reachResult] of reachResults) {\n switch (reachResult.verdict) {\n case 'affected':\n result.affectedCount++;\n break;\n case 'not_affected':\n result.notAffectedCount++;\n break;\n case 'under_investigation':\n result.underInvestigationCount++;\n break;\n }\n }\n\n // Generate impact summaries for affected vulnerabilities\n for (const [vulnNodeId, reachResult] of reachResults) {\n if (reachResult.verdict === 'affected') {\n await reachability.getImpactSummary(vulnNodeId);\n }\n }\n\n onProgress?.('reachability-analysis', 4, 4);\n\n result.duration = Date.now() - start;\n return result;\n }\n\n /**\n * Create vulnerability database adapters based on config.\n */\n private createVulnAdapters(): import('./vuln/types').VulnDatabaseAdapter[] {\n const adapters: import('./vuln/types').VulnDatabaseAdapter[] = [];\n\n for (const dbName of this.config.securityDatabases) {\n switch (dbName.toUpperCase()) {\n case 'OSV':\n adapters.push(new OsvAdapter());\n break;\n default:\n logWarn(`[sec:pipeline] Unknown vulnerability database \"${dbName}\", skipping`);\n break;\n }\n }\n\n // Fallback to OSV if no valid adapters configured\n if (adapters.length === 0) {\n adapters.push(new OsvAdapter());\n }\n\n return adapters;\n }\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAiBA,oBAA+B;AAC/B,wBAA0C;AAC1C,oBAA4C;AAC5C,yBAA2B;AAC3B,0BAAqC;AACrC,oBAAwB;AAKjB,MAAM,iBAAiB;AAAA,EAK5B,YAAY,IAAmB,QAAyB,aAAqB;AAC3E,SAAK,KAAK;AACV,SAAK,SAAS;AACd,SAAK,cAAc;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,IACJ,YACyB;AACzB,UAAM,QAAQ,KAAK,IAAI;AAEvB,UAAM,SAAyB;AAAA,MAC7B,qBAAqB;AAAA,MACrB,qBAAqB;AAAA,MACrB,sBAAsB;AAAA,MACtB,eAAe;AAAA,MACf,kBAAkB;AAAA,MAClB,yBAAyB;AAAA,MACzB,UAAU;AAAA,IACZ;AAGA,iBAAa,oBAAoB,GAAG,CAAC;AAErC,UAAM,SAAS,IAAI,6BAAe,KAAK,IAAI,KAAK,aAAa;AAAA,MAC3D,SAAS,KAAK,OAAO;AAAA,MACrB,SAAS,KAAK,OAAO;AAAA,IACvB,CAAC;AACD,UAAM,cAAc,MAAM,OAAO,SAAS;AAE1C,WAAO,sBAAsB,YAAY;AACzC,WAAO,sBAAsB,YAAY;AAEzC,iBAAa,oBAAoB,GAAG,CAAC;AAGrC,iBAAa,0BAA0B,GAAG,CAAC;AAE3C,UAAM,aAAa,IAAI,4CAA0B,KAAK,IAAI,KAAK,WAAW;AAC1E,UAAM,WAAW,UAAU;AAC3B,UAAM,WAAW,mBAAmB,EAAE;AACtC,UAAM,WAAW,QAAQ;AAEzB,iBAAa,0BAA0B,GAAG,CAAC;AAG3C,QAAI,KAAK,OAAO,oBAAoB;AAClC,mBAAa,4BAA4B,GAAG,CAAC;AAE7C,YAAM,WAAW,KAAK,mBAAmB;AACzC,YAAM,aAAa,IAAI,0CAA4B,UAAU,KAAK,EAAE;AACpE,YAAM,eAAe,MAAM,WAAW,UAAU;AAEhD,aAAO,uBAAuB,aAAa;AAE3C,mBAAa,4BAA4B,GAAG,CAAC;AAAA,IAC/C,OAAO;AACL,mBAAa,4BAA4B,GAAG,CAAC;AAAA,IAC/C;AAGA,iBAAa,yBAAyB,GAAG,CAAC;AAE1C,UAAM,eAAe,IAAI,yCAAqB,KAAK,IAAI,KAAK,MAAM;AAClE,UAAM,eAAe,MAAM,aAAa,WAAW;AAGnD,eAAW,CAAC,EAAE,WAAW,KAAK,cAAc;AAC1C,cAAQ,YAAY,SAAS;AAAA,QAC3B,KAAK;AACH,iBAAO;AACP;AAAA,QACF,KAAK;AACH,iBAAO;AACP;AAAA,QACF,KAAK;AACH,iBAAO;AACP;AAAA,MACJ;AAAA,IACF;AAGA,eAAW,CAAC,YAAY,WAAW,KAAK,cAAc;AACpD,UAAI,YAAY,YAAY,YAAY;AACtC,cAAM,aAAa,iBAAiB,UAAU;AAAA,MAChD;AAAA,IACF;AAEA,iBAAa,yBAAyB,GAAG,CAAC;AAE1C,WAAO,WAAW,KAAK,IAAI,IAAI;AAC/B,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,qBAAmE;AACzE,UAAM,WAAyD,CAAC;AAEhE,eAAW,UAAU,KAAK,OAAO,mBAAmB;AAClD,cAAQ,OAAO,YAAY,GAAG;AAAA,QAC5B,KAAK;AACH,mBAAS,KAAK,IAAI,8BAAW,CAAC;AAC9B;AAAA,QACF;AACE,qCAAQ,kDAAkD,MAAM,aAAa;AAC7E;AAAA,MACJ;AAAA,IACF;AAGA,QAAI,SAAS,WAAW,GAAG;AACzB,eAAS,KAAK,IAAI,8BAAW,CAAC;AAAA,IAChC;AAEA,WAAO;AAAA,EACT;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|