@strav/cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@strav/cli",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "CLI framework and code generators for the Strav framework",
6
+ "license": "MIT",
7
+ "keywords": [
8
+ "bun",
9
+ "framework",
10
+ "typescript",
11
+ "strav",
12
+ "cli"
13
+ ],
14
+ "strav": {
15
+ "commands": "src/commands"
16
+ },
17
+ "files": [
18
+ "src/",
19
+ "package.json",
20
+ "tsconfig.json",
21
+ "CHANGELOG.md"
22
+ ],
23
+ "exports": {
24
+ ".": "./src/index.ts",
25
+ "./cli": "./src/cli/index.ts",
26
+ "./cli/*": "./src/cli/*.ts",
27
+ "./commands": "./src/commands/index.ts",
28
+ "./commands/*": "./src/commands/*.ts",
29
+ "./generators": "./src/generators/index.ts",
30
+ "./generators/*": "./src/generators/*.ts"
31
+ },
32
+ "bin": {
33
+ "strav": "./src/cli/strav.ts"
34
+ },
35
+ "peerDependencies": {
36
+ "@strav/kernel": "0.1.0",
37
+ "@strav/http": "0.1.0",
38
+ "@strav/database": "0.1.0",
39
+ "@strav/queue": "0.1.0",
40
+ "@strav/signal": "0.1.0"
41
+ },
42
+ "dependencies": {
43
+ "chalk": "^5.6.2",
44
+ "commander": "^14.0.3",
45
+ "prettier": "^3.8.1"
46
+ },
47
+ "scripts": {
48
+ "test": "bun test tests/",
49
+ "typecheck": "tsc --noEmit"
50
+ }
51
+ }
@@ -0,0 +1,79 @@
1
+ import Configuration from '@stravigor/kernel/config/configuration'
2
+ import Database from '@stravigor/database/database/database'
3
+ import SchemaRegistry from '@stravigor/database/schema/registry'
4
+ import DatabaseIntrospector from '@stravigor/database/database/introspector'
5
+ import Application from '@stravigor/kernel/core/application'
6
+ import type ServiceProvider from '@stravigor/kernel/core/service_provider'
7
+ import { discoverDomains } from '@stravigor/database'
8
+ import { getDatabasePaths } from '../config/loader.ts'
9
+
10
+ export interface BootstrapResult {
11
+ config: Configuration
12
+ db: Database
13
+ registry: SchemaRegistry
14
+ introspector: DatabaseIntrospector
15
+ }
16
+
17
+ /**
18
+ * Bootstrap the core framework services needed by CLI commands.
19
+ *
20
+ * Loads configuration, connects to the database, discovers and validates
21
+ * schemas, and creates an introspector instance.
22
+ *
23
+ * @param scope - Optional domain to discover schemas from (e.g., 'public', 'tenant', 'factory')
24
+ */
25
+ export async function bootstrap(scope?: string): Promise<BootstrapResult> {
26
+ const config = new Configuration('./config')
27
+ await config.load()
28
+
29
+ const db = new Database(config)
30
+
31
+ const registry = new SchemaRegistry()
32
+
33
+ // Get the configured database paths
34
+ const dbPaths = await getDatabasePaths()
35
+
36
+ if (scope && scope !== 'public') {
37
+ // For non-public domains, we need to load public schemas first since they may reference them
38
+ await registry.discover(dbPaths.schemas, 'public')
39
+ await registry.discover(dbPaths.schemas, scope)
40
+ } else if (scope === 'public') {
41
+ // For public schemas, only load public
42
+ await registry.discover(dbPaths.schemas, 'public')
43
+ } else {
44
+ // Default: discover all schemas (backward compatibility)
45
+ await registry.discover(dbPaths.schemas)
46
+ }
47
+
48
+ registry.validate()
49
+
50
+ const introspector = new DatabaseIntrospector(db)
51
+
52
+ return { config, db, registry, introspector }
53
+ }
54
+
55
+ /** Cleanly close the database connection. */
56
+ export async function shutdown(db: Database): Promise<void> {
57
+ await db.close()
58
+ }
59
+
60
+ /**
61
+ * Bootstrap an Application with the given service providers.
62
+ *
63
+ * Creates a fresh Application, registers all providers, boots them
64
+ * in dependency order, and returns the running application.
65
+ * Signal handlers for graceful shutdown are installed automatically.
66
+ *
67
+ * @example
68
+ * const app = await withProviders([
69
+ * new ConfigProvider(),
70
+ * new DatabaseProvider(),
71
+ * new AuthProvider({ resolver: (id) => User.find(id) }),
72
+ * ])
73
+ */
74
+ export async function withProviders(providers: ServiceProvider[]): Promise<Application> {
75
+ const app = new Application()
76
+ for (const provider of providers) app.use(provider)
77
+ await app.start()
78
+ return app
79
+ }
@@ -0,0 +1,180 @@
1
+ import { readdirSync, existsSync, realpathSync } from 'node:fs'
2
+ import { join } from 'node:path'
3
+ import type { Command } from 'commander'
4
+ import chalk from 'chalk'
5
+
6
+ /**
7
+ * Discovers and registers CLI commands from two sources:
8
+ *
9
+ * 1. **Package commands** — installed `@stravigor/*` packages that declare
10
+ * `"strav": { "commands": "src/commands" }` in their `package.json`.
11
+ * 2. **User commands** — `.ts` files in a `./commands/` directory at the
12
+ * project root.
13
+ *
14
+ * Every discovered file must export a `register(program: Command): void`
15
+ * function.
16
+ *
17
+ * @example
18
+ * // In strav.ts:
19
+ * await CommandLoader.discover(program)
20
+ *
21
+ * @example
22
+ * // In a package (e.g. @stravigor/search):
23
+ * // package.json: { "strav": { "commands": "src/commands" } }
24
+ * // src/commands/search_import.ts:
25
+ * export function register(program: Command): void {
26
+ * program.command('search:import <model>').action(async () => { ... })
27
+ * }
28
+ *
29
+ * @example
30
+ * // User-defined command:
31
+ * // commands/deploy.ts:
32
+ * export function register(program: Command): void {
33
+ * program.command('deploy').action(async () => { ... })
34
+ * }
35
+ */
36
+ export default class CommandLoader {
37
+ /**
38
+ * Discover and register commands from packages and the user's commands directory.
39
+ *
40
+ * @param baseDir - Project root directory. Defaults to `process.cwd()`.
41
+ */
42
+ static async discover(program: Command, baseDir?: string): Promise<void> {
43
+ const root = baseDir ?? process.cwd()
44
+ await this.loadPackageCommands(program, root)
45
+ await this.loadUserCommands(program, root)
46
+ }
47
+
48
+ // ── Package commands ───────────────────────────────────────────────────
49
+
50
+ private static async loadPackageCommands(program: Command, root: string): Promise<void> {
51
+ const packages = await this.resolvePackages(root)
52
+
53
+ for (const { root: pkgRoot, commandsDir } of packages) {
54
+ try {
55
+ const dir = join(pkgRoot, commandsDir)
56
+ if (!dirExists(dir)) continue
57
+
58
+ const files = readdirSync(dir).filter(f => f.endsWith('.ts'))
59
+ for (const file of files) {
60
+ const filePath = join(dir, file)
61
+ try {
62
+ const module = await import(filePath)
63
+ if (typeof module.register === 'function') {
64
+ module.register(program)
65
+ }
66
+ } catch (err) {
67
+ console.error(
68
+ chalk.yellow(
69
+ `Warning: Failed to load command "${file}": ${err instanceof Error ? err.message : err}`
70
+ )
71
+ )
72
+ }
73
+ }
74
+ } catch {
75
+ // Skip packages whose commands directory can't be read
76
+ }
77
+ }
78
+ }
79
+
80
+ // ── User commands ──────────────────────────────────────────────────────
81
+
82
+ private static async loadUserCommands(program: Command, root: string): Promise<void> {
83
+ const userDir = join(root, 'commands')
84
+ if (!dirExists(userDir)) return
85
+
86
+ const files = readdirSync(userDir).filter(f => f.endsWith('.ts'))
87
+
88
+ for (const file of files) {
89
+ const filePath = join(userDir, file)
90
+ try {
91
+ const module = await import(filePath)
92
+ if (typeof module.register === 'function') {
93
+ module.register(program)
94
+ }
95
+ } catch (err) {
96
+ console.error(
97
+ chalk.yellow(
98
+ `Warning: Failed to load command "${file}": ${err instanceof Error ? err.message : err}`
99
+ )
100
+ )
101
+ }
102
+ }
103
+ }
104
+
105
+ // ── Package resolution ─────────────────────────────────────────────────
106
+
107
+ private static async resolvePackages(
108
+ root: string
109
+ ): Promise<Array<{ root: string; commandsDir: string }>> {
110
+ const results: Array<{ root: string; commandsDir: string }> = []
111
+ const seen = new Set<string>()
112
+
113
+ // 1. Check node_modules/@stravigor/*
114
+ const nodeModulesDir = join(root, 'node_modules', '@stravigor')
115
+ if (dirExists(nodeModulesDir)) {
116
+ const dirs = readdirSync(nodeModulesDir)
117
+ for (const dir of dirs) {
118
+ const pkgRoot = join(nodeModulesDir, dir)
119
+ const realRoot = realPath(pkgRoot)
120
+ if (seen.has(realRoot)) continue
121
+ seen.add(realRoot)
122
+ const commandsDir = await readStravCommands(pkgRoot)
123
+ if (commandsDir) results.push({ root: pkgRoot, commandsDir })
124
+ }
125
+ }
126
+
127
+ // 2. Check Bun workspace packages
128
+ try {
129
+ const rootPkgPath = join(root, 'package.json')
130
+ if (existsSync(rootPkgPath)) {
131
+ const rootPkg = await Bun.file(rootPkgPath).json()
132
+ const workspaces: string[] = rootPkg.workspaces ?? []
133
+ for (const ws of workspaces) {
134
+ const wsPath = join(root, ws)
135
+ const realRoot = realPath(wsPath)
136
+ if (seen.has(realRoot)) continue
137
+ seen.add(realRoot)
138
+ const commandsDir = await readStravCommands(wsPath)
139
+ if (commandsDir) results.push({ root: wsPath, commandsDir })
140
+ }
141
+ }
142
+ } catch {
143
+ // No package.json or not in a workspace
144
+ }
145
+
146
+ return results
147
+ }
148
+ }
149
+
150
+ // ── Helpers ────────────────────────────────────────────────────────────────
151
+
152
+ /** Resolve symlinks to avoid double-loading workspace packages. */
153
+ function realPath(p: string): string {
154
+ try {
155
+ return realpathSync(p)
156
+ } catch {
157
+ return p
158
+ }
159
+ }
160
+
161
+ function dirExists(path: string): boolean {
162
+ try {
163
+ readdirSync(path)
164
+ return true
165
+ } catch {
166
+ return false
167
+ }
168
+ }
169
+
170
+ /** Read the `strav.commands` field from a package's `package.json`. */
171
+ async function readStravCommands(packageRoot: string): Promise<string | null> {
172
+ try {
173
+ const pkgPath = join(packageRoot, 'package.json')
174
+ if (!existsSync(pkgPath)) return null
175
+ const pkg = await Bun.file(pkgPath).json()
176
+ return pkg?.strav?.commands ?? null
177
+ } catch {
178
+ return null
179
+ }
180
+ }
@@ -0,0 +1,3 @@
1
+ export { bootstrap, shutdown, withProviders } from './bootstrap'
2
+ export type { BootstrapResult } from './bootstrap'
3
+ export { default as CommandLoader } from './command_loader'
@@ -0,0 +1,13 @@
1
+ import 'reflect-metadata'
2
+ import { Command } from 'commander'
3
+ import { join } from 'node:path'
4
+ import CommandLoader from './command_loader.ts'
5
+
6
+ const pkg = await Bun.file(join(import.meta.dir, '../../package.json')).json()
7
+ const program = new Command()
8
+
9
+ program.name('strav').description('Strav CLI').version(pkg.version)
10
+
11
+ await CommandLoader.discover(program)
12
+
13
+ program.parse()
@@ -0,0 +1,77 @@
1
+ import { join } from 'node:path'
2
+ import { existsSync } from 'node:fs'
3
+ import type { Command } from 'commander'
4
+ import chalk from 'chalk'
5
+ import { bootstrap, shutdown } from '../cli/bootstrap.ts'
6
+ import { freshDatabase, requireLocalEnv } from './migration_fresh.ts'
7
+ import { toSnakeCase } from '@stravigor/kernel/helpers/strings'
8
+ import BaseModel from '@stravigor/database/orm/base_model'
9
+ import { Seeder } from '@stravigor/database/database/seeder'
10
+
11
+ const SEEDERS_PATH = 'database/seeders'
12
+
13
+ export function register(program: Command): void {
14
+ program
15
+ .command('seed')
16
+ .alias('db:seed')
17
+ .description('Seed the database with records')
18
+ .option('-c, --class <name>', 'Run a specific seeder class')
19
+ .option('--fresh', 'Drop all tables and re-migrate before seeding')
20
+ .action(async ({ class: className, fresh }: { class?: string; fresh?: boolean }) => {
21
+ let db
22
+ try {
23
+ const { db: database, registry, introspector } = await bootstrap()
24
+ db = database
25
+
26
+ // Wire BaseModel so factories / seeders can use the ORM
27
+ new BaseModel(db)
28
+
29
+ // --fresh: reset the database first
30
+ if (fresh) {
31
+ requireLocalEnv('seed --fresh')
32
+
33
+ const applied = await freshDatabase(db, registry, introspector)
34
+ console.log(chalk.green(`\nFresh migration complete. Applied ${applied} migration(s).`))
35
+ }
36
+
37
+ // Resolve the seeder file
38
+ const fileName = className
39
+ ? toSnakeCase(className.replace(/Seeder$/i, '') || 'database') + '_seeder'
40
+ : 'database_seeder'
41
+
42
+ const seederPath = join(process.cwd(), SEEDERS_PATH, `${fileName}.ts`)
43
+
44
+ if (!existsSync(seederPath)) {
45
+ console.error(chalk.red(`Seeder not found: `) + chalk.dim(seederPath))
46
+ console.error(
47
+ chalk.dim(
48
+ ` Run ${chalk.cyan(`strav generate:seeder ${className ?? 'DatabaseSeeder'}`)} to create it.`
49
+ )
50
+ )
51
+ process.exit(1)
52
+ }
53
+
54
+ console.log(chalk.cyan(`\nSeeding database...`))
55
+
56
+ const mod = await import(seederPath)
57
+ const SeederClass = mod.default
58
+
59
+ if (!SeederClass || !(SeederClass.prototype instanceof Seeder)) {
60
+ console.error(
61
+ chalk.red(`Error: `) + `Default export of ${fileName}.ts must extend Seeder.`
62
+ )
63
+ process.exit(1)
64
+ }
65
+
66
+ const seeder = new SeederClass(db) as Seeder
67
+ await seeder.run()
68
+
69
+ console.log(chalk.green('Database seeding complete.'))
70
+ } catch (err) {
71
+ console.error(chalk.red(`Error: ${err instanceof Error ? err.message : err}`))
72
+ process.exit(1)
73
+ } finally {
74
+ if (db) await shutdown(db)
75
+ }
76
+ })
77
+ }
@@ -0,0 +1,74 @@
1
+ import { join } from 'node:path'
2
+ import type { Command } from 'commander'
3
+ import chalk from 'chalk'
4
+ import SchemaRegistry from '@stravigor/database/schema/registry'
5
+ import ApiGenerator from '../generators/api_generator.ts'
6
+ import RouteGenerator from '../generators/route_generator.ts'
7
+ import TestGenerator from '../generators/test_generator.ts'
8
+ import DocGenerator from '../generators/doc_generator.ts'
9
+ import type { ApiRoutingConfig } from '../generators/route_generator.ts'
10
+ import type { GeneratorConfig } from '../generators/config.ts'
11
+ import { loadGeneratorConfig, getDatabasePaths } from '../config/loader.ts'
12
+
13
+ export function register(program: Command): void {
14
+ program
15
+ .command('generate:api')
16
+ .alias('g:api')
17
+ .description(
18
+ 'Generate services, controllers, policies, validators, events, and routes from schemas'
19
+ )
20
+ .action(async () => {
21
+ try {
22
+ console.log(chalk.cyan('Generating API layer from schemas...'))
23
+
24
+ // Get configured database paths
25
+ const dbPaths = await getDatabasePaths()
26
+
27
+ const registry = new SchemaRegistry()
28
+ await registry.discover(dbPaths.schemas)
29
+ registry.validate()
30
+
31
+ const schemas = registry.resolve()
32
+ const representation = registry.buildRepresentation()
33
+
34
+ // Load generator config (if available)
35
+ const config = await loadGeneratorConfig()
36
+
37
+ const apiGen = new ApiGenerator(schemas, representation, config)
38
+ const apiFiles = await apiGen.writeAll()
39
+
40
+ // Load API routing config from config/http.ts (if available)
41
+ let apiConfig: Partial<ApiRoutingConfig> | undefined
42
+ try {
43
+ const httpConfig = (await import(join(process.cwd(), 'config/http.ts'))).default
44
+ apiConfig = httpConfig.api
45
+ } catch {
46
+ // No config/http.ts or no api section — use defaults
47
+ }
48
+
49
+ const routeGen = new RouteGenerator(schemas, config, apiConfig)
50
+ const routeFiles = await routeGen.writeAll()
51
+
52
+ const testGen = new TestGenerator(schemas, representation, config, apiConfig)
53
+ const testFiles = await testGen.writeAll()
54
+
55
+ const docGen = new DocGenerator(schemas, representation, config, apiConfig)
56
+ const docFiles = await docGen.writeAll()
57
+
58
+ const files = [...apiFiles, ...routeFiles, ...testFiles, ...docFiles]
59
+
60
+ if (files.length === 0) {
61
+ console.log(chalk.yellow('No API files to generate.'))
62
+ return
63
+ }
64
+
65
+ console.log(chalk.green(`\nGenerated ${files.length} file(s):`))
66
+ for (const file of files) {
67
+ console.log(chalk.dim(` ${file.path}`))
68
+ }
69
+ } catch (err) {
70
+ console.error(chalk.red(`Error: ${err instanceof Error ? err.message : err}`))
71
+ process.exit(1)
72
+ }
73
+ })
74
+ }
@@ -0,0 +1,47 @@
1
+ import { join } from 'node:path'
2
+ import type { Command } from 'commander'
3
+ import chalk from 'chalk'
4
+
5
+ export function register(program: Command): void {
6
+ program
7
+ .command('generate:key')
8
+ .alias('g:key')
9
+ .description('Generate an APP_KEY and write it to the .env file')
10
+ .option('-f, --force', 'Overwrite existing APP_KEY if present')
11
+ .action(async ({ force }: { force?: boolean }) => {
12
+ try {
13
+ const key = crypto.randomUUID()
14
+ const envPath = join(process.cwd(), '.env')
15
+ const file = Bun.file(envPath)
16
+
17
+ if (await file.exists()) {
18
+ const contents = await file.text()
19
+ const hasKey = /^APP_KEY\s*=/m.test(contents)
20
+
21
+ if (hasKey && !force) {
22
+ const current = contents.match(/^APP_KEY\s*=\s*(.*)$/m)?.[1] ?? ''
23
+ if (current) {
24
+ console.log(chalk.yellow('APP_KEY already exists in .env. Use --force to overwrite.'))
25
+ return
26
+ }
27
+ }
28
+
29
+ if (hasKey) {
30
+ const updated = contents.replace(/^APP_KEY\s*=.*$/m, `APP_KEY=${key}`)
31
+ await Bun.write(envPath, updated)
32
+ } else {
33
+ const separator = contents.endsWith('\n') ? '' : '\n'
34
+ await Bun.write(envPath, contents + separator + `APP_KEY=${key}\n`)
35
+ }
36
+ } else {
37
+ await Bun.write(envPath, `APP_KEY=${key}\n`)
38
+ }
39
+
40
+ console.log(chalk.green('APP_KEY generated successfully.'))
41
+ console.log(chalk.dim(` ${key}`))
42
+ } catch (err) {
43
+ console.error(chalk.red(`Error: ${err instanceof Error ? err.message : err}`))
44
+ process.exit(1)
45
+ }
46
+ })
47
+ }
@@ -0,0 +1,117 @@
1
+ import { join } from 'node:path'
2
+ import type { Command } from 'commander'
3
+ import chalk from 'chalk'
4
+ import SchemaRegistry from '@stravigor/database/schema/registry'
5
+ import ModelGenerator from '../generators/model_generator.ts'
6
+ import type { GeneratorConfig } from '../generators/config.ts'
7
+ import { discoverDomains } from '@stravigor/database'
8
+ import { loadGeneratorConfig, getDatabasePaths } from '../config/loader.ts'
9
+
10
+ export function register(program: Command): void {
11
+ program
12
+ .command('generate:models')
13
+ .alias('g:models')
14
+ .description('Generate model classes and enums from schema definitions')
15
+ .option('--scope <scope>', 'Generate models for specific domain or "all" for all domains', 'all')
16
+ .action(async (options) => {
17
+ try {
18
+ const scope = options.scope as string
19
+
20
+ // Get configured database paths
21
+ const dbPaths = await getDatabasePaths()
22
+
23
+ // Validate scope against available domains or 'all'
24
+ const availableDomains = discoverDomains(dbPaths.schemas)
25
+ if (scope !== 'all' && !availableDomains.includes(scope)) {
26
+ console.error(chalk.red(`Invalid domain: ${scope}. Available domains: ${availableDomains.join(', ')}, all`))
27
+ process.exit(1)
28
+ }
29
+
30
+ console.log(chalk.cyan(`Generating models from ${scope === 'all' ? 'all' : scope} schemas...`))
31
+
32
+ // Load generator config (if available)
33
+ const config = await loadGeneratorConfig()
34
+
35
+ const allFiles: any[] = []
36
+ const scopesToProcess = scope === 'all' ? availableDomains : [scope]
37
+
38
+ // When generating models for specific domains, we need all schemas loaded
39
+ // to handle cross-domain references properly
40
+ const fullRegistry = new SchemaRegistry()
41
+
42
+ // Always load public schemas first (base schemas)
43
+ await fullRegistry.discover(dbPaths.schemas, 'public')
44
+
45
+ // Load schemas from all other domains if needed for validation or if generating models for those domains
46
+ for (const domain of availableDomains) {
47
+ if (domain !== 'public' && (scope === 'all' || scope === domain)) {
48
+ await fullRegistry.discover(dbPaths.schemas, domain)
49
+ }
50
+ }
51
+
52
+ // Validate all loaded schemas
53
+ fullRegistry.validate()
54
+
55
+ // Build representation from all loaded schemas
56
+ const fullRepresentation = fullRegistry.buildRepresentation()
57
+
58
+ // Build a map of schema name -> domain for cross-domain reference resolution
59
+ const allSchemasMap = new Map<string, string>()
60
+
61
+ // Load public schemas to identify which ones are public
62
+ const publicRegistry = new SchemaRegistry()
63
+ await publicRegistry.discover(dbPaths.schemas, 'public')
64
+ const publicSchemaNames = new Set(publicRegistry.all().map(s => s.name))
65
+
66
+ // Map schemas to their respective domains by checking which domain directory they came from
67
+ for (const schema of fullRegistry.all()) {
68
+ if (publicSchemaNames.has(schema.name)) {
69
+ allSchemasMap.set(schema.name, 'public')
70
+ } else {
71
+ // For non-public schemas, find which domain they belong to
72
+ // by checking which domains were loaded
73
+ for (const domain of availableDomains) {
74
+ if (domain !== 'public') {
75
+ const domainRegistry = new SchemaRegistry()
76
+ await domainRegistry.discover(dbPaths.schemas, domain)
77
+ const domainSchemaNames = new Set(domainRegistry.all().map(s => s.name))
78
+ if (domainSchemaNames.has(schema.name)) {
79
+ allSchemasMap.set(schema.name, domain)
80
+ break
81
+ }
82
+ }
83
+ }
84
+ }
85
+ }
86
+
87
+ for (const currentScope of scopesToProcess) {
88
+ // Get just the schemas for the current scope
89
+ const scopeRegistry = new SchemaRegistry()
90
+ await scopeRegistry.discover(dbPaths.schemas, currentScope)
91
+
92
+ if (scopeRegistry.all().length === 0) {
93
+ console.log(chalk.yellow(`No schemas found for domain: ${currentScope}`))
94
+ continue
95
+ }
96
+
97
+ const scopeSchemas = scopeRegistry.all()
98
+ const generator = new ModelGenerator(scopeSchemas, fullRepresentation, config, currentScope, allSchemasMap)
99
+ const files = await generator.writeAll()
100
+ allFiles.push(...files)
101
+ }
102
+
103
+ if (allFiles.length === 0) {
104
+ console.log(chalk.yellow('No models to generate.'))
105
+ return
106
+ }
107
+
108
+ console.log(chalk.green(`\nGenerated ${allFiles.length} file(s):`))
109
+ for (const file of allFiles) {
110
+ console.log(chalk.dim(` ${file.path}`))
111
+ }
112
+ } catch (err) {
113
+ console.error(chalk.red(`Error: ${err instanceof Error ? err.message : err}`))
114
+ process.exit(1)
115
+ }
116
+ })
117
+ }