adorn-api 1.0.15 → 1.0.17

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