adorn-api 1.0.15 → 1.0.16
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/adapter/express/bootstrap.d.ts +3 -1
- package/dist/adapter/express/bootstrap.d.ts.map +1 -1
- package/dist/adapter/express/swagger.d.ts +8 -0
- package/dist/adapter/express/swagger.d.ts.map +1 -1
- package/dist/adapter/express/types.d.ts +7 -0
- package/dist/adapter/express/types.d.ts.map +1 -1
- package/dist/cli/progress.d.ts +107 -0
- package/dist/cli/progress.d.ts.map +1 -0
- package/dist/cli.cjs +1213 -41
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1212 -40
- package/dist/cli.js.map +1 -1
- package/dist/compiler/schema/index.d.ts +2 -0
- package/dist/compiler/schema/index.d.ts.map +1 -1
- package/dist/compiler/schema/partitioner.d.ts +85 -0
- package/dist/compiler/schema/partitioner.d.ts.map +1 -0
- package/dist/compiler/schema/splitOpenapi.d.ts +39 -0
- package/dist/compiler/schema/splitOpenapi.d.ts.map +1 -0
- package/dist/express.cjs +61 -6
- package/dist/express.cjs.map +1 -1
- package/dist/express.js +63 -8
- package/dist/express.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -7,8 +7,8 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
7
7
|
});
|
|
8
8
|
|
|
9
9
|
// src/cli.ts
|
|
10
|
-
import { writeFileSync, mkdirSync, rmSync, existsSync, readFileSync as
|
|
11
|
-
import { resolve as
|
|
10
|
+
import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, rmSync, existsSync as existsSync2, readFileSync as readFileSync3, statSync } from "fs";
|
|
11
|
+
import { resolve as resolve3 } from "path";
|
|
12
12
|
|
|
13
13
|
// src/compiler/runner/createProgram.ts
|
|
14
14
|
import ts from "typescript";
|
|
@@ -2333,16 +2333,961 @@ function writeCache(params) {
|
|
|
2333
2333
|
fs3.writeFileSync(path3.join(outDirAbs, "cache.json"), JSON.stringify(cache, null, 2), "utf8");
|
|
2334
2334
|
}
|
|
2335
2335
|
|
|
2336
|
-
// src/cli.ts
|
|
2337
|
-
import ts12 from "typescript";
|
|
2336
|
+
// src/cli/progress.ts
|
|
2338
2337
|
import process from "process";
|
|
2338
|
+
var ProgressTracker = class {
|
|
2339
|
+
phases = /* @__PURE__ */ new Map();
|
|
2340
|
+
startTime;
|
|
2341
|
+
verbose = false;
|
|
2342
|
+
quiet = false;
|
|
2343
|
+
indentLevel = 0;
|
|
2344
|
+
constructor(options = {}) {
|
|
2345
|
+
this.verbose = options.verbose ?? false;
|
|
2346
|
+
this.quiet = options.quiet ?? false;
|
|
2347
|
+
this.startTime = performance.now();
|
|
2348
|
+
}
|
|
2349
|
+
/**
|
|
2350
|
+
* Start a new phase.
|
|
2351
|
+
*/
|
|
2352
|
+
startPhase(name, message) {
|
|
2353
|
+
this.phases.set(name, {
|
|
2354
|
+
name,
|
|
2355
|
+
startTime: performance.now(),
|
|
2356
|
+
status: "running",
|
|
2357
|
+
message
|
|
2358
|
+
});
|
|
2359
|
+
if (!this.quiet) {
|
|
2360
|
+
this.log(`\u25CF ${message || name}`);
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
/**
|
|
2364
|
+
* Complete a phase successfully.
|
|
2365
|
+
*/
|
|
2366
|
+
completePhase(name, message) {
|
|
2367
|
+
const phase = this.phases.get(name);
|
|
2368
|
+
if (phase) {
|
|
2369
|
+
phase.endTime = performance.now();
|
|
2370
|
+
phase.status = "completed";
|
|
2371
|
+
phase.message = message;
|
|
2372
|
+
}
|
|
2373
|
+
if (!this.quiet) {
|
|
2374
|
+
const elapsed = phase ? this.formatElapsed(phase.startTime, phase.endTime) : "";
|
|
2375
|
+
const status = this.verbose ? `\u2713 ${message || name} ${elapsed}` : `\u2713 ${message || name}`;
|
|
2376
|
+
this.log(status);
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
/**
|
|
2380
|
+
* Mark a phase as failed.
|
|
2381
|
+
*/
|
|
2382
|
+
failPhase(name, message) {
|
|
2383
|
+
const phase = this.phases.get(name);
|
|
2384
|
+
if (phase) {
|
|
2385
|
+
phase.endTime = performance.now();
|
|
2386
|
+
phase.status = "failed";
|
|
2387
|
+
phase.message = message;
|
|
2388
|
+
}
|
|
2389
|
+
if (!this.quiet) {
|
|
2390
|
+
this.log(`\u2717 ${message || name}`);
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
/**
|
|
2394
|
+
* Log a verbose message.
|
|
2395
|
+
*/
|
|
2396
|
+
verboseLog(message) {
|
|
2397
|
+
if (this.verbose && !this.quiet) {
|
|
2398
|
+
const elapsed = this.formatElapsed(this.startTime);
|
|
2399
|
+
this.log(`[${elapsed}] ${message}`);
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
/**
|
|
2403
|
+
* Log a regular message.
|
|
2404
|
+
*/
|
|
2405
|
+
log(message) {
|
|
2406
|
+
const indent = " ".repeat(this.indentLevel);
|
|
2407
|
+
process.stdout.write(indent + message + "\n");
|
|
2408
|
+
}
|
|
2409
|
+
/**
|
|
2410
|
+
* Log a sub-message (indented).
|
|
2411
|
+
*/
|
|
2412
|
+
logSub(message) {
|
|
2413
|
+
this.indentLevel++;
|
|
2414
|
+
this.log(message);
|
|
2415
|
+
this.indentLevel--;
|
|
2416
|
+
}
|
|
2417
|
+
/**
|
|
2418
|
+
* Get the total elapsed time in milliseconds.
|
|
2419
|
+
*/
|
|
2420
|
+
getTotalElapsed() {
|
|
2421
|
+
return performance.now() - this.startTime;
|
|
2422
|
+
}
|
|
2423
|
+
/**
|
|
2424
|
+
* Format elapsed time as a human-readable string.
|
|
2425
|
+
*/
|
|
2426
|
+
formatElapsed(startTime, endTime) {
|
|
2427
|
+
const elapsed = (endTime ?? performance.now()) - (startTime ?? this.startTime);
|
|
2428
|
+
if (elapsed < 1) {
|
|
2429
|
+
return `${(elapsed * 1e3).toFixed(0)}ms`;
|
|
2430
|
+
} else if (elapsed < 1e3) {
|
|
2431
|
+
return `${elapsed.toFixed(0)}ms`;
|
|
2432
|
+
} else {
|
|
2433
|
+
return `${(elapsed / 1e3).toFixed(2)}s`;
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2436
|
+
/**
|
|
2437
|
+
* Get all completed phases with their timings.
|
|
2438
|
+
*/
|
|
2439
|
+
getPhases() {
|
|
2440
|
+
return Array.from(this.phases.values());
|
|
2441
|
+
}
|
|
2442
|
+
/**
|
|
2443
|
+
* Print a build summary.
|
|
2444
|
+
*/
|
|
2445
|
+
printSummary(stats) {
|
|
2446
|
+
if (this.quiet) return;
|
|
2447
|
+
const totalTime = this.getTotalElapsed();
|
|
2448
|
+
this.log("");
|
|
2449
|
+
this.log("Build Summary:");
|
|
2450
|
+
this.log(` Controllers: ${stats.controllers}`);
|
|
2451
|
+
this.log(` Operations: ${stats.operations}`);
|
|
2452
|
+
this.log(` Schemas: ${stats.schemas}`);
|
|
2453
|
+
this.log(` Source files: ${stats.sourceFiles}`);
|
|
2454
|
+
this.log(` Output dir: ${stats.artifactsWritten[0]?.split("/").slice(0, -1).join("/") || ".adorn"}`);
|
|
2455
|
+
this.log("");
|
|
2456
|
+
this.log("Timings:");
|
|
2457
|
+
for (const phase of this.phases.values()) {
|
|
2458
|
+
if (phase.status === "completed" && phase.endTime) {
|
|
2459
|
+
const elapsed = phase.endTime - phase.startTime;
|
|
2460
|
+
const timeStr = elapsed < 1 ? `${(elapsed * 1e3).toFixed(0)}ms` : elapsed < 1e3 ? `${elapsed.toFixed(0)}ms` : `${(elapsed / 1e3).toFixed(2)}s`;
|
|
2461
|
+
this.log(` ${phase.name.padEnd(20)} ${timeStr}`);
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
this.log(` ${"\u2500".repeat(21)}`);
|
|
2465
|
+
this.log(` Total time: ${this.formatElapsed()}`);
|
|
2466
|
+
this.log("");
|
|
2467
|
+
}
|
|
2468
|
+
/**
|
|
2469
|
+
* Print artifact list.
|
|
2470
|
+
*/
|
|
2471
|
+
printArtifacts(artifacts) {
|
|
2472
|
+
if (this.quiet) return;
|
|
2473
|
+
this.log("Written artifacts:");
|
|
2474
|
+
for (const artifact of artifacts) {
|
|
2475
|
+
const sizeStr = artifact.size ? ` (${artifact.size >= 1024 ? `${(artifact.size / 1024).toFixed(1)} KB` : `${artifact.size} B`})` : "";
|
|
2476
|
+
this.log(` \u251C\u2500\u2500 ${artifact.name}${sizeStr}`);
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
};
|
|
2480
|
+
var Spinner = class {
|
|
2481
|
+
frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u28B0", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
2482
|
+
interval;
|
|
2483
|
+
message;
|
|
2484
|
+
constructor(message = "") {
|
|
2485
|
+
this.message = message;
|
|
2486
|
+
}
|
|
2487
|
+
/**
|
|
2488
|
+
* Start the spinner.
|
|
2489
|
+
*/
|
|
2490
|
+
start() {
|
|
2491
|
+
let frameIndex = 0;
|
|
2492
|
+
this.interval = setInterval(() => {
|
|
2493
|
+
const frame = this.frames[frameIndex];
|
|
2494
|
+
process.stdout.write(`\r${frame} ${this.message}`);
|
|
2495
|
+
frameIndex = (frameIndex + 1) % this.frames.length;
|
|
2496
|
+
}, 80);
|
|
2497
|
+
}
|
|
2498
|
+
/**
|
|
2499
|
+
* Stop the spinner with a completion message.
|
|
2500
|
+
*/
|
|
2501
|
+
stop(completedMessage) {
|
|
2502
|
+
if (this.interval) {
|
|
2503
|
+
clearInterval(this.interval);
|
|
2504
|
+
this.interval = void 0;
|
|
2505
|
+
}
|
|
2506
|
+
process.stdout.write("\r" + " ".repeat(50) + "\r");
|
|
2507
|
+
if (completedMessage) {
|
|
2508
|
+
process.stdout.write(completedMessage + "\n");
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
/**
|
|
2512
|
+
* Stop the spinner with a failure message.
|
|
2513
|
+
*/
|
|
2514
|
+
fail(failedMessage) {
|
|
2515
|
+
this.stop();
|
|
2516
|
+
if (failedMessage) {
|
|
2517
|
+
process.stdout.write(`\u2717 ${failedMessage}
|
|
2518
|
+
`);
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
};
|
|
2522
|
+
|
|
2523
|
+
// src/compiler/schema/partitioner.ts
|
|
2524
|
+
var DEFAULT_CONFIG = {
|
|
2525
|
+
strategy: "auto",
|
|
2526
|
+
threshold: 50,
|
|
2527
|
+
maxGroupSize: 50,
|
|
2528
|
+
complexityThreshold: 10,
|
|
2529
|
+
verbose: false
|
|
2530
|
+
};
|
|
2531
|
+
function calculateSchemaComplexity(schema) {
|
|
2532
|
+
let propertyCount = 0;
|
|
2533
|
+
let nestedDepth = 0;
|
|
2534
|
+
let refCount = 0;
|
|
2535
|
+
let hasUnion = false;
|
|
2536
|
+
let hasIntersection = false;
|
|
2537
|
+
let hasEnum = false;
|
|
2538
|
+
const jsonSize = JSON.stringify(schema).length;
|
|
2539
|
+
const analyze = (s, depth) => {
|
|
2540
|
+
if (!s || typeof s !== "object") return;
|
|
2541
|
+
nestedDepth = Math.max(nestedDepth, depth);
|
|
2542
|
+
if (s.type === "object" && s.properties) {
|
|
2543
|
+
propertyCount += Object.keys(s.properties).length;
|
|
2544
|
+
for (const prop of Object.values(s.properties)) {
|
|
2545
|
+
analyze(prop, depth + 1);
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
if (s.$ref) refCount++;
|
|
2549
|
+
if (s.anyOf || s.oneOf) hasUnion = true;
|
|
2550
|
+
if (s.allOf) hasIntersection = true;
|
|
2551
|
+
if (s.enum) hasEnum = true;
|
|
2552
|
+
if (s.items) {
|
|
2553
|
+
analyze(s.items, depth + 1);
|
|
2554
|
+
}
|
|
2555
|
+
};
|
|
2556
|
+
analyze(schema, 0);
|
|
2557
|
+
const complexity = propertyCount * 1 + nestedDepth * 2 + refCount * 0.5 + (hasUnion ? 5 : 0) + (hasIntersection ? 5 : 0) + (hasEnum ? 1 : 0);
|
|
2558
|
+
return {
|
|
2559
|
+
propertyCount,
|
|
2560
|
+
nestedDepth,
|
|
2561
|
+
refCount,
|
|
2562
|
+
hasUnion,
|
|
2563
|
+
hasIntersection,
|
|
2564
|
+
hasEnum,
|
|
2565
|
+
jsonSize
|
|
2566
|
+
};
|
|
2567
|
+
}
|
|
2568
|
+
function countExternalRefs(schema, allSchemas) {
|
|
2569
|
+
let count = 0;
|
|
2570
|
+
const analyze = (s) => {
|
|
2571
|
+
if (!s || typeof s !== "object") return;
|
|
2572
|
+
if (s.$ref && typeof s.$ref === "string") {
|
|
2573
|
+
const refName = s.$ref.replace("#/components/schemas/", "");
|
|
2574
|
+
if (refName && allSchemas.has(refName)) {
|
|
2575
|
+
count++;
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
if (s.properties) {
|
|
2579
|
+
for (const prop of Object.values(s.properties)) {
|
|
2580
|
+
analyze(prop);
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
if (s.items) analyze(s.items);
|
|
2584
|
+
if (s.anyOf) s.anyOf.forEach(analyze);
|
|
2585
|
+
if (s.oneOf) s.oneOf.forEach(analyze);
|
|
2586
|
+
if (s.allOf) s.allOf.forEach(analyze);
|
|
2587
|
+
};
|
|
2588
|
+
analyze(schema);
|
|
2589
|
+
return count;
|
|
2590
|
+
}
|
|
2591
|
+
function analyzeDependencyDensity(schemas) {
|
|
2592
|
+
let totalDeps = 0;
|
|
2593
|
+
let maxDeps = 0;
|
|
2594
|
+
for (const schema of schemas.values()) {
|
|
2595
|
+
const deps = countExternalRefs(schema, schemas);
|
|
2596
|
+
totalDeps += deps;
|
|
2597
|
+
maxDeps = Math.max(maxDeps, deps);
|
|
2598
|
+
}
|
|
2599
|
+
return {
|
|
2600
|
+
avgDeps: schemas.size > 0 ? totalDeps / schemas.size : 0,
|
|
2601
|
+
maxDeps
|
|
2602
|
+
};
|
|
2603
|
+
}
|
|
2604
|
+
function partitionByController(schemas, graph, config) {
|
|
2605
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2606
|
+
const sharedSchemas = /* @__PURE__ */ new Map();
|
|
2607
|
+
groups.set("_shared", sharedSchemas);
|
|
2608
|
+
const schemaUsage = /* @__PURE__ */ new Map();
|
|
2609
|
+
for (const [nodeId, node] of graph.nodes.entries()) {
|
|
2610
|
+
if (node.kind === "Operation") {
|
|
2611
|
+
const opNode = node;
|
|
2612
|
+
const returnType = opNode.operation?.returnType;
|
|
2613
|
+
if (returnType && schemas.has(returnType)) {
|
|
2614
|
+
if (!schemaUsage.has(returnType)) {
|
|
2615
|
+
schemaUsage.set(returnType, /* @__PURE__ */ new Set());
|
|
2616
|
+
}
|
|
2617
|
+
schemaUsage.get(returnType).add(node.metadata.name);
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2621
|
+
for (const [schemaName, schema] of schemas.entries()) {
|
|
2622
|
+
const usage = schemaUsage.get(schemaName);
|
|
2623
|
+
if (!usage || usage.size === 0) {
|
|
2624
|
+
sharedSchemas.set(schemaName, schema);
|
|
2625
|
+
} else if (usage.size === 1) {
|
|
2626
|
+
const controllerName = Array.from(usage)[0].split(":")[0] || "default";
|
|
2627
|
+
const groupName = controllerName.toLowerCase();
|
|
2628
|
+
if (!groups.has(groupName)) {
|
|
2629
|
+
groups.set(groupName, /* @__PURE__ */ new Map());
|
|
2630
|
+
}
|
|
2631
|
+
groups.get(groupName).set(schemaName, schema);
|
|
2632
|
+
} else {
|
|
2633
|
+
sharedSchemas.set(schemaName, schema);
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
return Array.from(groups.entries()).map(([name, schemaMap]) => {
|
|
2637
|
+
let totalComplexity = 0;
|
|
2638
|
+
const dependencies = [];
|
|
2639
|
+
for (const [schemaName, schema] of schemaMap.entries()) {
|
|
2640
|
+
totalComplexity += calculateSchemaComplexity(schema).propertyCount;
|
|
2641
|
+
const deps = countExternalRefs(schema, schemas);
|
|
2642
|
+
for (let i = 0; i < deps; i++) {
|
|
2643
|
+
dependencies.push(schemaName);
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
return {
|
|
2647
|
+
name,
|
|
2648
|
+
schemas: schemaMap,
|
|
2649
|
+
complexity: totalComplexity,
|
|
2650
|
+
dependencies
|
|
2651
|
+
};
|
|
2652
|
+
});
|
|
2653
|
+
}
|
|
2654
|
+
function partitionByDependency(schemas, schemaGraph, config) {
|
|
2655
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2656
|
+
const sccs = schemaGraph.findStronglyConnectedComponents();
|
|
2657
|
+
const processed = /* @__PURE__ */ new Set();
|
|
2658
|
+
for (const scc of sccs) {
|
|
2659
|
+
if (scc.length === 1 && processed.has(scc[0])) continue;
|
|
2660
|
+
const groupSchemas = /* @__PURE__ */ new Map();
|
|
2661
|
+
const groupName = `dependent-${groups.size + 1}`;
|
|
2662
|
+
for (const nodeId of scc) {
|
|
2663
|
+
const node = schemaGraph.getGraph().nodes.get(nodeId);
|
|
2664
|
+
if (node && node.kind === "TypeDefinition") {
|
|
2665
|
+
const schemaName = node.metadata.name;
|
|
2666
|
+
if (schemas.has(schemaName)) {
|
|
2667
|
+
groupSchemas.set(schemaName, schemas.get(schemaName));
|
|
2668
|
+
processed.add(nodeId);
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
if (groupSchemas.size > 0) {
|
|
2673
|
+
groups.set(groupName, groupSchemas);
|
|
2674
|
+
}
|
|
2675
|
+
}
|
|
2676
|
+
for (const [nodeId, node] of schemaGraph.getGraph().nodes.entries()) {
|
|
2677
|
+
if (processed.has(nodeId)) continue;
|
|
2678
|
+
if (node.kind !== "TypeDefinition") continue;
|
|
2679
|
+
const schemaName = node.metadata.name;
|
|
2680
|
+
if (!schemas.has(schemaName)) continue;
|
|
2681
|
+
const groupSchemas = /* @__PURE__ */ new Map();
|
|
2682
|
+
groupSchemas.set(schemaName, schemas.get(schemaName));
|
|
2683
|
+
groups.set(`standalone-${groups.size + 1}`, groupSchemas);
|
|
2684
|
+
processed.add(nodeId);
|
|
2685
|
+
}
|
|
2686
|
+
return Array.from(groups.entries()).map(([name, schemaMap]) => {
|
|
2687
|
+
let totalComplexity = 0;
|
|
2688
|
+
const dependencies = [];
|
|
2689
|
+
for (const [schemaName, schema] of schemaMap.entries()) {
|
|
2690
|
+
totalComplexity += calculateSchemaComplexity(schema).propertyCount;
|
|
2691
|
+
const deps = countExternalRefs(schema, schemas);
|
|
2692
|
+
for (let i = 0; i < deps; i++) {
|
|
2693
|
+
dependencies.push(schemaName);
|
|
2694
|
+
}
|
|
2695
|
+
}
|
|
2696
|
+
return {
|
|
2697
|
+
name,
|
|
2698
|
+
schemas: schemaMap,
|
|
2699
|
+
complexity: totalComplexity,
|
|
2700
|
+
dependencies
|
|
2701
|
+
};
|
|
2702
|
+
});
|
|
2703
|
+
}
|
|
2704
|
+
function partitionBySize(schemas, config) {
|
|
2705
|
+
const sortedSchemas = Array.from(schemas.entries()).sort((a, b) => {
|
|
2706
|
+
const complexityA = calculateSchemaComplexity(a[1]).propertyCount;
|
|
2707
|
+
const complexityB = calculateSchemaComplexity(b[1]).propertyCount;
|
|
2708
|
+
return complexityB - complexityA;
|
|
2709
|
+
});
|
|
2710
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2711
|
+
let currentGroup = /* @__PURE__ */ new Map();
|
|
2712
|
+
let currentCount = 0;
|
|
2713
|
+
let groupIndex = 1;
|
|
2714
|
+
for (const [schemaName, schema] of sortedSchemas) {
|
|
2715
|
+
if (currentCount >= config.maxGroupSize) {
|
|
2716
|
+
groups.set(`group-${groupIndex}`, currentGroup);
|
|
2717
|
+
currentGroup = /* @__PURE__ */ new Map();
|
|
2718
|
+
currentCount = 0;
|
|
2719
|
+
groupIndex++;
|
|
2720
|
+
}
|
|
2721
|
+
currentGroup.set(schemaName, schema);
|
|
2722
|
+
currentCount++;
|
|
2723
|
+
}
|
|
2724
|
+
if (currentCount > 0) {
|
|
2725
|
+
groups.set(`group-${groupIndex}`, currentGroup);
|
|
2726
|
+
}
|
|
2727
|
+
return Array.from(groups.entries()).map(([name, schemaMap]) => {
|
|
2728
|
+
let totalComplexity = 0;
|
|
2729
|
+
const dependencies = [];
|
|
2730
|
+
for (const [schemaName, schema] of schemaMap.entries()) {
|
|
2731
|
+
totalComplexity += calculateSchemaComplexity(schema).propertyCount;
|
|
2732
|
+
const deps = countExternalRefs(schema, schemas);
|
|
2733
|
+
for (let i = 0; i < deps; i++) {
|
|
2734
|
+
dependencies.push(schemaName);
|
|
2735
|
+
}
|
|
2736
|
+
}
|
|
2737
|
+
return {
|
|
2738
|
+
name,
|
|
2739
|
+
schemas: schemaMap,
|
|
2740
|
+
complexity: totalComplexity,
|
|
2741
|
+
dependencies
|
|
2742
|
+
};
|
|
2743
|
+
});
|
|
2744
|
+
}
|
|
2745
|
+
function determineBestStrategy(schemas, graph, schemaGraph, config) {
|
|
2746
|
+
const schemaCount = schemas.size;
|
|
2747
|
+
const { avgDeps } = analyzeDependencyDensity(schemas);
|
|
2748
|
+
let controllerGroups = 0;
|
|
2749
|
+
for (const node of graph.nodes.values()) {
|
|
2750
|
+
if (node.kind === "Controller") {
|
|
2751
|
+
controllerGroups++;
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
if (schemaCount < config.threshold) {
|
|
2755
|
+
return "none";
|
|
2756
|
+
}
|
|
2757
|
+
if (avgDeps > 3) {
|
|
2758
|
+
return "dependency";
|
|
2759
|
+
}
|
|
2760
|
+
if (controllerGroups > 1 && avgDeps < 2) {
|
|
2761
|
+
return "controller";
|
|
2762
|
+
}
|
|
2763
|
+
return "size";
|
|
2764
|
+
}
|
|
2765
|
+
function partitionSchemas(schemas, graph, schemaGraph, config = {}) {
|
|
2766
|
+
const finalConfig = { ...DEFAULT_CONFIG, ...config };
|
|
2767
|
+
const schemaCount = schemas.size;
|
|
2768
|
+
let totalComplexity = 0;
|
|
2769
|
+
let totalSchemas = 0;
|
|
2770
|
+
const { avgDeps } = analyzeDependencyDensity(schemas);
|
|
2771
|
+
let controllerGroups = 0;
|
|
2772
|
+
for (const node of graph.nodes.values()) {
|
|
2773
|
+
if (node.kind === "Controller") {
|
|
2774
|
+
controllerGroups++;
|
|
2775
|
+
}
|
|
2776
|
+
}
|
|
2777
|
+
for (const schema of schemas.values()) {
|
|
2778
|
+
totalComplexity += calculateSchemaComplexity(schema).propertyCount;
|
|
2779
|
+
totalSchemas++;
|
|
2780
|
+
}
|
|
2781
|
+
const avgComplexity = totalSchemas > 0 ? totalComplexity / totalSchemas : 0;
|
|
2782
|
+
let strategy = finalConfig.strategy;
|
|
2783
|
+
let recommendation = "";
|
|
2784
|
+
if (strategy === "auto") {
|
|
2785
|
+
strategy = determineBestStrategy(schemas, graph, schemaGraph, finalConfig);
|
|
2786
|
+
if (schemaCount < finalConfig.threshold) {
|
|
2787
|
+
recommendation = `Schema count (${schemaCount}) below threshold (${finalConfig.threshold}), single file optimal`;
|
|
2788
|
+
} else if (strategy === "dependency") {
|
|
2789
|
+
recommendation = `High dependency density (${avgDeps.toFixed(2)} avg refs/schema), using dependency-based partitioning`;
|
|
2790
|
+
} else if (strategy === "controller") {
|
|
2791
|
+
recommendation = `Found ${controllerGroups} controller groups with low coupling, using controller-based partitioning`;
|
|
2792
|
+
} else {
|
|
2793
|
+
recommendation = `Using size-based partitioning with max ${finalConfig.maxGroupSize} schemas per group`;
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
2796
|
+
let groups = [];
|
|
2797
|
+
if (strategy === "none") {
|
|
2798
|
+
groups = [{
|
|
2799
|
+
name: "all",
|
|
2800
|
+
schemas: new Map(schemas),
|
|
2801
|
+
complexity: totalComplexity,
|
|
2802
|
+
dependencies: []
|
|
2803
|
+
}];
|
|
2804
|
+
recommendation = recommendation || "Single file mode (--no-split)";
|
|
2805
|
+
} else if (strategy === "controller") {
|
|
2806
|
+
groups = partitionByController(schemas, graph, finalConfig);
|
|
2807
|
+
} else if (strategy === "dependency") {
|
|
2808
|
+
groups = partitionByDependency(schemas, schemaGraph, finalConfig);
|
|
2809
|
+
} else {
|
|
2810
|
+
groups = partitionBySize(schemas, finalConfig);
|
|
2811
|
+
}
|
|
2812
|
+
const shouldSplit = strategy !== "none" && schemaCount >= finalConfig.threshold;
|
|
2813
|
+
return {
|
|
2814
|
+
shouldSplit,
|
|
2815
|
+
strategy,
|
|
2816
|
+
groups,
|
|
2817
|
+
recommendation,
|
|
2818
|
+
metrics: {
|
|
2819
|
+
totalSchemas: schemaCount,
|
|
2820
|
+
averageComplexity: avgComplexity,
|
|
2821
|
+
avgDependencyDensity: avgDeps,
|
|
2822
|
+
controllerGroups
|
|
2823
|
+
}
|
|
2824
|
+
};
|
|
2825
|
+
}
|
|
2826
|
+
|
|
2827
|
+
// src/compiler/schema/splitOpenapi.ts
|
|
2828
|
+
import { writeFileSync, mkdirSync, existsSync, readFileSync as readFileSync2 } from "fs";
|
|
2829
|
+
import { resolve as resolve2 } from "path";
|
|
2830
|
+
function sanitizeFilename(name) {
|
|
2831
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
2832
|
+
}
|
|
2833
|
+
function getSchemaFilename(group) {
|
|
2834
|
+
const name = sanitizeFilename(group.name);
|
|
2835
|
+
return `schemas/${name}.json`;
|
|
2836
|
+
}
|
|
2837
|
+
function collectAllSchemas(groups) {
|
|
2838
|
+
const result = /* @__PURE__ */ new Map();
|
|
2839
|
+
for (const group of groups) {
|
|
2840
|
+
for (const [schemaName, schema] of group.schemas.entries()) {
|
|
2841
|
+
result.set(schemaName, { schema, group: group.name });
|
|
2842
|
+
}
|
|
2843
|
+
}
|
|
2844
|
+
return result;
|
|
2845
|
+
}
|
|
2846
|
+
function convertToExternalRef(schema, schemaMap) {
|
|
2847
|
+
if (!schema || typeof schema !== "object") return schema;
|
|
2848
|
+
const result = { ...schema };
|
|
2849
|
+
if (schema.$ref && typeof schema.$ref === "string") {
|
|
2850
|
+
const refName = schema.$ref.replace("#/components/schemas/", "");
|
|
2851
|
+
if (refName && schemaMap.has(refName)) {
|
|
2852
|
+
const target = schemaMap.get(refName);
|
|
2853
|
+
const filename = sanitizeFilename(target.group);
|
|
2854
|
+
result.$ref = `schemas/${filename}.json#/components/schemas/${refName}`;
|
|
2855
|
+
}
|
|
2856
|
+
}
|
|
2857
|
+
const nestedProps = ["properties", "items", "additionalProperties"];
|
|
2858
|
+
for (const prop of nestedProps) {
|
|
2859
|
+
if (prop in result) {
|
|
2860
|
+
const value = result[prop];
|
|
2861
|
+
if (Array.isArray(value)) {
|
|
2862
|
+
result[prop] = value.map(
|
|
2863
|
+
(item) => typeof item === "object" ? convertToExternalRef(item, schemaMap) : item
|
|
2864
|
+
);
|
|
2865
|
+
} else if (typeof value === "object" && value !== null) {
|
|
2866
|
+
result[prop] = convertToExternalRef(value, schemaMap);
|
|
2867
|
+
}
|
|
2868
|
+
}
|
|
2869
|
+
}
|
|
2870
|
+
const arrayProps = ["anyOf", "oneOf", "allOf"];
|
|
2871
|
+
for (const prop of arrayProps) {
|
|
2872
|
+
if (prop in result && Array.isArray(result[prop])) {
|
|
2873
|
+
result[prop] = result[prop].map(
|
|
2874
|
+
(item) => typeof item === "object" ? convertToExternalRef(item, schemaMap) : item
|
|
2875
|
+
);
|
|
2876
|
+
}
|
|
2877
|
+
}
|
|
2878
|
+
return result;
|
|
2879
|
+
}
|
|
2880
|
+
function generateSchemaFileContent(group, schemaMap) {
|
|
2881
|
+
const content = {};
|
|
2882
|
+
for (const [schemaName, schema] of group.schemas.entries()) {
|
|
2883
|
+
content[schemaName] = convertToExternalRef(schema, schemaMap);
|
|
2884
|
+
}
|
|
2885
|
+
return content;
|
|
2886
|
+
}
|
|
2887
|
+
function generateSchemaIndex(groups, schemaMap) {
|
|
2888
|
+
const index = {
|
|
2889
|
+
schemas: {}
|
|
2890
|
+
};
|
|
2891
|
+
for (const [schemaName, { group }] of schemaMap.entries()) {
|
|
2892
|
+
const filename = sanitizeFilename(group);
|
|
2893
|
+
index.schemas[schemaName] = {
|
|
2894
|
+
$ref: `schemas/${filename}.json#/components/schemas/${schemaName}`
|
|
2895
|
+
};
|
|
2896
|
+
}
|
|
2897
|
+
return index;
|
|
2898
|
+
}
|
|
2899
|
+
function generateModularOpenAPI(openapi, partitioning, config) {
|
|
2900
|
+
const {
|
|
2901
|
+
outputDir,
|
|
2902
|
+
schemasDir = "schemas",
|
|
2903
|
+
createIndexFile = true,
|
|
2904
|
+
prettyPrint = true
|
|
2905
|
+
} = config;
|
|
2906
|
+
const indent = prettyPrint ? 2 : 0;
|
|
2907
|
+
let totalSize = 0;
|
|
2908
|
+
const schemaFiles = [];
|
|
2909
|
+
if (!partitioning.shouldSplit || partitioning.groups.length === 1) {
|
|
2910
|
+
const mainPath2 = resolve2(outputDir, "openapi.json");
|
|
2911
|
+
writeFileSync(mainPath2, JSON.stringify(openapi, null, indent));
|
|
2912
|
+
totalSize = Buffer.byteLength(JSON.stringify(openapi));
|
|
2913
|
+
return {
|
|
2914
|
+
mainSpec: mainPath2,
|
|
2915
|
+
schemaFiles: [],
|
|
2916
|
+
totalSize,
|
|
2917
|
+
splitEnabled: false
|
|
2918
|
+
};
|
|
2919
|
+
}
|
|
2920
|
+
const schemasPath = resolve2(outputDir, schemasDir);
|
|
2921
|
+
mkdirSync(schemasPath, { recursive: true });
|
|
2922
|
+
const schemaMap = collectAllSchemas(partitioning.groups);
|
|
2923
|
+
const schemaToFile = /* @__PURE__ */ new Map();
|
|
2924
|
+
for (const group of partitioning.groups) {
|
|
2925
|
+
const filename = getSchemaFilename(group);
|
|
2926
|
+
const filePath = resolve2(outputDir, filename);
|
|
2927
|
+
const content = generateSchemaFileContent(group, schemaMap);
|
|
2928
|
+
writeFileSync(filePath, JSON.stringify(content, null, indent));
|
|
2929
|
+
for (const schemaName of group.schemas.keys()) {
|
|
2930
|
+
schemaToFile.set(schemaName, filename);
|
|
2931
|
+
}
|
|
2932
|
+
schemaFiles.push(filePath);
|
|
2933
|
+
totalSize += Buffer.byteLength(JSON.stringify(content));
|
|
2934
|
+
}
|
|
2935
|
+
let indexFile;
|
|
2936
|
+
if (createIndexFile) {
|
|
2937
|
+
const indexPath = resolve2(outputDir, "schemas/index.json");
|
|
2938
|
+
const indexContent = generateSchemaIndex(partitioning.groups, schemaMap);
|
|
2939
|
+
writeFileSync(indexPath, JSON.stringify(indexContent, null, indent));
|
|
2940
|
+
totalSize += Buffer.byteLength(JSON.stringify(indexContent));
|
|
2941
|
+
indexFile = indexPath;
|
|
2942
|
+
}
|
|
2943
|
+
const mainSpec = generateMainSpec(openapi, schemaMap, schemaToFile);
|
|
2944
|
+
const mainPath = resolve2(outputDir, "openapi.json");
|
|
2945
|
+
writeFileSync(mainPath, JSON.stringify(mainSpec, null, indent));
|
|
2946
|
+
totalSize += Buffer.byteLength(JSON.stringify(mainSpec));
|
|
2947
|
+
return {
|
|
2948
|
+
mainSpec: mainPath,
|
|
2949
|
+
schemaFiles,
|
|
2950
|
+
indexFile,
|
|
2951
|
+
totalSize,
|
|
2952
|
+
splitEnabled: true
|
|
2953
|
+
};
|
|
2954
|
+
}
|
|
2955
|
+
function generateMainSpec(original, schemaMap, schemaToFile) {
|
|
2956
|
+
const schemas = {};
|
|
2957
|
+
for (const [schemaName, { group }] of schemaMap.entries()) {
|
|
2958
|
+
const filename = sanitizeFilename(group);
|
|
2959
|
+
schemas[schemaName] = {
|
|
2960
|
+
$ref: `schemas/${filename}.json#/components/schemas/${schemaName}`
|
|
2961
|
+
};
|
|
2962
|
+
}
|
|
2963
|
+
return {
|
|
2964
|
+
...original,
|
|
2965
|
+
components: {
|
|
2966
|
+
...original.components,
|
|
2967
|
+
schemas
|
|
2968
|
+
},
|
|
2969
|
+
"x-original-schemas": Object.keys(schemaMap).length,
|
|
2970
|
+
"x-split-enabled": true,
|
|
2971
|
+
"x-schema-files": Array.from(new Set(schemaToFile.values()))
|
|
2972
|
+
};
|
|
2973
|
+
}
|
|
2974
|
+
|
|
2975
|
+
// src/compiler/graph/types.ts
|
|
2976
|
+
function createGraph(tsVersion) {
|
|
2977
|
+
return {
|
|
2978
|
+
nodes: /* @__PURE__ */ new Map(),
|
|
2979
|
+
roots: /* @__PURE__ */ new Set(),
|
|
2980
|
+
version: "1.0.0",
|
|
2981
|
+
metadata: {
|
|
2982
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2983
|
+
generatedBy: "adorn-api-gems",
|
|
2984
|
+
tsVersion
|
|
2985
|
+
}
|
|
2986
|
+
};
|
|
2987
|
+
}
|
|
2988
|
+
function addNode(graph, node) {
|
|
2989
|
+
graph.nodes.set(node.id, node);
|
|
2990
|
+
}
|
|
2991
|
+
function addEdge(graph, sourceId, targetId, relation, properties) {
|
|
2992
|
+
const sourceNode = graph.nodes.get(sourceId);
|
|
2993
|
+
if (!sourceNode) {
|
|
2994
|
+
throw new Error(`Source node ${sourceId} not found`);
|
|
2995
|
+
}
|
|
2996
|
+
const targetExists = graph.nodes.has(targetId);
|
|
2997
|
+
if (!targetExists) {
|
|
2998
|
+
throw new Error(`Target node ${targetId} not found`);
|
|
2999
|
+
}
|
|
3000
|
+
sourceNode.edges.push({
|
|
3001
|
+
targetId,
|
|
3002
|
+
relation,
|
|
3003
|
+
properties
|
|
3004
|
+
});
|
|
3005
|
+
}
|
|
3006
|
+
function getEdgesByRelation(graph, relation) {
|
|
3007
|
+
const edges = [];
|
|
3008
|
+
for (const [id, node] of graph.nodes.entries()) {
|
|
3009
|
+
for (const edge of node.edges) {
|
|
3010
|
+
if (edge.relation === relation) {
|
|
3011
|
+
edges.push({ sourceId: id, edge });
|
|
3012
|
+
}
|
|
3013
|
+
}
|
|
3014
|
+
}
|
|
3015
|
+
return edges;
|
|
3016
|
+
}
|
|
3017
|
+
|
|
3018
|
+
// src/compiler/graph/builder.ts
|
|
3019
|
+
import ts12 from "typescript";
|
|
3020
|
+
|
|
3021
|
+
// src/compiler/graph/schemaGraph.ts
|
|
3022
|
+
var SchemaGraph = class {
|
|
3023
|
+
graph;
|
|
3024
|
+
adjacency = /* @__PURE__ */ new Map();
|
|
3025
|
+
reverseAdjacency = /* @__PURE__ */ new Map();
|
|
3026
|
+
constructor(graph) {
|
|
3027
|
+
this.graph = graph;
|
|
3028
|
+
this.buildAdjacencyLists();
|
|
3029
|
+
}
|
|
3030
|
+
/**
|
|
3031
|
+
* Build adjacency lists for faster traversal
|
|
3032
|
+
*/
|
|
3033
|
+
buildAdjacencyLists() {
|
|
3034
|
+
for (const [id, node] of this.graph.nodes.entries()) {
|
|
3035
|
+
this.adjacency.set(id, /* @__PURE__ */ new Set());
|
|
3036
|
+
this.reverseAdjacency.set(id, /* @__PURE__ */ new Set());
|
|
3037
|
+
}
|
|
3038
|
+
for (const [sourceId, node] of this.graph.nodes.entries()) {
|
|
3039
|
+
for (const edge of node.edges) {
|
|
3040
|
+
this.adjacency.get(sourceId)?.add(edge.targetId);
|
|
3041
|
+
this.reverseAdjacency.get(edge.targetId)?.add(sourceId);
|
|
3042
|
+
}
|
|
3043
|
+
}
|
|
3044
|
+
}
|
|
3045
|
+
/**
|
|
3046
|
+
* Find all nodes that use a given type
|
|
3047
|
+
*/
|
|
3048
|
+
findTypeUsages(typeId) {
|
|
3049
|
+
const usages = [];
|
|
3050
|
+
const usesEdges = getEdgesByRelation(this.graph, "uses");
|
|
3051
|
+
for (const { sourceId, edge } of usesEdges) {
|
|
3052
|
+
if (edge.targetId === typeId) {
|
|
3053
|
+
usages.push(sourceId);
|
|
3054
|
+
}
|
|
3055
|
+
}
|
|
3056
|
+
return usages;
|
|
3057
|
+
}
|
|
3058
|
+
/**
|
|
3059
|
+
* Detect cycles in the dependency graph
|
|
3060
|
+
*/
|
|
3061
|
+
detectCycles() {
|
|
3062
|
+
const visited = /* @__PURE__ */ new Set();
|
|
3063
|
+
const recursionStack = /* @__PURE__ */ new Set();
|
|
3064
|
+
const cycles = [];
|
|
3065
|
+
for (const nodeId of this.graph.nodes.keys()) {
|
|
3066
|
+
if (!visited.has(nodeId)) {
|
|
3067
|
+
this.detectCyclesDFS(nodeId, visited, recursionStack, [], cycles);
|
|
3068
|
+
}
|
|
3069
|
+
}
|
|
3070
|
+
return {
|
|
3071
|
+
hasCycles: cycles.length > 0,
|
|
3072
|
+
cycles,
|
|
3073
|
+
cycleCount: cycles.length
|
|
3074
|
+
};
|
|
3075
|
+
}
|
|
3076
|
+
/**
|
|
3077
|
+
* Depth-first search for cycle detection
|
|
3078
|
+
*/
|
|
3079
|
+
detectCyclesDFS(nodeId, visited, recursionStack, path4, cycles) {
|
|
3080
|
+
visited.add(nodeId);
|
|
3081
|
+
recursionStack.add(nodeId);
|
|
3082
|
+
path4.push(nodeId);
|
|
3083
|
+
const neighbors = this.adjacency.get(nodeId) || /* @__PURE__ */ new Set();
|
|
3084
|
+
for (const neighbor of neighbors) {
|
|
3085
|
+
if (!visited.has(neighbor)) {
|
|
3086
|
+
this.detectCyclesDFS(neighbor, visited, recursionStack, path4, cycles);
|
|
3087
|
+
} else if (recursionStack.has(neighbor)) {
|
|
3088
|
+
const cycleStart = path4.indexOf(neighbor);
|
|
3089
|
+
cycles.push([...path4.slice(cycleStart), neighbor]);
|
|
3090
|
+
}
|
|
3091
|
+
}
|
|
3092
|
+
recursionStack.delete(nodeId);
|
|
3093
|
+
path4.pop();
|
|
3094
|
+
}
|
|
3095
|
+
/**
|
|
3096
|
+
* Find strongly connected components using Tarjan's algorithm
|
|
3097
|
+
*/
|
|
3098
|
+
findStronglyConnectedComponents() {
|
|
3099
|
+
let index = 0;
|
|
3100
|
+
const stack = [];
|
|
3101
|
+
const indices = /* @__PURE__ */ new Map();
|
|
3102
|
+
const lowlinks = /* @__PURE__ */ new Map();
|
|
3103
|
+
const onStack = /* @__PURE__ */ new Set();
|
|
3104
|
+
const sccs = [];
|
|
3105
|
+
const strongConnect = (v) => {
|
|
3106
|
+
indices.set(v, index);
|
|
3107
|
+
lowlinks.set(v, index);
|
|
3108
|
+
index++;
|
|
3109
|
+
stack.push(v);
|
|
3110
|
+
onStack.add(v);
|
|
3111
|
+
const neighbors = this.adjacency.get(v) || /* @__PURE__ */ new Set();
|
|
3112
|
+
for (const w of neighbors) {
|
|
3113
|
+
if (!indices.has(w)) {
|
|
3114
|
+
strongConnect(w);
|
|
3115
|
+
lowlinks.set(v, Math.min(lowlinks.get(v), lowlinks.get(w)));
|
|
3116
|
+
} else if (onStack.has(w)) {
|
|
3117
|
+
lowlinks.set(v, Math.min(lowlinks.get(v), indices.get(w)));
|
|
3118
|
+
}
|
|
3119
|
+
}
|
|
3120
|
+
if (lowlinks.get(v) === indices.get(v)) {
|
|
3121
|
+
const scc = [];
|
|
3122
|
+
let w;
|
|
3123
|
+
do {
|
|
3124
|
+
w = stack.pop();
|
|
3125
|
+
onStack.delete(w);
|
|
3126
|
+
scc.push(w);
|
|
3127
|
+
} while (w !== v);
|
|
3128
|
+
sccs.push(scc);
|
|
3129
|
+
}
|
|
3130
|
+
};
|
|
3131
|
+
for (const nodeId of this.graph.nodes.keys()) {
|
|
3132
|
+
if (!indices.has(nodeId)) {
|
|
3133
|
+
strongConnect(nodeId);
|
|
3134
|
+
}
|
|
3135
|
+
}
|
|
3136
|
+
return sccs;
|
|
3137
|
+
}
|
|
3138
|
+
/**
|
|
3139
|
+
* Topological sort of the graph
|
|
3140
|
+
*/
|
|
3141
|
+
topologicalSort() {
|
|
3142
|
+
const inDegree = /* @__PURE__ */ new Map();
|
|
3143
|
+
for (const nodeId of this.graph.nodes.keys()) {
|
|
3144
|
+
inDegree.set(nodeId, 0);
|
|
3145
|
+
}
|
|
3146
|
+
for (const [sourceId, node] of this.graph.nodes.entries()) {
|
|
3147
|
+
for (const edge of node.edges) {
|
|
3148
|
+
inDegree.set(
|
|
3149
|
+
edge.targetId,
|
|
3150
|
+
(inDegree.get(edge.targetId) || 0) + 1
|
|
3151
|
+
);
|
|
3152
|
+
}
|
|
3153
|
+
}
|
|
3154
|
+
const queue = [];
|
|
3155
|
+
for (const [nodeId, degree] of inDegree.entries()) {
|
|
3156
|
+
if (degree === 0) {
|
|
3157
|
+
queue.push(nodeId);
|
|
3158
|
+
}
|
|
3159
|
+
}
|
|
3160
|
+
const sorted = [];
|
|
3161
|
+
while (queue.length > 0) {
|
|
3162
|
+
const current = queue.shift();
|
|
3163
|
+
sorted.push(current);
|
|
3164
|
+
const neighbors = this.adjacency.get(current) || /* @__PURE__ */ new Set();
|
|
3165
|
+
for (const neighbor of neighbors) {
|
|
3166
|
+
inDegree.set(neighbor, inDegree.get(neighbor) - 1);
|
|
3167
|
+
if (inDegree.get(neighbor) === 0) {
|
|
3168
|
+
queue.push(neighbor);
|
|
3169
|
+
}
|
|
3170
|
+
}
|
|
3171
|
+
}
|
|
3172
|
+
return sorted;
|
|
3173
|
+
}
|
|
3174
|
+
/**
|
|
3175
|
+
* Find nodes reachable from a given start node
|
|
3176
|
+
*/
|
|
3177
|
+
findReachable(startNodeId) {
|
|
3178
|
+
const reachable = /* @__PURE__ */ new Set();
|
|
3179
|
+
const visited = /* @__PURE__ */ new Set();
|
|
3180
|
+
const queue = [startNodeId];
|
|
3181
|
+
while (queue.length > 0) {
|
|
3182
|
+
const current = queue.shift();
|
|
3183
|
+
if (visited.has(current)) continue;
|
|
3184
|
+
visited.add(current);
|
|
3185
|
+
reachable.add(current);
|
|
3186
|
+
const neighbors = this.adjacency.get(current) || /* @__PURE__ */ new Set();
|
|
3187
|
+
for (const neighbor of neighbors) {
|
|
3188
|
+
if (!visited.has(neighbor)) {
|
|
3189
|
+
queue.push(neighbor);
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
3192
|
+
}
|
|
3193
|
+
return reachable;
|
|
3194
|
+
}
|
|
3195
|
+
/**
|
|
3196
|
+
* Find shortest path between two nodes (BFS)
|
|
3197
|
+
*/
|
|
3198
|
+
findShortestPath(fromId, toId) {
|
|
3199
|
+
const visited = /* @__PURE__ */ new Set();
|
|
3200
|
+
const previous = /* @__PURE__ */ new Map();
|
|
3201
|
+
const queue = [fromId];
|
|
3202
|
+
visited.add(fromId);
|
|
3203
|
+
while (queue.length > 0) {
|
|
3204
|
+
const current = queue.shift();
|
|
3205
|
+
if (current === toId) {
|
|
3206
|
+
return this.reconstructPath(previous, toId);
|
|
3207
|
+
}
|
|
3208
|
+
const neighbors = this.adjacency.get(current) || /* @__PURE__ */ new Set();
|
|
3209
|
+
for (const neighbor of neighbors) {
|
|
3210
|
+
if (!visited.has(neighbor)) {
|
|
3211
|
+
visited.add(neighbor);
|
|
3212
|
+
previous.set(neighbor, current);
|
|
3213
|
+
queue.push(neighbor);
|
|
3214
|
+
}
|
|
3215
|
+
}
|
|
3216
|
+
}
|
|
3217
|
+
return null;
|
|
3218
|
+
}
|
|
3219
|
+
/**
|
|
3220
|
+
* Reconstruct path from previous map
|
|
3221
|
+
*/
|
|
3222
|
+
reconstructPath(previous, toId) {
|
|
3223
|
+
const path4 = [toId];
|
|
3224
|
+
let current = toId;
|
|
3225
|
+
while (current !== void 0) {
|
|
3226
|
+
current = previous.get(current);
|
|
3227
|
+
if (current !== void 0) {
|
|
3228
|
+
path4.unshift(current);
|
|
3229
|
+
}
|
|
3230
|
+
}
|
|
3231
|
+
return path4;
|
|
3232
|
+
}
|
|
3233
|
+
/**
|
|
3234
|
+
* Get nodes grouped by their depth from roots
|
|
3235
|
+
*/
|
|
3236
|
+
getDepthGroups() {
|
|
3237
|
+
const depths = /* @__PURE__ */ new Map();
|
|
3238
|
+
const groups = /* @__PURE__ */ new Map();
|
|
3239
|
+
for (const rootId of this.graph.roots) {
|
|
3240
|
+
const queue = [rootId];
|
|
3241
|
+
depths.set(rootId, 0);
|
|
3242
|
+
while (queue.length > 0) {
|
|
3243
|
+
const current = queue.shift();
|
|
3244
|
+
const currentDepth = depths.get(current);
|
|
3245
|
+
for (const neighbor of this.adjacency.get(current) || /* @__PURE__ */ new Set()) {
|
|
3246
|
+
const newDepth = currentDepth + 1;
|
|
3247
|
+
if (!depths.has(neighbor) || depths.get(neighbor) > newDepth) {
|
|
3248
|
+
depths.set(neighbor, newDepth);
|
|
3249
|
+
queue.push(neighbor);
|
|
3250
|
+
}
|
|
3251
|
+
}
|
|
3252
|
+
}
|
|
3253
|
+
}
|
|
3254
|
+
for (const [nodeId, depth] of depths.entries()) {
|
|
3255
|
+
if (!groups.has(depth)) {
|
|
3256
|
+
groups.set(depth, []);
|
|
3257
|
+
}
|
|
3258
|
+
groups.get(depth).push(nodeId);
|
|
3259
|
+
}
|
|
3260
|
+
return groups;
|
|
3261
|
+
}
|
|
3262
|
+
/**
|
|
3263
|
+
* Get the underlying graph
|
|
3264
|
+
*/
|
|
3265
|
+
getGraph() {
|
|
3266
|
+
return this.graph;
|
|
3267
|
+
}
|
|
3268
|
+
};
|
|
3269
|
+
|
|
3270
|
+
// src/cli.ts
|
|
3271
|
+
import ts13 from "typescript";
|
|
3272
|
+
import process2 from "process";
|
|
2339
3273
|
var ADORN_VERSION = "0.1.0";
|
|
2340
|
-
function log(msg) {
|
|
2341
|
-
|
|
3274
|
+
function log(msg, options) {
|
|
3275
|
+
if (options?.indent) {
|
|
3276
|
+
process2.stdout.write(" " + msg + "\n");
|
|
3277
|
+
} else {
|
|
3278
|
+
process2.stdout.write(msg + "\n");
|
|
3279
|
+
}
|
|
2342
3280
|
}
|
|
2343
|
-
function
|
|
2344
|
-
if (
|
|
2345
|
-
|
|
3281
|
+
function formatBytes(bytes) {
|
|
3282
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
3283
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
3284
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
3285
|
+
}
|
|
3286
|
+
function getFileSize(path4) {
|
|
3287
|
+
try {
|
|
3288
|
+
return statSync(path4).size;
|
|
3289
|
+
} catch {
|
|
3290
|
+
return void 0;
|
|
2346
3291
|
}
|
|
2347
3292
|
}
|
|
2348
3293
|
function sanitizeForJson(obj) {
|
|
@@ -2365,49 +3310,241 @@ function sanitizeForJson(obj) {
|
|
|
2365
3310
|
}
|
|
2366
3311
|
return result;
|
|
2367
3312
|
}
|
|
3313
|
+
function buildControllerGraph(controllers) {
|
|
3314
|
+
const graph = createGraph(ts13.version);
|
|
3315
|
+
const nodeMap = /* @__PURE__ */ new Map();
|
|
3316
|
+
for (const ctrl of controllers) {
|
|
3317
|
+
const nodeId = `Controller:${ctrl.className}`;
|
|
3318
|
+
const node = {
|
|
3319
|
+
id: nodeId,
|
|
3320
|
+
kind: "Controller",
|
|
3321
|
+
metadata: {
|
|
3322
|
+
name: ctrl.className,
|
|
3323
|
+
sourceLocation: { filePath: "", line: 0, column: 0 },
|
|
3324
|
+
tags: /* @__PURE__ */ new Set(),
|
|
3325
|
+
annotations: /* @__PURE__ */ new Map()
|
|
3326
|
+
},
|
|
3327
|
+
edges: [],
|
|
3328
|
+
controller: {
|
|
3329
|
+
basePath: ctrl.basePath
|
|
3330
|
+
}
|
|
3331
|
+
};
|
|
3332
|
+
addNode(graph, node);
|
|
3333
|
+
nodeMap.set(nodeId, node);
|
|
3334
|
+
}
|
|
3335
|
+
let opIndex = 0;
|
|
3336
|
+
for (const ctrl of controllers) {
|
|
3337
|
+
for (const op of ctrl.operations) {
|
|
3338
|
+
const nodeId = `Operation:${op.operationId}`;
|
|
3339
|
+
const node = {
|
|
3340
|
+
id: nodeId,
|
|
3341
|
+
kind: "Operation",
|
|
3342
|
+
metadata: {
|
|
3343
|
+
name: op.operationId,
|
|
3344
|
+
sourceLocation: { filePath: "", line: 0, column: 0 },
|
|
3345
|
+
tags: /* @__PURE__ */ new Set(),
|
|
3346
|
+
annotations: /* @__PURE__ */ new Map()
|
|
3347
|
+
},
|
|
3348
|
+
edges: [],
|
|
3349
|
+
operation: {
|
|
3350
|
+
httpMethod: op.httpMethod,
|
|
3351
|
+
path: op.path,
|
|
3352
|
+
operationId: op.operationId,
|
|
3353
|
+
returnType: op.returnType || ""
|
|
3354
|
+
}
|
|
3355
|
+
};
|
|
3356
|
+
addNode(graph, node);
|
|
3357
|
+
nodeMap.set(nodeId, node);
|
|
3358
|
+
const ctrlNode = nodeMap.get(`Controller:${ctrl.className}`);
|
|
3359
|
+
if (ctrlNode) {
|
|
3360
|
+
addEdge(graph, ctrlNode.id, node.id, "contains");
|
|
3361
|
+
}
|
|
3362
|
+
opIndex++;
|
|
3363
|
+
}
|
|
3364
|
+
}
|
|
3365
|
+
for (const ctrl of controllers) {
|
|
3366
|
+
for (const op of ctrl.operations) {
|
|
3367
|
+
if (op.returnType && !nodeMap.has(op.returnType)) {
|
|
3368
|
+
const node = {
|
|
3369
|
+
id: op.returnType,
|
|
3370
|
+
kind: "TypeDefinition",
|
|
3371
|
+
metadata: {
|
|
3372
|
+
name: op.returnType,
|
|
3373
|
+
sourceLocation: { filePath: "", line: 0, column: 0 },
|
|
3374
|
+
tags: /* @__PURE__ */ new Set(),
|
|
3375
|
+
annotations: /* @__PURE__ */ new Map()
|
|
3376
|
+
},
|
|
3377
|
+
edges: [],
|
|
3378
|
+
typeDef: {
|
|
3379
|
+
isGeneric: false,
|
|
3380
|
+
properties: /* @__PURE__ */ new Map()
|
|
3381
|
+
}
|
|
3382
|
+
};
|
|
3383
|
+
addNode(graph, node);
|
|
3384
|
+
nodeMap.set(op.returnType, node);
|
|
3385
|
+
const opNodeId = `Operation:${op.operationId}`;
|
|
3386
|
+
const opNode = nodeMap.get(opNodeId);
|
|
3387
|
+
if (opNode) {
|
|
3388
|
+
addEdge(graph, opNode.id, node.id, "uses");
|
|
3389
|
+
}
|
|
3390
|
+
}
|
|
3391
|
+
}
|
|
3392
|
+
}
|
|
3393
|
+
return graph;
|
|
3394
|
+
}
|
|
2368
3395
|
async function buildCommand(args) {
|
|
3396
|
+
const progress = new ProgressTracker({ verbose: args.includes("--verbose"), quiet: args.includes("--quiet") });
|
|
2369
3397
|
const projectIndex = args.indexOf("-p");
|
|
2370
3398
|
const projectPath = projectIndex !== -1 ? args[projectIndex + 1] : "./tsconfig.json";
|
|
2371
3399
|
const outputDir = args.includes("--output") ? args[args.indexOf("--output") + 1] : ".adorn";
|
|
2372
3400
|
const ifStale = args.includes("--if-stale");
|
|
2373
3401
|
const validationModeIndex = args.indexOf("--validation-mode");
|
|
2374
3402
|
const validationMode = validationModeIndex !== -1 ? args[validationModeIndex + 1] : "ajv-runtime";
|
|
3403
|
+
const verbose = args.includes("--verbose");
|
|
3404
|
+
const quiet = args.includes("--quiet");
|
|
3405
|
+
const noSplit = args.includes("--no-split");
|
|
3406
|
+
const splitStrategyIndex = args.indexOf("--split-strategy");
|
|
3407
|
+
const splitStrategy = splitStrategyIndex !== -1 ? args[splitStrategyIndex + 1] : void 0;
|
|
3408
|
+
const splitThresholdIndex = args.indexOf("--split-threshold");
|
|
3409
|
+
const splitThreshold = splitThresholdIndex !== -1 ? parseInt(args[splitThresholdIndex + 1], 10) : 50;
|
|
2375
3410
|
if (validationMode !== "none" && validationMode !== "ajv-runtime" && validationMode !== "precompiled") {
|
|
2376
3411
|
console.error(`Invalid validation mode: ${validationMode}. Valid values: none, ajv-runtime, precompiled`);
|
|
2377
|
-
|
|
3412
|
+
process2.exit(1);
|
|
3413
|
+
}
|
|
3414
|
+
const outputPath = resolve3(outputDir);
|
|
3415
|
+
if (!quiet) {
|
|
3416
|
+
log(`adorn-api v${ADORN_VERSION} - Building API artifacts`);
|
|
3417
|
+
log("");
|
|
2378
3418
|
}
|
|
2379
|
-
const outputPath = resolve2(outputDir);
|
|
2380
3419
|
if (ifStale) {
|
|
3420
|
+
progress.startPhase("staleness-check", "Checking for stale artifacts");
|
|
2381
3421
|
const stale = await isStale({
|
|
2382
3422
|
outDir: outputDir,
|
|
2383
3423
|
project: projectPath,
|
|
2384
3424
|
adornVersion: ADORN_VERSION,
|
|
2385
|
-
typescriptVersion:
|
|
3425
|
+
typescriptVersion: ts13.version
|
|
2386
3426
|
});
|
|
2387
3427
|
if (!stale.stale) {
|
|
2388
|
-
|
|
3428
|
+
progress.completePhase("staleness-check");
|
|
3429
|
+
if (!quiet) {
|
|
3430
|
+
log("adorn-api: artifacts up-to-date");
|
|
3431
|
+
}
|
|
2389
3432
|
return;
|
|
2390
3433
|
}
|
|
2391
|
-
|
|
2392
|
-
|
|
3434
|
+
progress.completePhase("staleness-check", `Artifacts stale (${stale.reason})`);
|
|
3435
|
+
if (verbose) {
|
|
3436
|
+
progress.verboseLog(`Stale reason: ${stale.detail || stale.reason}`);
|
|
3437
|
+
}
|
|
2393
3438
|
} else {
|
|
2394
|
-
|
|
3439
|
+
progress.startPhase("configuration", "Initializing build");
|
|
3440
|
+
progress.completePhase("configuration", "Build forced (--if-stale not used)");
|
|
3441
|
+
}
|
|
3442
|
+
progress.startPhase("program", "Loading TypeScript configuration");
|
|
3443
|
+
if (verbose) {
|
|
3444
|
+
progress.verboseLog(`Loading ${projectPath}`);
|
|
2395
3445
|
}
|
|
2396
3446
|
const { program, checker, sourceFiles } = createProgramFromConfig(projectPath);
|
|
3447
|
+
const projectSourceFiles = sourceFiles.filter((sf) => !sf.fileName.includes("node_modules"));
|
|
3448
|
+
progress.completePhase("program");
|
|
3449
|
+
if (verbose) {
|
|
3450
|
+
progress.verboseLog(`Found ${projectSourceFiles.length} source files`);
|
|
3451
|
+
}
|
|
3452
|
+
progress.startPhase("scan", "Scanning for controllers");
|
|
2397
3453
|
const controllers = scanControllers(sourceFiles, checker);
|
|
2398
3454
|
if (controllers.length === 0) {
|
|
2399
3455
|
console.warn("No controllers found!");
|
|
2400
|
-
|
|
3456
|
+
process2.exit(1);
|
|
3457
|
+
}
|
|
3458
|
+
const totalOperations = controllers.reduce((sum, ctrl) => sum + ctrl.operations.length, 0);
|
|
3459
|
+
progress.completePhase("scan", `Found ${controllers.length} controller(s) with ${totalOperations} operation(s)`);
|
|
3460
|
+
if (verbose) {
|
|
3461
|
+
for (const ctrl of controllers) {
|
|
3462
|
+
progress.verboseLog(`Controller: ${ctrl.className} (${ctrl.basePath}) - ${ctrl.operations.length} operations`);
|
|
3463
|
+
}
|
|
3464
|
+
}
|
|
3465
|
+
progress.startPhase("openapi", "Generating OpenAPI schema");
|
|
3466
|
+
if (verbose) {
|
|
3467
|
+
progress.verboseLog("Processing schemas from type definitions");
|
|
2401
3468
|
}
|
|
2402
|
-
log(`Found ${controllers.length} controller(s)`);
|
|
2403
3469
|
const openapi = generateOpenAPI(controllers, checker, { title: "API", version: "1.0.0" });
|
|
3470
|
+
const schemaCount = Object.keys(openapi.components?.schemas || {}).length;
|
|
3471
|
+
let splitEnabled = false;
|
|
3472
|
+
if (!noSplit && schemaCount >= splitThreshold) {
|
|
3473
|
+
progress.verboseLog(`Schema count (${schemaCount}) >= threshold (${splitThreshold}), analyzing for auto-split...`);
|
|
3474
|
+
const graph = buildControllerGraph(controllers);
|
|
3475
|
+
const schemaGraph = new SchemaGraph(graph);
|
|
3476
|
+
const schemasMap = new Map(Object.entries(openapi.components?.schemas || {}));
|
|
3477
|
+
const strategy = splitStrategy || "auto";
|
|
3478
|
+
const partitioning = partitionSchemas(schemasMap, graph, schemaGraph, {
|
|
3479
|
+
strategy,
|
|
3480
|
+
threshold: splitThreshold,
|
|
3481
|
+
verbose
|
|
3482
|
+
});
|
|
3483
|
+
splitEnabled = partitioning.shouldSplit;
|
|
3484
|
+
if (splitEnabled) {
|
|
3485
|
+
progress.verboseLog(`Partitioning result: ${partitioning.strategy} strategy`);
|
|
3486
|
+
progress.verboseLog(`Recommendation: ${partitioning.recommendation}`);
|
|
3487
|
+
generateModularOpenAPI(openapi, partitioning, {
|
|
3488
|
+
outputDir: outputPath,
|
|
3489
|
+
schemasDir: "schemas",
|
|
3490
|
+
createIndexFile: true,
|
|
3491
|
+
prettyPrint: true
|
|
3492
|
+
});
|
|
3493
|
+
if (!quiet) {
|
|
3494
|
+
log(` Auto-split enabled: ${partitioning.strategy} strategy`);
|
|
3495
|
+
log(` Schema groups: ${partitioning.groups.length}`);
|
|
3496
|
+
}
|
|
3497
|
+
} else {
|
|
3498
|
+
if (!quiet) {
|
|
3499
|
+
log(` Auto-split not needed: ${partitioning.recommendation}`);
|
|
3500
|
+
}
|
|
3501
|
+
}
|
|
3502
|
+
} else if (noSplit) {
|
|
3503
|
+
if (!quiet) {
|
|
3504
|
+
log(` Splitting disabled (--no-split)`);
|
|
3505
|
+
}
|
|
3506
|
+
} else {
|
|
3507
|
+
if (!quiet) {
|
|
3508
|
+
log(` Schema count (${schemaCount}) below threshold (${splitThreshold}), single file mode`);
|
|
3509
|
+
}
|
|
3510
|
+
}
|
|
3511
|
+
progress.completePhase("openapi", `Generated ${schemaCount} schema(s)${splitEnabled ? " (split into groups)" : ""}`);
|
|
3512
|
+
progress.startPhase("manifest", "Generating manifest");
|
|
2404
3513
|
const manifest = generateManifest(controllers, checker, ADORN_VERSION, validationMode);
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
3514
|
+
progress.completePhase("manifest");
|
|
3515
|
+
progress.startPhase("write", "Writing artifacts");
|
|
3516
|
+
mkdirSync2(outputPath, { recursive: true });
|
|
3517
|
+
const openapiPath = resolve3(outputPath, "openapi.json");
|
|
3518
|
+
const manifestPath = resolve3(outputPath, "manifest.json");
|
|
3519
|
+
if (!splitEnabled) {
|
|
3520
|
+
writeFileSync2(openapiPath, JSON.stringify(sanitizeForJson(openapi), null, 2));
|
|
3521
|
+
}
|
|
3522
|
+
writeFileSync2(manifestPath, JSON.stringify(manifest, null, 2));
|
|
3523
|
+
const artifacts = [
|
|
3524
|
+
{ name: "openapi.json", size: getFileSize(openapiPath) },
|
|
3525
|
+
{ name: "manifest.json", size: getFileSize(manifestPath) }
|
|
3526
|
+
];
|
|
3527
|
+
if (splitEnabled) {
|
|
3528
|
+
const schemasDir = resolve3(outputPath, "schemas");
|
|
3529
|
+
if (existsSync2(schemasDir)) {
|
|
3530
|
+
const fs4 = await import("fs");
|
|
3531
|
+
const files = fs4.readdirSync(schemasDir);
|
|
3532
|
+
for (const file of files) {
|
|
3533
|
+
const filePath = resolve3(schemasDir, file);
|
|
3534
|
+
artifacts.push({ name: `schemas/${file}`, size: getFileSize(filePath) });
|
|
3535
|
+
}
|
|
3536
|
+
}
|
|
3537
|
+
}
|
|
3538
|
+
if (verbose) {
|
|
3539
|
+
for (const artifact of artifacts) {
|
|
3540
|
+
progress.verboseLog(`Written: ${artifact.name} (${formatBytes(artifact.size || 0)})`);
|
|
3541
|
+
}
|
|
3542
|
+
}
|
|
2408
3543
|
if (validationMode === "precompiled") {
|
|
2409
|
-
|
|
2410
|
-
const manifestObj = JSON.parse(
|
|
3544
|
+
progress.startPhase("validators", "Generating precompiled validators");
|
|
3545
|
+
const manifestObj = JSON.parse(readFileSync3(manifestPath, "utf-8"));
|
|
3546
|
+
const spinner = new Spinner("Generating validators...");
|
|
3547
|
+
if (!quiet) spinner.start();
|
|
2411
3548
|
await emitPrecompiledValidators({
|
|
2412
3549
|
outDir: outputPath,
|
|
2413
3550
|
openapi,
|
|
@@ -2415,45 +3552,71 @@ async function buildCommand(args) {
|
|
|
2415
3552
|
strict: "off",
|
|
2416
3553
|
formatsMode: "full"
|
|
2417
3554
|
});
|
|
3555
|
+
if (!quiet) spinner.stop();
|
|
2418
3556
|
manifestObj.validation = {
|
|
2419
3557
|
mode: "precompiled",
|
|
2420
3558
|
precompiledModule: "./validators.mjs"
|
|
2421
3559
|
};
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
3560
|
+
writeFileSync2(manifestPath, JSON.stringify(manifestObj, null, 2));
|
|
3561
|
+
const validatorsCjsPath = resolve3(outputPath, "validators.cjs");
|
|
3562
|
+
const validatorsMjsPath = resolve3(outputPath, "validators.mjs");
|
|
3563
|
+
const validatorsMetaPath = resolve3(outputPath, "validators.meta.json");
|
|
3564
|
+
artifacts.push(
|
|
3565
|
+
{ name: "validators.cjs", size: getFileSize(validatorsCjsPath) },
|
|
3566
|
+
{ name: "validators.mjs", size: getFileSize(validatorsMjsPath) },
|
|
3567
|
+
{ name: "validators.meta.json", size: getFileSize(validatorsMetaPath) }
|
|
3568
|
+
);
|
|
3569
|
+
progress.completePhase("validators");
|
|
3570
|
+
if (verbose) {
|
|
3571
|
+
progress.verboseLog("Precompiled validators generated successfully");
|
|
3572
|
+
}
|
|
2426
3573
|
}
|
|
3574
|
+
progress.startPhase("cache", "Writing cache");
|
|
2427
3575
|
writeCache({
|
|
2428
3576
|
outDir: outputDir,
|
|
2429
|
-
tsconfigAbs:
|
|
3577
|
+
tsconfigAbs: resolve3(projectPath),
|
|
2430
3578
|
program,
|
|
2431
3579
|
adornVersion: ADORN_VERSION
|
|
2432
3580
|
});
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
3581
|
+
const cachePath = resolve3(outputPath, "cache.json");
|
|
3582
|
+
artifacts.push({ name: "cache.json", size: getFileSize(cachePath) });
|
|
3583
|
+
progress.completePhase("cache");
|
|
3584
|
+
if (verbose) {
|
|
3585
|
+
progress.verboseLog(`Written: cache.json (${formatBytes(getFileSize(cachePath) || 0)})`);
|
|
3586
|
+
}
|
|
3587
|
+
const stats = {
|
|
3588
|
+
controllers: controllers.length,
|
|
3589
|
+
operations: totalOperations,
|
|
3590
|
+
schemas: schemaCount,
|
|
3591
|
+
sourceFiles: projectSourceFiles.length,
|
|
3592
|
+
artifactsWritten: artifacts.map((a) => a.name),
|
|
3593
|
+
splitEnabled
|
|
3594
|
+
};
|
|
3595
|
+
progress.printSummary(stats);
|
|
3596
|
+
progress.printArtifacts(artifacts);
|
|
2437
3597
|
}
|
|
2438
3598
|
function cleanCommand(args) {
|
|
3599
|
+
const quiet = args.includes("--quiet");
|
|
2439
3600
|
const outputDir = args.includes("--output") ? args[args.indexOf("--output") + 1] : ".adorn";
|
|
2440
|
-
const outputPath =
|
|
2441
|
-
if (
|
|
3601
|
+
const outputPath = resolve3(outputDir);
|
|
3602
|
+
if (existsSync2(outputPath)) {
|
|
2442
3603
|
rmSync(outputPath, { recursive: true, force: true });
|
|
2443
3604
|
}
|
|
2444
|
-
|
|
3605
|
+
if (!quiet) {
|
|
3606
|
+
log(`adorn-api: cleaned ${outputDir}`);
|
|
3607
|
+
}
|
|
2445
3608
|
}
|
|
2446
|
-
var command =
|
|
3609
|
+
var command = process2.argv[2];
|
|
2447
3610
|
if (command === "build") {
|
|
2448
|
-
buildCommand(
|
|
3611
|
+
buildCommand(process2.argv.slice(3)).catch((err) => {
|
|
2449
3612
|
console.error(err);
|
|
2450
|
-
|
|
3613
|
+
process2.exit(1);
|
|
2451
3614
|
});
|
|
2452
3615
|
} else if (command === "clean") {
|
|
2453
|
-
cleanCommand(
|
|
3616
|
+
cleanCommand(process2.argv.slice(3));
|
|
2454
3617
|
} else {
|
|
2455
3618
|
console.log(`
|
|
2456
|
-
adorn-api CLI
|
|
3619
|
+
adorn-api CLI v${ADORN_VERSION}
|
|
2457
3620
|
|
|
2458
3621
|
Commands:
|
|
2459
3622
|
build Generate OpenAPI and manifest from TypeScript source
|
|
@@ -2464,11 +3627,20 @@ Options:
|
|
|
2464
3627
|
--output <dir> Output directory (default: .adorn)
|
|
2465
3628
|
--if-stale Only rebuild if artifacts are stale
|
|
2466
3629
|
--validation-mode <mode> Validation mode: none, ajv-runtime, precompiled (default: ajv-runtime)
|
|
3630
|
+
--no-split Disable automatic schema splitting (default: auto-split enabled)
|
|
3631
|
+
--split-strategy <mode> Override splitting strategy: controller, dependency, size, auto (default: auto)
|
|
3632
|
+
--split-threshold <num> Schema count threshold for auto-split (default: 50)
|
|
3633
|
+
--verbose Show detailed progress information
|
|
3634
|
+
--quiet Suppress non-essential output
|
|
2467
3635
|
|
|
2468
3636
|
Examples:
|
|
2469
3637
|
adorn-api build -p ./tsconfig.json --output .adorn
|
|
2470
3638
|
adorn-api build --if-stale
|
|
2471
3639
|
adorn-api build --validation-mode precompiled
|
|
3640
|
+
adorn-api build --verbose
|
|
3641
|
+
adorn-api build --no-split # Force single file mode
|
|
3642
|
+
adorn-api build --split-strategy controller # Force controller-based splitting
|
|
3643
|
+
adorn-api build --split-threshold 100 # Increase threshold to 100
|
|
2472
3644
|
adorn-api clean
|
|
2473
3645
|
`);
|
|
2474
3646
|
}
|