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