@famgia/omnify-cli 0.0.5 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js ADDED
@@ -0,0 +1,916 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import { existsSync as existsSync4 } from "fs";
5
+ import { resolve as resolve6 } from "path";
6
+ import { Command } from "commander";
7
+ import { OmnifyError as OmnifyError5 } from "@famgia/omnify-core";
8
+
9
+ // src/commands/init.ts
10
+ import { existsSync, mkdirSync, writeFileSync } from "fs";
11
+ import { resolve } from "path";
12
+ import { select, confirm, input } from "@inquirer/prompts";
13
+
14
+ // src/output/logger.ts
15
+ import pc from "picocolors";
16
+ import { formatError, getExitCode } from "@famgia/omnify-core";
17
+ var Logger = class {
18
+ _verbose;
19
+ _quiet;
20
+ _startTime;
21
+ constructor(options = {}) {
22
+ this._verbose = options.verbose ?? false;
23
+ this._quiet = options.quiet ?? false;
24
+ this._startTime = Date.now();
25
+ }
26
+ /**
27
+ * Enable or disable verbose mode.
28
+ */
29
+ setVerbose(verbose) {
30
+ this._verbose = verbose;
31
+ }
32
+ /**
33
+ * Enable or disable quiet mode.
34
+ */
35
+ setQuiet(quiet) {
36
+ this._quiet = quiet;
37
+ }
38
+ /**
39
+ * Log an info message.
40
+ */
41
+ info(message) {
42
+ if (!this._quiet) {
43
+ console.log(message);
44
+ }
45
+ }
46
+ /**
47
+ * Log a success message.
48
+ */
49
+ success(message) {
50
+ if (!this._quiet) {
51
+ console.log(pc.green("\u2713") + " " + message);
52
+ }
53
+ }
54
+ /**
55
+ * Log a warning message.
56
+ */
57
+ warn(message) {
58
+ if (!this._quiet) {
59
+ console.log(pc.yellow("\u26A0") + " " + pc.yellow(message));
60
+ }
61
+ }
62
+ /**
63
+ * Log an error message.
64
+ */
65
+ error(message) {
66
+ console.error(pc.red("\u2717") + " " + pc.red(message));
67
+ }
68
+ /**
69
+ * Log a debug message (only in verbose mode).
70
+ */
71
+ debug(message) {
72
+ if (this._verbose && !this._quiet) {
73
+ console.log(pc.dim(" " + message));
74
+ }
75
+ }
76
+ /**
77
+ * Log a step message.
78
+ */
79
+ step(message) {
80
+ if (!this._quiet) {
81
+ console.log(pc.cyan("\u2192") + " " + message);
82
+ }
83
+ }
84
+ /**
85
+ * Log a header.
86
+ */
87
+ header(message) {
88
+ if (!this._quiet) {
89
+ console.log();
90
+ console.log(pc.bold(message));
91
+ console.log();
92
+ }
93
+ }
94
+ /**
95
+ * Log a list item.
96
+ */
97
+ list(items) {
98
+ if (!this._quiet) {
99
+ for (const item of items) {
100
+ console.log(" \u2022 " + item);
101
+ }
102
+ }
103
+ }
104
+ /**
105
+ * Log a timing message.
106
+ */
107
+ timing(message) {
108
+ if (this._verbose && !this._quiet) {
109
+ const elapsed = Date.now() - this._startTime;
110
+ console.log(pc.dim(` [${elapsed}ms] ${message}`));
111
+ }
112
+ }
113
+ /**
114
+ * Log an empty line.
115
+ */
116
+ newline() {
117
+ if (!this._quiet) {
118
+ console.log();
119
+ }
120
+ }
121
+ /**
122
+ * Format and log an OmnifyError.
123
+ */
124
+ formatError(error) {
125
+ const formatted = formatError(error, { color: true });
126
+ console.error(formatted);
127
+ }
128
+ /**
129
+ * Get exit code for an error.
130
+ */
131
+ getExitCode(error) {
132
+ return getExitCode(error);
133
+ }
134
+ };
135
+ var logger = new Logger();
136
+
137
+ // src/commands/init.ts
138
+ var EXAMPLE_SCHEMA = `# Example User schema
139
+ # See https://github.com/famgia/omnify for documentation
140
+
141
+ name: User
142
+ displayName: User Account
143
+ kind: object
144
+
145
+ properties:
146
+ email:
147
+ type: Email
148
+ unique: true
149
+ displayName: Email Address
150
+
151
+ name:
152
+ type: String
153
+ displayName: Full Name
154
+
155
+ bio:
156
+ type: Text
157
+ nullable: true
158
+ displayName: Biography
159
+
160
+ options:
161
+ timestamps: true
162
+ softDelete: true
163
+ `;
164
+ function generateConfig(config) {
165
+ const imports = [`import { defineConfig } from '@famgia/omnify';`];
166
+ const plugins = [];
167
+ if (config.migrationTool === "laravel") {
168
+ imports.push(`import laravel from '@famgia/omnify-laravel/plugin';`);
169
+ plugins.push(` laravel({
170
+ migrationsPath: '${config.migrationsPath}',
171
+ typesPath: '${config.typesPath}',
172
+ singleFile: true,
173
+ generateMigrations: true,
174
+ generateTypes: ${config.generateTypes},
175
+ }),`);
176
+ }
177
+ if (config.migrationTool === "prisma") {
178
+ plugins.push(` // Prisma plugin coming soon!
179
+ // prisma({ schemaPath: 'prisma/schema.prisma' }),`);
180
+ }
181
+ if (config.migrationTool === "drizzle") {
182
+ plugins.push(` // Drizzle plugin coming soon!
183
+ // drizzle({ schemaPath: 'src/db/schema.ts' }),`);
184
+ }
185
+ if (config.migrationTool === "none" && config.generateTypes) {
186
+ imports.push(`import laravel from '@famgia/omnify-laravel/plugin';`);
187
+ plugins.push(` laravel({
188
+ typesPath: '${config.typesPath}',
189
+ generateMigrations: false,
190
+ generateTypes: true,
191
+ }),`);
192
+ }
193
+ const dbUrlExamples = {
194
+ mysql: "mysql://root:password@localhost:3306/omnify_dev",
195
+ postgres: "postgres://postgres:password@localhost:5432/omnify_dev",
196
+ sqlite: "sqlite://./omnify_dev.db"
197
+ };
198
+ return `${imports.join("\n")}
199
+
200
+ export default defineConfig({
201
+ // Schema files location
202
+ schemasDir: '${config.schemasDir}',
203
+
204
+ // Lock file for tracking schema changes
205
+ lockFilePath: './omnify.lock',
206
+
207
+ // Database configuration
208
+ database: {
209
+ driver: '${config.database}',
210
+ // REQUIRED: Set your development database URL
211
+ // devUrl: '${dbUrlExamples[config.database]}',
212
+ },
213
+
214
+ // Generator plugins
215
+ plugins: [
216
+ ${plugins.join("\n\n")}
217
+ ],
218
+ });
219
+ `;
220
+ }
221
+ async function runInit(options) {
222
+ const cwd = process.cwd();
223
+ logger.header("Omnify Project Setup");
224
+ logger.newline();
225
+ const configPath2 = resolve(cwd, "omnify.config.ts");
226
+ if (existsSync(configPath2) && !options.force) {
227
+ logger.warn("omnify.config.ts already exists. Use --force to overwrite.");
228
+ return;
229
+ }
230
+ let config;
231
+ if (options.yes) {
232
+ config = {
233
+ database: "mysql",
234
+ migrationTool: "laravel",
235
+ generateTypes: true,
236
+ migrationsPath: "database/migrations",
237
+ typesPath: "resources/js/types",
238
+ schemasDir: "./schemas"
239
+ };
240
+ logger.info("Using default configuration...");
241
+ } else {
242
+ logger.info("Answer a few questions to configure your project:\n");
243
+ const database = await select({
244
+ message: "Which database?",
245
+ choices: [
246
+ { name: "MySQL / MariaDB", value: "mysql" },
247
+ { name: "PostgreSQL", value: "postgres" },
248
+ { name: "SQLite", value: "sqlite" }
249
+ ],
250
+ default: "mysql"
251
+ });
252
+ const migrationTool = await select({
253
+ message: "Which migration tool?",
254
+ choices: [
255
+ { name: "Laravel (PHP)", value: "laravel" },
256
+ { name: "Prisma (coming soon)", value: "prisma", disabled: true },
257
+ { name: "Drizzle (coming soon)", value: "drizzle", disabled: true },
258
+ { name: "None (types only)", value: "none" }
259
+ ],
260
+ default: "laravel"
261
+ });
262
+ const generateTypes = await confirm({
263
+ message: "Generate TypeScript types?",
264
+ default: true
265
+ });
266
+ const defaultPaths = {
267
+ laravel: { migrations: "database/migrations", types: "resources/js/types" },
268
+ prisma: { migrations: "prisma/migrations", types: "src/types" },
269
+ drizzle: { migrations: "drizzle", types: "src/types" },
270
+ none: { migrations: "", types: "types" }
271
+ };
272
+ const defaults = defaultPaths[migrationTool];
273
+ let migrationsPath = defaults.migrations;
274
+ let typesPath = defaults.types;
275
+ if (migrationTool !== "none") {
276
+ migrationsPath = await input({
277
+ message: "Migrations output path:",
278
+ default: defaults.migrations
279
+ });
280
+ }
281
+ if (generateTypes) {
282
+ typesPath = await input({
283
+ message: "TypeScript types path:",
284
+ default: defaults.types
285
+ });
286
+ }
287
+ const schemasDir2 = await input({
288
+ message: "Schemas directory:",
289
+ default: "./schemas"
290
+ });
291
+ config = {
292
+ database,
293
+ migrationTool,
294
+ generateTypes,
295
+ migrationsPath,
296
+ typesPath,
297
+ schemasDir: schemasDir2
298
+ };
299
+ }
300
+ logger.newline();
301
+ logger.step("Creating project files...");
302
+ const schemasDir = resolve(cwd, config.schemasDir);
303
+ if (!existsSync(schemasDir)) {
304
+ mkdirSync(schemasDir, { recursive: true });
305
+ logger.debug(`Created ${config.schemasDir}/ directory`);
306
+ }
307
+ const examplePath = resolve(schemasDir, "User.yaml");
308
+ if (!existsSync(examplePath) || options.force) {
309
+ writeFileSync(examplePath, EXAMPLE_SCHEMA);
310
+ logger.debug("Created example schema: User.yaml");
311
+ }
312
+ const configContent = generateConfig(config);
313
+ writeFileSync(configPath2, configContent);
314
+ logger.debug("Created omnify.config.ts");
315
+ logger.newline();
316
+ logger.success("Project initialized!");
317
+ logger.newline();
318
+ const toolName = config.migrationTool === "laravel" ? "Laravel" : config.migrationTool === "prisma" ? "Prisma" : config.migrationTool === "drizzle" ? "Drizzle" : "None";
319
+ logger.info("Configuration:");
320
+ logger.list([
321
+ `Database: ${config.database}`,
322
+ `Migration tool: ${toolName}`,
323
+ `TypeScript types: ${config.generateTypes ? "Yes" : "No"}`
324
+ ]);
325
+ logger.newline();
326
+ logger.info("Files created:");
327
+ logger.list(["omnify.config.ts", `${config.schemasDir}/User.yaml`]);
328
+ logger.newline();
329
+ logger.info("Next steps:");
330
+ logger.newline();
331
+ logger.step("1. Set database URL in omnify.config.ts");
332
+ logger.newline();
333
+ logger.step("2. Define schemas in " + config.schemasDir + "/");
334
+ logger.newline();
335
+ logger.step("3. Generate:");
336
+ logger.info(" npx omnify validate");
337
+ logger.info(" npx omnify generate");
338
+ logger.newline();
339
+ }
340
+ function registerInitCommand(program2) {
341
+ program2.command("init").description("Initialize a new omnify project").option("-f, --force", "Overwrite existing files").option("-y, --yes", "Use default configuration (skip prompts)").action(async (options) => {
342
+ try {
343
+ await runInit(options);
344
+ } catch (error) {
345
+ if (error instanceof Error) {
346
+ if (error.message.includes("User force closed")) {
347
+ logger.newline();
348
+ logger.info("Setup cancelled.");
349
+ process.exit(0);
350
+ }
351
+ logger.error(error.message);
352
+ }
353
+ process.exit(1);
354
+ }
355
+ });
356
+ }
357
+
358
+ // src/commands/validate.ts
359
+ import { resolve as resolve3, dirname as dirname2 } from "path";
360
+ import { loadSchemas, validateSchemas, OmnifyError as OmnifyError2 } from "@famgia/omnify-core";
361
+
362
+ // src/config/loader.ts
363
+ import { existsSync as existsSync2 } from "fs";
364
+ import { resolve as resolve2, dirname } from "path";
365
+ import { createJiti } from "jiti";
366
+ import { configError, configNotFoundError } from "@famgia/omnify-core";
367
+ var CONFIG_FILES = [
368
+ "omnify.config.ts",
369
+ "omnify.config.js",
370
+ "omnify.config.mjs",
371
+ "omnify.config.cjs"
372
+ ];
373
+ function findConfigFile(startDir) {
374
+ const cwd = resolve2(startDir);
375
+ for (const filename of CONFIG_FILES) {
376
+ const configPath2 = resolve2(cwd, filename);
377
+ if (existsSync2(configPath2)) {
378
+ return configPath2;
379
+ }
380
+ }
381
+ return null;
382
+ }
383
+ async function loadConfigFile(configPath2) {
384
+ const jiti = createJiti(configPath2, {
385
+ interopDefault: true,
386
+ moduleCache: false
387
+ });
388
+ try {
389
+ const module = await jiti.import(configPath2);
390
+ const config = module;
391
+ if ("default" in config) {
392
+ return config.default;
393
+ }
394
+ return config;
395
+ } catch (error) {
396
+ const message = error instanceof Error ? error.message : String(error);
397
+ throw configError(
398
+ `Failed to load config file: ${message}. Check your omnify.config.ts for syntax errors.`,
399
+ "E002"
400
+ );
401
+ }
402
+ }
403
+ async function resolvePlugins(plugins, configPath2) {
404
+ if (!plugins || plugins.length === 0) {
405
+ return [];
406
+ }
407
+ const resolved = [];
408
+ const configDir = configPath2 ? dirname(configPath2) : process.cwd();
409
+ for (const plugin of plugins) {
410
+ if (typeof plugin === "string") {
411
+ const jiti = createJiti(configDir, {
412
+ interopDefault: true,
413
+ moduleCache: false
414
+ });
415
+ try {
416
+ const module = await jiti.import(plugin);
417
+ const loadedPlugin = module.default ?? module;
418
+ resolved.push(loadedPlugin);
419
+ } catch (error) {
420
+ const message = error instanceof Error ? error.message : String(error);
421
+ throw configError(
422
+ `Failed to load plugin '${plugin}': ${message}. Ensure the plugin is installed: npm install ${plugin}`,
423
+ "E301"
424
+ );
425
+ }
426
+ } else {
427
+ resolved.push(plugin);
428
+ }
429
+ }
430
+ return resolved;
431
+ }
432
+ async function resolveConfig(userConfig, configPath2) {
433
+ const plugins = await resolvePlugins(userConfig.plugins, configPath2);
434
+ const databaseConfig = {
435
+ driver: userConfig.database.driver,
436
+ enableFieldComments: userConfig.database.enableFieldComments ?? false
437
+ };
438
+ const database = userConfig.database.devUrl !== void 0 ? { ...databaseConfig, devUrl: userConfig.database.devUrl } : databaseConfig;
439
+ const laravelConfig = {
440
+ migrationsPath: userConfig.output?.laravel?.migrationsPath ?? "database/migrations"
441
+ };
442
+ const laravel = buildLaravelConfig(laravelConfig, userConfig.output?.laravel);
443
+ const typescript = {
444
+ path: userConfig.output?.typescript?.path ?? "types",
445
+ singleFile: userConfig.output?.typescript?.singleFile ?? true,
446
+ generateEnums: userConfig.output?.typescript?.generateEnums ?? true,
447
+ generateRelationships: userConfig.output?.typescript?.generateRelationships ?? true
448
+ };
449
+ const result = {
450
+ schemasDir: userConfig.schemasDir ?? "./schemas",
451
+ database,
452
+ output: {
453
+ laravel,
454
+ typescript
455
+ },
456
+ plugins,
457
+ verbose: userConfig.verbose ?? false,
458
+ lockFilePath: userConfig.lockFilePath ?? ".omnify.lock"
459
+ };
460
+ return result;
461
+ }
462
+ function buildLaravelConfig(base, userLaravel) {
463
+ const config = { ...base };
464
+ if (userLaravel?.modelsPath !== void 0) {
465
+ config.modelsPath = userLaravel.modelsPath;
466
+ }
467
+ if (userLaravel?.modelsNamespace !== void 0) {
468
+ config.modelsNamespace = userLaravel.modelsNamespace;
469
+ }
470
+ if (userLaravel?.factoriesPath !== void 0) {
471
+ config.factoriesPath = userLaravel.factoriesPath;
472
+ }
473
+ if (userLaravel?.enumsPath !== void 0) {
474
+ config.enumsPath = userLaravel.enumsPath;
475
+ }
476
+ if (userLaravel?.enumsNamespace !== void 0) {
477
+ config.enumsNamespace = userLaravel.enumsNamespace;
478
+ }
479
+ return config;
480
+ }
481
+ function validateConfig(config, rootDir) {
482
+ const schemaPath = resolve2(rootDir, config.schemasDir);
483
+ if (!existsSync2(schemaPath)) {
484
+ throw configError(
485
+ `Schema directory not found: ${schemaPath}. Create the '${config.schemasDir}' directory or update schemasDir in config.`,
486
+ "E002"
487
+ );
488
+ }
489
+ }
490
+ function requireDevUrl(config) {
491
+ if (!config.database.devUrl) {
492
+ throw configError(
493
+ `database.devUrl is required for diff and generate operations. Add devUrl to your database config, e.g., "mysql://root@localhost:3306/omnify_dev"`,
494
+ "E003"
495
+ );
496
+ }
497
+ }
498
+ async function loadConfig(startDir = process.cwd()) {
499
+ const cwd = resolve2(startDir);
500
+ const configPath2 = findConfigFile(cwd);
501
+ if (configPath2) {
502
+ const userConfig = await loadConfigFile(configPath2);
503
+ const config = await resolveConfig(userConfig, configPath2);
504
+ return {
505
+ config,
506
+ configPath: configPath2
507
+ };
508
+ }
509
+ throw configNotFoundError(resolve2(cwd, "omnify.config.ts"));
510
+ }
511
+
512
+ // src/commands/validate.ts
513
+ async function runValidate(options) {
514
+ logger.setVerbose(options.verbose ?? false);
515
+ logger.header("Validating Schemas");
516
+ logger.debug("Loading configuration...");
517
+ logger.timing("Config load start");
518
+ const { config, configPath: configPath2 } = await loadConfig();
519
+ logger.timing("Config loaded");
520
+ const rootDir = configPath2 ? dirname2(configPath2) : process.cwd();
521
+ validateConfig(config, rootDir);
522
+ const schemaPath = resolve3(rootDir, config.schemasDir);
523
+ logger.step(`Loading schemas from ${schemaPath}`);
524
+ logger.timing("Schema load start");
525
+ const schemas = await loadSchemas(schemaPath);
526
+ const schemaCount = Object.keys(schemas).length;
527
+ logger.timing("Schemas loaded");
528
+ if (schemaCount === 0) {
529
+ logger.warn("No schema files found");
530
+ return;
531
+ }
532
+ logger.debug(`Found ${schemaCount} schema(s)`);
533
+ logger.step("Validating schemas...");
534
+ logger.timing("Validation start");
535
+ const result = validateSchemas(schemas);
536
+ logger.timing("Validation complete");
537
+ if (result.valid) {
538
+ logger.success(`All ${schemaCount} schema(s) are valid`);
539
+ } else {
540
+ logger.error(`Found ${result.errors.length} validation error(s)`);
541
+ logger.newline();
542
+ for (const error of result.errors) {
543
+ const omnifyError = OmnifyError2.fromInfo(error);
544
+ logger.formatError(omnifyError);
545
+ logger.newline();
546
+ }
547
+ process.exit(2);
548
+ }
549
+ }
550
+ function registerValidateCommand(program2) {
551
+ program2.command("validate").description("Validate schema files").option("-v, --verbose", "Show detailed output").action(async (options) => {
552
+ try {
553
+ await runValidate(options);
554
+ } catch (error) {
555
+ if (error instanceof OmnifyError2) {
556
+ logger.formatError(error);
557
+ process.exit(logger.getExitCode(error));
558
+ } else if (error instanceof Error) {
559
+ logger.error(error.message);
560
+ process.exit(1);
561
+ }
562
+ process.exit(1);
563
+ }
564
+ });
565
+ }
566
+
567
+ // src/commands/diff.ts
568
+ import { resolve as resolve4, dirname as dirname3 } from "path";
569
+ import { loadSchemas as loadSchemas2, validateSchemas as validateSchemas2, OmnifyError as OmnifyError3 } from "@famgia/omnify-core";
570
+
571
+ // src/operations/diff.ts
572
+ import {
573
+ generatePreview,
574
+ formatPreview
575
+ } from "@famgia/omnify-atlas";
576
+ async function runDiffOperation(options) {
577
+ const { schemas, devUrl, driver, workDir } = options;
578
+ const preview = await generatePreview(schemas, {
579
+ driver,
580
+ devUrl,
581
+ workDir
582
+ }, {
583
+ warnDestructive: true,
584
+ showSql: true
585
+ });
586
+ const formattedPreview = formatPreview(preview, "text");
587
+ return {
588
+ hasChanges: preview.hasChanges,
589
+ hasDestructiveChanges: preview.hasDestructiveChanges,
590
+ preview,
591
+ formattedPreview,
592
+ sql: preview.sql
593
+ };
594
+ }
595
+
596
+ // src/commands/diff.ts
597
+ import pc2 from "picocolors";
598
+ async function runDiff(options) {
599
+ logger.setVerbose(options.verbose ?? false);
600
+ logger.header("Checking for Schema Changes");
601
+ logger.debug("Loading configuration...");
602
+ const { config, configPath: configPath2 } = await loadConfig();
603
+ const rootDir = configPath2 ? dirname3(configPath2) : process.cwd();
604
+ validateConfig(config, rootDir);
605
+ requireDevUrl(config);
606
+ const schemaPath = resolve4(rootDir, config.schemasDir);
607
+ logger.step(`Loading schemas from ${schemaPath}`);
608
+ const schemas = await loadSchemas2(schemaPath);
609
+ const schemaCount = Object.keys(schemas).length;
610
+ if (schemaCount === 0) {
611
+ logger.warn("No schema files found");
612
+ return;
613
+ }
614
+ logger.debug(`Found ${schemaCount} schema(s)`);
615
+ logger.step("Validating schemas...");
616
+ const validationResult = validateSchemas2(schemas);
617
+ if (!validationResult.valid) {
618
+ logger.error("Schema validation failed. Fix errors before running diff.");
619
+ for (const error of validationResult.errors) {
620
+ const omnifyError = OmnifyError3.fromInfo(error);
621
+ logger.formatError(omnifyError);
622
+ }
623
+ process.exit(2);
624
+ }
625
+ logger.step("Running Atlas diff...");
626
+ const lockPath = resolve4(rootDir, config.lockFilePath);
627
+ const diffResult = await runDiffOperation({
628
+ schemas,
629
+ devUrl: config.database.devUrl,
630
+ lockFilePath: lockPath,
631
+ driver: config.database.driver,
632
+ workDir: rootDir
633
+ });
634
+ if (!diffResult.hasChanges) {
635
+ logger.success("No changes detected");
636
+ return;
637
+ }
638
+ logger.newline();
639
+ console.log(pc2.bold("Changes detected:"));
640
+ console.log();
641
+ console.log(diffResult.formattedPreview);
642
+ if (diffResult.hasDestructiveChanges) {
643
+ logger.newline();
644
+ logger.warn("This preview contains destructive changes. Review carefully.");
645
+ }
646
+ if (options.check) {
647
+ logger.newline();
648
+ logger.info("Changes detected (--check mode)");
649
+ process.exit(1);
650
+ }
651
+ logger.newline();
652
+ logger.info('Run "omnify generate" to create migrations');
653
+ }
654
+ function registerDiffCommand(program2) {
655
+ program2.command("diff").description("Show pending schema changes").option("-v, --verbose", "Show detailed output").option("--check", "Exit with code 1 if changes exist (for CI)").action(async (options) => {
656
+ try {
657
+ await runDiff(options);
658
+ } catch (error) {
659
+ if (error instanceof OmnifyError3) {
660
+ logger.formatError(error);
661
+ process.exit(logger.getExitCode(error));
662
+ } else if (error instanceof Error) {
663
+ logger.error(error.message);
664
+ process.exit(1);
665
+ }
666
+ process.exit(1);
667
+ }
668
+ });
669
+ }
670
+
671
+ // src/commands/generate.ts
672
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
673
+ import { resolve as resolve5, dirname as dirname4 } from "path";
674
+ import {
675
+ loadSchemas as loadSchemas3,
676
+ validateSchemas as validateSchemas3,
677
+ OmnifyError as OmnifyError4,
678
+ PluginManager
679
+ } from "@famgia/omnify-core";
680
+ import {
681
+ writeLockFile,
682
+ readLockFile,
683
+ updateLockFile,
684
+ buildSchemaHashes
685
+ } from "@famgia/omnify-atlas";
686
+ import { generateMigrations, generateTypeScript } from "@famgia/omnify-laravel";
687
+ function hasPluginGenerators(plugins) {
688
+ return plugins.some((p) => p.generators && p.generators.length > 0);
689
+ }
690
+ function writeGeneratorOutputs(outputs, rootDir) {
691
+ const counts = { migrations: 0, types: 0, other: 0 };
692
+ for (const output of outputs) {
693
+ const filePath = resolve5(rootDir, output.path);
694
+ const dir = dirname4(filePath);
695
+ if (!existsSync3(dir)) {
696
+ mkdirSync2(dir, { recursive: true });
697
+ logger.debug(`Created directory: ${dir}`);
698
+ }
699
+ writeFileSync2(filePath, output.content);
700
+ logger.debug(`Created: ${output.path}`);
701
+ if (output.type === "migration") counts.migrations++;
702
+ else if (output.type === "type") counts.types++;
703
+ else counts.other++;
704
+ }
705
+ return counts;
706
+ }
707
+ async function runPluginGeneration(plugins, schemas, rootDir, verbose) {
708
+ const pluginManager = new PluginManager({
709
+ cwd: rootDir,
710
+ verbose,
711
+ logger: {
712
+ debug: (msg) => logger.debug(msg),
713
+ info: (msg) => logger.info(msg),
714
+ warn: (msg) => logger.warn(msg),
715
+ error: (msg) => logger.error(msg)
716
+ }
717
+ });
718
+ for (const plugin of plugins) {
719
+ await pluginManager.register(plugin);
720
+ }
721
+ const result = await pluginManager.runGenerators(schemas);
722
+ if (!result.success) {
723
+ for (const error of result.errors) {
724
+ logger.error(`Generator ${error.generatorName} failed: ${error.message}`);
725
+ }
726
+ throw new Error("Generator execution failed");
727
+ }
728
+ return writeGeneratorOutputs(result.outputs, rootDir);
729
+ }
730
+ function runDirectGeneration(schemas, config, rootDir, options) {
731
+ let migrationsGenerated = 0;
732
+ let typesGenerated = 0;
733
+ if (!options.typesOnly && config.output.laravel) {
734
+ logger.step("Generating Laravel migrations...");
735
+ const migrationsDir = resolve5(rootDir, config.output.laravel.migrationsPath);
736
+ if (!existsSync3(migrationsDir)) {
737
+ mkdirSync2(migrationsDir, { recursive: true });
738
+ logger.debug(`Created directory: ${migrationsDir}`);
739
+ }
740
+ const migrations = generateMigrations(schemas);
741
+ for (const migration of migrations) {
742
+ const filePath = resolve5(migrationsDir, migration.fileName);
743
+ writeFileSync2(filePath, migration.content);
744
+ logger.debug(`Created: ${migration.fileName}`);
745
+ migrationsGenerated++;
746
+ }
747
+ logger.success(`Generated ${migrationsGenerated} migration(s)`);
748
+ }
749
+ if (!options.migrationsOnly && config.output.typescript) {
750
+ logger.step("Generating TypeScript types...");
751
+ const typesDir = resolve5(rootDir, config.output.typescript.path);
752
+ if (!existsSync3(typesDir)) {
753
+ mkdirSync2(typesDir, { recursive: true });
754
+ logger.debug(`Created directory: ${typesDir}`);
755
+ }
756
+ const typeFiles = generateTypeScript(schemas, {
757
+ singleFile: config.output.typescript.singleFile
758
+ });
759
+ for (const file of typeFiles) {
760
+ const filePath = resolve5(typesDir, file.fileName);
761
+ writeFileSync2(filePath, file.content);
762
+ logger.debug(`Created: ${file.fileName}`);
763
+ typesGenerated++;
764
+ }
765
+ logger.success(`Generated ${typesGenerated} TypeScript file(s)`);
766
+ }
767
+ return { migrations: migrationsGenerated, types: typesGenerated };
768
+ }
769
+ async function runGenerate(options) {
770
+ logger.setVerbose(options.verbose ?? false);
771
+ logger.header("Generating Outputs");
772
+ logger.debug("Loading configuration...");
773
+ const { config, configPath: configPath2 } = await loadConfig();
774
+ const rootDir = configPath2 ? dirname4(configPath2) : process.cwd();
775
+ validateConfig(config, rootDir);
776
+ requireDevUrl(config);
777
+ const schemaPath = resolve5(rootDir, config.schemasDir);
778
+ logger.step(`Loading schemas from ${schemaPath}`);
779
+ const schemas = await loadSchemas3(schemaPath);
780
+ const schemaCount = Object.keys(schemas).length;
781
+ if (schemaCount === 0) {
782
+ logger.warn("No schema files found");
783
+ return;
784
+ }
785
+ logger.debug(`Found ${schemaCount} schema(s)`);
786
+ logger.step("Validating schemas...");
787
+ const validationResult = validateSchemas3(schemas);
788
+ if (!validationResult.valid) {
789
+ logger.error("Schema validation failed. Fix errors before generating.");
790
+ for (const error of validationResult.errors) {
791
+ const omnifyError = OmnifyError4.fromInfo(error);
792
+ logger.formatError(omnifyError);
793
+ }
794
+ process.exit(2);
795
+ }
796
+ logger.step("Checking for changes...");
797
+ const lockPath = resolve5(rootDir, config.lockFilePath);
798
+ const diffResult = await runDiffOperation({
799
+ schemas,
800
+ devUrl: config.database.devUrl,
801
+ lockFilePath: lockPath,
802
+ driver: config.database.driver,
803
+ workDir: rootDir
804
+ });
805
+ if (!diffResult.hasChanges && !options.force) {
806
+ logger.success("No changes to generate");
807
+ return;
808
+ }
809
+ let migrationsGenerated = 0;
810
+ let typesGenerated = 0;
811
+ const usePlugins = hasPluginGenerators(config.plugins);
812
+ if (usePlugins) {
813
+ logger.step("Running plugin generators...");
814
+ const counts = await runPluginGeneration(
815
+ config.plugins,
816
+ schemas,
817
+ rootDir,
818
+ options.verbose ?? false
819
+ );
820
+ migrationsGenerated = counts.migrations;
821
+ typesGenerated = counts.types;
822
+ if (counts.migrations > 0) {
823
+ logger.success(`Generated ${counts.migrations} migration(s)`);
824
+ }
825
+ if (counts.types > 0) {
826
+ logger.success(`Generated ${counts.types} TypeScript file(s)`);
827
+ }
828
+ if (counts.other > 0) {
829
+ logger.success(`Generated ${counts.other} other file(s)`);
830
+ }
831
+ } else {
832
+ const counts = runDirectGeneration(schemas, config, rootDir, options);
833
+ migrationsGenerated = counts.migrations;
834
+ typesGenerated = counts.types;
835
+ }
836
+ logger.step("Updating lock file...");
837
+ const existingLock = await readLockFile(lockPath);
838
+ const schemaHashes = await buildSchemaHashes(schemas);
839
+ const newLockFile = updateLockFile(existingLock, schemaHashes, config.database.driver);
840
+ await writeLockFile(lockPath, newLockFile);
841
+ logger.debug(`Updated: ${config.lockFilePath}`);
842
+ logger.newline();
843
+ logger.success("Generation complete!");
844
+ if (migrationsGenerated > 0 && config.output.laravel) {
845
+ logger.info(` Migrations: ${config.output.laravel.migrationsPath}/`);
846
+ }
847
+ if (typesGenerated > 0 && config.output.typescript) {
848
+ logger.info(` Types: ${config.output.typescript.path}/`);
849
+ }
850
+ }
851
+ function registerGenerateCommand(program2) {
852
+ program2.command("generate").description("Generate Laravel migrations and TypeScript types").option("-v, --verbose", "Show detailed output").option("--migrations-only", "Only generate migrations").option("--types-only", "Only generate TypeScript types").option("-f, --force", "Generate even if no changes detected").action(async (options) => {
853
+ try {
854
+ await runGenerate(options);
855
+ } catch (error) {
856
+ if (error instanceof OmnifyError4) {
857
+ logger.formatError(error);
858
+ process.exit(logger.getExitCode(error));
859
+ } else if (error instanceof Error) {
860
+ logger.error(error.message);
861
+ process.exit(1);
862
+ }
863
+ process.exit(1);
864
+ }
865
+ });
866
+ }
867
+
868
+ // src/cli.ts
869
+ var VERSION = "0.0.5";
870
+ var program = new Command();
871
+ program.name("omnify").description("Schema-first database migrations for Laravel and TypeScript").version(VERSION);
872
+ registerInitCommand(program);
873
+ registerValidateCommand(program);
874
+ registerDiffCommand(program);
875
+ registerGenerateCommand(program);
876
+ process.on("uncaughtException", (error) => {
877
+ if (error instanceof OmnifyError5) {
878
+ logger.formatError(error);
879
+ process.exit(logger.getExitCode(error));
880
+ } else {
881
+ logger.error(error.message);
882
+ process.exit(1);
883
+ }
884
+ });
885
+ process.on("unhandledRejection", (reason) => {
886
+ if (reason instanceof OmnifyError5) {
887
+ logger.formatError(reason);
888
+ process.exit(logger.getExitCode(reason));
889
+ } else if (reason instanceof Error) {
890
+ logger.error(reason.message);
891
+ } else {
892
+ logger.error(String(reason));
893
+ }
894
+ process.exit(1);
895
+ });
896
+ var args = process.argv.slice(2);
897
+ var firstArg = args[0];
898
+ var hasCommand = firstArg !== void 0 && !firstArg.startsWith("-");
899
+ var configPath = resolve6(process.cwd(), "omnify.config.ts");
900
+ var hasConfig = existsSync4(configPath);
901
+ if (!hasCommand && !hasConfig) {
902
+ runInit({}).catch((error) => {
903
+ if (error instanceof Error) {
904
+ if (error.message.includes("User force closed")) {
905
+ logger.newline();
906
+ logger.info("Setup cancelled.");
907
+ process.exit(0);
908
+ }
909
+ logger.error(error.message);
910
+ }
911
+ process.exit(1);
912
+ });
913
+ } else {
914
+ program.parse();
915
+ }
916
+ //# sourceMappingURL=cli.js.map