api-tests-coverage 1.0.21 → 1.0.22
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/dashboard/dist/assets/_basePickBy-BKGHUeDJ.js +1 -0
- package/dist/dashboard/dist/assets/_baseUniq-CtA-DQF7.js +1 -0
- package/dist/dashboard/dist/assets/arc-CbXP3Mc9.js +1 -0
- package/dist/dashboard/dist/assets/architectureDiagram-VXUJARFQ-n7QxasMM.js +36 -0
- package/dist/dashboard/dist/assets/blockDiagram-VD42YOAC-MXnGwKRn.js +122 -0
- package/dist/dashboard/dist/assets/c4Diagram-YG6GDRKO-B3yZ5P9k.js +10 -0
- package/dist/dashboard/dist/assets/channel-C7QwX7U8.js +1 -0
- package/dist/dashboard/dist/assets/chunk-4BX2VUAB-Dw3El5KF.js +1 -0
- package/dist/dashboard/dist/assets/chunk-55IACEB6-T8Jf00IL.js +1 -0
- package/dist/dashboard/dist/assets/chunk-B4BG7PRW-DAJalKYJ.js +165 -0
- package/dist/dashboard/dist/assets/chunk-DI55MBZ5-p7o_KuDF.js +220 -0
- package/dist/dashboard/dist/assets/chunk-FMBD7UC4-B0wUAfQR.js +15 -0
- package/dist/dashboard/dist/assets/chunk-QN33PNHL-BTFwTEw8.js +1 -0
- package/dist/dashboard/dist/assets/chunk-QZHKN3VN-CNXHnjkC.js +1 -0
- package/dist/dashboard/dist/assets/chunk-TZMSLE5B-BIVywBYp.js +1 -0
- package/dist/dashboard/dist/assets/classDiagram-2ON5EDUG-Dkb-G0UK.js +1 -0
- package/dist/dashboard/dist/assets/classDiagram-v2-WZHVMYZB-Dkb-G0UK.js +1 -0
- package/dist/dashboard/dist/assets/clone-BSdXhKmH.js +1 -0
- package/dist/dashboard/dist/assets/cose-bilkent-S5V4N54A-DuALhY5a.js +1 -0
- package/dist/dashboard/dist/assets/cytoscape.esm-CyJtwmzi.js +331 -0
- package/dist/dashboard/dist/assets/dagre-6UL2VRFP-w6endfy9.js +4 -0
- package/dist/dashboard/dist/assets/diagram-PSM6KHXK-BDnUwPQC.js +24 -0
- package/dist/dashboard/dist/assets/diagram-QEK2KX5R-Cbb7ctAo.js +43 -0
- package/dist/dashboard/dist/assets/diagram-S2PKOQOG-76ascveh.js +24 -0
- package/dist/dashboard/dist/assets/erDiagram-Q2GNP2WA-BQQnVF0J.js +60 -0
- package/dist/dashboard/dist/assets/flowDiagram-NV44I4VS-dKukiSxD.js +162 -0
- package/dist/dashboard/dist/assets/ganttDiagram-JELNMOA3-BqnazZuZ.js +267 -0
- package/dist/dashboard/dist/assets/gitGraphDiagram-V2S2FVAM-BUFcnXCj.js +65 -0
- package/dist/dashboard/dist/assets/graph-CGQIvL3r.js +1 -0
- package/dist/dashboard/dist/assets/index-CadOHtae.css +1 -0
- package/dist/dashboard/dist/assets/index-DwqfA4mc.js +777 -0
- package/dist/dashboard/dist/assets/infoDiagram-HS3SLOUP-BdylFPLI.js +2 -0
- package/dist/dashboard/dist/assets/journeyDiagram-XKPGCS4Q-BzJTBkhp.js +139 -0
- package/dist/dashboard/dist/assets/kanban-definition-3W4ZIXB7-StKomgio.js +89 -0
- package/dist/dashboard/dist/assets/katex-O9d3_IXG.js +261 -0
- package/dist/dashboard/dist/assets/layout-BnsX73QS.js +1 -0
- package/dist/dashboard/dist/assets/mindmap-definition-VGOIOE7T-HqFRhVFX.js +68 -0
- package/dist/dashboard/dist/assets/pieDiagram-ADFJNKIX-2I2tFH0C.js +30 -0
- package/dist/dashboard/dist/assets/quadrantDiagram-AYHSOK5B-PlELkdBW.js +7 -0
- package/dist/dashboard/dist/assets/requirementDiagram-UZGBJVZJ-_9eLQNcZ.js +64 -0
- package/dist/dashboard/dist/assets/sankeyDiagram-TZEHDZUN-DHl1Ss7O.js +10 -0
- package/dist/dashboard/dist/assets/sequenceDiagram-WL72ISMW-B9p0m3Uf.js +145 -0
- package/dist/dashboard/dist/assets/stateDiagram-FKZM4ZOC--rpDODjT.js +1 -0
- package/dist/dashboard/dist/assets/stateDiagram-v2-4FDKWEC3-Ca9uGk46.js +1 -0
- package/dist/dashboard/dist/assets/timeline-definition-IT6M3QCI-BCHaGBHB.js +61 -0
- package/dist/dashboard/dist/assets/treemap-GDKQZRPO-CgiqDY8M.js +162 -0
- package/dist/dashboard/dist/assets/xychartDiagram-PRI3JC2R-BMvBBbeV.js +7 -0
- package/dist/dashboard/dist/index.html +14 -0
- package/dist/dashboard/dist/reports/business-coverage.json +201 -0
- package/dist/dashboard/dist/reports/coverage-intelligence.json +728 -0
- package/dist/dashboard/dist/reports/coverage-summary.json +995 -0
- package/dist/dashboard/dist/reports/endpoint-coverage.json +336 -0
- package/dist/dashboard/dist/reports/error-coverage.json +367 -0
- package/dist/dashboard/dist/reports/missing-tests-recommendations.json +285 -0
- package/dist/dashboard/dist/reports/risk-prioritization.json +312 -0
- package/dist/dashboard/dist/reports/security-coverage.json +299 -0
- package/dist/dashboard/dist/vite.svg +1 -0
- package/dist/src/generation/context-builder.d.ts +6 -0
- package/dist/src/generation/context-builder.d.ts.map +1 -0
- package/dist/src/generation/context-builder.js +202 -0
- package/dist/src/generation/engine.d.ts +40 -0
- package/dist/src/generation/engine.d.ts.map +1 -0
- package/dist/src/generation/engine.js +378 -0
- package/dist/src/generation/file-router.d.ts +7 -0
- package/dist/src/generation/file-router.d.ts.map +1 -0
- package/dist/src/generation/file-router.js +79 -0
- package/dist/src/generation/gap-extractor.d.ts +12 -0
- package/dist/src/generation/gap-extractor.d.ts.map +1 -0
- package/dist/src/generation/gap-extractor.js +281 -0
- package/dist/src/generation/template-renderer.d.ts +10 -0
- package/dist/src/generation/template-renderer.d.ts.map +1 -0
- package/dist/src/generation/template-renderer.js +526 -0
- package/dist/src/generation/types.d.ts +73 -0
- package/dist/src/generation/types.d.ts.map +1 -0
- package/dist/src/generation/types.js +2 -0
- package/dist/src/index.js +154 -0
- package/dist/src/pipeline/detectors/expressMiddlewareDetector.d.ts +21 -0
- package/dist/src/pipeline/detectors/expressMiddlewareDetector.d.ts.map +1 -0
- package/dist/src/pipeline/detectors/expressMiddlewareDetector.js +201 -0
- package/dist/src/pipeline/detectors/flaskBlueprintDetector.d.ts +23 -0
- package/dist/src/pipeline/detectors/flaskBlueprintDetector.d.ts.map +1 -0
- package/dist/src/pipeline/detectors/flaskBlueprintDetector.js +263 -0
- package/dist/src/pipeline/detectors/springDddDetector.d.ts +23 -0
- package/dist/src/pipeline/detectors/springDddDetector.d.ts.map +1 -0
- package/dist/src/pipeline/detectors/springDddDetector.js +237 -0
- package/dist/src/pipeline/detectors/types.d.ts +97 -0
- package/dist/src/pipeline/detectors/types.d.ts.map +1 -0
- package/dist/src/pipeline/detectors/types.js +15 -0
- package/dist/src/reporting.d.ts.map +1 -1
- package/dist/src/reporting.js +2 -1
- package/dist/src/streaming/detectors/eventBridgeDetector.d.ts +5 -0
- package/dist/src/streaming/detectors/eventBridgeDetector.d.ts.map +1 -0
- package/dist/src/streaming/detectors/eventBridgeDetector.js +82 -0
- package/dist/src/streaming/detectors/kafkaDetector.d.ts +5 -0
- package/dist/src/streaming/detectors/kafkaDetector.d.ts.map +1 -0
- package/dist/src/streaming/detectors/kafkaDetector.js +97 -0
- package/dist/src/streaming/detectors/natsDetector.d.ts +5 -0
- package/dist/src/streaming/detectors/natsDetector.d.ts.map +1 -0
- package/dist/src/streaming/detectors/natsDetector.js +96 -0
- package/dist/src/streaming/detectors/pubsubDetector.d.ts +5 -0
- package/dist/src/streaming/detectors/pubsubDetector.d.ts.map +1 -0
- package/dist/src/streaming/detectors/pubsubDetector.js +82 -0
- package/dist/src/streaming/detectors/rabbitmqDetector.d.ts +5 -0
- package/dist/src/streaming/detectors/rabbitmqDetector.d.ts.map +1 -0
- package/dist/src/streaming/detectors/rabbitmqDetector.js +103 -0
- package/dist/src/streaming/detectors/redisPubsubDetector.d.ts +5 -0
- package/dist/src/streaming/detectors/redisPubsubDetector.d.ts.map +1 -0
- package/dist/src/streaming/detectors/redisPubsubDetector.js +81 -0
- package/dist/src/streaming/detectors/snsDetector.d.ts +5 -0
- package/dist/src/streaming/detectors/snsDetector.d.ts.map +1 -0
- package/dist/src/streaming/detectors/snsDetector.js +81 -0
- package/dist/src/streaming/detectors/sqsDetector.d.ts +5 -0
- package/dist/src/streaming/detectors/sqsDetector.d.ts.map +1 -0
- package/dist/src/streaming/detectors/sqsDetector.js +81 -0
- package/dist/src/streaming/eventCoverage.d.ts +46 -0
- package/dist/src/streaming/eventCoverage.d.ts.map +1 -0
- package/dist/src/streaming/eventCoverage.js +270 -0
- package/dist/src/streaming/types.d.ts +34 -0
- package/dist/src/streaming/types.d.ts.map +1 -0
- package/dist/src/streaming/types.js +2 -0
- package/dist/src/summary/markdownRenderer.d.ts.map +1 -1
- package/dist/src/summary/markdownRenderer.js +1 -0
- package/dist/src/summary/summaryTypes.d.ts.map +1 -1
- package/dist/src/summary/summaryTypes.js +1 -0
- package/package.json +1 -1
package/dist/src/index.js
CHANGED
|
@@ -2114,6 +2114,160 @@ program
|
|
|
2114
2114
|
// Keep the process alive — the HTTP server holds the event loop open
|
|
2115
2115
|
}
|
|
2116
2116
|
});
|
|
2117
|
+
// ─── event-coverage command ───────────────────────────────────────────────────
|
|
2118
|
+
program
|
|
2119
|
+
.command('event-coverage')
|
|
2120
|
+
.description('Analyze how thoroughly tests cover event/message-driven interactions (Kafka, RabbitMQ, SNS, SQS, etc.)')
|
|
2121
|
+
.option('--source <glob>', 'Glob pattern for source files to scan for event patterns', 'src/**/*.ts')
|
|
2122
|
+
.option('--tests <glob>', 'Glob pattern for test files', 'tests/**/*.ts')
|
|
2123
|
+
.option('--format <formats>', 'Comma-separated list of report formats: json,html,csv,junit (default: json,html)', 'json,html')
|
|
2124
|
+
.option('--threshold-event <percent>', 'Minimum required event coverage percentage (0-100)', parseFloat, 0)
|
|
2125
|
+
.action(async (options) => {
|
|
2126
|
+
var _a, _b;
|
|
2127
|
+
const { metricsPort, serviceName } = setupObservability();
|
|
2128
|
+
const logger = (0, observability_1.getLogger)();
|
|
2129
|
+
const parentOpts = program.opts();
|
|
2130
|
+
const config = loadCoverageConfig(parentOpts.config, {
|
|
2131
|
+
event: options.thresholdEvent,
|
|
2132
|
+
});
|
|
2133
|
+
const sourceGlob = options.source;
|
|
2134
|
+
const testsGlob = (config.testPatterns && config.testPatterns.length > 0)
|
|
2135
|
+
? config.testPatterns[0]
|
|
2136
|
+
: options.tests;
|
|
2137
|
+
const reportsDir = path.resolve('reports');
|
|
2138
|
+
const formats = (0, reporting_1.parseFormats)(options.format);
|
|
2139
|
+
const span = (0, observability_1.startSpan)('event-coverage', { sourceGlob, testsGlob });
|
|
2140
|
+
logger.info({ event: 'analysis_start', coverageType: 'event', sourceGlob }, `Scanning events in: ${sourceGlob}`);
|
|
2141
|
+
console.log(`Scanning events in: ${sourceGlob}`);
|
|
2142
|
+
const { analyzeEventCoverage, writeEventReport } = await Promise.resolve().then(() => __importStar(require('./streaming/eventCoverage')));
|
|
2143
|
+
const eventResult = analyzeEventCoverage(sourceGlob, testsGlob);
|
|
2144
|
+
writeEventReport(eventResult, reportsDir);
|
|
2145
|
+
const result = {
|
|
2146
|
+
type: 'event',
|
|
2147
|
+
totalItems: eventResult.totalItems,
|
|
2148
|
+
coveredItems: eventResult.coveredItems,
|
|
2149
|
+
coveragePercent: eventResult.coveragePercent,
|
|
2150
|
+
details: eventResult.details,
|
|
2151
|
+
};
|
|
2152
|
+
const thresholds = { ...((_a = config.thresholds) !== null && _a !== void 0 ? _a : {}) };
|
|
2153
|
+
// Run plugins
|
|
2154
|
+
const pluginContext = {
|
|
2155
|
+
testPatterns: (_b = config.testPatterns) !== null && _b !== void 0 ? _b : [],
|
|
2156
|
+
results: [result],
|
|
2157
|
+
config,
|
|
2158
|
+
};
|
|
2159
|
+
const pluginResults = await (0, pluginLoader_1.runPlugins)(config, pluginContext);
|
|
2160
|
+
const allResults = [result, ...pluginResults];
|
|
2161
|
+
const observabilityInfo = (0, observability_1.buildObservabilityInfo)(metricsPort);
|
|
2162
|
+
(0, reporting_1.generateMultiFormatReports)(allResults, formats, reportsDir, thresholds, observabilityInfo);
|
|
2163
|
+
console.log(`Event coverage: ${eventResult.coveredItems}/${eventResult.totalItems} events covered (${eventResult.coveragePercent}%)`);
|
|
2164
|
+
if (eventResult.details.unresolvedCount > 0) {
|
|
2165
|
+
console.log(` Unresolved topics: ${eventResult.details.unresolvedCount}`);
|
|
2166
|
+
}
|
|
2167
|
+
for (const [broker, count] of Object.entries(eventResult.details.brokerBreakdown)) {
|
|
2168
|
+
if (count > 0)
|
|
2169
|
+
console.log(` ${broker}: ${count} nodes`);
|
|
2170
|
+
}
|
|
2171
|
+
console.log(`Reports written to: ${reportsDir}`);
|
|
2172
|
+
span.end({ totalItems: eventResult.totalItems, coveredItems: eventResult.coveredItems, coveragePercent: eventResult.coveragePercent });
|
|
2173
|
+
logger.info({ event: 'analysis_complete', coverageType: 'event' }, 'Event coverage analysis complete');
|
|
2174
|
+
await finaliseObservability(allResults, thresholds, metricsPort, serviceName);
|
|
2175
|
+
// Threshold check
|
|
2176
|
+
const failures = (0, reporting_1.checkThresholds)(allResults, thresholds);
|
|
2177
|
+
if (failures.length > 0) {
|
|
2178
|
+
for (const msg of failures) {
|
|
2179
|
+
console.error(`THRESHOLD FAILURE: ${msg}`);
|
|
2180
|
+
}
|
|
2181
|
+
process.exitCode = 1;
|
|
2182
|
+
}
|
|
2183
|
+
});
|
|
2184
|
+
// ─── generate-tests command ──────────────────────────────────────────────────
|
|
2185
|
+
program
|
|
2186
|
+
.command('generate-tests')
|
|
2187
|
+
.description('Generate test files for coverage gaps detected in reports/')
|
|
2188
|
+
.option('--source <glob>', 'Glob pattern for source files', 'src/**/*.ts')
|
|
2189
|
+
.option('--tests <glob>', 'Glob pattern for existing test files', 'tests/**/*.ts')
|
|
2190
|
+
.option('--out-dir <dir>', 'Output directory for generated tests')
|
|
2191
|
+
.option('--base-url <url>', 'Base URL for API requests', 'http://localhost:3000')
|
|
2192
|
+
.option('--dry-run', 'Show what would be generated without writing files', false)
|
|
2193
|
+
.action(async (options) => {
|
|
2194
|
+
const logger = (0, observability_1.getLogger)();
|
|
2195
|
+
const reportsDir = path.resolve('reports');
|
|
2196
|
+
logger.info({ event: 'generation_start' }, 'Starting test generation...');
|
|
2197
|
+
console.log('Starting test generation...');
|
|
2198
|
+
const { TestGenerationEngine } = await Promise.resolve().then(() => __importStar(require('./generation/engine')));
|
|
2199
|
+
const engine = new TestGenerationEngine(process.cwd(), reportsDir);
|
|
2200
|
+
const generated = engine.generateTests({
|
|
2201
|
+
dryRun: options.dryRun,
|
|
2202
|
+
outDir: options.outDir,
|
|
2203
|
+
baseUrl: options.baseUrl,
|
|
2204
|
+
});
|
|
2205
|
+
console.log(`Generated ${generated.length} test file(s)`);
|
|
2206
|
+
for (const f of generated) {
|
|
2207
|
+
console.log(` ${options.dryRun ? '[dry-run] ' : ''}${f.outputPath} (${f.gapId})`);
|
|
2208
|
+
}
|
|
2209
|
+
if (options.dryRun) {
|
|
2210
|
+
console.log('\nDry run complete — no files were written.');
|
|
2211
|
+
}
|
|
2212
|
+
});
|
|
2213
|
+
// ─── score-tests command ─────────────────────────────────────────────────────
|
|
2214
|
+
program
|
|
2215
|
+
.command('score-tests')
|
|
2216
|
+
.description('Score existing test files for quality (status assertions, error paths, matchers)')
|
|
2217
|
+
.option('--tests <glob>', 'Glob pattern for test files', 'tests/**/*.ts')
|
|
2218
|
+
.option('--threshold-quality <percent>', 'Minimum average quality score (0-100)', parseFloat, 0)
|
|
2219
|
+
.action(async (options) => {
|
|
2220
|
+
const logger = (0, observability_1.getLogger)();
|
|
2221
|
+
const testsGlob = options.tests;
|
|
2222
|
+
logger.info({ event: 'scoring_start' }, 'Scoring test quality...');
|
|
2223
|
+
console.log(`Scoring test quality for: ${testsGlob}`);
|
|
2224
|
+
const { TestGenerationEngine } = await Promise.resolve().then(() => __importStar(require('./generation/engine')));
|
|
2225
|
+
const engine = new TestGenerationEngine(process.cwd());
|
|
2226
|
+
const reports = engine.scoreTests(testsGlob);
|
|
2227
|
+
const avgScore = reports.length > 0
|
|
2228
|
+
? Math.round(reports.reduce((s, r) => s + r.overallScore, 0) / reports.length)
|
|
2229
|
+
: 0;
|
|
2230
|
+
console.log(`\nScored ${reports.length} test file(s)`);
|
|
2231
|
+
console.log(`Average quality score: ${avgScore}/100`);
|
|
2232
|
+
const lowQuality = reports.filter((r) => r.overallScore < 60);
|
|
2233
|
+
if (lowQuality.length > 0) {
|
|
2234
|
+
console.log(`\nFiles below 60 quality:`);
|
|
2235
|
+
for (const r of lowQuality) {
|
|
2236
|
+
console.log(` ${r.file}: ${r.overallScore}/100`);
|
|
2237
|
+
for (const v of r.violations) {
|
|
2238
|
+
console.log(` - ${v}`);
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
const thresholdQuality = options.thresholdQuality;
|
|
2243
|
+
if (thresholdQuality > 0 && avgScore < thresholdQuality) {
|
|
2244
|
+
console.error(`THRESHOLD FAILURE: Average quality ${avgScore}% < ${thresholdQuality}%`);
|
|
2245
|
+
process.exitCode = 1;
|
|
2246
|
+
}
|
|
2247
|
+
});
|
|
2248
|
+
// ─── export-ai-flows command ─────────────────────────────────────────────────
|
|
2249
|
+
program
|
|
2250
|
+
.command('export-ai-flows')
|
|
2251
|
+
.description('Export AI-ready test generation flows for Copilot / LLM consumption')
|
|
2252
|
+
.option('--source <glob>', 'Glob pattern for source files', 'src/**/*.ts')
|
|
2253
|
+
.option('--tests <glob>', 'Glob pattern for test files', 'tests/**/*.ts')
|
|
2254
|
+
.action(async (options) => {
|
|
2255
|
+
const logger = (0, observability_1.getLogger)();
|
|
2256
|
+
const reportsDir = path.resolve('reports');
|
|
2257
|
+
logger.info({ event: 'ai_flows_start' }, 'Exporting AI-ready flows...');
|
|
2258
|
+
console.log('Exporting AI-ready flows...');
|
|
2259
|
+
const { TestGenerationEngine } = await Promise.resolve().then(() => __importStar(require('./generation/engine')));
|
|
2260
|
+
const engine = new TestGenerationEngine(process.cwd(), reportsDir);
|
|
2261
|
+
const manifest = engine.exportAiFlows({
|
|
2262
|
+
sourceGlob: options.source,
|
|
2263
|
+
testsGlob: options.tests,
|
|
2264
|
+
});
|
|
2265
|
+
console.log(`Exported ${manifest.totalFlows} AI-ready flow(s)`);
|
|
2266
|
+
for (const [type, count] of Object.entries(manifest.byType)) {
|
|
2267
|
+
console.log(` ${type}: ${count}`);
|
|
2268
|
+
}
|
|
2269
|
+
console.log(`\nReports written to: ${reportsDir}`);
|
|
2270
|
+
});
|
|
2117
2271
|
// ─── serve command ────────────────────────────────────────────────────────────
|
|
2118
2272
|
program
|
|
2119
2273
|
.command('serve')
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Express middleware-as-auth detector (Feature 27)
|
|
3
|
+
*
|
|
4
|
+
* Detects from raw JavaScript/TypeScript file content:
|
|
5
|
+
* 1. router.use(path, [auth.required | auth.optional], subRouter) →
|
|
6
|
+
* router-mount nodes with middleware classification
|
|
7
|
+
* 2. jwt({ credentialsRequired: false }) → security: { optional: true }
|
|
8
|
+
* 3. jwt({ secret, ... }) (no credentialsRequired: false) → security: { required: true }
|
|
9
|
+
* 4. 4-argument Express error handlers (err, req, res, next) → error-handler nodes
|
|
10
|
+
* 5. if (err.name === '...') branches inside error handlers → exception-branch nodes
|
|
11
|
+
*
|
|
12
|
+
* Auth middleware inheritance rule: when auth.required / auth.optional appears in
|
|
13
|
+
* router.use(), ALL routes in the mounted sub-router inherit that security classification.
|
|
14
|
+
* This is flagged in the node metadata so the cross-file resolver can propagate it.
|
|
15
|
+
*/
|
|
16
|
+
import type { DetectedNode, Detector } from './types';
|
|
17
|
+
export declare class ExpressMiddlewareDetector implements Detector {
|
|
18
|
+
readonly name = "express-middleware";
|
|
19
|
+
detect(fileContent: string, filePath: string): DetectedNode[];
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=expressMiddlewareDetector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expressMiddlewareDetector.d.ts","sourceRoot":"","sources":["../../../../src/pipeline/detectors/expressMiddlewareDetector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAItD,qBAAa,yBAA0B,YAAW,QAAQ;IACxD,QAAQ,CAAC,IAAI,wBAAwB;IAErC,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,YAAY,EAAE;CAyL9D"}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Express middleware-as-auth detector (Feature 27)
|
|
4
|
+
*
|
|
5
|
+
* Detects from raw JavaScript/TypeScript file content:
|
|
6
|
+
* 1. router.use(path, [auth.required | auth.optional], subRouter) →
|
|
7
|
+
* router-mount nodes with middleware classification
|
|
8
|
+
* 2. jwt({ credentialsRequired: false }) → security: { optional: true }
|
|
9
|
+
* 3. jwt({ secret, ... }) (no credentialsRequired: false) → security: { required: true }
|
|
10
|
+
* 4. 4-argument Express error handlers (err, req, res, next) → error-handler nodes
|
|
11
|
+
* 5. if (err.name === '...') branches inside error handlers → exception-branch nodes
|
|
12
|
+
*
|
|
13
|
+
* Auth middleware inheritance rule: when auth.required / auth.optional appears in
|
|
14
|
+
* router.use(), ALL routes in the mounted sub-router inherit that security classification.
|
|
15
|
+
* This is flagged in the node metadata so the cross-file resolver can propagate it.
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.ExpressMiddlewareDetector = void 0;
|
|
19
|
+
const types_1 = require("./types");
|
|
20
|
+
class ExpressMiddlewareDetector {
|
|
21
|
+
constructor() {
|
|
22
|
+
this.name = 'express-middleware';
|
|
23
|
+
}
|
|
24
|
+
detect(fileContent, filePath) {
|
|
25
|
+
var _a, _b, _c;
|
|
26
|
+
if (!filePath.endsWith('.js') && !filePath.endsWith('.ts') &&
|
|
27
|
+
!filePath.endsWith('.mjs') && !filePath.endsWith('.cjs')) {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
// Quick bail: skip files with no router/app patterns
|
|
31
|
+
if (!fileContent.includes('router') &&
|
|
32
|
+
!fileContent.includes('app.use') &&
|
|
33
|
+
!fileContent.includes('express')) {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
const nodes = [];
|
|
37
|
+
const lines = fileContent.split('\n');
|
|
38
|
+
// ── Detect jwt middleware definitions ─────────────────────────────────
|
|
39
|
+
// const auth = { required: jwt({...}), optional: jwt({ credentialsRequired: false, ...}) }
|
|
40
|
+
const authVarPattern = /const\s+auth\s*=\s*\{([^}]+)\}/s;
|
|
41
|
+
const authVarMatch = fileContent.match(authVarPattern);
|
|
42
|
+
const authObjectDefs = new Map();
|
|
43
|
+
if (authVarMatch) {
|
|
44
|
+
const body = authVarMatch[1];
|
|
45
|
+
// required: jwt({...}) without credentialsRequired: false
|
|
46
|
+
if (/required\s*:.*?jwt\s*\(/.test(body) && !/credentialsRequired\s*:\s*false/.test((_a = body.split('optional')[0]) !== null && _a !== void 0 ? _a : '')) {
|
|
47
|
+
authObjectDefs.set('auth.required', { type: 'jwt', required: true, optional: false, sourcePattern: 'auth.required' });
|
|
48
|
+
}
|
|
49
|
+
// optional: jwt({ credentialsRequired: false, ...})
|
|
50
|
+
if (/optional\s*:.*?credentialsRequired\s*:\s*false/.test(body)) {
|
|
51
|
+
authObjectDefs.set('auth.optional', { type: 'jwt', required: false, optional: true, sourcePattern: 'auth.optional' });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Also detect standalone: jwt({ credentialsRequired: false })
|
|
55
|
+
const jwtOptionalPattern = /jwt\s*\(\s*\{[^}]*credentialsRequired\s*:\s*false[^}]*\}\s*\)/;
|
|
56
|
+
const jwtRequiredPattern = /jwt\s*\(\s*\{[^}]*secret[^}]*\}\s*\)/;
|
|
57
|
+
// ── Detect router.use(path, [middleware], subRouter) ─────────────────
|
|
58
|
+
for (let i = 0; i < lines.length; i++) {
|
|
59
|
+
const line = lines[i];
|
|
60
|
+
// router.use('/path', middleware, require('./sub'))
|
|
61
|
+
// router.use('/path', require('./sub'))
|
|
62
|
+
const routerUsePattern = /(?:router|app)\s*\.\s*use\s*\(\s*['"`]([^'"`]+)['"`]\s*,\s*(.*?)\)/;
|
|
63
|
+
const routerUseMatch = line.match(routerUsePattern);
|
|
64
|
+
if (routerUseMatch) {
|
|
65
|
+
const mountPath = routerUseMatch[1];
|
|
66
|
+
const rest = routerUseMatch[2];
|
|
67
|
+
// Determine security from middleware args
|
|
68
|
+
let security;
|
|
69
|
+
if (/auth\.required/.test(rest)) {
|
|
70
|
+
security = (_b = authObjectDefs.get('auth.required')) !== null && _b !== void 0 ? _b : { type: 'jwt', required: true, optional: false, sourcePattern: 'auth.required' };
|
|
71
|
+
}
|
|
72
|
+
else if (/auth\.optional/.test(rest)) {
|
|
73
|
+
security = (_c = authObjectDefs.get('auth.optional')) !== null && _c !== void 0 ? _c : { type: 'jwt', required: false, optional: true, sourcePattern: 'auth.optional' };
|
|
74
|
+
}
|
|
75
|
+
else if (jwtOptionalPattern.test(rest)) {
|
|
76
|
+
security = { type: 'jwt', required: false, optional: true, sourcePattern: 'jwt(credentialsRequired:false)' };
|
|
77
|
+
}
|
|
78
|
+
else if (jwtRequiredPattern.test(rest)) {
|
|
79
|
+
security = { type: 'jwt', required: true, optional: false, sourcePattern: 'jwt(secret)' };
|
|
80
|
+
}
|
|
81
|
+
// Determine target module (require('./x') or just the last identifier)
|
|
82
|
+
const requireMatch = rest.match(/require\s*\(['"`]([^'"`]+)['"`]\)/);
|
|
83
|
+
const targetModule = requireMatch ? requireMatch[1] : extractLastIdentifier(rest);
|
|
84
|
+
nodes.push({
|
|
85
|
+
id: (0, types_1.makeNodeId)(filePath, 'router-mount', mountPath, i + 1),
|
|
86
|
+
kind: 'router-mount',
|
|
87
|
+
name: mountPath,
|
|
88
|
+
filePath,
|
|
89
|
+
line: i + 1,
|
|
90
|
+
httpPath: mountPath,
|
|
91
|
+
urlPrefix: mountPath,
|
|
92
|
+
targetModule,
|
|
93
|
+
security,
|
|
94
|
+
resolution: targetModule ? 'cross-file-unresolved' : 'resolved',
|
|
95
|
+
diagnosticMessage: targetModule
|
|
96
|
+
? `Sub-router '${targetModule}' defined in another file`
|
|
97
|
+
: undefined,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
// router.use('/path', router) — no middleware between path and router
|
|
101
|
+
const simpleMountPattern = /(?:router|app)\s*\.\s*use\s*\(\s*['"`]([^'"`]+)['"`]\s*,\s*(require\s*\([^)]+\)|\w+)\s*\)/;
|
|
102
|
+
const simpleMountMatch = line.match(simpleMountPattern);
|
|
103
|
+
if (simpleMountMatch && !routerUseMatch) {
|
|
104
|
+
const mountPath = simpleMountMatch[1];
|
|
105
|
+
const targetExpr = simpleMountMatch[2];
|
|
106
|
+
const requireMatch2 = targetExpr.match(/require\s*\(['"`]([^'"`]+)['"`]\)/);
|
|
107
|
+
const targetModule = requireMatch2 ? requireMatch2[1] : targetExpr;
|
|
108
|
+
nodes.push({
|
|
109
|
+
id: (0, types_1.makeNodeId)(filePath, 'router-mount', mountPath, i + 1),
|
|
110
|
+
kind: 'router-mount',
|
|
111
|
+
name: mountPath,
|
|
112
|
+
filePath,
|
|
113
|
+
line: i + 1,
|
|
114
|
+
httpPath: mountPath,
|
|
115
|
+
urlPrefix: mountPath,
|
|
116
|
+
targetModule,
|
|
117
|
+
resolution: 'cross-file-unresolved',
|
|
118
|
+
diagnosticMessage: `Sub-router '${targetModule}' defined in another file`,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// ── Detect 4-argument error handlers ─────────────────────────────────
|
|
123
|
+
// function(err, req, res, next) { ... }
|
|
124
|
+
// (err, req, res, next) => { ... }
|
|
125
|
+
const errorHandlerPattern = /(?:function\s*\w*\s*\(|(?:\w+\s*,\s*){0,1}\()\s*err\s*,\s*\w+\s*,\s*\w+\s*,\s*next\s*[)]/;
|
|
126
|
+
for (let i = 0; i < lines.length; i++) {
|
|
127
|
+
const line = lines[i];
|
|
128
|
+
if (!errorHandlerPattern.test(line))
|
|
129
|
+
continue;
|
|
130
|
+
// Find the end of this error handler block
|
|
131
|
+
const handlerStart = i;
|
|
132
|
+
const braceIdx = fileContent.indexOf('{', fileContent.split('\n').slice(0, i + 1).join('\n').length);
|
|
133
|
+
let handlerBody = '';
|
|
134
|
+
if (braceIdx >= 0) {
|
|
135
|
+
handlerBody = extractBraceBody(fileContent, braceIdx);
|
|
136
|
+
}
|
|
137
|
+
nodes.push({
|
|
138
|
+
id: (0, types_1.makeNodeId)(filePath, 'error-handler', `errorHandler:${i + 1}`, i + 1),
|
|
139
|
+
kind: 'error-handler',
|
|
140
|
+
name: `errorHandler:${i + 1}`,
|
|
141
|
+
filePath,
|
|
142
|
+
line: i + 1,
|
|
143
|
+
resolution: 'resolved',
|
|
144
|
+
});
|
|
145
|
+
// Extract if (err.name === 'SomeName') branches
|
|
146
|
+
const errNamePattern = /err\.name\s*===?\s*['"`](\w+)['"`]/g;
|
|
147
|
+
let errMatch;
|
|
148
|
+
let bodyOffset = 0;
|
|
149
|
+
while ((errMatch = errNamePattern.exec(handlerBody)) !== null) {
|
|
150
|
+
const bodyLines = handlerBody.substring(0, errMatch.index).split('\n');
|
|
151
|
+
const branchLine = handlerStart + bodyLines.length;
|
|
152
|
+
// Find status code in the branch
|
|
153
|
+
const branchContext = handlerBody.substring(errMatch.index, errMatch.index + 200);
|
|
154
|
+
const statusMatch = branchContext.match(/\.status\s*\(\s*(\d{3})\s*\)/);
|
|
155
|
+
const statusCode = statusMatch ? parseInt(statusMatch[1], 10) : undefined;
|
|
156
|
+
nodes.push({
|
|
157
|
+
id: (0, types_1.makeNodeId)(filePath, 'exception-branch', errMatch[1], branchLine),
|
|
158
|
+
kind: 'exception-branch',
|
|
159
|
+
name: errMatch[1],
|
|
160
|
+
filePath,
|
|
161
|
+
line: branchLine,
|
|
162
|
+
errorName: errMatch[1],
|
|
163
|
+
errorStatusCode: statusCode,
|
|
164
|
+
resolution: 'resolved',
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// ── Detect auth middleware object definitions ──────────────────────────
|
|
169
|
+
// Emit auth-middleware node for downstream use
|
|
170
|
+
if (authObjectDefs.size > 0) {
|
|
171
|
+
for (const [key, sec] of authObjectDefs) {
|
|
172
|
+
nodes.push({
|
|
173
|
+
id: (0, types_1.makeNodeId)(filePath, 'auth-middleware', key, undefined),
|
|
174
|
+
kind: 'auth-middleware',
|
|
175
|
+
name: key,
|
|
176
|
+
filePath,
|
|
177
|
+
security: sec,
|
|
178
|
+
resolution: 'resolved',
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return nodes;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
exports.ExpressMiddlewareDetector = ExpressMiddlewareDetector;
|
|
186
|
+
function extractLastIdentifier(expr) {
|
|
187
|
+
const m = expr.match(/(\w+)\s*$/);
|
|
188
|
+
return m ? m[1] : undefined;
|
|
189
|
+
}
|
|
190
|
+
function extractBraceBody(source, openBrace) {
|
|
191
|
+
let depth = 1;
|
|
192
|
+
let i = openBrace + 1;
|
|
193
|
+
while (i < source.length && depth > 0) {
|
|
194
|
+
if (source[i] === '{')
|
|
195
|
+
depth++;
|
|
196
|
+
if (source[i] === '}')
|
|
197
|
+
depth--;
|
|
198
|
+
i++;
|
|
199
|
+
}
|
|
200
|
+
return source.substring(openBrace, i);
|
|
201
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flask Blueprint detector (Feature 27)
|
|
3
|
+
*
|
|
4
|
+
* Detects from raw Python file content:
|
|
5
|
+
* 1. Blueprint() constructor calls → blueprint-definition nodes
|
|
6
|
+
* 2. @blueprint.route() decorators with HTTP methods → endpoint nodes (marked
|
|
7
|
+
* cross-file-unresolved until url_prefix is resolved by FlaskBlueprintResolver)
|
|
8
|
+
* 3. app.register_blueprint() calls → blueprint-registration nodes
|
|
9
|
+
* 4. @use_kwargs({...}) decorator → parameter nodes with location inference
|
|
10
|
+
* 5. @marshal_with(schema, code=N) → response-schema nodes
|
|
11
|
+
* 6. @jwt_required / @jwt_optional / @jwt_required(optional=True) → security nodes
|
|
12
|
+
*
|
|
13
|
+
* RULE-SA01: never infers role from directory — only from code content
|
|
14
|
+
* RULE-SA02: endpoint nodes are emitted with resolution: 'cross-file-unresolved'
|
|
15
|
+
* when url_prefix is not visible in the same file
|
|
16
|
+
* RULE-SA04: @jwt_optional → security.optional = true — NEVER classified as public
|
|
17
|
+
*/
|
|
18
|
+
import type { DetectedNode, Detector } from './types';
|
|
19
|
+
export declare class FlaskBlueprintDetector implements Detector {
|
|
20
|
+
readonly name = "flask-blueprint";
|
|
21
|
+
detect(fileContent: string, filePath: string): DetectedNode[];
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=flaskBlueprintDetector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flaskBlueprintDetector.d.ts","sourceRoot":"","sources":["../../../../src/pipeline/detectors/flaskBlueprintDetector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAGtD,qBAAa,sBAAuB,YAAW,QAAQ;IACrD,QAAQ,CAAC,IAAI,qBAAqB;IAElC,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,YAAY,EAAE;CAyJ9D"}
|