@famgia/omnify-cli 0.0.4 → 0.0.5

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/index.js CHANGED
@@ -1,12 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
+ import { existsSync as existsSync4 } from "fs";
5
+ import { resolve as resolve6 } from "path";
4
6
  import { Command } from "commander";
5
7
  import { OmnifyError as OmnifyError5 } from "@famgia/omnify-core";
6
8
 
7
9
  // src/commands/init.ts
8
10
  import { existsSync, mkdirSync, writeFileSync } from "fs";
9
11
  import { resolve } from "path";
12
+ import { select, confirm, input } from "@inquirer/prompts";
10
13
 
11
14
  // src/output/logger.ts
12
15
  import pc from "picocolors";
@@ -133,7 +136,7 @@ var logger = new Logger();
133
136
 
134
137
  // src/commands/init.ts
135
138
  var EXAMPLE_SCHEMA = `# Example User schema
136
- # See https://omnify.dev/docs/schemas for documentation
139
+ # See https://github.com/famgia/omnify for documentation
137
140
 
138
141
  name: User
139
142
  displayName: User Account
@@ -158,78 +161,193 @@ options:
158
161
  timestamps: true
159
162
  softDelete: true
160
163
  `;
161
- var EXAMPLE_CONFIG = `import { defineConfig } from '@famgia/omnify-cli';
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")}
162
199
 
163
200
  export default defineConfig({
164
201
  // Schema files location
165
- schemasDir: './schemas',
202
+ schemasDir: '${config.schemasDir}',
203
+
204
+ // Lock file for tracking schema changes
205
+ lockFilePath: './omnify.lock',
166
206
 
167
207
  // Database configuration
168
208
  database: {
169
- driver: 'mysql',
170
- // Development database URL for Atlas diff operations
171
- // devUrl: 'mysql://root@localhost:3306/omnify_dev',
172
- },
173
-
174
- // Output configuration
175
- output: {
176
- laravel: {
177
- migrationsPath: 'database/migrations',
178
- },
179
- typescript: {
180
- path: 'types',
181
- singleFile: true,
182
- },
209
+ driver: '${config.database}',
210
+ // REQUIRED: Set your development database URL
211
+ // devUrl: '${dbUrlExamples[config.database]}',
183
212
  },
184
213
 
185
- // Plugins for custom types
214
+ // Generator plugins
186
215
  plugins: [
187
- // '@famgia/omnify-japan',
216
+ ${plugins.join("\n\n")}
188
217
  ],
189
218
  });
190
219
  `;
220
+ }
191
221
  async function runInit(options) {
192
222
  const cwd = process.cwd();
193
- logger.header("Initializing Omnify Project");
194
- const configPath = resolve(cwd, "omnify.config.ts");
195
- if (existsSync(configPath) && !options.force) {
223
+ logger.header("Omnify Project Setup");
224
+ logger.newline();
225
+ const configPath2 = resolve(cwd, "omnify.config.ts");
226
+ if (existsSync(configPath2) && !options.force) {
196
227
  logger.warn("omnify.config.ts already exists. Use --force to overwrite.");
197
228
  return;
198
229
  }
199
- const schemasDir = resolve(cwd, "schemas");
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);
200
303
  if (!existsSync(schemasDir)) {
201
304
  mkdirSync(schemasDir, { recursive: true });
202
- logger.step("Created schemas/ directory");
203
- } else {
204
- logger.debug("schemas/ directory already exists");
305
+ logger.debug(`Created ${config.schemasDir}/ directory`);
205
306
  }
206
307
  const examplePath = resolve(schemasDir, "User.yaml");
207
308
  if (!existsSync(examplePath) || options.force) {
208
309
  writeFileSync(examplePath, EXAMPLE_SCHEMA);
209
- logger.step("Created example schema: schemas/User.yaml");
210
- } else {
211
- logger.debug("Example schema already exists");
310
+ logger.debug("Created example schema: User.yaml");
212
311
  }
213
- writeFileSync(configPath, EXAMPLE_CONFIG);
214
- logger.step("Created omnify.config.ts");
312
+ const configContent = generateConfig(config);
313
+ writeFileSync(configPath2, configContent);
314
+ logger.debug("Created omnify.config.ts");
215
315
  logger.newline();
216
- logger.success("Project initialized successfully!");
316
+ logger.success("Project initialized!");
217
317
  logger.newline();
218
- logger.info("Next steps:");
318
+ const toolName = config.migrationTool === "laravel" ? "Laravel" : config.migrationTool === "prisma" ? "Prisma" : config.migrationTool === "drizzle" ? "Drizzle" : "None";
319
+ logger.info("Configuration:");
219
320
  logger.list([
220
- "Edit omnify.config.ts to configure your database",
221
- "Add your schema files to the schemas/ directory",
222
- 'Run "omnify validate" to check your schemas',
223
- 'Run "omnify diff" to preview changes',
224
- 'Run "omnify generate" to create migrations and types'
321
+ `Database: ${config.database}`,
322
+ `Migration tool: ${toolName}`,
323
+ `TypeScript types: ${config.generateTypes ? "Yes" : "No"}`
225
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();
226
339
  }
227
340
  function registerInitCommand(program2) {
228
- program2.command("init").description("Initialize a new omnify project").option("-f, --force", "Overwrite existing files").action(async (options) => {
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) => {
229
342
  try {
230
343
  await runInit(options);
231
344
  } catch (error) {
232
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
+ }
233
351
  logger.error(error.message);
234
352
  }
235
353
  process.exit(1);
@@ -255,20 +373,20 @@ var CONFIG_FILES = [
255
373
  function findConfigFile(startDir) {
256
374
  const cwd = resolve2(startDir);
257
375
  for (const filename of CONFIG_FILES) {
258
- const configPath = resolve2(cwd, filename);
259
- if (existsSync2(configPath)) {
260
- return configPath;
376
+ const configPath2 = resolve2(cwd, filename);
377
+ if (existsSync2(configPath2)) {
378
+ return configPath2;
261
379
  }
262
380
  }
263
381
  return null;
264
382
  }
265
- async function loadConfigFile(configPath) {
266
- const jiti = createJiti(configPath, {
383
+ async function loadConfigFile(configPath2) {
384
+ const jiti = createJiti(configPath2, {
267
385
  interopDefault: true,
268
386
  moduleCache: false
269
387
  });
270
388
  try {
271
- const module = await jiti.import(configPath);
389
+ const module = await jiti.import(configPath2);
272
390
  const config = module;
273
391
  if ("default" in config) {
274
392
  return config.default;
@@ -282,12 +400,12 @@ async function loadConfigFile(configPath) {
282
400
  );
283
401
  }
284
402
  }
285
- async function resolvePlugins(plugins, configPath) {
403
+ async function resolvePlugins(plugins, configPath2) {
286
404
  if (!plugins || plugins.length === 0) {
287
405
  return [];
288
406
  }
289
407
  const resolved = [];
290
- const configDir = configPath ? dirname(configPath) : process.cwd();
408
+ const configDir = configPath2 ? dirname(configPath2) : process.cwd();
291
409
  for (const plugin of plugins) {
292
410
  if (typeof plugin === "string") {
293
411
  const jiti = createJiti(configDir, {
@@ -311,8 +429,8 @@ async function resolvePlugins(plugins, configPath) {
311
429
  }
312
430
  return resolved;
313
431
  }
314
- async function resolveConfig(userConfig, configPath) {
315
- const plugins = await resolvePlugins(userConfig.plugins, configPath);
432
+ async function resolveConfig(userConfig, configPath2) {
433
+ const plugins = await resolvePlugins(userConfig.plugins, configPath2);
316
434
  const databaseConfig = {
317
435
  driver: userConfig.database.driver,
318
436
  enableFieldComments: userConfig.database.enableFieldComments ?? false
@@ -379,13 +497,13 @@ function requireDevUrl(config) {
379
497
  }
380
498
  async function loadConfig(startDir = process.cwd()) {
381
499
  const cwd = resolve2(startDir);
382
- const configPath = findConfigFile(cwd);
383
- if (configPath) {
384
- const userConfig = await loadConfigFile(configPath);
385
- const config = await resolveConfig(userConfig, configPath);
500
+ const configPath2 = findConfigFile(cwd);
501
+ if (configPath2) {
502
+ const userConfig = await loadConfigFile(configPath2);
503
+ const config = await resolveConfig(userConfig, configPath2);
386
504
  return {
387
505
  config,
388
- configPath
506
+ configPath: configPath2
389
507
  };
390
508
  }
391
509
  throw configNotFoundError(resolve2(cwd, "omnify.config.ts"));
@@ -400,9 +518,9 @@ async function runValidate(options) {
400
518
  logger.header("Validating Schemas");
401
519
  logger.debug("Loading configuration...");
402
520
  logger.timing("Config load start");
403
- const { config, configPath } = await loadConfig();
521
+ const { config, configPath: configPath2 } = await loadConfig();
404
522
  logger.timing("Config loaded");
405
- const rootDir = configPath ? dirname2(configPath) : process.cwd();
523
+ const rootDir = configPath2 ? dirname2(configPath2) : process.cwd();
406
524
  validateConfig(config, rootDir);
407
525
  const schemaPath = resolve3(rootDir, config.schemasDir);
408
526
  logger.step(`Loading schemas from ${schemaPath}`);
@@ -484,8 +602,8 @@ async function runDiff(options) {
484
602
  logger.setVerbose(options.verbose ?? false);
485
603
  logger.header("Checking for Schema Changes");
486
604
  logger.debug("Loading configuration...");
487
- const { config, configPath } = await loadConfig();
488
- const rootDir = configPath ? dirname3(configPath) : process.cwd();
605
+ const { config, configPath: configPath2 } = await loadConfig();
606
+ const rootDir = configPath2 ? dirname3(configPath2) : process.cwd();
489
607
  validateConfig(config, rootDir);
490
608
  requireDevUrl(config);
491
609
  const schemaPath = resolve4(rootDir, config.schemasDir);
@@ -556,15 +674,102 @@ function registerDiffCommand(program2) {
556
674
  // src/commands/generate.ts
557
675
  import { existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
558
676
  import { resolve as resolve5, dirname as dirname4 } from "path";
559
- import { loadSchemas as loadSchemas3, validateSchemas as validateSchemas3, OmnifyError as OmnifyError4 } from "@famgia/omnify-core";
677
+ import {
678
+ loadSchemas as loadSchemas3,
679
+ validateSchemas as validateSchemas3,
680
+ OmnifyError as OmnifyError4,
681
+ PluginManager
682
+ } from "@famgia/omnify-core";
560
683
  import { updateLockFile } from "@famgia/omnify-atlas";
561
684
  import { generateMigrations, generateTypeScript } from "@famgia/omnify-laravel";
685
+ function hasPluginGenerators(plugins) {
686
+ return plugins.some((p) => p.generators && p.generators.length > 0);
687
+ }
688
+ function writeGeneratorOutputs(outputs, rootDir) {
689
+ const counts = { migrations: 0, types: 0, other: 0 };
690
+ for (const output of outputs) {
691
+ const filePath = resolve5(rootDir, output.path);
692
+ const dir = dirname4(filePath);
693
+ if (!existsSync3(dir)) {
694
+ mkdirSync2(dir, { recursive: true });
695
+ logger.debug(`Created directory: ${dir}`);
696
+ }
697
+ writeFileSync2(filePath, output.content);
698
+ logger.debug(`Created: ${output.path}`);
699
+ if (output.type === "migration") counts.migrations++;
700
+ else if (output.type === "type") counts.types++;
701
+ else counts.other++;
702
+ }
703
+ return counts;
704
+ }
705
+ async function runPluginGeneration(plugins, schemas, rootDir, verbose) {
706
+ const pluginManager = new PluginManager({
707
+ cwd: rootDir,
708
+ verbose,
709
+ logger: {
710
+ debug: (msg) => logger.debug(msg),
711
+ info: (msg) => logger.info(msg),
712
+ warn: (msg) => logger.warn(msg),
713
+ error: (msg) => logger.error(msg)
714
+ }
715
+ });
716
+ for (const plugin of plugins) {
717
+ await pluginManager.register(plugin);
718
+ }
719
+ const result = await pluginManager.runGenerators(schemas);
720
+ if (!result.success) {
721
+ for (const error of result.errors) {
722
+ logger.error(`Generator ${error.generatorName} failed: ${error.message}`);
723
+ }
724
+ throw new Error("Generator execution failed");
725
+ }
726
+ return writeGeneratorOutputs(result.outputs, rootDir);
727
+ }
728
+ function runDirectGeneration(schemas, config, rootDir, options) {
729
+ let migrationsGenerated = 0;
730
+ let typesGenerated = 0;
731
+ if (!options.typesOnly) {
732
+ logger.step("Generating Laravel migrations...");
733
+ const migrationsDir = resolve5(rootDir, config.output.laravel.migrationsPath);
734
+ if (!existsSync3(migrationsDir)) {
735
+ mkdirSync2(migrationsDir, { recursive: true });
736
+ logger.debug(`Created directory: ${migrationsDir}`);
737
+ }
738
+ const migrations = generateMigrations(schemas);
739
+ for (const migration of migrations) {
740
+ const filePath = resolve5(migrationsDir, migration.fileName);
741
+ writeFileSync2(filePath, migration.content);
742
+ logger.debug(`Created: ${migration.fileName}`);
743
+ migrationsGenerated++;
744
+ }
745
+ logger.success(`Generated ${migrationsGenerated} migration(s)`);
746
+ }
747
+ if (!options.migrationsOnly) {
748
+ logger.step("Generating TypeScript types...");
749
+ const typesDir = resolve5(rootDir, config.output.typescript.path);
750
+ if (!existsSync3(typesDir)) {
751
+ mkdirSync2(typesDir, { recursive: true });
752
+ logger.debug(`Created directory: ${typesDir}`);
753
+ }
754
+ const typeFiles = generateTypeScript(schemas, {
755
+ singleFile: config.output.typescript.singleFile
756
+ });
757
+ for (const file of typeFiles) {
758
+ const filePath = resolve5(typesDir, file.fileName);
759
+ writeFileSync2(filePath, file.content);
760
+ logger.debug(`Created: ${file.fileName}`);
761
+ typesGenerated++;
762
+ }
763
+ logger.success(`Generated ${typesGenerated} TypeScript file(s)`);
764
+ }
765
+ return { migrations: migrationsGenerated, types: typesGenerated };
766
+ }
562
767
  async function runGenerate(options) {
563
768
  logger.setVerbose(options.verbose ?? false);
564
769
  logger.header("Generating Outputs");
565
770
  logger.debug("Loading configuration...");
566
- const { config, configPath } = await loadConfig();
567
- const rootDir = configPath ? dirname4(configPath) : process.cwd();
771
+ const { config, configPath: configPath2 } = await loadConfig();
772
+ const rootDir = configPath2 ? dirname4(configPath2) : process.cwd();
568
773
  validateConfig(config, rootDir);
569
774
  requireDevUrl(config);
570
775
  const schemaPath = resolve5(rootDir, config.schemasDir);
@@ -601,39 +806,30 @@ async function runGenerate(options) {
601
806
  }
602
807
  let migrationsGenerated = 0;
603
808
  let typesGenerated = 0;
604
- if (!options.typesOnly) {
605
- logger.step("Generating Laravel migrations...");
606
- const migrationsDir = resolve5(rootDir, config.output.laravel.migrationsPath);
607
- if (!existsSync3(migrationsDir)) {
608
- mkdirSync2(migrationsDir, { recursive: true });
609
- logger.debug(`Created directory: ${migrationsDir}`);
610
- }
611
- const migrations = generateMigrations(schemas);
612
- for (const migration of migrations) {
613
- const filePath = resolve5(migrationsDir, migration.fileName);
614
- writeFileSync2(filePath, migration.content);
615
- logger.debug(`Created: ${migration.fileName}`);
616
- migrationsGenerated++;
809
+ const usePlugins = hasPluginGenerators(config.plugins);
810
+ if (usePlugins) {
811
+ logger.step("Running plugin generators...");
812
+ const counts = await runPluginGeneration(
813
+ config.plugins,
814
+ schemas,
815
+ rootDir,
816
+ options.verbose ?? false
817
+ );
818
+ migrationsGenerated = counts.migrations;
819
+ typesGenerated = counts.types;
820
+ if (counts.migrations > 0) {
821
+ logger.success(`Generated ${counts.migrations} migration(s)`);
617
822
  }
618
- logger.success(`Generated ${migrationsGenerated} migration(s)`);
619
- }
620
- if (!options.migrationsOnly) {
621
- logger.step("Generating TypeScript types...");
622
- const typesDir = resolve5(rootDir, config.output.typescript.path);
623
- if (!existsSync3(typesDir)) {
624
- mkdirSync2(typesDir, { recursive: true });
625
- logger.debug(`Created directory: ${typesDir}`);
823
+ if (counts.types > 0) {
824
+ logger.success(`Generated ${counts.types} TypeScript file(s)`);
626
825
  }
627
- const typeFiles = generateTypeScript(schemas, {
628
- singleFile: config.output.typescript.singleFile
629
- });
630
- for (const file of typeFiles) {
631
- const filePath = resolve5(typesDir, file.fileName);
632
- writeFileSync2(filePath, file.content);
633
- logger.debug(`Created: ${file.fileName}`);
634
- typesGenerated++;
826
+ if (counts.other > 0) {
827
+ logger.success(`Generated ${counts.other} other file(s)`);
635
828
  }
636
- logger.success(`Generated ${typesGenerated} TypeScript file(s)`);
829
+ } else {
830
+ const counts = runDirectGeneration(schemas, config, rootDir, options);
831
+ migrationsGenerated = counts.migrations;
832
+ typesGenerated = counts.types;
637
833
  }
638
834
  logger.step("Updating lock file...");
639
835
  await updateLockFile(lockPath, schemas);
@@ -692,7 +888,26 @@ process.on("unhandledRejection", (reason) => {
692
888
  }
693
889
  process.exit(1);
694
890
  });
695
- program.parse();
891
+ var args = process.argv.slice(2);
892
+ var firstArg = args[0];
893
+ var hasCommand = firstArg !== void 0 && !firstArg.startsWith("-");
894
+ var configPath = resolve6(process.cwd(), "omnify.config.ts");
895
+ var hasConfig = existsSync4(configPath);
896
+ if (!hasCommand && !hasConfig) {
897
+ runInit({}).catch((error) => {
898
+ if (error instanceof Error) {
899
+ if (error.message.includes("User force closed")) {
900
+ logger.newline();
901
+ logger.info("Setup cancelled.");
902
+ process.exit(0);
903
+ }
904
+ logger.error(error.message);
905
+ }
906
+ process.exit(1);
907
+ });
908
+ } else {
909
+ program.parse();
910
+ }
696
911
  export {
697
912
  defineConfig,
698
913
  loadConfig