@arcbridge/core 0.5.0 → 0.6.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/index.d.ts +4 -4
- package/dist/index.js +1091 -62
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -54,7 +54,8 @@ var ArcBridgeConfigSchema = z2.object({
|
|
|
54
54
|
"api-service",
|
|
55
55
|
"dotnet-webapi",
|
|
56
56
|
"unity-game",
|
|
57
|
-
"angular-app"
|
|
57
|
+
"angular-app",
|
|
58
|
+
"fullstack-nextjs-dotnet"
|
|
58
59
|
]).default("nextjs-app-router"),
|
|
59
60
|
services: z2.array(ServiceSchema).default([]),
|
|
60
61
|
platforms: z2.array(z2.enum(["claude", "copilot", "gemini", "codex", "opencode"])).default(["claude"]),
|
|
@@ -809,6 +810,46 @@ function configTemplate6(input) {
|
|
|
809
810
|
};
|
|
810
811
|
}
|
|
811
812
|
|
|
813
|
+
// src/templates/config/fullstack-nextjs-dotnet.ts
|
|
814
|
+
function configTemplate7(input) {
|
|
815
|
+
return {
|
|
816
|
+
schema_version: 1,
|
|
817
|
+
project_name: input.name,
|
|
818
|
+
project_type: "fullstack-nextjs-dotnet",
|
|
819
|
+
services: [
|
|
820
|
+
{ name: "frontend", path: "frontend", type: "nextjs", tsconfig: "frontend/tsconfig.json" },
|
|
821
|
+
{ name: "api", path: "api", type: "dotnet" }
|
|
822
|
+
],
|
|
823
|
+
platforms: input.platforms,
|
|
824
|
+
quality_priorities: input.quality_priorities,
|
|
825
|
+
indexing: {
|
|
826
|
+
include: [
|
|
827
|
+
"frontend/src/**/*",
|
|
828
|
+
"frontend/app/**/*",
|
|
829
|
+
"api/**/*.cs",
|
|
830
|
+
"shared/**/*",
|
|
831
|
+
"contracts/**/*"
|
|
832
|
+
],
|
|
833
|
+
exclude: ["node_modules", "dist", ".next", "bin", "obj", "coverage"],
|
|
834
|
+
default_mode: "fast",
|
|
835
|
+
csharp_indexer: "auto"
|
|
836
|
+
},
|
|
837
|
+
testing: {
|
|
838
|
+
test_command: "npm test",
|
|
839
|
+
timeout_ms: 6e4
|
|
840
|
+
},
|
|
841
|
+
drift: {
|
|
842
|
+
ignore_paths: []
|
|
843
|
+
},
|
|
844
|
+
metrics: { auto_record: false },
|
|
845
|
+
sync: {
|
|
846
|
+
auto_detect_drift: true,
|
|
847
|
+
drift_severity_threshold: "warning",
|
|
848
|
+
propose_updates_on: "phase-complete"
|
|
849
|
+
}
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
|
|
812
853
|
// src/generators/config-generator.ts
|
|
813
854
|
var configTemplates = {
|
|
814
855
|
"nextjs-app-router": configTemplate,
|
|
@@ -816,7 +857,8 @@ var configTemplates = {
|
|
|
816
857
|
"api-service": configTemplate3,
|
|
817
858
|
"dotnet-webapi": configTemplate4,
|
|
818
859
|
"unity-game": configTemplate5,
|
|
819
|
-
"angular-app": configTemplate6
|
|
860
|
+
"angular-app": configTemplate6,
|
|
861
|
+
"fullstack-nextjs-dotnet": configTemplate7
|
|
820
862
|
};
|
|
821
863
|
function generateConfig(targetDir, input) {
|
|
822
864
|
const templateFn = configTemplates[input.template] ?? configTemplate;
|
|
@@ -845,7 +887,7 @@ function introductionTemplate(input) {
|
|
|
845
887
|
|
|
846
888
|
## Requirements Overview
|
|
847
889
|
|
|
848
|
-
${input.name} is a ${input.template === "nextjs-app-router" ? "Next.js application using the App Router" : input.template === "angular-app" ? "Angular application with standalone components" : input.template === "dotnet-webapi" ? "ASP.NET Core Web API" : input.template === "unity-game" ? "Unity game built with a code-heavy C# architecture" : "web application"}.
|
|
890
|
+
${input.name} is a ${input.template === "nextjs-app-router" ? "Next.js application using the App Router" : input.template === "angular-app" ? "Angular application with standalone components" : input.template === "dotnet-webapi" ? "ASP.NET Core Web API" : input.template === "unity-game" ? "Unity game built with a code-heavy C# architecture" : input.template === "fullstack-nextjs-dotnet" ? "fullstack application with a Next.js frontend and .NET API backend" : "web application"}.
|
|
849
891
|
|
|
850
892
|
### Key Features
|
|
851
893
|
|
|
@@ -1047,7 +1089,66 @@ function getEntrypoints(template, srcPrefix, appPrefix) {
|
|
|
1047
1089
|
function buildingBlocksTemplate(input) {
|
|
1048
1090
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1049
1091
|
const layout = detectProjectLayout(input.projectRoot, input.template);
|
|
1050
|
-
const defaultBlocks = input.template === "dotnet-webapi" ? buildDotnetBlocks(input) : input.template === "unity-game" ? buildUnityBlocks() : input.template === "angular-app" ? buildAngularBlocks(layout) : buildJsBlocks(input, layout);
|
|
1092
|
+
const defaultBlocks = input.template === "fullstack-nextjs-dotnet" ? buildFullstackBlocks() : input.template === "dotnet-webapi" ? buildDotnetBlocks(input) : input.template === "unity-game" ? buildUnityBlocks() : input.template === "angular-app" ? buildAngularBlocks(layout) : buildJsBlocks(input, layout);
|
|
1093
|
+
function buildFullstackBlocks() {
|
|
1094
|
+
return [
|
|
1095
|
+
{
|
|
1096
|
+
id: "frontend-shell",
|
|
1097
|
+
name: "Frontend Shell",
|
|
1098
|
+
level: 1,
|
|
1099
|
+
code_paths: ["frontend/app/", "frontend/src/app/"],
|
|
1100
|
+
interfaces: [],
|
|
1101
|
+
quality_scenarios: ["PERF-01"],
|
|
1102
|
+
adrs: [],
|
|
1103
|
+
responsibility: "Next.js App Router layouts, pages, and top-level page structure",
|
|
1104
|
+
service: "frontend"
|
|
1105
|
+
},
|
|
1106
|
+
{
|
|
1107
|
+
id: "frontend-components",
|
|
1108
|
+
name: "Frontend Components",
|
|
1109
|
+
level: 1,
|
|
1110
|
+
code_paths: ["frontend/src/components/", "frontend/components/"],
|
|
1111
|
+
interfaces: [],
|
|
1112
|
+
quality_scenarios: ["A11Y-01"],
|
|
1113
|
+
adrs: [],
|
|
1114
|
+
responsibility: "Shared, reusable React UI components",
|
|
1115
|
+
service: "frontend"
|
|
1116
|
+
},
|
|
1117
|
+
{
|
|
1118
|
+
id: "api-controllers",
|
|
1119
|
+
name: "API Controllers",
|
|
1120
|
+
level: 1,
|
|
1121
|
+
code_paths: ["api/Controllers/"],
|
|
1122
|
+
interfaces: [],
|
|
1123
|
+
quality_scenarios: ["SEC-03"],
|
|
1124
|
+
adrs: [],
|
|
1125
|
+
responsibility: "ASP.NET Core API endpoint definitions and request handling",
|
|
1126
|
+
service: "api"
|
|
1127
|
+
},
|
|
1128
|
+
{
|
|
1129
|
+
id: "api-services",
|
|
1130
|
+
name: "API Services",
|
|
1131
|
+
level: 1,
|
|
1132
|
+
code_paths: ["api/Services/"],
|
|
1133
|
+
interfaces: ["api-controllers"],
|
|
1134
|
+
quality_scenarios: [],
|
|
1135
|
+
adrs: [],
|
|
1136
|
+
responsibility: "Business logic, use cases, and orchestration between domain and infrastructure",
|
|
1137
|
+
service: "api"
|
|
1138
|
+
},
|
|
1139
|
+
{
|
|
1140
|
+
id: "shared-contracts",
|
|
1141
|
+
name: "Shared Contracts",
|
|
1142
|
+
level: 1,
|
|
1143
|
+
code_paths: ["shared/", "contracts/"],
|
|
1144
|
+
interfaces: ["frontend-shell", "api-controllers"],
|
|
1145
|
+
quality_scenarios: ["MAINT-01"],
|
|
1146
|
+
adrs: [],
|
|
1147
|
+
responsibility: "API types, schemas, and contracts shared between the Next.js frontend and .NET backend",
|
|
1148
|
+
service: "api"
|
|
1149
|
+
}
|
|
1150
|
+
];
|
|
1151
|
+
}
|
|
1051
1152
|
function buildJsBlocks(inp, lt) {
|
|
1052
1153
|
const src = lt.srcPrefix;
|
|
1053
1154
|
const entries = lt.entrypoints;
|
|
@@ -2275,13 +2376,169 @@ var ANGULAR_SCENARIOS = {
|
|
|
2275
2376
|
}
|
|
2276
2377
|
]
|
|
2277
2378
|
};
|
|
2379
|
+
var FULLSTACK_NEXTJS_DOTNET_SCENARIOS = {
|
|
2380
|
+
security: [
|
|
2381
|
+
{
|
|
2382
|
+
id: "SEC-02",
|
|
2383
|
+
name: "No secrets in client bundles or source",
|
|
2384
|
+
category: "security",
|
|
2385
|
+
priority: "must",
|
|
2386
|
+
scenario: "Client-side JavaScript bundle and .NET source/config files are analyzed",
|
|
2387
|
+
expected: "No API keys, tokens, or secrets are found in client-side code or hardcoded in appsettings",
|
|
2388
|
+
linked_code: [],
|
|
2389
|
+
linked_tests: [],
|
|
2390
|
+
linked_blocks: ["frontend-shell", "api-controllers"],
|
|
2391
|
+
verification: "automatic",
|
|
2392
|
+
status: "untested"
|
|
2393
|
+
},
|
|
2394
|
+
{
|
|
2395
|
+
id: "SEC-04",
|
|
2396
|
+
name: "CORS policy restricts origins",
|
|
2397
|
+
category: "security",
|
|
2398
|
+
priority: "should",
|
|
2399
|
+
scenario: "A cross-origin request is made from an untrusted domain",
|
|
2400
|
+
expected: "Request is rejected by CORS middleware; only the frontend origin is allowed",
|
|
2401
|
+
linked_code: [],
|
|
2402
|
+
linked_tests: [],
|
|
2403
|
+
linked_blocks: ["api-controllers"],
|
|
2404
|
+
verification: "automatic",
|
|
2405
|
+
status: "untested"
|
|
2406
|
+
},
|
|
2407
|
+
{
|
|
2408
|
+
id: "SEC-05",
|
|
2409
|
+
name: "Cross-service auth token handling",
|
|
2410
|
+
category: "security",
|
|
2411
|
+
priority: "must",
|
|
2412
|
+
scenario: "Frontend sends an auth token to the .NET API on every protected request",
|
|
2413
|
+
expected: "Token is validated server-side; expired or invalid tokens return 401; tokens are never stored in localStorage",
|
|
2414
|
+
linked_code: [],
|
|
2415
|
+
linked_tests: [],
|
|
2416
|
+
linked_blocks: ["frontend-shell", "api-services"],
|
|
2417
|
+
verification: "automatic",
|
|
2418
|
+
status: "untested"
|
|
2419
|
+
}
|
|
2420
|
+
],
|
|
2421
|
+
performance: [
|
|
2422
|
+
{
|
|
2423
|
+
id: "PERF-01",
|
|
2424
|
+
name: "Initial page load under 3s",
|
|
2425
|
+
category: "performance",
|
|
2426
|
+
priority: "should",
|
|
2427
|
+
scenario: "User loads the landing page on a 3G connection",
|
|
2428
|
+
expected: "Largest Contentful Paint (LCP) is under 3 seconds",
|
|
2429
|
+
linked_code: [],
|
|
2430
|
+
linked_tests: [],
|
|
2431
|
+
linked_blocks: ["frontend-shell"],
|
|
2432
|
+
verification: "semi-automatic",
|
|
2433
|
+
status: "untested"
|
|
2434
|
+
},
|
|
2435
|
+
{
|
|
2436
|
+
id: "PERF-03",
|
|
2437
|
+
name: "Frontend bundle under 200KB gzipped",
|
|
2438
|
+
category: "performance",
|
|
2439
|
+
priority: "should",
|
|
2440
|
+
scenario: "Production build output is analyzed",
|
|
2441
|
+
expected: "Main JavaScript bundle is under 200KB gzipped",
|
|
2442
|
+
linked_code: [],
|
|
2443
|
+
linked_tests: [],
|
|
2444
|
+
linked_blocks: ["frontend-shell", "frontend-components"],
|
|
2445
|
+
verification: "semi-automatic",
|
|
2446
|
+
status: "untested"
|
|
2447
|
+
},
|
|
2448
|
+
{
|
|
2449
|
+
id: "PERF-04",
|
|
2450
|
+
name: "Async all the way in .NET API",
|
|
2451
|
+
category: "performance",
|
|
2452
|
+
priority: "must",
|
|
2453
|
+
scenario: "I/O-bound operations in the API are reviewed",
|
|
2454
|
+
expected: "All I/O operations use async/await; no sync-over-async or blocking calls",
|
|
2455
|
+
linked_code: [],
|
|
2456
|
+
linked_tests: [],
|
|
2457
|
+
linked_blocks: ["api-controllers", "api-services"],
|
|
2458
|
+
verification: "semi-automatic",
|
|
2459
|
+
status: "untested"
|
|
2460
|
+
}
|
|
2461
|
+
],
|
|
2462
|
+
reliability: [
|
|
2463
|
+
{
|
|
2464
|
+
id: "REL-02",
|
|
2465
|
+
name: "Health check covers dependencies",
|
|
2466
|
+
category: "reliability",
|
|
2467
|
+
priority: "should",
|
|
2468
|
+
scenario: "Orchestrator calls the /health endpoint",
|
|
2469
|
+
expected: "Returns degraded or unhealthy if database or critical external service is unreachable",
|
|
2470
|
+
linked_code: [],
|
|
2471
|
+
linked_tests: [],
|
|
2472
|
+
linked_blocks: ["api-controllers"],
|
|
2473
|
+
verification: "automatic",
|
|
2474
|
+
status: "untested"
|
|
2475
|
+
},
|
|
2476
|
+
{
|
|
2477
|
+
id: "REL-03",
|
|
2478
|
+
name: "Structured logging with correlation",
|
|
2479
|
+
category: "reliability",
|
|
2480
|
+
priority: "should",
|
|
2481
|
+
scenario: "A request flows through frontend to API and back",
|
|
2482
|
+
expected: "All log entries share a correlation ID across services; logs are structured JSON",
|
|
2483
|
+
linked_code: [],
|
|
2484
|
+
linked_tests: [],
|
|
2485
|
+
linked_blocks: ["api-services"],
|
|
2486
|
+
verification: "semi-automatic",
|
|
2487
|
+
status: "untested"
|
|
2488
|
+
}
|
|
2489
|
+
],
|
|
2490
|
+
accessibility: [
|
|
2491
|
+
{
|
|
2492
|
+
id: "A11Y-01",
|
|
2493
|
+
name: "WCAG 2.1 AA compliance",
|
|
2494
|
+
category: "accessibility",
|
|
2495
|
+
priority: "should",
|
|
2496
|
+
scenario: "All pages are audited with axe-core",
|
|
2497
|
+
expected: "No critical or serious accessibility violations",
|
|
2498
|
+
linked_code: [],
|
|
2499
|
+
linked_tests: [],
|
|
2500
|
+
linked_blocks: ["frontend-components"],
|
|
2501
|
+
verification: "semi-automatic",
|
|
2502
|
+
status: "untested"
|
|
2503
|
+
},
|
|
2504
|
+
{
|
|
2505
|
+
id: "A11Y-02",
|
|
2506
|
+
name: "Keyboard navigation",
|
|
2507
|
+
category: "accessibility",
|
|
2508
|
+
priority: "should",
|
|
2509
|
+
scenario: "User navigates the entire application using only keyboard",
|
|
2510
|
+
expected: "All interactive elements are reachable and operable via keyboard",
|
|
2511
|
+
linked_code: [],
|
|
2512
|
+
linked_tests: [],
|
|
2513
|
+
linked_blocks: ["frontend-components"],
|
|
2514
|
+
verification: "manual",
|
|
2515
|
+
status: "untested"
|
|
2516
|
+
}
|
|
2517
|
+
],
|
|
2518
|
+
maintainability: [
|
|
2519
|
+
{
|
|
2520
|
+
id: "MAINT-03",
|
|
2521
|
+
name: "API contract consistency",
|
|
2522
|
+
category: "maintainability",
|
|
2523
|
+
priority: "must",
|
|
2524
|
+
scenario: "Frontend type definitions are compared against API OpenAPI spec or shared contracts",
|
|
2525
|
+
expected: "All frontend API types match the backend contract; CI check prevents drift",
|
|
2526
|
+
linked_code: [],
|
|
2527
|
+
linked_tests: [],
|
|
2528
|
+
linked_blocks: ["shared-contracts"],
|
|
2529
|
+
verification: "automatic",
|
|
2530
|
+
status: "untested"
|
|
2531
|
+
}
|
|
2532
|
+
]
|
|
2533
|
+
};
|
|
2278
2534
|
var TEMPLATE_SCENARIOS = {
|
|
2279
2535
|
"nextjs-app-router": FRONTEND_SCENARIOS,
|
|
2280
2536
|
"react-vite": FRONTEND_SCENARIOS,
|
|
2281
2537
|
"angular-app": ANGULAR_SCENARIOS,
|
|
2282
2538
|
"api-service": API_SCENARIOS,
|
|
2283
2539
|
"dotnet-webapi": DOTNET_SCENARIOS,
|
|
2284
|
-
"unity-game": UNITY_GAME_SCENARIOS
|
|
2540
|
+
"unity-game": UNITY_GAME_SCENARIOS,
|
|
2541
|
+
"fullstack-nextjs-dotnet": FULLSTACK_NEXTJS_DOTNET_SCENARIOS
|
|
2285
2542
|
};
|
|
2286
2543
|
function mergeScenarios(template, category) {
|
|
2287
2544
|
const shared = SHARED_SCENARIOS[category] ?? [];
|
|
@@ -3857,90 +4114,445 @@ function phaseTasksTemplate6(_input, phaseId) {
|
|
|
3857
4114
|
]
|
|
3858
4115
|
},
|
|
3859
4116
|
{
|
|
3860
|
-
id: "task-0.2-routing",
|
|
3861
|
-
title: "Set up routing with lazy loading",
|
|
4117
|
+
id: "task-0.2-routing",
|
|
4118
|
+
title: "Set up routing with lazy loading",
|
|
4119
|
+
status: "todo",
|
|
4120
|
+
building_block: "app-shell",
|
|
4121
|
+
quality_scenarios: ["PERF-05"],
|
|
4122
|
+
acceptance_criteria: [
|
|
4123
|
+
"App routes defined in app.routes.ts",
|
|
4124
|
+
"At least one feature route uses loadComponent or loadChildren",
|
|
4125
|
+
"Route guards and resolvers use functional approach"
|
|
4126
|
+
]
|
|
4127
|
+
},
|
|
4128
|
+
{
|
|
4129
|
+
id: "task-0.3-core-services",
|
|
4130
|
+
title: "Set up core services and HTTP interceptor",
|
|
4131
|
+
status: "todo",
|
|
4132
|
+
building_block: "core-services",
|
|
4133
|
+
quality_scenarios: [],
|
|
4134
|
+
acceptance_criteria: [
|
|
4135
|
+
"HTTP interceptor for API base URL and error handling",
|
|
4136
|
+
"Core services provided in root",
|
|
4137
|
+
"Environment configuration set up"
|
|
4138
|
+
]
|
|
4139
|
+
},
|
|
4140
|
+
{
|
|
4141
|
+
id: "task-0.4-testing",
|
|
4142
|
+
title: "Set up testing infrastructure",
|
|
4143
|
+
status: "todo",
|
|
4144
|
+
quality_scenarios: ["MAINT-02"],
|
|
4145
|
+
acceptance_criteria: [
|
|
4146
|
+
"Unit test framework configured (Vitest or Karma/Jasmine)",
|
|
4147
|
+
"First component test passes",
|
|
4148
|
+
"Test coverage reporting enabled"
|
|
4149
|
+
]
|
|
4150
|
+
}
|
|
4151
|
+
]
|
|
4152
|
+
},
|
|
4153
|
+
"phase-1-foundation": {
|
|
4154
|
+
schema_version: 1,
|
|
4155
|
+
phase_id: "phase-1-foundation",
|
|
4156
|
+
tasks: [
|
|
4157
|
+
{
|
|
4158
|
+
id: "task-1.1-shared-components",
|
|
4159
|
+
title: "Build shared component library",
|
|
4160
|
+
status: "todo",
|
|
4161
|
+
building_block: "shared-components",
|
|
4162
|
+
quality_scenarios: ["A11Y-01"],
|
|
4163
|
+
acceptance_criteria: [
|
|
4164
|
+
"Reusable UI components in shared/ directory",
|
|
4165
|
+
"Components use OnPush change detection or signals",
|
|
4166
|
+
"Components follow accessibility guidelines"
|
|
4167
|
+
]
|
|
4168
|
+
},
|
|
4169
|
+
{
|
|
4170
|
+
id: "task-1.2-auth",
|
|
4171
|
+
title: "Implement authentication and route guards",
|
|
4172
|
+
status: "todo",
|
|
4173
|
+
building_block: "core-services",
|
|
4174
|
+
quality_scenarios: ["SEC-01"],
|
|
4175
|
+
acceptance_criteria: [
|
|
4176
|
+
"Auth service with login/logout/token management",
|
|
4177
|
+
"Functional route guard protecting private routes",
|
|
4178
|
+
"Auth interceptor attaching tokens to API requests"
|
|
4179
|
+
]
|
|
4180
|
+
},
|
|
4181
|
+
{
|
|
4182
|
+
id: "task-1.3-api-client",
|
|
4183
|
+
title: "Set up API client layer",
|
|
4184
|
+
status: "todo",
|
|
4185
|
+
building_block: "api-client",
|
|
4186
|
+
quality_scenarios: [],
|
|
4187
|
+
acceptance_criteria: [
|
|
4188
|
+
"Typed API service methods for backend endpoints",
|
|
4189
|
+
"Error handling and retry logic",
|
|
4190
|
+
"Loading/error state management"
|
|
4191
|
+
]
|
|
4192
|
+
},
|
|
4193
|
+
{
|
|
4194
|
+
id: "task-1.4-document-decisions",
|
|
4195
|
+
title: "Document architectural decisions as ADRs",
|
|
4196
|
+
status: "todo",
|
|
4197
|
+
quality_scenarios: [],
|
|
4198
|
+
acceptance_criteria: [
|
|
4199
|
+
"ADR for each significant architecture/pattern choice",
|
|
4200
|
+
"ADRs linked to affected building blocks and code paths"
|
|
4201
|
+
]
|
|
4202
|
+
}
|
|
4203
|
+
]
|
|
4204
|
+
}
|
|
4205
|
+
};
|
|
4206
|
+
return tasksByPhase[phaseId] ?? null;
|
|
4207
|
+
}
|
|
4208
|
+
|
|
4209
|
+
// src/templates/phases/fullstack-nextjs-dotnet.ts
|
|
4210
|
+
function phasePlanTemplate7(_input) {
|
|
4211
|
+
const phases = [
|
|
4212
|
+
{
|
|
4213
|
+
id: "phase-0-setup",
|
|
4214
|
+
name: "Monorepo Setup",
|
|
4215
|
+
phase_number: 0,
|
|
4216
|
+
status: "planned",
|
|
4217
|
+
description: "Initialize monorepo structure, scaffold Next.js frontend and .NET API backend, configure shared tooling",
|
|
4218
|
+
gate_requirements: [
|
|
4219
|
+
"Monorepo structure with frontend/ and api/ directories",
|
|
4220
|
+
"Next.js app builds and runs at localhost:3000",
|
|
4221
|
+
".NET API builds and runs with health check at /health",
|
|
4222
|
+
"Shared dev scripts (start, build, test) work from root"
|
|
4223
|
+
]
|
|
4224
|
+
},
|
|
4225
|
+
{
|
|
4226
|
+
id: "phase-1-api-foundation",
|
|
4227
|
+
name: "API Foundation",
|
|
4228
|
+
phase_number: 1,
|
|
4229
|
+
status: "planned",
|
|
4230
|
+
description: "Build .NET API controllers, services, authentication middleware, and database access layer",
|
|
4231
|
+
gate_requirements: [
|
|
4232
|
+
"Auth middleware protects API endpoints",
|
|
4233
|
+
"Global exception handler returns ProblemDetails",
|
|
4234
|
+
"Database access layer configured with migrations",
|
|
4235
|
+
"OpenAPI/Swagger documentation generated",
|
|
4236
|
+
"Quality scenarios SEC-01, SEC-02 verified"
|
|
4237
|
+
]
|
|
4238
|
+
},
|
|
4239
|
+
{
|
|
4240
|
+
id: "phase-2-frontend-foundation",
|
|
4241
|
+
name: "Frontend Foundation",
|
|
4242
|
+
phase_number: 2,
|
|
4243
|
+
status: "planned",
|
|
4244
|
+
description: "Build Next.js layouts, reusable components, API client layer, and authentication UI",
|
|
4245
|
+
gate_requirements: [
|
|
4246
|
+
"App shell with layout and navigation working",
|
|
4247
|
+
"API client layer communicates with .NET backend",
|
|
4248
|
+
"Auth UI (login, register, protected routes) functional",
|
|
4249
|
+
"Component library with consistent styling",
|
|
4250
|
+
"Quality scenarios A11Y-01, PERF-01 verified"
|
|
4251
|
+
]
|
|
4252
|
+
},
|
|
4253
|
+
{
|
|
4254
|
+
id: "phase-3-feature-integration",
|
|
4255
|
+
name: "Feature Integration",
|
|
4256
|
+
phase_number: 3,
|
|
4257
|
+
status: "planned",
|
|
4258
|
+
description: "Implement end-to-end features across frontend and backend, API contract synchronization, and shared types",
|
|
4259
|
+
gate_requirements: [
|
|
4260
|
+
"At least one full CRUD feature works end-to-end",
|
|
4261
|
+
"API contracts validated between frontend and backend",
|
|
4262
|
+
"Shared type definitions keep frontend and API in sync",
|
|
4263
|
+
"Integration tests cover cross-service workflows"
|
|
4264
|
+
]
|
|
4265
|
+
},
|
|
4266
|
+
{
|
|
4267
|
+
id: "phase-4-production",
|
|
4268
|
+
name: "Production Readiness",
|
|
4269
|
+
phase_number: 4,
|
|
4270
|
+
status: "planned",
|
|
4271
|
+
description: "CI/CD pipeline, deployment configuration, monitoring, and performance optimization",
|
|
4272
|
+
gate_requirements: [
|
|
4273
|
+
"CI pipeline builds and tests both services",
|
|
4274
|
+
"Docker Compose or equivalent runs the full stack",
|
|
4275
|
+
"Rate limiting and CORS configured for production",
|
|
4276
|
+
"All quality scenarios passing",
|
|
4277
|
+
"Production deployment successful"
|
|
4278
|
+
]
|
|
4279
|
+
}
|
|
4280
|
+
];
|
|
4281
|
+
return { schema_version: 1, phases };
|
|
4282
|
+
}
|
|
4283
|
+
function phaseTasksTemplate7(_input, phaseId) {
|
|
4284
|
+
const tasksByPhase = {
|
|
4285
|
+
"phase-0-setup": {
|
|
4286
|
+
schema_version: 1,
|
|
4287
|
+
phase_id: "phase-0-setup",
|
|
4288
|
+
tasks: [
|
|
4289
|
+
{
|
|
4290
|
+
id: "task-0.1-monorepo-structure",
|
|
4291
|
+
title: "Set up monorepo structure with frontend and api directories",
|
|
4292
|
+
status: "todo",
|
|
4293
|
+
building_block: "frontend-shell",
|
|
4294
|
+
quality_scenarios: [],
|
|
4295
|
+
acceptance_criteria: [
|
|
4296
|
+
"Root package.json with workspace scripts",
|
|
4297
|
+
"frontend/ directory with Next.js app (App Router)",
|
|
4298
|
+
"api/ directory with ASP.NET Core Web API project",
|
|
4299
|
+
"Shared .editorconfig and linting configuration"
|
|
4300
|
+
]
|
|
4301
|
+
},
|
|
4302
|
+
{
|
|
4303
|
+
id: "task-0.2-frontend-scaffold",
|
|
4304
|
+
title: "Scaffold Next.js frontend with App Router",
|
|
4305
|
+
status: "todo",
|
|
4306
|
+
building_block: "frontend-shell",
|
|
4307
|
+
quality_scenarios: ["MAINT-01"],
|
|
4308
|
+
acceptance_criteria: [
|
|
4309
|
+
"Next.js app created with TypeScript and App Router",
|
|
4310
|
+
"Root layout with metadata and font configuration",
|
|
4311
|
+
"Landing page renders at localhost:3000",
|
|
4312
|
+
"ESLint and Prettier configured"
|
|
4313
|
+
]
|
|
4314
|
+
},
|
|
4315
|
+
{
|
|
4316
|
+
id: "task-0.3-api-scaffold",
|
|
4317
|
+
title: "Scaffold .NET API with health check",
|
|
4318
|
+
status: "todo",
|
|
4319
|
+
building_block: "api-controllers",
|
|
4320
|
+
quality_scenarios: [],
|
|
4321
|
+
acceptance_criteria: [
|
|
4322
|
+
"ASP.NET Core Web API project created",
|
|
4323
|
+
"Health check endpoint responds at /health",
|
|
4324
|
+
"DI container configured with core services",
|
|
4325
|
+
"Logging and configuration in place"
|
|
4326
|
+
]
|
|
4327
|
+
},
|
|
4328
|
+
{
|
|
4329
|
+
id: "task-0.4-shared-tooling",
|
|
4330
|
+
title: "Configure shared development tooling and scripts",
|
|
4331
|
+
status: "todo",
|
|
4332
|
+
building_block: "shared-contracts",
|
|
4333
|
+
quality_scenarios: ["MAINT-02"],
|
|
4334
|
+
acceptance_criteria: [
|
|
4335
|
+
"Root-level scripts to start, build, and test both services",
|
|
4336
|
+
"Shared TypeScript types or OpenAPI contract directory",
|
|
4337
|
+
"Testing infrastructure for both frontend and backend",
|
|
4338
|
+
"Git hooks for linting and formatting"
|
|
4339
|
+
]
|
|
4340
|
+
}
|
|
4341
|
+
]
|
|
4342
|
+
},
|
|
4343
|
+
"phase-1-api-foundation": {
|
|
4344
|
+
schema_version: 1,
|
|
4345
|
+
phase_id: "phase-1-api-foundation",
|
|
4346
|
+
tasks: [
|
|
4347
|
+
{
|
|
4348
|
+
id: "task-1.1-api-auth",
|
|
4349
|
+
title: "Implement API authentication and authorization",
|
|
4350
|
+
status: "todo",
|
|
4351
|
+
building_block: "api-services",
|
|
4352
|
+
quality_scenarios: ["SEC-01", "SEC-02"],
|
|
4353
|
+
acceptance_criteria: [
|
|
4354
|
+
"JWT bearer auth configured in .NET API",
|
|
4355
|
+
"Authorization policies defined for role-based access",
|
|
4356
|
+
"Auth middleware applied to protected endpoints",
|
|
4357
|
+
"Token validation and refresh logic implemented"
|
|
4358
|
+
]
|
|
4359
|
+
},
|
|
4360
|
+
{
|
|
4361
|
+
id: "task-1.2-api-controllers",
|
|
4362
|
+
title: "Build core API controllers and endpoints",
|
|
4363
|
+
status: "todo",
|
|
4364
|
+
building_block: "api-controllers",
|
|
4365
|
+
quality_scenarios: ["SEC-03"],
|
|
4366
|
+
acceptance_criteria: [
|
|
4367
|
+
"RESTful controllers for core resources",
|
|
4368
|
+
"Input validation via FluentValidation or data annotations",
|
|
4369
|
+
"Consistent response format with ProblemDetails for errors",
|
|
4370
|
+
"OpenAPI/Swagger documentation generated and accessible"
|
|
4371
|
+
]
|
|
4372
|
+
},
|
|
4373
|
+
{
|
|
4374
|
+
id: "task-1.3-api-services",
|
|
4375
|
+
title: "Implement business logic and service layer",
|
|
4376
|
+
status: "todo",
|
|
4377
|
+
building_block: "api-services",
|
|
4378
|
+
quality_scenarios: ["MAINT-01"],
|
|
4379
|
+
acceptance_criteria: [
|
|
4380
|
+
"Service classes encapsulate business logic",
|
|
4381
|
+
"Repository pattern for data access",
|
|
4382
|
+
"EF Core or Dapper configured with migrations",
|
|
4383
|
+
"Unit tests cover service layer logic"
|
|
4384
|
+
]
|
|
4385
|
+
},
|
|
4386
|
+
{
|
|
4387
|
+
id: "task-1.4-api-middleware",
|
|
4388
|
+
title: "Configure middleware pipeline and error handling",
|
|
4389
|
+
status: "todo",
|
|
4390
|
+
building_block: "api-controllers",
|
|
4391
|
+
quality_scenarios: ["REL-01"],
|
|
4392
|
+
acceptance_criteria: [
|
|
4393
|
+
"Global exception handler returns RFC 7807 ProblemDetails",
|
|
4394
|
+
"Request/response logging with correlation IDs",
|
|
4395
|
+
"CORS policy configured for frontend origin",
|
|
4396
|
+
"No stack traces leaked in production"
|
|
4397
|
+
]
|
|
4398
|
+
}
|
|
4399
|
+
]
|
|
4400
|
+
},
|
|
4401
|
+
"phase-2-frontend-foundation": {
|
|
4402
|
+
schema_version: 1,
|
|
4403
|
+
phase_id: "phase-2-frontend-foundation",
|
|
4404
|
+
tasks: [
|
|
4405
|
+
{
|
|
4406
|
+
id: "task-2.1-frontend-layout",
|
|
4407
|
+
title: "Build app shell with layouts and navigation",
|
|
4408
|
+
status: "todo",
|
|
4409
|
+
building_block: "frontend-shell",
|
|
4410
|
+
quality_scenarios: ["A11Y-01"],
|
|
4411
|
+
acceptance_criteria: [
|
|
4412
|
+
"Root layout with header, sidebar, and main content area",
|
|
4413
|
+
"Navigation component with active state indicators",
|
|
4414
|
+
"Responsive layout that works on mobile and desktop",
|
|
4415
|
+
"Loading and error boundary components"
|
|
4416
|
+
]
|
|
4417
|
+
},
|
|
4418
|
+
{
|
|
4419
|
+
id: "task-2.2-frontend-components",
|
|
4420
|
+
title: "Create reusable UI component library",
|
|
4421
|
+
status: "todo",
|
|
4422
|
+
building_block: "frontend-components",
|
|
4423
|
+
quality_scenarios: ["A11Y-02"],
|
|
4424
|
+
acceptance_criteria: [
|
|
4425
|
+
"Button, Input, Card, Modal, and Table components",
|
|
4426
|
+
"Consistent styling with design tokens or Tailwind",
|
|
4427
|
+
"All components meet WCAG 2.1 AA accessibility",
|
|
4428
|
+
"Component documentation or Storybook setup"
|
|
4429
|
+
]
|
|
4430
|
+
},
|
|
4431
|
+
{
|
|
4432
|
+
id: "task-2.3-api-client",
|
|
4433
|
+
title: "Build API client layer for backend communication",
|
|
4434
|
+
status: "todo",
|
|
4435
|
+
building_block: "shared-contracts",
|
|
4436
|
+
quality_scenarios: ["REL-01"],
|
|
4437
|
+
acceptance_criteria: [
|
|
4438
|
+
"Typed API client generated from OpenAPI spec or manually defined",
|
|
4439
|
+
"Error handling with user-friendly messages",
|
|
4440
|
+
"Auth token injection in request headers",
|
|
4441
|
+
"Request/response interceptors for logging and retry"
|
|
4442
|
+
]
|
|
4443
|
+
},
|
|
4444
|
+
{
|
|
4445
|
+
id: "task-2.4-frontend-auth",
|
|
4446
|
+
title: "Implement frontend authentication UI and flow",
|
|
3862
4447
|
status: "todo",
|
|
3863
|
-
building_block: "
|
|
3864
|
-
quality_scenarios: ["
|
|
4448
|
+
building_block: "frontend-shell",
|
|
4449
|
+
quality_scenarios: ["SEC-01"],
|
|
3865
4450
|
acceptance_criteria: [
|
|
3866
|
-
"
|
|
3867
|
-
"
|
|
3868
|
-
"
|
|
4451
|
+
"Login and registration pages",
|
|
4452
|
+
"Protected route middleware or layout",
|
|
4453
|
+
"Token storage and refresh handling",
|
|
4454
|
+
"Auth state management (context or store)"
|
|
4455
|
+
]
|
|
4456
|
+
}
|
|
4457
|
+
]
|
|
4458
|
+
},
|
|
4459
|
+
"phase-3-feature-integration": {
|
|
4460
|
+
schema_version: 1,
|
|
4461
|
+
phase_id: "phase-3-feature-integration",
|
|
4462
|
+
tasks: [
|
|
4463
|
+
{
|
|
4464
|
+
id: "task-3.1-e2e-feature",
|
|
4465
|
+
title: "Implement first end-to-end CRUD feature",
|
|
4466
|
+
status: "todo",
|
|
4467
|
+
building_block: "api-controllers",
|
|
4468
|
+
quality_scenarios: ["PERF-02"],
|
|
4469
|
+
acceptance_criteria: [
|
|
4470
|
+
"Full CRUD operations from frontend through API to database",
|
|
4471
|
+
"Optimistic UI updates where appropriate",
|
|
4472
|
+
"Server-side validation reflected in frontend forms",
|
|
4473
|
+
"List, detail, create, edit, and delete views working"
|
|
3869
4474
|
]
|
|
3870
4475
|
},
|
|
3871
4476
|
{
|
|
3872
|
-
id: "task-
|
|
3873
|
-
title: "Set up
|
|
4477
|
+
id: "task-3.2-contract-sync",
|
|
4478
|
+
title: "Set up API contract synchronization",
|
|
3874
4479
|
status: "todo",
|
|
3875
|
-
building_block: "
|
|
3876
|
-
quality_scenarios: [],
|
|
4480
|
+
building_block: "shared-contracts",
|
|
4481
|
+
quality_scenarios: ["MAINT-01"],
|
|
3877
4482
|
acceptance_criteria: [
|
|
3878
|
-
"
|
|
3879
|
-
"
|
|
3880
|
-
"
|
|
4483
|
+
"OpenAPI spec or shared type definitions in contracts directory",
|
|
4484
|
+
"Frontend types generated from or validated against API contract",
|
|
4485
|
+
"CI check prevents contract drift between frontend and backend",
|
|
4486
|
+
"Documentation for updating contracts when API changes"
|
|
3881
4487
|
]
|
|
3882
4488
|
},
|
|
3883
4489
|
{
|
|
3884
|
-
id: "task-
|
|
3885
|
-
title: "
|
|
4490
|
+
id: "task-3.3-integration-tests",
|
|
4491
|
+
title: "Write cross-service integration tests",
|
|
3886
4492
|
status: "todo",
|
|
4493
|
+
building_block: "shared-contracts",
|
|
3887
4494
|
quality_scenarios: ["MAINT-02"],
|
|
3888
4495
|
acceptance_criteria: [
|
|
3889
|
-
"
|
|
3890
|
-
"
|
|
3891
|
-
"
|
|
4496
|
+
"Integration tests exercise frontend-to-API workflows",
|
|
4497
|
+
"Test environment starts both services",
|
|
4498
|
+
"Auth flow tested end-to-end",
|
|
4499
|
+
"Error scenarios covered (API down, validation failures)"
|
|
3892
4500
|
]
|
|
3893
4501
|
}
|
|
3894
4502
|
]
|
|
3895
4503
|
},
|
|
3896
|
-
"phase-
|
|
4504
|
+
"phase-4-production": {
|
|
3897
4505
|
schema_version: 1,
|
|
3898
|
-
phase_id: "phase-
|
|
4506
|
+
phase_id: "phase-4-production",
|
|
3899
4507
|
tasks: [
|
|
3900
4508
|
{
|
|
3901
|
-
id: "task-
|
|
3902
|
-
title: "
|
|
4509
|
+
id: "task-4.1-ci-cd",
|
|
4510
|
+
title: "Set up CI/CD pipeline for both services",
|
|
3903
4511
|
status: "todo",
|
|
3904
|
-
|
|
3905
|
-
quality_scenarios: ["A11Y-01"],
|
|
4512
|
+
quality_scenarios: ["MAINT-02"],
|
|
3906
4513
|
acceptance_criteria: [
|
|
3907
|
-
"
|
|
3908
|
-
"
|
|
3909
|
-
"
|
|
4514
|
+
"CI builds and tests both frontend and API",
|
|
4515
|
+
"Lint, type-check, and test steps for each service",
|
|
4516
|
+
"Build artifacts produced for deployment",
|
|
4517
|
+
"Pipeline fails fast on errors"
|
|
3910
4518
|
]
|
|
3911
4519
|
},
|
|
3912
4520
|
{
|
|
3913
|
-
id: "task-
|
|
3914
|
-
title: "
|
|
4521
|
+
id: "task-4.2-deployment",
|
|
4522
|
+
title: "Configure deployment for full stack",
|
|
3915
4523
|
status: "todo",
|
|
3916
|
-
|
|
3917
|
-
quality_scenarios: ["SEC-01"],
|
|
4524
|
+
quality_scenarios: ["REL-01"],
|
|
3918
4525
|
acceptance_criteria: [
|
|
3919
|
-
"
|
|
3920
|
-
"
|
|
3921
|
-
"
|
|
4526
|
+
"Docker Compose for local full-stack development",
|
|
4527
|
+
"Dockerfiles for frontend and API",
|
|
4528
|
+
"Environment-specific configuration (dev, staging, prod)",
|
|
4529
|
+
"Database migration runs as part of deployment"
|
|
3922
4530
|
]
|
|
3923
4531
|
},
|
|
3924
4532
|
{
|
|
3925
|
-
id: "task-
|
|
3926
|
-
title: "
|
|
4533
|
+
id: "task-4.3-monitoring",
|
|
4534
|
+
title: "Add monitoring, logging, and rate limiting",
|
|
3927
4535
|
status: "todo",
|
|
3928
|
-
building_block: "api-
|
|
3929
|
-
quality_scenarios: [],
|
|
4536
|
+
building_block: "api-services",
|
|
4537
|
+
quality_scenarios: ["PERF-02", "SEC-04"],
|
|
3930
4538
|
acceptance_criteria: [
|
|
3931
|
-
"
|
|
3932
|
-
"
|
|
3933
|
-
"
|
|
4539
|
+
"Structured logging in API with Serilog or similar",
|
|
4540
|
+
"Frontend error tracking (Sentry or similar)",
|
|
4541
|
+
"Rate limiting configured on API endpoints",
|
|
4542
|
+
"Health check dashboard or monitoring alerts"
|
|
3934
4543
|
]
|
|
3935
4544
|
},
|
|
3936
4545
|
{
|
|
3937
|
-
id: "task-
|
|
3938
|
-
title: "
|
|
4546
|
+
id: "task-4.4-performance",
|
|
4547
|
+
title: "Optimize performance across the stack",
|
|
3939
4548
|
status: "todo",
|
|
3940
|
-
|
|
4549
|
+
building_block: "frontend-shell",
|
|
4550
|
+
quality_scenarios: ["PERF-01", "PERF-03"],
|
|
3941
4551
|
acceptance_criteria: [
|
|
3942
|
-
"
|
|
3943
|
-
"
|
|
4552
|
+
"Frontend bundle size optimized (code splitting, tree shaking)",
|
|
4553
|
+
"API response times meet p95 < 200ms target",
|
|
4554
|
+
"Database queries optimized with proper indexing",
|
|
4555
|
+
"Static assets cached with appropriate headers"
|
|
3944
4556
|
]
|
|
3945
4557
|
}
|
|
3946
4558
|
]
|
|
@@ -3956,7 +4568,8 @@ var planTemplates = {
|
|
|
3956
4568
|
"api-service": { plan: phasePlanTemplate3, tasks: phaseTasksTemplate3 },
|
|
3957
4569
|
"dotnet-webapi": { plan: phasePlanTemplate4, tasks: phaseTasksTemplate4 },
|
|
3958
4570
|
"unity-game": { plan: phasePlanTemplate5, tasks: phaseTasksTemplate5 },
|
|
3959
|
-
"angular-app": { plan: phasePlanTemplate6, tasks: phaseTasksTemplate6 }
|
|
4571
|
+
"angular-app": { plan: phasePlanTemplate6, tasks: phaseTasksTemplate6 },
|
|
4572
|
+
"fullstack-nextjs-dotnet": { plan: phasePlanTemplate7, tasks: phaseTasksTemplate7 }
|
|
3960
4573
|
};
|
|
3961
4574
|
function generatePlan(targetDir, input) {
|
|
3962
4575
|
const planDir = join5(targetDir, ".arcbridge", "plan");
|
|
@@ -4630,7 +5243,7 @@ You cannot see screenshots, but you CAN reason about UI quality through code:
|
|
|
4630
5243
|
}
|
|
4631
5244
|
|
|
4632
5245
|
// src/generators/agent-generator.ts
|
|
4633
|
-
var UI_TEMPLATES = /* @__PURE__ */ new Set(["nextjs-app-router", "react-vite", "angular-app", "unity-game"]);
|
|
5246
|
+
var UI_TEMPLATES = /* @__PURE__ */ new Set(["nextjs-app-router", "react-vite", "angular-app", "unity-game", "fullstack-nextjs-dotnet"]);
|
|
4634
5247
|
function writeAgentRole(dir, role) {
|
|
4635
5248
|
const { system_prompt, ...frontmatter } = role;
|
|
4636
5249
|
const content = matter2.stringify(system_prompt, frontmatter);
|
|
@@ -5903,7 +6516,7 @@ function analyzeRoutes(projectRoot, db, service = "main") {
|
|
|
5903
6516
|
}
|
|
5904
6517
|
}
|
|
5905
6518
|
walkAppDir(appDir, "/", routes, layoutStack, service, projectRoot);
|
|
5906
|
-
writeRoutes(db, routes);
|
|
6519
|
+
writeRoutes(db, routes, service);
|
|
5907
6520
|
return routes.length;
|
|
5908
6521
|
}
|
|
5909
6522
|
function walkAppDir(dir, routePath, routes, layoutStack, service, projectRoot) {
|
|
@@ -5986,9 +6599,9 @@ function extractHttpMethods(filePath) {
|
|
|
5986
6599
|
}
|
|
5987
6600
|
return methods;
|
|
5988
6601
|
}
|
|
5989
|
-
function writeRoutes(db, routes) {
|
|
6602
|
+
function writeRoutes(db, routes, service) {
|
|
6603
|
+
db.prepare("DELETE FROM routes WHERE service = ?").run(service);
|
|
5990
6604
|
if (routes.length === 0) return;
|
|
5991
|
-
db.prepare("DELETE FROM routes").run();
|
|
5992
6605
|
const insert = db.prepare(`
|
|
5993
6606
|
INSERT OR IGNORE INTO routes (
|
|
5994
6607
|
id, route_path, kind, http_methods, has_auth, parent_layout, service
|
|
@@ -5997,7 +6610,7 @@ function writeRoutes(db, routes) {
|
|
|
5997
6610
|
transaction(db, () => {
|
|
5998
6611
|
for (const r of routes) {
|
|
5999
6612
|
insert.run(
|
|
6000
|
-
r.id
|
|
6613
|
+
`${service}::${r.id}`,
|
|
6001
6614
|
r.routePath,
|
|
6002
6615
|
r.kind,
|
|
6003
6616
|
JSON.stringify(r.httpMethods),
|
|
@@ -6303,7 +6916,7 @@ function indexDotnetProjectRoslyn(db, options) {
|
|
|
6303
6916
|
transaction(db, () => {
|
|
6304
6917
|
for (const route of output.routes) {
|
|
6305
6918
|
insertRoute.run(
|
|
6306
|
-
route.id
|
|
6919
|
+
`${service}::${route.id}`,
|
|
6307
6920
|
route.routePath,
|
|
6308
6921
|
route.kind,
|
|
6309
6922
|
JSON.stringify(route.httpMethods),
|
|
@@ -7247,7 +7860,7 @@ async function indexCSharpTreeSitter(db, options) {
|
|
|
7247
7860
|
transaction(db, () => {
|
|
7248
7861
|
for (const route of allRoutes) {
|
|
7249
7862
|
insertRoute.run(
|
|
7250
|
-
route.id
|
|
7863
|
+
`${service}::${route.id}`,
|
|
7251
7864
|
route.routePath,
|
|
7252
7865
|
route.kind,
|
|
7253
7866
|
JSON.stringify(route.httpMethods),
|
|
@@ -7726,6 +8339,144 @@ function isTypeContext2(node) {
|
|
|
7726
8339
|
parent.type === "typed_parameter" && node !== parent.childForFieldName("name") || parent.type === "typed_default_parameter" && node !== parent.childForFieldName("name");
|
|
7727
8340
|
}
|
|
7728
8341
|
|
|
8342
|
+
// src/indexer/python/route-analyzer.ts
|
|
8343
|
+
var FASTAPI_HTTP_METHODS = /* @__PURE__ */ new Set([
|
|
8344
|
+
"get",
|
|
8345
|
+
"post",
|
|
8346
|
+
"put",
|
|
8347
|
+
"delete",
|
|
8348
|
+
"patch",
|
|
8349
|
+
"head",
|
|
8350
|
+
"options"
|
|
8351
|
+
]);
|
|
8352
|
+
function extractPythonRoutes(tree, _relativePath) {
|
|
8353
|
+
const routes = [];
|
|
8354
|
+
const decoratedDefs = findAllNodes2(tree.rootNode, "decorated_definition");
|
|
8355
|
+
for (const decorated of decoratedDefs) {
|
|
8356
|
+
const decorators = decorated.namedChildren.filter(
|
|
8357
|
+
(c) => c.type === "decorator"
|
|
8358
|
+
);
|
|
8359
|
+
for (const decorator of decorators) {
|
|
8360
|
+
const routeInfo = parseDecorator(decorator);
|
|
8361
|
+
if (!routeInfo) continue;
|
|
8362
|
+
for (const method of routeInfo.methods) {
|
|
8363
|
+
const routePath = routeInfo.path.startsWith("/") ? routeInfo.path : `/${routeInfo.path}`;
|
|
8364
|
+
const id = `route::${routePath.slice(1)}::${method}`;
|
|
8365
|
+
routes.push({
|
|
8366
|
+
id,
|
|
8367
|
+
routePath,
|
|
8368
|
+
kind: "api-route",
|
|
8369
|
+
httpMethods: [method],
|
|
8370
|
+
hasAuth: routeInfo.hasAuth
|
|
8371
|
+
});
|
|
8372
|
+
}
|
|
8373
|
+
}
|
|
8374
|
+
}
|
|
8375
|
+
return routes;
|
|
8376
|
+
}
|
|
8377
|
+
function parseDecorator(decorator) {
|
|
8378
|
+
const callNode = findFirstChild(decorator, "call");
|
|
8379
|
+
if (!callNode) return null;
|
|
8380
|
+
const funcNode = callNode.childForFieldName("function");
|
|
8381
|
+
if (!funcNode || funcNode.type !== "attribute") return null;
|
|
8382
|
+
const attrName = funcNode.childForFieldName("attribute");
|
|
8383
|
+
if (!attrName) return null;
|
|
8384
|
+
const methodName = attrName.text;
|
|
8385
|
+
if (FASTAPI_HTTP_METHODS.has(methodName)) {
|
|
8386
|
+
const path = getFirstStringArgument2(callNode);
|
|
8387
|
+
if (path === null) return null;
|
|
8388
|
+
const hasAuth = detectAuthInCall(callNode);
|
|
8389
|
+
return {
|
|
8390
|
+
path,
|
|
8391
|
+
methods: [methodName.toUpperCase()],
|
|
8392
|
+
hasAuth
|
|
8393
|
+
};
|
|
8394
|
+
}
|
|
8395
|
+
if (methodName === "route") {
|
|
8396
|
+
const path = getFirstStringArgument2(callNode);
|
|
8397
|
+
if (path === null) return null;
|
|
8398
|
+
const methods = getMethodsKeywordArgument(callNode).map((m) => m.toUpperCase());
|
|
8399
|
+
const hasAuth = detectAuthInCall(callNode);
|
|
8400
|
+
return {
|
|
8401
|
+
path,
|
|
8402
|
+
methods: methods.length > 0 ? methods : ["GET"],
|
|
8403
|
+
hasAuth
|
|
8404
|
+
};
|
|
8405
|
+
}
|
|
8406
|
+
return null;
|
|
8407
|
+
}
|
|
8408
|
+
function detectAuthInCall(callNode) {
|
|
8409
|
+
const args = callNode.childForFieldName("arguments");
|
|
8410
|
+
if (!args) return false;
|
|
8411
|
+
const text = args.text;
|
|
8412
|
+
if (/Depends\s*\(\s*auth/.test(text)) return true;
|
|
8413
|
+
if (/login_required|auth_required|require_auth|jwt_required/.test(text))
|
|
8414
|
+
return true;
|
|
8415
|
+
return false;
|
|
8416
|
+
}
|
|
8417
|
+
function getFirstStringArgument2(callNode) {
|
|
8418
|
+
const args = callNode.childForFieldName("arguments");
|
|
8419
|
+
if (!args) return null;
|
|
8420
|
+
for (const child of args.namedChildren) {
|
|
8421
|
+
if (child.type === "keyword_argument") continue;
|
|
8422
|
+
if (child.type === "string") {
|
|
8423
|
+
return stripQuotes(child.text);
|
|
8424
|
+
}
|
|
8425
|
+
const strNode = findFirstChild(child, "string");
|
|
8426
|
+
if (strNode) {
|
|
8427
|
+
return stripQuotes(strNode.text);
|
|
8428
|
+
}
|
|
8429
|
+
}
|
|
8430
|
+
return null;
|
|
8431
|
+
}
|
|
8432
|
+
function getMethodsKeywordArgument(callNode) {
|
|
8433
|
+
const args = callNode.childForFieldName("arguments");
|
|
8434
|
+
if (!args) return [];
|
|
8435
|
+
for (const child of args.namedChildren) {
|
|
8436
|
+
if (child.type !== "keyword_argument") continue;
|
|
8437
|
+
const nameNode = child.childForFieldName("name");
|
|
8438
|
+
if (!nameNode || nameNode.text !== "methods") continue;
|
|
8439
|
+
const valueNode = child.childForFieldName("value");
|
|
8440
|
+
if (!valueNode) continue;
|
|
8441
|
+
return extractStringListValues(valueNode);
|
|
8442
|
+
}
|
|
8443
|
+
return [];
|
|
8444
|
+
}
|
|
8445
|
+
function extractStringListValues(node) {
|
|
8446
|
+
const values = [];
|
|
8447
|
+
if (node.type === "list") {
|
|
8448
|
+
for (const child of node.namedChildren) {
|
|
8449
|
+
if (child.type === "string") {
|
|
8450
|
+
values.push(stripQuotes(child.text));
|
|
8451
|
+
}
|
|
8452
|
+
}
|
|
8453
|
+
}
|
|
8454
|
+
return values;
|
|
8455
|
+
}
|
|
8456
|
+
function findAllNodes2(root, type) {
|
|
8457
|
+
const results = [];
|
|
8458
|
+
function walk(node) {
|
|
8459
|
+
if (node.type === type) results.push(node);
|
|
8460
|
+
for (const child of node.namedChildren) {
|
|
8461
|
+
walk(child);
|
|
8462
|
+
}
|
|
8463
|
+
}
|
|
8464
|
+
walk(root);
|
|
8465
|
+
return results;
|
|
8466
|
+
}
|
|
8467
|
+
function findFirstChild(node, type) {
|
|
8468
|
+
for (const child of node.namedChildren) {
|
|
8469
|
+
if (child.type === type) return child;
|
|
8470
|
+
}
|
|
8471
|
+
for (const child of node.children) {
|
|
8472
|
+
if (child.type === type) return child;
|
|
8473
|
+
}
|
|
8474
|
+
return null;
|
|
8475
|
+
}
|
|
8476
|
+
function stripQuotes(text) {
|
|
8477
|
+
return text.replace(/^[fFrRbBuU]*["']|["']$/g, "");
|
|
8478
|
+
}
|
|
8479
|
+
|
|
7729
8480
|
// src/indexer/python/indexer.ts
|
|
7730
8481
|
async function indexPythonTreeSitter(db, options) {
|
|
7731
8482
|
const start = Date.now();
|
|
@@ -7796,11 +8547,35 @@ async function indexPythonTreeSitter(db, options) {
|
|
|
7796
8547
|
"DELETE FROM dependencies WHERE source_symbol IN (SELECT id FROM symbols WHERE service = ? AND language = 'python')"
|
|
7797
8548
|
).run(service);
|
|
7798
8549
|
writeDependencies(db, allDeps);
|
|
8550
|
+
const allRoutes = [];
|
|
8551
|
+
for (const [relPath, cached] of fileCache) {
|
|
8552
|
+
const routes = extractPythonRoutes(cached.tree, relPath);
|
|
8553
|
+
allRoutes.push(...routes);
|
|
8554
|
+
}
|
|
8555
|
+
db.prepare("DELETE FROM routes WHERE service = ?").run(service);
|
|
8556
|
+
if (allRoutes.length > 0) {
|
|
8557
|
+
const insertRoute = db.prepare(`
|
|
8558
|
+
INSERT OR REPLACE INTO routes (id, route_path, kind, http_methods, has_auth, service)
|
|
8559
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
8560
|
+
`);
|
|
8561
|
+
transaction(db, () => {
|
|
8562
|
+
for (const route of allRoutes) {
|
|
8563
|
+
insertRoute.run(
|
|
8564
|
+
`${service}::${route.id}`,
|
|
8565
|
+
route.routePath,
|
|
8566
|
+
route.kind,
|
|
8567
|
+
JSON.stringify(route.httpMethods),
|
|
8568
|
+
route.hasAuth ? 1 : 0,
|
|
8569
|
+
service
|
|
8570
|
+
);
|
|
8571
|
+
}
|
|
8572
|
+
});
|
|
8573
|
+
}
|
|
7799
8574
|
return {
|
|
7800
8575
|
symbolsIndexed: allNewSymbols.length,
|
|
7801
8576
|
dependenciesIndexed: allDeps.length,
|
|
7802
8577
|
componentsAnalyzed: 0,
|
|
7803
|
-
routesAnalyzed:
|
|
8578
|
+
routesAnalyzed: allRoutes.length,
|
|
7804
8579
|
filesProcessed: changedFiles.length,
|
|
7805
8580
|
filesSkipped,
|
|
7806
8581
|
filesRemoved: removed.length,
|
|
@@ -8299,6 +9074,236 @@ function collectTypeIdentifiersRecursive3(node, names) {
|
|
|
8299
9074
|
}
|
|
8300
9075
|
}
|
|
8301
9076
|
|
|
9077
|
+
// src/indexer/go/route-analyzer.ts
|
|
9078
|
+
var GIN_METHODS = {
|
|
9079
|
+
GET: "GET",
|
|
9080
|
+
POST: "POST",
|
|
9081
|
+
PUT: "PUT",
|
|
9082
|
+
DELETE: "DELETE",
|
|
9083
|
+
PATCH: "PATCH",
|
|
9084
|
+
HEAD: "HEAD",
|
|
9085
|
+
OPTIONS: "OPTIONS"
|
|
9086
|
+
};
|
|
9087
|
+
var CHI_METHODS = {
|
|
9088
|
+
Get: "GET",
|
|
9089
|
+
Post: "POST",
|
|
9090
|
+
Put: "PUT",
|
|
9091
|
+
Delete: "DELETE",
|
|
9092
|
+
Patch: "PATCH",
|
|
9093
|
+
Head: "HEAD",
|
|
9094
|
+
Options: "OPTIONS"
|
|
9095
|
+
};
|
|
9096
|
+
var NET_HTTP_METHODS = /* @__PURE__ */ new Set(["HandleFunc", "Handle"]);
|
|
9097
|
+
function extractGoRoutes(tree, _relativePath) {
|
|
9098
|
+
const routes = [];
|
|
9099
|
+
const scopedPrefixes = extractGroupPrefixes2(tree.rootNode);
|
|
9100
|
+
const authScopes = findAuthUseScopes(tree.rootNode);
|
|
9101
|
+
const calls = findAllNodes3(tree.rootNode, "call_expression");
|
|
9102
|
+
for (const call of calls) {
|
|
9103
|
+
const funcNode = call.childForFieldName("function");
|
|
9104
|
+
if (!funcNode || funcNode.type !== "selector_expression") continue;
|
|
9105
|
+
const fieldNode = funcNode.childForFieldName("field");
|
|
9106
|
+
if (!fieldNode) continue;
|
|
9107
|
+
const methodName = fieldNode.text;
|
|
9108
|
+
const receiverNode = funcNode.childForFieldName("operand");
|
|
9109
|
+
const receiverName = receiverNode?.type === "identifier" ? receiverNode.text : null;
|
|
9110
|
+
let httpMethods = null;
|
|
9111
|
+
let isNetHttp = false;
|
|
9112
|
+
if (GIN_METHODS[methodName]) {
|
|
9113
|
+
httpMethods = [GIN_METHODS[methodName]];
|
|
9114
|
+
} else if (CHI_METHODS[methodName]) {
|
|
9115
|
+
httpMethods = [CHI_METHODS[methodName]];
|
|
9116
|
+
} else if (NET_HTTP_METHODS.has(methodName)) {
|
|
9117
|
+
httpMethods = [];
|
|
9118
|
+
isNetHttp = true;
|
|
9119
|
+
}
|
|
9120
|
+
if (httpMethods === null) continue;
|
|
9121
|
+
const routeTemplate = getFirstStringArgument3(call);
|
|
9122
|
+
if (routeTemplate === null) continue;
|
|
9123
|
+
const prefix = receiverName ? resolvePrefix(receiverName, call, scopedPrefixes) : "";
|
|
9124
|
+
let routePath;
|
|
9125
|
+
if (prefix && routeTemplate) {
|
|
9126
|
+
routePath = `${prefix}/${routeTemplate}`.replace(/\/{2,}/g, "/");
|
|
9127
|
+
} else if (prefix) {
|
|
9128
|
+
routePath = prefix;
|
|
9129
|
+
} else {
|
|
9130
|
+
routePath = routeTemplate;
|
|
9131
|
+
}
|
|
9132
|
+
routePath = routePath.startsWith("/") ? routePath : `/${routePath}`;
|
|
9133
|
+
routePath = routePath.replace(/\/{2,}/g, "/").replace(/\/+$/, "") || "/";
|
|
9134
|
+
let hasAuth = false;
|
|
9135
|
+
if (!isNetHttp) {
|
|
9136
|
+
hasAuth = hasAuthArgument(call);
|
|
9137
|
+
if (!hasAuth && authScopes.length > 0) {
|
|
9138
|
+
hasAuth = isInsideAuthScope(call, authScopes);
|
|
9139
|
+
}
|
|
9140
|
+
}
|
|
9141
|
+
const cleanPath = routePath.slice(1) || "";
|
|
9142
|
+
const idMethod = httpMethods.length > 0 ? httpMethods.join(",") : "ANY";
|
|
9143
|
+
const id = `route::${cleanPath}::${idMethod}`;
|
|
9144
|
+
routes.push({
|
|
9145
|
+
id,
|
|
9146
|
+
routePath,
|
|
9147
|
+
kind: "api-route",
|
|
9148
|
+
httpMethods,
|
|
9149
|
+
hasAuth
|
|
9150
|
+
});
|
|
9151
|
+
}
|
|
9152
|
+
return routes;
|
|
9153
|
+
}
|
|
9154
|
+
function extractGroupPrefixes2(root) {
|
|
9155
|
+
const prefixes = [];
|
|
9156
|
+
const calls = findAllNodes3(root, "call_expression");
|
|
9157
|
+
for (const call of calls) {
|
|
9158
|
+
const funcNode = call.childForFieldName("function");
|
|
9159
|
+
if (funcNode?.type !== "selector_expression") continue;
|
|
9160
|
+
const fieldNode = funcNode.childForFieldName("field");
|
|
9161
|
+
const methodName = fieldNode?.text;
|
|
9162
|
+
if (methodName !== "Group" && methodName !== "Route") continue;
|
|
9163
|
+
const prefix = getFirstStringArgument3(call);
|
|
9164
|
+
if (prefix === null) continue;
|
|
9165
|
+
const assignNode = findAncestor(call, ["short_var_declaration", "assignment_statement"]);
|
|
9166
|
+
if (assignNode) {
|
|
9167
|
+
const left = assignNode.childForFieldName("left");
|
|
9168
|
+
if (left) {
|
|
9169
|
+
const varNode = left.type === "expression_list" ? left.namedChildren[0] : left;
|
|
9170
|
+
if (varNode?.type === "identifier") {
|
|
9171
|
+
const scope = findAncestor(call, [
|
|
9172
|
+
"func_literal",
|
|
9173
|
+
"function_declaration",
|
|
9174
|
+
"method_declaration"
|
|
9175
|
+
]);
|
|
9176
|
+
prefixes.push({ varName: varNode.text, prefix, scope });
|
|
9177
|
+
}
|
|
9178
|
+
}
|
|
9179
|
+
}
|
|
9180
|
+
if (methodName === "Route") {
|
|
9181
|
+
const argList = findChild7(call, "argument_list");
|
|
9182
|
+
if (argList) {
|
|
9183
|
+
for (const arg of argList.namedChildren) {
|
|
9184
|
+
if (arg.type === "func_literal") {
|
|
9185
|
+
const paramList = arg.childForFieldName("parameters");
|
|
9186
|
+
if (paramList) {
|
|
9187
|
+
const firstParam = paramList.namedChildren[0];
|
|
9188
|
+
if (firstParam?.type === "parameter_declaration") {
|
|
9189
|
+
const paramName = firstParam.childForFieldName("name");
|
|
9190
|
+
if (paramName?.type === "identifier") {
|
|
9191
|
+
prefixes.push({ varName: paramName.text, prefix, scope: arg });
|
|
9192
|
+
}
|
|
9193
|
+
}
|
|
9194
|
+
}
|
|
9195
|
+
}
|
|
9196
|
+
}
|
|
9197
|
+
}
|
|
9198
|
+
}
|
|
9199
|
+
}
|
|
9200
|
+
return prefixes;
|
|
9201
|
+
}
|
|
9202
|
+
function resolvePrefix(receiverName, callNode, scopedPrefixes) {
|
|
9203
|
+
let best = null;
|
|
9204
|
+
for (const sp of scopedPrefixes) {
|
|
9205
|
+
if (sp.varName !== receiverName) continue;
|
|
9206
|
+
if (sp.scope === null) {
|
|
9207
|
+
if (!best) best = sp;
|
|
9208
|
+
} else {
|
|
9209
|
+
if (isDescendantOf(callNode, sp.scope)) {
|
|
9210
|
+
if (!best || best.scope === null || isDescendantOf(sp.scope, best.scope)) {
|
|
9211
|
+
best = sp;
|
|
9212
|
+
}
|
|
9213
|
+
}
|
|
9214
|
+
}
|
|
9215
|
+
}
|
|
9216
|
+
return best?.prefix ?? "";
|
|
9217
|
+
}
|
|
9218
|
+
function isSameNode(a, b) {
|
|
9219
|
+
return a.type === b.type && a.startPosition.row === b.startPosition.row && a.startPosition.column === b.startPosition.column && a.endPosition.row === b.endPosition.row && a.endPosition.column === b.endPosition.column;
|
|
9220
|
+
}
|
|
9221
|
+
function isDescendantOf(node, ancestor) {
|
|
9222
|
+
let current = node.parent;
|
|
9223
|
+
while (current) {
|
|
9224
|
+
if (isSameNode(current, ancestor)) return true;
|
|
9225
|
+
current = current.parent;
|
|
9226
|
+
}
|
|
9227
|
+
return false;
|
|
9228
|
+
}
|
|
9229
|
+
function findAncestor(node, types) {
|
|
9230
|
+
let current = node.parent;
|
|
9231
|
+
while (current) {
|
|
9232
|
+
if (types.includes(current.type)) return current;
|
|
9233
|
+
current = current.parent;
|
|
9234
|
+
}
|
|
9235
|
+
return null;
|
|
9236
|
+
}
|
|
9237
|
+
function findAuthUseScopes(root) {
|
|
9238
|
+
const scopes = [];
|
|
9239
|
+
const calls = findAllNodes3(root, "call_expression");
|
|
9240
|
+
for (const call of calls) {
|
|
9241
|
+
const funcNode = call.childForFieldName("function");
|
|
9242
|
+
if (funcNode?.type !== "selector_expression") continue;
|
|
9243
|
+
const fieldNode = funcNode.childForFieldName("field");
|
|
9244
|
+
if (fieldNode?.text !== "Use") continue;
|
|
9245
|
+
if (hasAuthArgument(call)) {
|
|
9246
|
+
let scope = call.parent;
|
|
9247
|
+
while (scope && scope.type !== "func_literal" && scope.type !== "function_declaration" && scope.type !== "method_declaration") {
|
|
9248
|
+
scope = scope.parent;
|
|
9249
|
+
}
|
|
9250
|
+
if (scope) {
|
|
9251
|
+
scopes.push(scope);
|
|
9252
|
+
}
|
|
9253
|
+
}
|
|
9254
|
+
}
|
|
9255
|
+
return scopes;
|
|
9256
|
+
}
|
|
9257
|
+
function isInsideAuthScope(node, scopes) {
|
|
9258
|
+
let current = node.parent;
|
|
9259
|
+
while (current) {
|
|
9260
|
+
for (const scope of scopes) {
|
|
9261
|
+
if (isSameNode(current, scope)) return true;
|
|
9262
|
+
}
|
|
9263
|
+
current = current.parent;
|
|
9264
|
+
}
|
|
9265
|
+
return false;
|
|
9266
|
+
}
|
|
9267
|
+
function hasAuthArgument(call) {
|
|
9268
|
+
const argList = findChild7(call, "argument_list");
|
|
9269
|
+
if (!argList) return false;
|
|
9270
|
+
for (const arg of argList.namedChildren) {
|
|
9271
|
+
const text = arg.text;
|
|
9272
|
+
if (/[Aa]uth/.test(text)) return true;
|
|
9273
|
+
}
|
|
9274
|
+
return false;
|
|
9275
|
+
}
|
|
9276
|
+
function findChild7(node, type) {
|
|
9277
|
+
for (const child of node.namedChildren) {
|
|
9278
|
+
if (child.type === type) return child;
|
|
9279
|
+
}
|
|
9280
|
+
return null;
|
|
9281
|
+
}
|
|
9282
|
+
function findAllNodes3(root, type) {
|
|
9283
|
+
const results = [];
|
|
9284
|
+
function walk(node) {
|
|
9285
|
+
if (node.type === type) results.push(node);
|
|
9286
|
+
for (const child of node.namedChildren) {
|
|
9287
|
+
walk(child);
|
|
9288
|
+
}
|
|
9289
|
+
}
|
|
9290
|
+
walk(root);
|
|
9291
|
+
return results;
|
|
9292
|
+
}
|
|
9293
|
+
function getFirstStringArgument3(node) {
|
|
9294
|
+
const argList = findChild7(node, "argument_list");
|
|
9295
|
+
if (!argList) return null;
|
|
9296
|
+
for (const arg of argList.namedChildren) {
|
|
9297
|
+
if (arg.type === "interpreted_string_literal") {
|
|
9298
|
+
return arg.text.replace(/^"|"$/g, "");
|
|
9299
|
+
}
|
|
9300
|
+
if (arg.type === "raw_string_literal") {
|
|
9301
|
+
return arg.text.replace(/^`|`$/g, "");
|
|
9302
|
+
}
|
|
9303
|
+
}
|
|
9304
|
+
return null;
|
|
9305
|
+
}
|
|
9306
|
+
|
|
8302
9307
|
// src/indexer/go/indexer.ts
|
|
8303
9308
|
async function indexGoTreeSitter(db, options) {
|
|
8304
9309
|
const start = Date.now();
|
|
@@ -8367,11 +9372,35 @@ async function indexGoTreeSitter(db, options) {
|
|
|
8367
9372
|
"DELETE FROM dependencies WHERE source_symbol IN (SELECT id FROM symbols WHERE service = ? AND language = 'go')"
|
|
8368
9373
|
).run(service);
|
|
8369
9374
|
writeDependencies(db, allDeps);
|
|
9375
|
+
const allRoutes = [];
|
|
9376
|
+
for (const [relPath, cached] of fileCache) {
|
|
9377
|
+
const routes = extractGoRoutes(cached.tree, relPath);
|
|
9378
|
+
allRoutes.push(...routes);
|
|
9379
|
+
}
|
|
9380
|
+
db.prepare("DELETE FROM routes WHERE service = ?").run(service);
|
|
9381
|
+
if (allRoutes.length > 0) {
|
|
9382
|
+
const insertRoute = db.prepare(`
|
|
9383
|
+
INSERT OR REPLACE INTO routes (id, route_path, kind, http_methods, has_auth, service)
|
|
9384
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
9385
|
+
`);
|
|
9386
|
+
transaction(db, () => {
|
|
9387
|
+
for (const route of allRoutes) {
|
|
9388
|
+
insertRoute.run(
|
|
9389
|
+
`${service}::${route.id}`,
|
|
9390
|
+
route.routePath,
|
|
9391
|
+
route.kind,
|
|
9392
|
+
JSON.stringify(route.httpMethods),
|
|
9393
|
+
route.hasAuth ? 1 : 0,
|
|
9394
|
+
service
|
|
9395
|
+
);
|
|
9396
|
+
}
|
|
9397
|
+
});
|
|
9398
|
+
}
|
|
8370
9399
|
return {
|
|
8371
9400
|
symbolsIndexed: allNewSymbols.length,
|
|
8372
9401
|
dependenciesIndexed: allDeps.length,
|
|
8373
9402
|
componentsAnalyzed: 0,
|
|
8374
|
-
routesAnalyzed:
|
|
9403
|
+
routesAnalyzed: allRoutes.length,
|
|
8375
9404
|
filesProcessed: changedFiles.length,
|
|
8376
9405
|
filesSkipped,
|
|
8377
9406
|
filesRemoved: removed.length,
|