@kubb/core 5.0.0-beta.18 → 5.0.0-beta.19
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/{PluginDriver-Bc-k8EUQ.cjs → PluginDriver-DXp767s2.cjs} +72 -31
- package/dist/PluginDriver-DXp767s2.cjs.map +1 -0
- package/dist/{PluginDriver-T4-THx8t.js → PluginDriver-uNex0SAr.js} +72 -31
- package/dist/PluginDriver-uNex0SAr.js.map +1 -0
- package/dist/{createKubb-DajERI4b.d.ts → createKubb-BJGymYhe.d.ts} +200 -72
- package/dist/index.cjs +203 -144
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +203 -144
- package/dist/index.js.map +1 -1
- package/dist/mocks.cjs +25 -13
- package/dist/mocks.cjs.map +1 -1
- package/dist/mocks.d.ts +1 -1
- package/dist/mocks.js +25 -13
- package/dist/mocks.js.map +1 -1
- package/package.json +5 -5
- package/src/FileProcessor.ts +15 -16
- package/src/PluginDriver.ts +31 -14
- package/src/createKubb.ts +357 -174
- package/src/createRenderer.ts +1 -2
- package/src/defineParser.ts +1 -1
- package/src/defineResolver.ts +62 -60
- package/src/mocks.ts +3 -3
- package/dist/PluginDriver-Bc-k8EUQ.cjs.map +0 -1
- package/dist/PluginDriver-T4-THx8t.js.map +0 -1
package/src/createKubb.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { resolve } from 'node:path'
|
|
2
2
|
import { version as nodeVersion } from 'node:process'
|
|
3
3
|
import type { PossiblePromise } from '@internals/utils'
|
|
4
|
-
import { AsyncEventEmitter, BuildError, exists, formatMs, getElapsedMs, URLPath } from '@internals/utils'
|
|
5
|
-
import type { FileNode, InputNode,
|
|
4
|
+
import { AsyncEventEmitter, BuildError, exists, formatMs, getElapsedMs, URLPath, isPromise } from '@internals/utils'
|
|
5
|
+
import type { FileNode, InputNode, OperationNode, SchemaNode } from '@kubb/ast'
|
|
6
6
|
import { collectUsedSchemaNames, transform, walk } from '@kubb/ast'
|
|
7
7
|
import { version as KubbVersion } from '../package.json'
|
|
8
|
-
import { DEFAULT_BANNER, DEFAULT_EXTENSION, DEFAULT_STUDIO_URL,
|
|
8
|
+
import { DEFAULT_BANNER, DEFAULT_EXTENSION, DEFAULT_STUDIO_URL, STREAM_SCHEMA_THRESHOLD } from './constants.ts'
|
|
9
9
|
import type { Adapter, AdapterSource } from './createAdapter.ts'
|
|
10
10
|
import type { RendererFactory } from './createRenderer.ts'
|
|
11
11
|
import { createStorage, type Storage } from './createStorage.ts'
|
|
@@ -14,6 +14,7 @@ import type { Middleware } from './defineMiddleware.ts'
|
|
|
14
14
|
import type { Parser } from './defineParser.ts'
|
|
15
15
|
import type { KubbPluginEndContext, KubbPluginSetupContext, KubbPluginStartContext, NormalizedPlugin, Plugin } from './definePlugin.ts'
|
|
16
16
|
import { FileProcessor } from './FileProcessor.ts'
|
|
17
|
+
|
|
17
18
|
import { applyHookResult, PluginDriver } from './PluginDriver.ts'
|
|
18
19
|
import { fsStorage } from './storages/fsStorage.ts'
|
|
19
20
|
|
|
@@ -480,21 +481,16 @@ declare global {
|
|
|
480
481
|
|
|
481
482
|
/**
|
|
482
483
|
* Lifecycle events emitted during Kubb code generation.
|
|
483
|
-
*
|
|
484
|
+
* Attach listeners before calling `setup()` or `build()` to observe and react to build progress.
|
|
484
485
|
*
|
|
485
486
|
* @example
|
|
486
|
-
* ```
|
|
487
|
-
*
|
|
488
|
-
* import type { KubbHooks } from '@kubb/core'
|
|
489
|
-
*
|
|
490
|
-
* const hooks: AsyncEventEmitter<KubbHooks> = new AsyncEventEmitter()
|
|
491
|
-
*
|
|
492
|
-
* hooks.on('kubb:lifecycle:start', () => {
|
|
487
|
+
* ```ts
|
|
488
|
+
* kubb.hooks.on('kubb:lifecycle:start', () => {
|
|
493
489
|
* console.log('Starting Kubb generation')
|
|
494
490
|
* })
|
|
495
491
|
*
|
|
496
|
-
* hooks.on('kubb:plugin:end', ({ plugin, duration }) => {
|
|
497
|
-
* console.log(
|
|
492
|
+
* kubb.hooks.on('kubb:plugin:end', ({ plugin, duration }) => {
|
|
493
|
+
* console.log(`${plugin.name} completed in ${duration}ms`)
|
|
498
494
|
* })
|
|
499
495
|
* ```
|
|
500
496
|
*/
|
|
@@ -535,51 +531,96 @@ export interface KubbHooks {
|
|
|
535
531
|
}
|
|
536
532
|
|
|
537
533
|
export type KubbBuildStartContext = {
|
|
534
|
+
/**
|
|
535
|
+
* Resolved configuration for this build.
|
|
536
|
+
*/
|
|
538
537
|
config: Config
|
|
538
|
+
/**
|
|
539
|
+
* Adapter that parsed the input into the universal AST.
|
|
540
|
+
*/
|
|
539
541
|
adapter: Adapter
|
|
542
|
+
/**
|
|
543
|
+
* Parsed input node. For streaming builds the node is a synthetic empty shell
|
|
544
|
+
* with only `meta` populated — use `kubb:generate:schema` / `kubb:generate:operation` to observe individual nodes.
|
|
545
|
+
*/
|
|
540
546
|
inputNode: InputNode
|
|
547
|
+
/**
|
|
548
|
+
* Looks up a registered plugin by name, typed by the plugin registry.
|
|
549
|
+
*/
|
|
541
550
|
getPlugin<TName extends keyof Kubb.PluginRegistry>(name: TName): Plugin<Kubb.PluginRegistry[TName]> | undefined
|
|
542
551
|
getPlugin(name: string): Plugin | undefined
|
|
552
|
+
/**
|
|
553
|
+
* Snapshot of all files accumulated so far.
|
|
554
|
+
*/
|
|
543
555
|
readonly files: ReadonlyArray<FileNode>
|
|
556
|
+
/**
|
|
557
|
+
* Adds or merges one or more files into the file manager.
|
|
558
|
+
*/
|
|
544
559
|
upsertFile: (...files: Array<FileNode>) => void
|
|
545
560
|
}
|
|
546
561
|
|
|
547
562
|
export type KubbPluginsEndContext = {
|
|
563
|
+
/**
|
|
564
|
+
* Resolved configuration for this build.
|
|
565
|
+
*/
|
|
548
566
|
config: Config
|
|
567
|
+
/**
|
|
568
|
+
* Snapshot of all files accumulated across all plugins.
|
|
569
|
+
*/
|
|
549
570
|
readonly files: ReadonlyArray<FileNode>
|
|
571
|
+
/**
|
|
572
|
+
* Adds or merges one or more files into the file manager.
|
|
573
|
+
*/
|
|
550
574
|
upsertFile: (...files: Array<FileNode>) => void
|
|
551
575
|
}
|
|
552
576
|
|
|
553
577
|
export type KubbBuildEndContext = {
|
|
578
|
+
/**
|
|
579
|
+
* All files generated during this build.
|
|
580
|
+
*/
|
|
554
581
|
files: Array<FileNode>
|
|
582
|
+
/**
|
|
583
|
+
* Resolved configuration for this build.
|
|
584
|
+
*/
|
|
555
585
|
config: Config
|
|
586
|
+
/**
|
|
587
|
+
* Absolute path to the output directory.
|
|
588
|
+
*/
|
|
556
589
|
outputDir: string
|
|
557
590
|
}
|
|
558
591
|
|
|
559
592
|
export type KubbLifecycleStartContext = {
|
|
593
|
+
/**
|
|
594
|
+
* Current Kubb version string.
|
|
595
|
+
*/
|
|
560
596
|
version: string
|
|
561
597
|
}
|
|
562
598
|
|
|
563
599
|
export type KubbConfigEndContext = {
|
|
600
|
+
/**
|
|
601
|
+
* All resolved configs after defaults are applied.
|
|
602
|
+
*/
|
|
564
603
|
configs: Array<Config>
|
|
565
604
|
}
|
|
566
605
|
|
|
567
606
|
export type KubbGenerationStartContext = {
|
|
607
|
+
/**
|
|
608
|
+
* Resolved configuration for this generation run.
|
|
609
|
+
*/
|
|
568
610
|
config: Config
|
|
569
611
|
}
|
|
570
612
|
|
|
571
613
|
export type KubbGenerationEndContext = {
|
|
614
|
+
/**
|
|
615
|
+
* Resolved configuration for this generation run.
|
|
616
|
+
*/
|
|
572
617
|
config: Config
|
|
573
618
|
/**
|
|
574
|
-
* Read-only view of the files
|
|
575
|
-
*
|
|
576
|
-
* Keys are scoped to this run; files from earlier builds are not included.
|
|
577
|
-
* Reads go directly to `config.storage`, so nothing is buffered in memory.
|
|
619
|
+
* Read-only view of the files written during this build.
|
|
620
|
+
* Reads go directly to `config.storage` — nothing extra is held in memory.
|
|
578
621
|
*
|
|
579
622
|
* @example Read a generated file
|
|
580
|
-
*
|
|
581
|
-
* const code = await storage.getItem('/src/gen/pet.ts')
|
|
582
|
-
* ```
|
|
623
|
+
* `const code = await storage.getItem('/src/gen/pet.ts')`
|
|
583
624
|
*
|
|
584
625
|
* @example Walk every generated file
|
|
585
626
|
* ```ts
|
|
@@ -592,73 +633,178 @@ export type KubbGenerationEndContext = {
|
|
|
592
633
|
}
|
|
593
634
|
|
|
594
635
|
export type KubbGenerationSummaryContext = {
|
|
636
|
+
/**
|
|
637
|
+
* Resolved configuration for this generation run.
|
|
638
|
+
*/
|
|
595
639
|
config: Config
|
|
640
|
+
/**
|
|
641
|
+
* Plugins that threw during generation, paired with their errors.
|
|
642
|
+
*/
|
|
596
643
|
failedPlugins: Set<{ plugin: Plugin; error: Error }>
|
|
644
|
+
/**
|
|
645
|
+
* `'success'` when all plugins completed without errors, `'failed'` otherwise.
|
|
646
|
+
*/
|
|
597
647
|
status: 'success' | 'failed'
|
|
648
|
+
/**
|
|
649
|
+
* High-resolution start time from `process.hrtime()`.
|
|
650
|
+
*/
|
|
598
651
|
hrStart: [number, number]
|
|
652
|
+
/**
|
|
653
|
+
* Total number of files created during this run.
|
|
654
|
+
*/
|
|
599
655
|
filesCreated: number
|
|
656
|
+
/**
|
|
657
|
+
* Elapsed milliseconds per plugin, keyed by plugin name.
|
|
658
|
+
*/
|
|
600
659
|
pluginTimings?: Map<Plugin['name'], number>
|
|
601
660
|
}
|
|
602
661
|
|
|
603
662
|
export type KubbVersionNewContext = {
|
|
663
|
+
/**
|
|
664
|
+
* The installed Kubb version.
|
|
665
|
+
*/
|
|
604
666
|
currentVersion: string
|
|
667
|
+
/**
|
|
668
|
+
* The newest available version on npm.
|
|
669
|
+
*/
|
|
605
670
|
latestVersion: string
|
|
606
671
|
}
|
|
607
672
|
|
|
608
673
|
export type KubbInfoContext = {
|
|
674
|
+
/**
|
|
675
|
+
* Human-readable info message.
|
|
676
|
+
*/
|
|
609
677
|
message: string
|
|
678
|
+
/**
|
|
679
|
+
* Optional supplementary detail.
|
|
680
|
+
*/
|
|
610
681
|
info?: string
|
|
611
682
|
}
|
|
612
683
|
|
|
613
684
|
export type KubbErrorContext = {
|
|
685
|
+
/**
|
|
686
|
+
* The caught error.
|
|
687
|
+
*/
|
|
614
688
|
error: Error
|
|
689
|
+
/**
|
|
690
|
+
* Optional structured metadata for additional context.
|
|
691
|
+
*/
|
|
615
692
|
meta?: Record<string, unknown>
|
|
616
693
|
}
|
|
617
694
|
|
|
618
695
|
export type KubbSuccessContext = {
|
|
696
|
+
/**
|
|
697
|
+
* Human-readable success message.
|
|
698
|
+
*/
|
|
619
699
|
message: string
|
|
700
|
+
/**
|
|
701
|
+
* Optional supplementary detail.
|
|
702
|
+
*/
|
|
620
703
|
info?: string
|
|
621
704
|
}
|
|
622
705
|
|
|
623
706
|
export type KubbWarnContext = {
|
|
707
|
+
/**
|
|
708
|
+
* Human-readable warning message.
|
|
709
|
+
*/
|
|
624
710
|
message: string
|
|
711
|
+
/**
|
|
712
|
+
* Optional supplementary detail.
|
|
713
|
+
*/
|
|
625
714
|
info?: string
|
|
626
715
|
}
|
|
627
716
|
|
|
628
717
|
export type KubbDebugContext = {
|
|
718
|
+
/**
|
|
719
|
+
* Timestamp when the debug entry was created.
|
|
720
|
+
*/
|
|
629
721
|
date: Date
|
|
722
|
+
/**
|
|
723
|
+
* One or more log lines to emit.
|
|
724
|
+
*/
|
|
630
725
|
logs: Array<string>
|
|
726
|
+
/**
|
|
727
|
+
* Optional source file name associated with this entry.
|
|
728
|
+
*/
|
|
631
729
|
fileName?: string
|
|
632
730
|
}
|
|
633
731
|
|
|
634
732
|
export type KubbFilesProcessingStartContext = {
|
|
733
|
+
/**
|
|
734
|
+
* Files about to be serialised and written.
|
|
735
|
+
*/
|
|
635
736
|
files: Array<FileNode>
|
|
636
737
|
}
|
|
637
738
|
|
|
638
739
|
export type KubbFileProcessingUpdateContext = {
|
|
740
|
+
/**
|
|
741
|
+
* Number of files processed so far in this batch.
|
|
742
|
+
*/
|
|
639
743
|
processed: number
|
|
744
|
+
/**
|
|
745
|
+
* Total number of files in this batch.
|
|
746
|
+
*/
|
|
640
747
|
total: number
|
|
748
|
+
/**
|
|
749
|
+
* Completion percentage (`0`–`100`).
|
|
750
|
+
*/
|
|
641
751
|
percentage: number
|
|
752
|
+
/**
|
|
753
|
+
* Serialised file content, or `undefined` when the file produced no output.
|
|
754
|
+
*/
|
|
642
755
|
source?: string
|
|
756
|
+
/**
|
|
757
|
+
* The file that was just processed.
|
|
758
|
+
*/
|
|
643
759
|
file: FileNode
|
|
760
|
+
/**
|
|
761
|
+
* Resolved configuration for this build.
|
|
762
|
+
*/
|
|
644
763
|
config: Config
|
|
645
764
|
}
|
|
646
765
|
|
|
647
766
|
export type KubbFilesProcessingEndContext = {
|
|
767
|
+
/**
|
|
768
|
+
* All files that were serialised in this batch.
|
|
769
|
+
*/
|
|
648
770
|
files: Array<FileNode>
|
|
649
771
|
}
|
|
650
772
|
|
|
651
773
|
export type KubbHookStartContext = {
|
|
774
|
+
/**
|
|
775
|
+
* Optional identifier for correlating start/end events.
|
|
776
|
+
*/
|
|
652
777
|
id?: string
|
|
778
|
+
/**
|
|
779
|
+
* The shell command that is about to run.
|
|
780
|
+
*/
|
|
653
781
|
command: string
|
|
782
|
+
/**
|
|
783
|
+
* Parsed argument list, when available.
|
|
784
|
+
*/
|
|
654
785
|
args?: readonly string[]
|
|
655
786
|
}
|
|
656
787
|
|
|
657
788
|
export type KubbHookEndContext = {
|
|
789
|
+
/**
|
|
790
|
+
* Optional identifier matching the corresponding `kubb:hook:start` event.
|
|
791
|
+
*/
|
|
658
792
|
id?: string
|
|
793
|
+
/**
|
|
794
|
+
* The shell command that ran.
|
|
795
|
+
*/
|
|
659
796
|
command: string
|
|
797
|
+
/**
|
|
798
|
+
* Parsed argument list, when available.
|
|
799
|
+
*/
|
|
660
800
|
args?: readonly string[]
|
|
801
|
+
/**
|
|
802
|
+
* `true` when the command exited with code `0`.
|
|
803
|
+
*/
|
|
661
804
|
success: boolean
|
|
805
|
+
/**
|
|
806
|
+
* Error thrown by the command, or `null` on success.
|
|
807
|
+
*/
|
|
662
808
|
error: Error | null
|
|
663
809
|
}
|
|
664
810
|
|
|
@@ -666,9 +812,19 @@ export type KubbHookEndContext = {
|
|
|
666
812
|
* CLI options derived from command-line flags.
|
|
667
813
|
*/
|
|
668
814
|
export type CLIOptions = {
|
|
815
|
+
/**
|
|
816
|
+
* Path to the Kubb config file.
|
|
817
|
+
*/
|
|
669
818
|
config?: string
|
|
819
|
+
/**
|
|
820
|
+
* Re-run generation whenever input files change.
|
|
821
|
+
*/
|
|
670
822
|
watch?: boolean
|
|
671
|
-
/**
|
|
823
|
+
/**
|
|
824
|
+
* Controls how much output the CLI prints.
|
|
825
|
+
*
|
|
826
|
+
* @default 'silent'
|
|
827
|
+
*/
|
|
672
828
|
logLevel?: 'silent' | 'info' | 'debug'
|
|
673
829
|
}
|
|
674
830
|
|
|
@@ -689,31 +845,34 @@ type SetupOptions = {
|
|
|
689
845
|
*/
|
|
690
846
|
export type BuildOutput = {
|
|
691
847
|
/**
|
|
692
|
-
* Plugins that threw during
|
|
848
|
+
* Plugins that threw during generation, paired with their errors.
|
|
693
849
|
*/
|
|
694
850
|
failedPlugins: Set<{ plugin: Plugin; error: Error }>
|
|
851
|
+
/**
|
|
852
|
+
* All files generated during this build.
|
|
853
|
+
*/
|
|
695
854
|
files: Array<FileNode>
|
|
855
|
+
/**
|
|
856
|
+
* The plugin driver that orchestrated this build.
|
|
857
|
+
*/
|
|
696
858
|
driver: PluginDriver
|
|
697
859
|
/**
|
|
698
|
-
* Elapsed
|
|
860
|
+
* Elapsed milliseconds per plugin, keyed by plugin name.
|
|
699
861
|
*/
|
|
700
862
|
pluginTimings: Map<string, number>
|
|
863
|
+
/**
|
|
864
|
+
* Top-level error when the build threw before completing, otherwise `undefined`.
|
|
865
|
+
*/
|
|
701
866
|
error?: Error
|
|
702
867
|
/**
|
|
703
868
|
* Read-only view of every file written during this build.
|
|
704
|
-
*
|
|
705
|
-
* Keys are limited to this run. Reads go straight to `config.storage`,
|
|
706
|
-
* so nothing extra is held in memory.
|
|
869
|
+
* Reads go straight to `config.storage` — nothing extra is held in memory.
|
|
707
870
|
*
|
|
708
871
|
* @example Read a generated file
|
|
709
|
-
*
|
|
710
|
-
* const code = await buildOutput.storage.getItem('/src/gen/pet.ts')
|
|
711
|
-
* ```
|
|
872
|
+
* `const code = await buildOutput.storage.getItem('/src/gen/pet.ts')`
|
|
712
873
|
*
|
|
713
874
|
* @example List all generated file paths
|
|
714
|
-
*
|
|
715
|
-
* const paths = await buildOutput.storage.getKeys()
|
|
716
|
-
* ```
|
|
875
|
+
* `const paths = await buildOutput.storage.getKeys()`
|
|
717
876
|
*/
|
|
718
877
|
storage: Storage
|
|
719
878
|
}
|
|
@@ -945,19 +1104,10 @@ async function setup(userConfig: UserConfig, options: SetupOptions = {}): Promis
|
|
|
945
1104
|
` • Operations: ${operationCount}`,
|
|
946
1105
|
],
|
|
947
1106
|
})
|
|
948
|
-
} else {
|
|
949
|
-
driver.inputNode = await config.adapter.parse(source)
|
|
950
|
-
|
|
951
|
-
await hooks.emit('kubb:debug', {
|
|
952
|
-
date: new Date(),
|
|
953
|
-
logs: [
|
|
954
|
-
`✓ Adapter '${config.adapter.name}' resolved InputNode`,
|
|
955
|
-
` • Schemas: ${driver.inputNode.schemas.length}`,
|
|
956
|
-
` • Operations: ${driver.inputNode.operations.length}`,
|
|
957
|
-
],
|
|
958
|
-
})
|
|
959
1107
|
}
|
|
960
|
-
}
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
if (!driver.inputStreamNode) {
|
|
961
1111
|
driver.inputNode = await config.adapter.parse(source)
|
|
962
1112
|
|
|
963
1113
|
await hooks.emit('kubb:debug', {
|
|
@@ -988,22 +1138,27 @@ async function setup(userConfig: UserConfig, options: SetupOptions = {}): Promis
|
|
|
988
1138
|
}
|
|
989
1139
|
}
|
|
990
1140
|
|
|
991
|
-
/**
|
|
992
|
-
* Walks the AST and dispatches nodes to a plugin's direct AST hooks
|
|
993
|
-
* (`schema`, `operation`, `operations`).
|
|
994
|
-
*
|
|
995
|
-
* When `include` contains only operation-scoped filters (`tag`, `operationId`, `path`,
|
|
996
|
-
* `method`, `contentType`) and no `schemaName` filter, the function pre-computes the set
|
|
997
|
-
* of top-level schema names transitively reachable from the included operations and skips
|
|
998
|
-
* schemas that fall outside that set. This ensures that component schemas referenced
|
|
999
|
-
* exclusively by excluded operations are not generated.
|
|
1000
|
-
*/
|
|
1001
1141
|
type PluginStreamEntry = {
|
|
1002
1142
|
plugin: NormalizedPlugin
|
|
1003
1143
|
context: GeneratorContext
|
|
1004
1144
|
hrStart: ReturnType<typeof process.hrtime>
|
|
1005
1145
|
}
|
|
1006
1146
|
|
|
1147
|
+
type PluginState = {
|
|
1148
|
+
plugin: NormalizedPlugin
|
|
1149
|
+
generatorContext: GeneratorContext
|
|
1150
|
+
generators: Generator[]
|
|
1151
|
+
hrStart: ReturnType<typeof process.hrtime>
|
|
1152
|
+
failed: boolean
|
|
1153
|
+
error: Error | undefined
|
|
1154
|
+
/**
|
|
1155
|
+
* `true` when the plugin's options have no `include`, `exclude`, or `override`
|
|
1156
|
+
* filters. The per-node `resolveOptions` call always returns the same `options`
|
|
1157
|
+
* reference in that case, so the inner loop can skip it entirely.
|
|
1158
|
+
*/
|
|
1159
|
+
optionsAreStatic: boolean
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1007
1162
|
/**
|
|
1008
1163
|
* Single-pass fan-out for streaming mode.
|
|
1009
1164
|
*
|
|
@@ -1015,91 +1170,111 @@ type PluginStreamEntry = {
|
|
|
1015
1170
|
* Each plugin still gets independent `plugin:start` / `plugin:end` events and its own
|
|
1016
1171
|
* timing, but the schema and operation nodes are parsed only once total.
|
|
1017
1172
|
*/
|
|
1018
|
-
async function runPluginStreamHooks(
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
generatorContext: GeneratorContext
|
|
1031
|
-
generators: Generator[]
|
|
1032
|
-
hrStart: ReturnType<typeof process.hrtime>
|
|
1033
|
-
failed: boolean
|
|
1034
|
-
error: Error | undefined
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1173
|
+
async function runPluginStreamHooks({
|
|
1174
|
+
entries,
|
|
1175
|
+
driver,
|
|
1176
|
+
pluginTimings,
|
|
1177
|
+
failedPlugins,
|
|
1178
|
+
}: {
|
|
1179
|
+
entries: PluginStreamEntry[]
|
|
1180
|
+
driver: PluginDriver
|
|
1181
|
+
pluginTimings: Map<string, number>
|
|
1182
|
+
failedPlugins: Set<{ plugin: Plugin; error: Error }>
|
|
1183
|
+
}): Promise<void> {
|
|
1184
|
+
const inputStreamNode = driver.inputStreamNode!
|
|
1037
1185
|
function resolveRendererFor(gen: Generator, state: PluginState): RendererFactory | undefined {
|
|
1038
1186
|
return gen.renderer === null ? undefined : (gen.renderer ?? state.plugin.renderer ?? state.generatorContext.config.renderer)
|
|
1039
1187
|
}
|
|
1040
1188
|
|
|
1041
|
-
const states: PluginState[] = entries.map(({ plugin, context, hrStart }) =>
|
|
1042
|
-
plugin
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1189
|
+
const states: PluginState[] = entries.map(({ plugin, context, hrStart }) => {
|
|
1190
|
+
const { exclude, include, override } = plugin.options
|
|
1191
|
+
const hasExclude = Array.isArray(exclude) && exclude.length > 0
|
|
1192
|
+
const hasInclude = Array.isArray(include) && include.length > 0
|
|
1193
|
+
const hasOverride = Array.isArray(override) && override.length > 0
|
|
1194
|
+
return {
|
|
1195
|
+
plugin,
|
|
1196
|
+
generatorContext: { ...context, resolver: driver.getResolver(plugin.name) },
|
|
1197
|
+
generators: plugin.generators ?? [],
|
|
1198
|
+
hrStart,
|
|
1199
|
+
failed: false,
|
|
1200
|
+
error: undefined,
|
|
1201
|
+
optionsAreStatic: !hasExclude && !hasInclude && !hasOverride,
|
|
1202
|
+
}
|
|
1203
|
+
})
|
|
1049
1204
|
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
for (const state of states) {
|
|
1053
|
-
if (state.failed) continue
|
|
1054
|
-
try {
|
|
1055
|
-
const { plugin, generatorContext, generators } = state
|
|
1056
|
-
const { exclude, include, override } = plugin.options
|
|
1057
|
-
const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node
|
|
1058
|
-
const options = generatorContext.resolver.resolveOptions(transformedNode, { options: plugin.options, exclude, include, override })
|
|
1059
|
-
if (options === null) continue
|
|
1205
|
+
async function dispatchSchema(state: PluginState, node: SchemaNode): Promise<void> {
|
|
1206
|
+
if (state.failed) return
|
|
1060
1207
|
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1208
|
+
try {
|
|
1209
|
+
const { plugin, generatorContext, generators } = state
|
|
1210
|
+
const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node
|
|
1211
|
+
const { exclude, include, override } = plugin.options
|
|
1212
|
+
const options: typeof plugin.options | null = state.optionsAreStatic
|
|
1213
|
+
? plugin.options
|
|
1214
|
+
: generatorContext.resolver.resolveOptions(transformedNode, { options: plugin.options, exclude, include, override })
|
|
1215
|
+
|
|
1216
|
+
if (options === null) return
|
|
1217
|
+
|
|
1218
|
+
const ctx = { ...generatorContext, options }
|
|
1219
|
+
for (const gen of generators) {
|
|
1220
|
+
if (!gen.schema) continue
|
|
1221
|
+
|
|
1222
|
+
const raw = gen.schema(transformedNode, ctx)
|
|
1223
|
+
const result = isPromise(raw) ? await raw : raw
|
|
1224
|
+
const applied = applyHookResult({ result, driver, rendererFactory: resolveRendererFor(gen, state) })
|
|
1225
|
+
|
|
1226
|
+
if (isPromise(applied)) await applied
|
|
1071
1227
|
}
|
|
1228
|
+
await driver.hooks.emit('kubb:generate:schema', transformedNode, ctx)
|
|
1229
|
+
} catch (caughtError) {
|
|
1230
|
+
state.failed = true
|
|
1231
|
+
state.error = caughtError as Error
|
|
1072
1232
|
}
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
async function dispatchOperation(state: PluginState, node: OperationNode): Promise<void> {
|
|
1236
|
+
if (state.failed) return
|
|
1237
|
+
try {
|
|
1238
|
+
const { plugin, generatorContext, generators } = state
|
|
1239
|
+
const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node
|
|
1240
|
+
const { exclude, include, override } = plugin.options
|
|
1241
|
+
const options: typeof plugin.options | null = state.optionsAreStatic
|
|
1242
|
+
? plugin.options
|
|
1243
|
+
: generatorContext.resolver.resolveOptions(transformedNode, { options: plugin.options, exclude, include, override })
|
|
1244
|
+
|
|
1245
|
+
if (options === null) return
|
|
1246
|
+
|
|
1247
|
+
const ctx = { ...generatorContext, options }
|
|
1248
|
+
|
|
1249
|
+
for (const gen of generators) {
|
|
1250
|
+
if (!gen.operation) continue
|
|
1251
|
+
|
|
1252
|
+
const raw = gen.operation(transformedNode, ctx)
|
|
1253
|
+
const result = isPromise(raw) ? await raw : raw
|
|
1254
|
+
const applied = applyHookResult({ result, driver, rendererFactory: resolveRendererFor(gen, state) })
|
|
1255
|
+
|
|
1256
|
+
if (isPromise(applied)) await applied
|
|
1257
|
+
}
|
|
1258
|
+
await driver.hooks.emit('kubb:generate:operation', transformedNode, ctx)
|
|
1259
|
+
} catch (caughtError) {
|
|
1260
|
+
state.failed = true
|
|
1261
|
+
state.error = caughtError as Error
|
|
1076
1262
|
}
|
|
1077
1263
|
}
|
|
1078
1264
|
|
|
1265
|
+
for await (const node of inputStreamNode.schemas) {
|
|
1266
|
+
// Plugins are dispatched concurrently; per-plugin work (the inner generator
|
|
1267
|
+
// loop) stays sequential so `FileManager.upsert` ordering for any single
|
|
1268
|
+
// plugin chain remains deterministic.
|
|
1269
|
+
await Promise.all(states.map((state) => dispatchSchema(state, node)))
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1079
1272
|
const collectedOperations: OperationNode[] = []
|
|
1273
|
+
|
|
1080
1274
|
for await (const node of inputStreamNode.operations) {
|
|
1081
1275
|
collectedOperations.push(node)
|
|
1082
|
-
for (const state of states) {
|
|
1083
|
-
if (state.failed) continue
|
|
1084
|
-
try {
|
|
1085
|
-
const { plugin, generatorContext, generators } = state
|
|
1086
|
-
const { exclude, include, override } = plugin.options
|
|
1087
|
-
const transformedNode = plugin.transformer ? transform(node, plugin.transformer) : node
|
|
1088
|
-
const options = generatorContext.resolver.resolveOptions(transformedNode, { options: plugin.options, exclude, include, override })
|
|
1089
|
-
if (options === null) continue
|
|
1090
1276
|
|
|
1091
|
-
|
|
1092
|
-
for (const gen of generators) {
|
|
1093
|
-
if (!gen.operation) continue
|
|
1094
|
-
const result = await gen.operation(transformedNode, ctx)
|
|
1095
|
-
await applyHookResult(result, driver, resolveRendererFor(gen, state))
|
|
1096
|
-
}
|
|
1097
|
-
await driver.hooks.emit('kubb:generate:operation', transformedNode, ctx)
|
|
1098
|
-
} catch (caughtError) {
|
|
1099
|
-
state.failed = true
|
|
1100
|
-
state.error = caughtError as Error
|
|
1101
|
-
}
|
|
1102
|
-
}
|
|
1277
|
+
await Promise.all(states.map((state) => dispatchOperation(state, node)))
|
|
1103
1278
|
}
|
|
1104
1279
|
|
|
1105
1280
|
// After stream: gen.operations for each plugin, then emit plugin:end
|
|
@@ -1108,11 +1283,13 @@ async function runPluginStreamHooks(
|
|
|
1108
1283
|
try {
|
|
1109
1284
|
const { plugin, generatorContext, generators } = state
|
|
1110
1285
|
const ctx = { ...generatorContext, options: plugin.options }
|
|
1286
|
+
|
|
1111
1287
|
for (const gen of generators) {
|
|
1112
1288
|
if (!gen.operations) continue
|
|
1113
1289
|
const result = await gen.operations(collectedOperations, ctx)
|
|
1114
|
-
await applyHookResult(result, driver, resolveRendererFor(gen, state))
|
|
1290
|
+
await applyHookResult({ result, driver, rendererFactory: resolveRendererFor(gen, state) })
|
|
1115
1291
|
}
|
|
1292
|
+
|
|
1116
1293
|
await driver.hooks.emit('kubb:generate:operations', collectedOperations, ctx)
|
|
1117
1294
|
} catch (caughtError) {
|
|
1118
1295
|
state.failed = true
|
|
@@ -1123,12 +1300,12 @@ async function runPluginStreamHooks(
|
|
|
1123
1300
|
const duration = getElapsedMs(state.hrStart)
|
|
1124
1301
|
pluginTimings.set(state.plugin.name, duration)
|
|
1125
1302
|
|
|
1126
|
-
await hooks.emit('kubb:plugin:end', {
|
|
1303
|
+
await driver.hooks.emit('kubb:plugin:end', {
|
|
1127
1304
|
plugin: state.plugin,
|
|
1128
1305
|
duration,
|
|
1129
1306
|
success: !state.failed,
|
|
1130
1307
|
...(state.failed && state.error ? { error: state.error } : {}),
|
|
1131
|
-
config,
|
|
1308
|
+
config: driver.config,
|
|
1132
1309
|
get files() {
|
|
1133
1310
|
return driver.fileManager.files
|
|
1134
1311
|
},
|
|
@@ -1139,15 +1316,23 @@ async function runPluginStreamHooks(
|
|
|
1139
1316
|
failedPlugins.add({ plugin: state.plugin, error: state.error })
|
|
1140
1317
|
}
|
|
1141
1318
|
|
|
1142
|
-
await hooks.emit('kubb:debug', {
|
|
1319
|
+
await driver.hooks.emit('kubb:debug', {
|
|
1143
1320
|
date: new Date(),
|
|
1144
1321
|
logs: [state.failed ? '✗ Plugin start failed' : `✓ Plugin started successfully (${formatMs(duration)})`],
|
|
1145
1322
|
})
|
|
1146
1323
|
}
|
|
1147
|
-
|
|
1148
|
-
await flushPendingFiles()
|
|
1149
1324
|
}
|
|
1150
1325
|
|
|
1326
|
+
/**
|
|
1327
|
+
* Walks the AST and dispatches nodes to a plugin's direct AST hooks
|
|
1328
|
+
* (`schema`, `operation`, `operations`).
|
|
1329
|
+
*
|
|
1330
|
+
* When `include` contains only operation-scoped filters (`tag`, `operationId`, `path`,
|
|
1331
|
+
* `method`, `contentType`) and no `schemaName` filter, the function pre-computes the set
|
|
1332
|
+
* of top-level schema names transitively reachable from the included operations and skips
|
|
1333
|
+
* schemas that fall outside that set. This ensures that component schemas referenced
|
|
1334
|
+
* exclusively by excluded operations are not generated.
|
|
1335
|
+
*/
|
|
1151
1336
|
async function runPluginAstHooks(plugin: NormalizedPlugin, context: GeneratorContext): Promise<void> {
|
|
1152
1337
|
const { adapter, inputNode, resolver, driver } = context
|
|
1153
1338
|
const { exclude, include, override } = plugin.options
|
|
@@ -1168,7 +1353,6 @@ async function runPluginAstHooks(plugin: NormalizedPlugin, context: GeneratorCon
|
|
|
1168
1353
|
resolver: driver.getResolver(plugin.name),
|
|
1169
1354
|
}
|
|
1170
1355
|
|
|
1171
|
-
// ── BATCH PATH ────────────────────────────────────────────────────────────
|
|
1172
1356
|
// When `include` has operation-based filters (tag, operationId, path, method, contentType)
|
|
1173
1357
|
// but no schema-level filters (schemaName), pre-compute the set of top-level schema names
|
|
1174
1358
|
// that are transitively referenced by the included operations. Schemas outside that set are
|
|
@@ -1177,11 +1361,11 @@ async function runPluginAstHooks(plugin: NormalizedPlugin, context: GeneratorCon
|
|
|
1177
1361
|
const hasOperationBasedIncludes = include?.some(({ type }) => operationFilterTypes.has(type)) ?? false
|
|
1178
1362
|
const hasSchemaNameIncludes = include?.some(({ type }) => type === 'schemaName') ?? false
|
|
1179
1363
|
|
|
1180
|
-
|
|
1181
|
-
|
|
1364
|
+
const allowedSchemaNames: Set<string> | undefined = (() => {
|
|
1365
|
+
if (!hasOperationBasedIncludes || hasSchemaNameIncludes) return undefined
|
|
1182
1366
|
const includedOps = inputNode!.operations.filter((op) => resolver.resolveOptions(op, { options: plugin.options, exclude, include, override }) !== null)
|
|
1183
|
-
|
|
1184
|
-
}
|
|
1367
|
+
return collectUsedSchemaNames(includedOps, inputNode!.schemas)
|
|
1368
|
+
})()
|
|
1185
1369
|
|
|
1186
1370
|
await walk(inputNode!, {
|
|
1187
1371
|
depth: 'shallow',
|
|
@@ -1208,7 +1392,7 @@ async function runPluginAstHooks(plugin: NormalizedPlugin, context: GeneratorCon
|
|
|
1208
1392
|
.filter((gen) => gen.schema)
|
|
1209
1393
|
.map(async (gen) => {
|
|
1210
1394
|
const result = await gen.schema!(transformedNode, ctx)
|
|
1211
|
-
return applyHookResult(result, driver, resolveRenderer(gen))
|
|
1395
|
+
return applyHookResult({ result, driver, rendererFactory: resolveRenderer(gen) })
|
|
1212
1396
|
}),
|
|
1213
1397
|
)
|
|
1214
1398
|
|
|
@@ -1222,22 +1406,22 @@ async function runPluginAstHooks(plugin: NormalizedPlugin, context: GeneratorCon
|
|
|
1222
1406
|
include,
|
|
1223
1407
|
override,
|
|
1224
1408
|
})
|
|
1225
|
-
if (options
|
|
1226
|
-
collectedOperations.push(transformedNode)
|
|
1409
|
+
if (options === null) return
|
|
1227
1410
|
|
|
1228
|
-
|
|
1411
|
+
collectedOperations.push(transformedNode)
|
|
1229
1412
|
|
|
1230
|
-
|
|
1231
|
-
generators
|
|
1232
|
-
.filter((gen) => gen.operation)
|
|
1233
|
-
.map(async (gen) => {
|
|
1234
|
-
const result = await gen.operation!(transformedNode, ctx)
|
|
1235
|
-
return applyHookResult(result, driver, resolveRenderer(gen))
|
|
1236
|
-
}),
|
|
1237
|
-
)
|
|
1413
|
+
const ctx = { ...generatorContext, options }
|
|
1238
1414
|
|
|
1239
|
-
|
|
1240
|
-
|
|
1415
|
+
await Promise.all(
|
|
1416
|
+
generators
|
|
1417
|
+
.filter((gen) => gen.operation)
|
|
1418
|
+
.map(async (gen) => {
|
|
1419
|
+
const result = await gen.operation!(transformedNode, ctx)
|
|
1420
|
+
return applyHookResult({ result, driver, rendererFactory: resolveRenderer(gen) })
|
|
1421
|
+
}),
|
|
1422
|
+
)
|
|
1423
|
+
|
|
1424
|
+
await driver.hooks.emit('kubb:generate:operation', transformedNode, ctx)
|
|
1241
1425
|
},
|
|
1242
1426
|
})
|
|
1243
1427
|
|
|
@@ -1247,7 +1431,7 @@ async function runPluginAstHooks(plugin: NormalizedPlugin, context: GeneratorCon
|
|
|
1247
1431
|
for (const gen of generators) {
|
|
1248
1432
|
if (!gen.operations) continue
|
|
1249
1433
|
const result = await gen.operations(collectedOperations, ctx)
|
|
1250
|
-
await applyHookResult(result, driver, resolveRenderer(gen))
|
|
1434
|
+
await applyHookResult({ result, driver, rendererFactory: resolveRenderer(gen) })
|
|
1251
1435
|
}
|
|
1252
1436
|
|
|
1253
1437
|
await driver.hooks.emit('kubb:generate:operations', collectedOperations, ctx)
|
|
@@ -1272,8 +1456,8 @@ async function safeBuild(setupResult: SetupResult): Promise<BuildOutput> {
|
|
|
1272
1456
|
}
|
|
1273
1457
|
const fileProcessor = new FileProcessor()
|
|
1274
1458
|
|
|
1275
|
-
async function flushPendingFiles(
|
|
1276
|
-
const files = driver.fileManager.files.filter((f) => !writtenPaths.has(f.path)
|
|
1459
|
+
async function flushPendingFiles(): Promise<void> {
|
|
1460
|
+
const files = driver.fileManager.files.filter((f) => !writtenPaths.has(f.path))
|
|
1277
1461
|
if (files.length === 0) {
|
|
1278
1462
|
return
|
|
1279
1463
|
}
|
|
@@ -1287,7 +1471,7 @@ async function safeBuild(setupResult: SetupResult): Promise<BuildOutput> {
|
|
|
1287
1471
|
|
|
1288
1472
|
const stream = fileProcessor.stream(files, { parsers: parsersMap, extension: config.output.extension })
|
|
1289
1473
|
|
|
1290
|
-
for
|
|
1474
|
+
for (const { file, source, processed, total, percentage } of stream) {
|
|
1291
1475
|
await hooks.emit('kubb:file:processing:update', { file, source, processed, total, percentage, config })
|
|
1292
1476
|
if (source) {
|
|
1293
1477
|
await storage.setItem(file.path, source)
|
|
@@ -1320,7 +1504,6 @@ async function safeBuild(setupResult: SetupResult): Promise<BuildOutput> {
|
|
|
1320
1504
|
|
|
1321
1505
|
const inputStreamNode = driver.inputStreamNode
|
|
1322
1506
|
if (inputStreamNode) {
|
|
1323
|
-
// ── STREAMING: fan-out single-pass ────────────────────────────────────
|
|
1324
1507
|
// Emit plugin:start for all plugins up front, collect generator-plugins
|
|
1325
1508
|
// for the fan-out pass, then handle non-generator plugins immediately.
|
|
1326
1509
|
const streamPluginEntries: PluginStreamEntry[] = []
|
|
@@ -1337,32 +1520,32 @@ async function safeBuild(setupResult: SetupResult): Promise<BuildOutput> {
|
|
|
1337
1520
|
|
|
1338
1521
|
if (plugin.generators?.length || driver.hasRegisteredGenerators(plugin.name)) {
|
|
1339
1522
|
streamPluginEntries.push({ plugin, context, hrStart })
|
|
1340
|
-
|
|
1341
|
-
// No generators: plugin ran via setup hooks; finish it now.
|
|
1342
|
-
const duration = getElapsedMs(hrStart)
|
|
1343
|
-
pluginTimings.set(plugin.name, duration)
|
|
1344
|
-
await hooks.emit('kubb:plugin:end', {
|
|
1345
|
-
plugin,
|
|
1346
|
-
duration,
|
|
1347
|
-
success: true,
|
|
1348
|
-
config,
|
|
1349
|
-
get files() {
|
|
1350
|
-
return driver.fileManager.files
|
|
1351
|
-
},
|
|
1352
|
-
upsertFile: (...files) => driver.fileManager.upsert(...files),
|
|
1353
|
-
})
|
|
1354
|
-
await hooks.emit('kubb:debug', {
|
|
1355
|
-
date: new Date(),
|
|
1356
|
-
logs: [`✓ Plugin started successfully (${formatMs(duration)})`],
|
|
1357
|
-
})
|
|
1523
|
+
continue
|
|
1358
1524
|
}
|
|
1525
|
+
// No generators: plugin ran via setup hooks; finish it now.
|
|
1526
|
+
const duration = getElapsedMs(hrStart)
|
|
1527
|
+
pluginTimings.set(plugin.name, duration)
|
|
1528
|
+
await hooks.emit('kubb:plugin:end', {
|
|
1529
|
+
plugin,
|
|
1530
|
+
duration,
|
|
1531
|
+
success: true,
|
|
1532
|
+
config,
|
|
1533
|
+
get files() {
|
|
1534
|
+
return driver.fileManager.files
|
|
1535
|
+
},
|
|
1536
|
+
upsertFile: (...files) => driver.fileManager.upsert(...files),
|
|
1537
|
+
})
|
|
1538
|
+
await hooks.emit('kubb:debug', {
|
|
1539
|
+
date: new Date(),
|
|
1540
|
+
logs: [`✓ Plugin started successfully (${formatMs(duration)})`],
|
|
1541
|
+
})
|
|
1359
1542
|
}
|
|
1360
1543
|
|
|
1361
1544
|
if (streamPluginEntries.length > 0) {
|
|
1362
|
-
await runPluginStreamHooks(
|
|
1545
|
+
await runPluginStreamHooks({ entries: streamPluginEntries, driver, pluginTimings, failedPlugins })
|
|
1546
|
+
await flushPendingFiles()
|
|
1363
1547
|
}
|
|
1364
1548
|
} else {
|
|
1365
|
-
// ── BATCH: existing per-plugin sequential loop ────────────────────────
|
|
1366
1549
|
for (const plugin of driver.plugins.values()) {
|
|
1367
1550
|
const context = driver.getContext(plugin)
|
|
1368
1551
|
const hrStart = process.hrtime()
|