@famgia/omnify-cli 0.0.3 → 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,13 +1,15 @@
1
1
  #!/usr/bin/env node
2
- #!/usr/bin/env node
3
2
 
4
3
  // src/index.ts
4
+ import { existsSync as existsSync4 } from "fs";
5
+ import { resolve as resolve6 } from "path";
5
6
  import { Command } from "commander";
6
7
  import { OmnifyError as OmnifyError5 } from "@famgia/omnify-core";
7
8
 
8
9
  // src/commands/init.ts
9
10
  import { existsSync, mkdirSync, writeFileSync } from "fs";
10
11
  import { resolve } from "path";
12
+ import { select, confirm, input } from "@inquirer/prompts";
11
13
 
12
14
  // src/output/logger.ts
13
15
  import pc from "picocolors";
@@ -134,7 +136,7 @@ var logger = new Logger();
134
136
 
135
137
  // src/commands/init.ts
136
138
  var EXAMPLE_SCHEMA = `# Example User schema
137
- # See https://omnify.dev/docs/schemas for documentation
139
+ # See https://github.com/famgia/omnify for documentation
138
140
 
139
141
  name: User
140
142
  displayName: User Account
@@ -159,78 +161,193 @@ options:
159
161
  timestamps: true
160
162
  softDelete: true
161
163
  `;
162
- 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")}
163
199
 
164
200
  export default defineConfig({
165
201
  // Schema files location
166
- schemasDir: './schemas',
202
+ schemasDir: '${config.schemasDir}',
203
+
204
+ // Lock file for tracking schema changes
205
+ lockFilePath: './omnify.lock',
167
206
 
168
207
  // Database configuration
169
208
  database: {
170
- driver: 'mysql',
171
- // Development database URL for Atlas diff operations
172
- // devUrl: 'mysql://root@localhost:3306/omnify_dev',
173
- },
174
-
175
- // Output configuration
176
- output: {
177
- laravel: {
178
- migrationsPath: 'database/migrations',
179
- },
180
- typescript: {
181
- path: 'types',
182
- singleFile: true,
183
- },
209
+ driver: '${config.database}',
210
+ // REQUIRED: Set your development database URL
211
+ // devUrl: '${dbUrlExamples[config.database]}',
184
212
  },
185
213
 
186
- // Plugins for custom types
214
+ // Generator plugins
187
215
  plugins: [
188
- // '@famgia/omnify-japan',
216
+ ${plugins.join("\n\n")}
189
217
  ],
190
218
  });
191
219
  `;
220
+ }
192
221
  async function runInit(options) {
193
222
  const cwd = process.cwd();
194
- logger.header("Initializing Omnify Project");
195
- const configPath = resolve(cwd, "omnify.config.ts");
196
- 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) {
197
227
  logger.warn("omnify.config.ts already exists. Use --force to overwrite.");
198
228
  return;
199
229
  }
200
- 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);
201
303
  if (!existsSync(schemasDir)) {
202
304
  mkdirSync(schemasDir, { recursive: true });
203
- logger.step("Created schemas/ directory");
204
- } else {
205
- logger.debug("schemas/ directory already exists");
305
+ logger.debug(`Created ${config.schemasDir}/ directory`);
206
306
  }
207
307
  const examplePath = resolve(schemasDir, "User.yaml");
208
308
  if (!existsSync(examplePath) || options.force) {
209
309
  writeFileSync(examplePath, EXAMPLE_SCHEMA);
210
- logger.step("Created example schema: schemas/User.yaml");
211
- } else {
212
- logger.debug("Example schema already exists");
310
+ logger.debug("Created example schema: User.yaml");
213
311
  }
214
- writeFileSync(configPath, EXAMPLE_CONFIG);
215
- logger.step("Created omnify.config.ts");
312
+ const configContent = generateConfig(config);
313
+ writeFileSync(configPath2, configContent);
314
+ logger.debug("Created omnify.config.ts");
216
315
  logger.newline();
217
- logger.success("Project initialized successfully!");
316
+ logger.success("Project initialized!");
218
317
  logger.newline();
219
- logger.info("Next steps:");
318
+ const toolName = config.migrationTool === "laravel" ? "Laravel" : config.migrationTool === "prisma" ? "Prisma" : config.migrationTool === "drizzle" ? "Drizzle" : "None";
319
+ logger.info("Configuration:");
220
320
  logger.list([
221
- "Edit omnify.config.ts to configure your database",
222
- "Add your schema files to the schemas/ directory",
223
- 'Run "omnify validate" to check your schemas',
224
- 'Run "omnify diff" to preview changes',
225
- 'Run "omnify generate" to create migrations and types'
321
+ `Database: ${config.database}`,
322
+ `Migration tool: ${toolName}`,
323
+ `TypeScript types: ${config.generateTypes ? "Yes" : "No"}`
226
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();
227
339
  }
228
340
  function registerInitCommand(program2) {
229
- 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) => {
230
342
  try {
231
343
  await runInit(options);
232
344
  } catch (error) {
233
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
+ }
234
351
  logger.error(error.message);
235
352
  }
236
353
  process.exit(1);
@@ -256,20 +373,20 @@ var CONFIG_FILES = [
256
373
  function findConfigFile(startDir) {
257
374
  const cwd = resolve2(startDir);
258
375
  for (const filename of CONFIG_FILES) {
259
- const configPath = resolve2(cwd, filename);
260
- if (existsSync2(configPath)) {
261
- return configPath;
376
+ const configPath2 = resolve2(cwd, filename);
377
+ if (existsSync2(configPath2)) {
378
+ return configPath2;
262
379
  }
263
380
  }
264
381
  return null;
265
382
  }
266
- async function loadConfigFile(configPath) {
267
- const jiti = createJiti(configPath, {
383
+ async function loadConfigFile(configPath2) {
384
+ const jiti = createJiti(configPath2, {
268
385
  interopDefault: true,
269
386
  moduleCache: false
270
387
  });
271
388
  try {
272
- const module = await jiti.import(configPath);
389
+ const module = await jiti.import(configPath2);
273
390
  const config = module;
274
391
  if ("default" in config) {
275
392
  return config.default;
@@ -283,12 +400,12 @@ async function loadConfigFile(configPath) {
283
400
  );
284
401
  }
285
402
  }
286
- async function resolvePlugins(plugins, configPath) {
403
+ async function resolvePlugins(plugins, configPath2) {
287
404
  if (!plugins || plugins.length === 0) {
288
405
  return [];
289
406
  }
290
407
  const resolved = [];
291
- const configDir = configPath ? dirname(configPath) : process.cwd();
408
+ const configDir = configPath2 ? dirname(configPath2) : process.cwd();
292
409
  for (const plugin of plugins) {
293
410
  if (typeof plugin === "string") {
294
411
  const jiti = createJiti(configDir, {
@@ -312,8 +429,8 @@ async function resolvePlugins(plugins, configPath) {
312
429
  }
313
430
  return resolved;
314
431
  }
315
- async function resolveConfig(userConfig, configPath) {
316
- const plugins = await resolvePlugins(userConfig.plugins, configPath);
432
+ async function resolveConfig(userConfig, configPath2) {
433
+ const plugins = await resolvePlugins(userConfig.plugins, configPath2);
317
434
  const databaseConfig = {
318
435
  driver: userConfig.database.driver,
319
436
  enableFieldComments: userConfig.database.enableFieldComments ?? false
@@ -380,13 +497,13 @@ function requireDevUrl(config) {
380
497
  }
381
498
  async function loadConfig(startDir = process.cwd()) {
382
499
  const cwd = resolve2(startDir);
383
- const configPath = findConfigFile(cwd);
384
- if (configPath) {
385
- const userConfig = await loadConfigFile(configPath);
386
- 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);
387
504
  return {
388
505
  config,
389
- configPath
506
+ configPath: configPath2
390
507
  };
391
508
  }
392
509
  throw configNotFoundError(resolve2(cwd, "omnify.config.ts"));
@@ -401,9 +518,9 @@ async function runValidate(options) {
401
518
  logger.header("Validating Schemas");
402
519
  logger.debug("Loading configuration...");
403
520
  logger.timing("Config load start");
404
- const { config, configPath } = await loadConfig();
521
+ const { config, configPath: configPath2 } = await loadConfig();
405
522
  logger.timing("Config loaded");
406
- const rootDir = configPath ? dirname2(configPath) : process.cwd();
523
+ const rootDir = configPath2 ? dirname2(configPath2) : process.cwd();
407
524
  validateConfig(config, rootDir);
408
525
  const schemaPath = resolve3(rootDir, config.schemasDir);
409
526
  logger.step(`Loading schemas from ${schemaPath}`);
@@ -485,8 +602,8 @@ async function runDiff(options) {
485
602
  logger.setVerbose(options.verbose ?? false);
486
603
  logger.header("Checking for Schema Changes");
487
604
  logger.debug("Loading configuration...");
488
- const { config, configPath } = await loadConfig();
489
- const rootDir = configPath ? dirname3(configPath) : process.cwd();
605
+ const { config, configPath: configPath2 } = await loadConfig();
606
+ const rootDir = configPath2 ? dirname3(configPath2) : process.cwd();
490
607
  validateConfig(config, rootDir);
491
608
  requireDevUrl(config);
492
609
  const schemaPath = resolve4(rootDir, config.schemasDir);
@@ -557,15 +674,102 @@ function registerDiffCommand(program2) {
557
674
  // src/commands/generate.ts
558
675
  import { existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
559
676
  import { resolve as resolve5, dirname as dirname4 } from "path";
560
- 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";
561
683
  import { updateLockFile } from "@famgia/omnify-atlas";
562
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
+ }
563
767
  async function runGenerate(options) {
564
768
  logger.setVerbose(options.verbose ?? false);
565
769
  logger.header("Generating Outputs");
566
770
  logger.debug("Loading configuration...");
567
- const { config, configPath } = await loadConfig();
568
- const rootDir = configPath ? dirname4(configPath) : process.cwd();
771
+ const { config, configPath: configPath2 } = await loadConfig();
772
+ const rootDir = configPath2 ? dirname4(configPath2) : process.cwd();
569
773
  validateConfig(config, rootDir);
570
774
  requireDevUrl(config);
571
775
  const schemaPath = resolve5(rootDir, config.schemasDir);
@@ -602,39 +806,30 @@ async function runGenerate(options) {
602
806
  }
603
807
  let migrationsGenerated = 0;
604
808
  let typesGenerated = 0;
605
- if (!options.typesOnly) {
606
- logger.step("Generating Laravel migrations...");
607
- const migrationsDir = resolve5(rootDir, config.output.laravel.migrationsPath);
608
- if (!existsSync3(migrationsDir)) {
609
- mkdirSync2(migrationsDir, { recursive: true });
610
- logger.debug(`Created directory: ${migrationsDir}`);
611
- }
612
- const migrations = generateMigrations(schemas);
613
- for (const migration of migrations) {
614
- const filePath = resolve5(migrationsDir, migration.fileName);
615
- writeFileSync2(filePath, migration.content);
616
- logger.debug(`Created: ${migration.fileName}`);
617
- 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)`);
618
822
  }
619
- logger.success(`Generated ${migrationsGenerated} migration(s)`);
620
- }
621
- if (!options.migrationsOnly) {
622
- logger.step("Generating TypeScript types...");
623
- const typesDir = resolve5(rootDir, config.output.typescript.path);
624
- if (!existsSync3(typesDir)) {
625
- mkdirSync2(typesDir, { recursive: true });
626
- logger.debug(`Created directory: ${typesDir}`);
823
+ if (counts.types > 0) {
824
+ logger.success(`Generated ${counts.types} TypeScript file(s)`);
627
825
  }
628
- const typeFiles = generateTypeScript(schemas, {
629
- singleFile: config.output.typescript.singleFile
630
- });
631
- for (const file of typeFiles) {
632
- const filePath = resolve5(typesDir, file.fileName);
633
- writeFileSync2(filePath, file.content);
634
- logger.debug(`Created: ${file.fileName}`);
635
- typesGenerated++;
826
+ if (counts.other > 0) {
827
+ logger.success(`Generated ${counts.other} other file(s)`);
636
828
  }
637
- 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;
638
833
  }
639
834
  logger.step("Updating lock file...");
640
835
  await updateLockFile(lockPath, schemas);
@@ -693,7 +888,26 @@ process.on("unhandledRejection", (reason) => {
693
888
  }
694
889
  process.exit(1);
695
890
  });
696
- 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
+ }
697
911
  export {
698
912
  defineConfig,
699
913
  loadConfig