@famgia/omnify-cli 0.0.4 → 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/README.md +261 -23
- package/dist/cli.js +916 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +447 -278
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +144 -1
- package/dist/index.d.ts +144 -1
- package/dist/index.js +447 -277
- package/dist/index.js.map +1 -1
- package/package.json +10 -6
package/dist/index.js
CHANGED
|
@@ -1,12 +1,160 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
1
|
+
// src/config/loader.ts
|
|
2
|
+
import { existsSync } from "fs";
|
|
3
|
+
import { resolve, dirname } from "path";
|
|
4
|
+
import { createJiti } from "jiti";
|
|
5
|
+
import { configError, configNotFoundError } from "@famgia/omnify-core";
|
|
6
|
+
var CONFIG_FILES = [
|
|
7
|
+
"omnify.config.ts",
|
|
8
|
+
"omnify.config.js",
|
|
9
|
+
"omnify.config.mjs",
|
|
10
|
+
"omnify.config.cjs"
|
|
11
|
+
];
|
|
12
|
+
function findConfigFile(startDir) {
|
|
13
|
+
const cwd = resolve(startDir);
|
|
14
|
+
for (const filename of CONFIG_FILES) {
|
|
15
|
+
const configPath = resolve(cwd, filename);
|
|
16
|
+
if (existsSync(configPath)) {
|
|
17
|
+
return configPath;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
async function loadConfigFile(configPath) {
|
|
23
|
+
const jiti = createJiti(configPath, {
|
|
24
|
+
interopDefault: true,
|
|
25
|
+
moduleCache: false
|
|
26
|
+
});
|
|
27
|
+
try {
|
|
28
|
+
const module = await jiti.import(configPath);
|
|
29
|
+
const config = module;
|
|
30
|
+
if ("default" in config) {
|
|
31
|
+
return config.default;
|
|
32
|
+
}
|
|
33
|
+
return config;
|
|
34
|
+
} catch (error) {
|
|
35
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
36
|
+
throw configError(
|
|
37
|
+
`Failed to load config file: ${message}. Check your omnify.config.ts for syntax errors.`,
|
|
38
|
+
"E002"
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async function resolvePlugins(plugins, configPath) {
|
|
43
|
+
if (!plugins || plugins.length === 0) {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
const resolved = [];
|
|
47
|
+
const configDir = configPath ? dirname(configPath) : process.cwd();
|
|
48
|
+
for (const plugin of plugins) {
|
|
49
|
+
if (typeof plugin === "string") {
|
|
50
|
+
const jiti = createJiti(configDir, {
|
|
51
|
+
interopDefault: true,
|
|
52
|
+
moduleCache: false
|
|
53
|
+
});
|
|
54
|
+
try {
|
|
55
|
+
const module = await jiti.import(plugin);
|
|
56
|
+
const loadedPlugin = module.default ?? module;
|
|
57
|
+
resolved.push(loadedPlugin);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
60
|
+
throw configError(
|
|
61
|
+
`Failed to load plugin '${plugin}': ${message}. Ensure the plugin is installed: npm install ${plugin}`,
|
|
62
|
+
"E301"
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
resolved.push(plugin);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return resolved;
|
|
70
|
+
}
|
|
71
|
+
async function resolveConfig(userConfig, configPath) {
|
|
72
|
+
const plugins = await resolvePlugins(userConfig.plugins, configPath);
|
|
73
|
+
const databaseConfig = {
|
|
74
|
+
driver: userConfig.database.driver,
|
|
75
|
+
enableFieldComments: userConfig.database.enableFieldComments ?? false
|
|
76
|
+
};
|
|
77
|
+
const database = userConfig.database.devUrl !== void 0 ? { ...databaseConfig, devUrl: userConfig.database.devUrl } : databaseConfig;
|
|
78
|
+
const laravelConfig = {
|
|
79
|
+
migrationsPath: userConfig.output?.laravel?.migrationsPath ?? "database/migrations"
|
|
80
|
+
};
|
|
81
|
+
const laravel = buildLaravelConfig(laravelConfig, userConfig.output?.laravel);
|
|
82
|
+
const typescript = {
|
|
83
|
+
path: userConfig.output?.typescript?.path ?? "types",
|
|
84
|
+
singleFile: userConfig.output?.typescript?.singleFile ?? true,
|
|
85
|
+
generateEnums: userConfig.output?.typescript?.generateEnums ?? true,
|
|
86
|
+
generateRelationships: userConfig.output?.typescript?.generateRelationships ?? true
|
|
87
|
+
};
|
|
88
|
+
const result = {
|
|
89
|
+
schemasDir: userConfig.schemasDir ?? "./schemas",
|
|
90
|
+
database,
|
|
91
|
+
output: {
|
|
92
|
+
laravel,
|
|
93
|
+
typescript
|
|
94
|
+
},
|
|
95
|
+
plugins,
|
|
96
|
+
verbose: userConfig.verbose ?? false,
|
|
97
|
+
lockFilePath: userConfig.lockFilePath ?? ".omnify.lock"
|
|
98
|
+
};
|
|
99
|
+
return result;
|
|
100
|
+
}
|
|
101
|
+
function buildLaravelConfig(base, userLaravel) {
|
|
102
|
+
const config = { ...base };
|
|
103
|
+
if (userLaravel?.modelsPath !== void 0) {
|
|
104
|
+
config.modelsPath = userLaravel.modelsPath;
|
|
105
|
+
}
|
|
106
|
+
if (userLaravel?.modelsNamespace !== void 0) {
|
|
107
|
+
config.modelsNamespace = userLaravel.modelsNamespace;
|
|
108
|
+
}
|
|
109
|
+
if (userLaravel?.factoriesPath !== void 0) {
|
|
110
|
+
config.factoriesPath = userLaravel.factoriesPath;
|
|
111
|
+
}
|
|
112
|
+
if (userLaravel?.enumsPath !== void 0) {
|
|
113
|
+
config.enumsPath = userLaravel.enumsPath;
|
|
114
|
+
}
|
|
115
|
+
if (userLaravel?.enumsNamespace !== void 0) {
|
|
116
|
+
config.enumsNamespace = userLaravel.enumsNamespace;
|
|
117
|
+
}
|
|
118
|
+
return config;
|
|
119
|
+
}
|
|
120
|
+
function validateConfig(config, rootDir) {
|
|
121
|
+
const schemaPath = resolve(rootDir, config.schemasDir);
|
|
122
|
+
if (!existsSync(schemaPath)) {
|
|
123
|
+
throw configError(
|
|
124
|
+
`Schema directory not found: ${schemaPath}. Create the '${config.schemasDir}' directory or update schemasDir in config.`,
|
|
125
|
+
"E002"
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function requireDevUrl(config) {
|
|
130
|
+
if (!config.database.devUrl) {
|
|
131
|
+
throw configError(
|
|
132
|
+
`database.devUrl is required for diff and generate operations. Add devUrl to your database config, e.g., "mysql://root@localhost:3306/omnify_dev"`,
|
|
133
|
+
"E003"
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
async function loadConfig(startDir = process.cwd()) {
|
|
138
|
+
const cwd = resolve(startDir);
|
|
139
|
+
const configPath = findConfigFile(cwd);
|
|
140
|
+
if (configPath) {
|
|
141
|
+
const userConfig = await loadConfigFile(configPath);
|
|
142
|
+
const config = await resolveConfig(userConfig, configPath);
|
|
143
|
+
return {
|
|
144
|
+
config,
|
|
145
|
+
configPath
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
throw configNotFoundError(resolve(cwd, "omnify.config.ts"));
|
|
149
|
+
}
|
|
150
|
+
function defineConfig(config) {
|
|
151
|
+
return config;
|
|
152
|
+
}
|
|
6
153
|
|
|
7
154
|
// src/commands/init.ts
|
|
8
|
-
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
9
|
-
import { resolve } from "path";
|
|
155
|
+
import { existsSync as existsSync2, mkdirSync, writeFileSync } from "fs";
|
|
156
|
+
import { resolve as resolve2 } from "path";
|
|
157
|
+
import { select, confirm, input } from "@inquirer/prompts";
|
|
10
158
|
|
|
11
159
|
// src/output/logger.ts
|
|
12
160
|
import pc from "picocolors";
|
|
@@ -133,7 +281,7 @@ var logger = new Logger();
|
|
|
133
281
|
|
|
134
282
|
// src/commands/init.ts
|
|
135
283
|
var EXAMPLE_SCHEMA = `# Example User schema
|
|
136
|
-
# See https://
|
|
284
|
+
# See https://github.com/famgia/omnify for documentation
|
|
137
285
|
|
|
138
286
|
name: User
|
|
139
287
|
displayName: User Account
|
|
@@ -158,78 +306,193 @@ options:
|
|
|
158
306
|
timestamps: true
|
|
159
307
|
softDelete: true
|
|
160
308
|
`;
|
|
161
|
-
|
|
309
|
+
function generateConfig(config) {
|
|
310
|
+
const imports = [`import { defineConfig } from '@famgia/omnify';`];
|
|
311
|
+
const plugins = [];
|
|
312
|
+
if (config.migrationTool === "laravel") {
|
|
313
|
+
imports.push(`import laravel from '@famgia/omnify-laravel/plugin';`);
|
|
314
|
+
plugins.push(` laravel({
|
|
315
|
+
migrationsPath: '${config.migrationsPath}',
|
|
316
|
+
typesPath: '${config.typesPath}',
|
|
317
|
+
singleFile: true,
|
|
318
|
+
generateMigrations: true,
|
|
319
|
+
generateTypes: ${config.generateTypes},
|
|
320
|
+
}),`);
|
|
321
|
+
}
|
|
322
|
+
if (config.migrationTool === "prisma") {
|
|
323
|
+
plugins.push(` // Prisma plugin coming soon!
|
|
324
|
+
// prisma({ schemaPath: 'prisma/schema.prisma' }),`);
|
|
325
|
+
}
|
|
326
|
+
if (config.migrationTool === "drizzle") {
|
|
327
|
+
plugins.push(` // Drizzle plugin coming soon!
|
|
328
|
+
// drizzle({ schemaPath: 'src/db/schema.ts' }),`);
|
|
329
|
+
}
|
|
330
|
+
if (config.migrationTool === "none" && config.generateTypes) {
|
|
331
|
+
imports.push(`import laravel from '@famgia/omnify-laravel/plugin';`);
|
|
332
|
+
plugins.push(` laravel({
|
|
333
|
+
typesPath: '${config.typesPath}',
|
|
334
|
+
generateMigrations: false,
|
|
335
|
+
generateTypes: true,
|
|
336
|
+
}),`);
|
|
337
|
+
}
|
|
338
|
+
const dbUrlExamples = {
|
|
339
|
+
mysql: "mysql://root:password@localhost:3306/omnify_dev",
|
|
340
|
+
postgres: "postgres://postgres:password@localhost:5432/omnify_dev",
|
|
341
|
+
sqlite: "sqlite://./omnify_dev.db"
|
|
342
|
+
};
|
|
343
|
+
return `${imports.join("\n")}
|
|
162
344
|
|
|
163
345
|
export default defineConfig({
|
|
164
346
|
// Schema files location
|
|
165
|
-
schemasDir: '
|
|
347
|
+
schemasDir: '${config.schemasDir}',
|
|
348
|
+
|
|
349
|
+
// Lock file for tracking schema changes
|
|
350
|
+
lockFilePath: './omnify.lock',
|
|
166
351
|
|
|
167
352
|
// Database configuration
|
|
168
353
|
database: {
|
|
169
|
-
driver: '
|
|
170
|
-
//
|
|
171
|
-
// devUrl: '
|
|
354
|
+
driver: '${config.database}',
|
|
355
|
+
// REQUIRED: Set your development database URL
|
|
356
|
+
// devUrl: '${dbUrlExamples[config.database]}',
|
|
172
357
|
},
|
|
173
358
|
|
|
174
|
-
//
|
|
175
|
-
output: {
|
|
176
|
-
laravel: {
|
|
177
|
-
migrationsPath: 'database/migrations',
|
|
178
|
-
},
|
|
179
|
-
typescript: {
|
|
180
|
-
path: 'types',
|
|
181
|
-
singleFile: true,
|
|
182
|
-
},
|
|
183
|
-
},
|
|
184
|
-
|
|
185
|
-
// Plugins for custom types
|
|
359
|
+
// Generator plugins
|
|
186
360
|
plugins: [
|
|
187
|
-
|
|
361
|
+
${plugins.join("\n\n")}
|
|
188
362
|
],
|
|
189
363
|
});
|
|
190
364
|
`;
|
|
365
|
+
}
|
|
191
366
|
async function runInit(options) {
|
|
192
367
|
const cwd = process.cwd();
|
|
193
|
-
logger.header("
|
|
194
|
-
|
|
195
|
-
|
|
368
|
+
logger.header("Omnify Project Setup");
|
|
369
|
+
logger.newline();
|
|
370
|
+
const configPath = resolve2(cwd, "omnify.config.ts");
|
|
371
|
+
if (existsSync2(configPath) && !options.force) {
|
|
196
372
|
logger.warn("omnify.config.ts already exists. Use --force to overwrite.");
|
|
197
373
|
return;
|
|
198
374
|
}
|
|
199
|
-
|
|
200
|
-
if (
|
|
201
|
-
|
|
202
|
-
|
|
375
|
+
let config;
|
|
376
|
+
if (options.yes) {
|
|
377
|
+
config = {
|
|
378
|
+
database: "mysql",
|
|
379
|
+
migrationTool: "laravel",
|
|
380
|
+
generateTypes: true,
|
|
381
|
+
migrationsPath: "database/migrations",
|
|
382
|
+
typesPath: "resources/js/types",
|
|
383
|
+
schemasDir: "./schemas"
|
|
384
|
+
};
|
|
385
|
+
logger.info("Using default configuration...");
|
|
203
386
|
} else {
|
|
204
|
-
logger.
|
|
387
|
+
logger.info("Answer a few questions to configure your project:\n");
|
|
388
|
+
const database = await select({
|
|
389
|
+
message: "Which database?",
|
|
390
|
+
choices: [
|
|
391
|
+
{ name: "MySQL / MariaDB", value: "mysql" },
|
|
392
|
+
{ name: "PostgreSQL", value: "postgres" },
|
|
393
|
+
{ name: "SQLite", value: "sqlite" }
|
|
394
|
+
],
|
|
395
|
+
default: "mysql"
|
|
396
|
+
});
|
|
397
|
+
const migrationTool = await select({
|
|
398
|
+
message: "Which migration tool?",
|
|
399
|
+
choices: [
|
|
400
|
+
{ name: "Laravel (PHP)", value: "laravel" },
|
|
401
|
+
{ name: "Prisma (coming soon)", value: "prisma", disabled: true },
|
|
402
|
+
{ name: "Drizzle (coming soon)", value: "drizzle", disabled: true },
|
|
403
|
+
{ name: "None (types only)", value: "none" }
|
|
404
|
+
],
|
|
405
|
+
default: "laravel"
|
|
406
|
+
});
|
|
407
|
+
const generateTypes = await confirm({
|
|
408
|
+
message: "Generate TypeScript types?",
|
|
409
|
+
default: true
|
|
410
|
+
});
|
|
411
|
+
const defaultPaths = {
|
|
412
|
+
laravel: { migrations: "database/migrations", types: "resources/js/types" },
|
|
413
|
+
prisma: { migrations: "prisma/migrations", types: "src/types" },
|
|
414
|
+
drizzle: { migrations: "drizzle", types: "src/types" },
|
|
415
|
+
none: { migrations: "", types: "types" }
|
|
416
|
+
};
|
|
417
|
+
const defaults = defaultPaths[migrationTool];
|
|
418
|
+
let migrationsPath = defaults.migrations;
|
|
419
|
+
let typesPath = defaults.types;
|
|
420
|
+
if (migrationTool !== "none") {
|
|
421
|
+
migrationsPath = await input({
|
|
422
|
+
message: "Migrations output path:",
|
|
423
|
+
default: defaults.migrations
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
if (generateTypes) {
|
|
427
|
+
typesPath = await input({
|
|
428
|
+
message: "TypeScript types path:",
|
|
429
|
+
default: defaults.types
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
const schemasDir2 = await input({
|
|
433
|
+
message: "Schemas directory:",
|
|
434
|
+
default: "./schemas"
|
|
435
|
+
});
|
|
436
|
+
config = {
|
|
437
|
+
database,
|
|
438
|
+
migrationTool,
|
|
439
|
+
generateTypes,
|
|
440
|
+
migrationsPath,
|
|
441
|
+
typesPath,
|
|
442
|
+
schemasDir: schemasDir2
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
logger.newline();
|
|
446
|
+
logger.step("Creating project files...");
|
|
447
|
+
const schemasDir = resolve2(cwd, config.schemasDir);
|
|
448
|
+
if (!existsSync2(schemasDir)) {
|
|
449
|
+
mkdirSync(schemasDir, { recursive: true });
|
|
450
|
+
logger.debug(`Created ${config.schemasDir}/ directory`);
|
|
205
451
|
}
|
|
206
|
-
const examplePath =
|
|
207
|
-
if (!
|
|
452
|
+
const examplePath = resolve2(schemasDir, "User.yaml");
|
|
453
|
+
if (!existsSync2(examplePath) || options.force) {
|
|
208
454
|
writeFileSync(examplePath, EXAMPLE_SCHEMA);
|
|
209
|
-
logger.
|
|
210
|
-
} else {
|
|
211
|
-
logger.debug("Example schema already exists");
|
|
455
|
+
logger.debug("Created example schema: User.yaml");
|
|
212
456
|
}
|
|
213
|
-
|
|
214
|
-
|
|
457
|
+
const configContent = generateConfig(config);
|
|
458
|
+
writeFileSync(configPath, configContent);
|
|
459
|
+
logger.debug("Created omnify.config.ts");
|
|
215
460
|
logger.newline();
|
|
216
|
-
logger.success("Project initialized
|
|
461
|
+
logger.success("Project initialized!");
|
|
217
462
|
logger.newline();
|
|
218
|
-
|
|
463
|
+
const toolName = config.migrationTool === "laravel" ? "Laravel" : config.migrationTool === "prisma" ? "Prisma" : config.migrationTool === "drizzle" ? "Drizzle" : "None";
|
|
464
|
+
logger.info("Configuration:");
|
|
219
465
|
logger.list([
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
'Run "omnify diff" to preview changes',
|
|
224
|
-
'Run "omnify generate" to create migrations and types'
|
|
466
|
+
`Database: ${config.database}`,
|
|
467
|
+
`Migration tool: ${toolName}`,
|
|
468
|
+
`TypeScript types: ${config.generateTypes ? "Yes" : "No"}`
|
|
225
469
|
]);
|
|
470
|
+
logger.newline();
|
|
471
|
+
logger.info("Files created:");
|
|
472
|
+
logger.list(["omnify.config.ts", `${config.schemasDir}/User.yaml`]);
|
|
473
|
+
logger.newline();
|
|
474
|
+
logger.info("Next steps:");
|
|
475
|
+
logger.newline();
|
|
476
|
+
logger.step("1. Set database URL in omnify.config.ts");
|
|
477
|
+
logger.newline();
|
|
478
|
+
logger.step("2. Define schemas in " + config.schemasDir + "/");
|
|
479
|
+
logger.newline();
|
|
480
|
+
logger.step("3. Generate:");
|
|
481
|
+
logger.info(" npx omnify validate");
|
|
482
|
+
logger.info(" npx omnify generate");
|
|
483
|
+
logger.newline();
|
|
226
484
|
}
|
|
227
|
-
function registerInitCommand(
|
|
228
|
-
|
|
485
|
+
function registerInitCommand(program) {
|
|
486
|
+
program.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
487
|
try {
|
|
230
488
|
await runInit(options);
|
|
231
489
|
} catch (error) {
|
|
232
490
|
if (error instanceof Error) {
|
|
491
|
+
if (error.message.includes("User force closed")) {
|
|
492
|
+
logger.newline();
|
|
493
|
+
logger.info("Setup cancelled.");
|
|
494
|
+
process.exit(0);
|
|
495
|
+
}
|
|
233
496
|
logger.error(error.message);
|
|
234
497
|
}
|
|
235
498
|
process.exit(1);
|
|
@@ -240,161 +503,6 @@ function registerInitCommand(program2) {
|
|
|
240
503
|
// src/commands/validate.ts
|
|
241
504
|
import { resolve as resolve3, dirname as dirname2 } from "path";
|
|
242
505
|
import { loadSchemas, validateSchemas, OmnifyError as OmnifyError2 } from "@famgia/omnify-core";
|
|
243
|
-
|
|
244
|
-
// src/config/loader.ts
|
|
245
|
-
import { existsSync as existsSync2 } from "fs";
|
|
246
|
-
import { resolve as resolve2, dirname } from "path";
|
|
247
|
-
import { createJiti } from "jiti";
|
|
248
|
-
import { configError, configNotFoundError } from "@famgia/omnify-core";
|
|
249
|
-
var CONFIG_FILES = [
|
|
250
|
-
"omnify.config.ts",
|
|
251
|
-
"omnify.config.js",
|
|
252
|
-
"omnify.config.mjs",
|
|
253
|
-
"omnify.config.cjs"
|
|
254
|
-
];
|
|
255
|
-
function findConfigFile(startDir) {
|
|
256
|
-
const cwd = resolve2(startDir);
|
|
257
|
-
for (const filename of CONFIG_FILES) {
|
|
258
|
-
const configPath = resolve2(cwd, filename);
|
|
259
|
-
if (existsSync2(configPath)) {
|
|
260
|
-
return configPath;
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
return null;
|
|
264
|
-
}
|
|
265
|
-
async function loadConfigFile(configPath) {
|
|
266
|
-
const jiti = createJiti(configPath, {
|
|
267
|
-
interopDefault: true,
|
|
268
|
-
moduleCache: false
|
|
269
|
-
});
|
|
270
|
-
try {
|
|
271
|
-
const module = await jiti.import(configPath);
|
|
272
|
-
const config = module;
|
|
273
|
-
if ("default" in config) {
|
|
274
|
-
return config.default;
|
|
275
|
-
}
|
|
276
|
-
return config;
|
|
277
|
-
} catch (error) {
|
|
278
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
279
|
-
throw configError(
|
|
280
|
-
`Failed to load config file: ${message}. Check your omnify.config.ts for syntax errors.`,
|
|
281
|
-
"E002"
|
|
282
|
-
);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
async function resolvePlugins(plugins, configPath) {
|
|
286
|
-
if (!plugins || plugins.length === 0) {
|
|
287
|
-
return [];
|
|
288
|
-
}
|
|
289
|
-
const resolved = [];
|
|
290
|
-
const configDir = configPath ? dirname(configPath) : process.cwd();
|
|
291
|
-
for (const plugin of plugins) {
|
|
292
|
-
if (typeof plugin === "string") {
|
|
293
|
-
const jiti = createJiti(configDir, {
|
|
294
|
-
interopDefault: true,
|
|
295
|
-
moduleCache: false
|
|
296
|
-
});
|
|
297
|
-
try {
|
|
298
|
-
const module = await jiti.import(plugin);
|
|
299
|
-
const loadedPlugin = module.default ?? module;
|
|
300
|
-
resolved.push(loadedPlugin);
|
|
301
|
-
} catch (error) {
|
|
302
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
303
|
-
throw configError(
|
|
304
|
-
`Failed to load plugin '${plugin}': ${message}. Ensure the plugin is installed: npm install ${plugin}`,
|
|
305
|
-
"E301"
|
|
306
|
-
);
|
|
307
|
-
}
|
|
308
|
-
} else {
|
|
309
|
-
resolved.push(plugin);
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
return resolved;
|
|
313
|
-
}
|
|
314
|
-
async function resolveConfig(userConfig, configPath) {
|
|
315
|
-
const plugins = await resolvePlugins(userConfig.plugins, configPath);
|
|
316
|
-
const databaseConfig = {
|
|
317
|
-
driver: userConfig.database.driver,
|
|
318
|
-
enableFieldComments: userConfig.database.enableFieldComments ?? false
|
|
319
|
-
};
|
|
320
|
-
const database = userConfig.database.devUrl !== void 0 ? { ...databaseConfig, devUrl: userConfig.database.devUrl } : databaseConfig;
|
|
321
|
-
const laravelConfig = {
|
|
322
|
-
migrationsPath: userConfig.output?.laravel?.migrationsPath ?? "database/migrations"
|
|
323
|
-
};
|
|
324
|
-
const laravel = buildLaravelConfig(laravelConfig, userConfig.output?.laravel);
|
|
325
|
-
const typescript = {
|
|
326
|
-
path: userConfig.output?.typescript?.path ?? "types",
|
|
327
|
-
singleFile: userConfig.output?.typescript?.singleFile ?? true,
|
|
328
|
-
generateEnums: userConfig.output?.typescript?.generateEnums ?? true,
|
|
329
|
-
generateRelationships: userConfig.output?.typescript?.generateRelationships ?? true
|
|
330
|
-
};
|
|
331
|
-
const result = {
|
|
332
|
-
schemasDir: userConfig.schemasDir ?? "./schemas",
|
|
333
|
-
database,
|
|
334
|
-
output: {
|
|
335
|
-
laravel,
|
|
336
|
-
typescript
|
|
337
|
-
},
|
|
338
|
-
plugins,
|
|
339
|
-
verbose: userConfig.verbose ?? false,
|
|
340
|
-
lockFilePath: userConfig.lockFilePath ?? ".omnify.lock"
|
|
341
|
-
};
|
|
342
|
-
return result;
|
|
343
|
-
}
|
|
344
|
-
function buildLaravelConfig(base, userLaravel) {
|
|
345
|
-
const config = { ...base };
|
|
346
|
-
if (userLaravel?.modelsPath !== void 0) {
|
|
347
|
-
config.modelsPath = userLaravel.modelsPath;
|
|
348
|
-
}
|
|
349
|
-
if (userLaravel?.modelsNamespace !== void 0) {
|
|
350
|
-
config.modelsNamespace = userLaravel.modelsNamespace;
|
|
351
|
-
}
|
|
352
|
-
if (userLaravel?.factoriesPath !== void 0) {
|
|
353
|
-
config.factoriesPath = userLaravel.factoriesPath;
|
|
354
|
-
}
|
|
355
|
-
if (userLaravel?.enumsPath !== void 0) {
|
|
356
|
-
config.enumsPath = userLaravel.enumsPath;
|
|
357
|
-
}
|
|
358
|
-
if (userLaravel?.enumsNamespace !== void 0) {
|
|
359
|
-
config.enumsNamespace = userLaravel.enumsNamespace;
|
|
360
|
-
}
|
|
361
|
-
return config;
|
|
362
|
-
}
|
|
363
|
-
function validateConfig(config, rootDir) {
|
|
364
|
-
const schemaPath = resolve2(rootDir, config.schemasDir);
|
|
365
|
-
if (!existsSync2(schemaPath)) {
|
|
366
|
-
throw configError(
|
|
367
|
-
`Schema directory not found: ${schemaPath}. Create the '${config.schemasDir}' directory or update schemasDir in config.`,
|
|
368
|
-
"E002"
|
|
369
|
-
);
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
function requireDevUrl(config) {
|
|
373
|
-
if (!config.database.devUrl) {
|
|
374
|
-
throw configError(
|
|
375
|
-
`database.devUrl is required for diff and generate operations. Add devUrl to your database config, e.g., "mysql://root@localhost:3306/omnify_dev"`,
|
|
376
|
-
"E003"
|
|
377
|
-
);
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
async function loadConfig(startDir = process.cwd()) {
|
|
381
|
-
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);
|
|
386
|
-
return {
|
|
387
|
-
config,
|
|
388
|
-
configPath
|
|
389
|
-
};
|
|
390
|
-
}
|
|
391
|
-
throw configNotFoundError(resolve2(cwd, "omnify.config.ts"));
|
|
392
|
-
}
|
|
393
|
-
function defineConfig(config) {
|
|
394
|
-
return config;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
// src/commands/validate.ts
|
|
398
506
|
async function runValidate(options) {
|
|
399
507
|
logger.setVerbose(options.verbose ?? false);
|
|
400
508
|
logger.header("Validating Schemas");
|
|
@@ -432,8 +540,8 @@ async function runValidate(options) {
|
|
|
432
540
|
process.exit(2);
|
|
433
541
|
}
|
|
434
542
|
}
|
|
435
|
-
function registerValidateCommand(
|
|
436
|
-
|
|
543
|
+
function registerValidateCommand(program) {
|
|
544
|
+
program.command("validate").description("Validate schema files").option("-v, --verbose", "Show detailed output").action(async (options) => {
|
|
437
545
|
try {
|
|
438
546
|
await runValidate(options);
|
|
439
547
|
} catch (error) {
|
|
@@ -536,8 +644,8 @@ async function runDiff(options) {
|
|
|
536
644
|
logger.newline();
|
|
537
645
|
logger.info('Run "omnify generate" to create migrations');
|
|
538
646
|
}
|
|
539
|
-
function registerDiffCommand(
|
|
540
|
-
|
|
647
|
+
function registerDiffCommand(program) {
|
|
648
|
+
program.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) => {
|
|
541
649
|
try {
|
|
542
650
|
await runDiff(options);
|
|
543
651
|
} catch (error) {
|
|
@@ -556,9 +664,101 @@ function registerDiffCommand(program2) {
|
|
|
556
664
|
// src/commands/generate.ts
|
|
557
665
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
558
666
|
import { resolve as resolve5, dirname as dirname4 } from "path";
|
|
559
|
-
import {
|
|
560
|
-
|
|
667
|
+
import {
|
|
668
|
+
loadSchemas as loadSchemas3,
|
|
669
|
+
validateSchemas as validateSchemas3,
|
|
670
|
+
OmnifyError as OmnifyError4,
|
|
671
|
+
PluginManager
|
|
672
|
+
} from "@famgia/omnify-core";
|
|
673
|
+
import {
|
|
674
|
+
writeLockFile,
|
|
675
|
+
readLockFile,
|
|
676
|
+
updateLockFile,
|
|
677
|
+
buildSchemaHashes
|
|
678
|
+
} from "@famgia/omnify-atlas";
|
|
561
679
|
import { generateMigrations, generateTypeScript } from "@famgia/omnify-laravel";
|
|
680
|
+
function hasPluginGenerators(plugins) {
|
|
681
|
+
return plugins.some((p) => p.generators && p.generators.length > 0);
|
|
682
|
+
}
|
|
683
|
+
function writeGeneratorOutputs(outputs, rootDir) {
|
|
684
|
+
const counts = { migrations: 0, types: 0, other: 0 };
|
|
685
|
+
for (const output of outputs) {
|
|
686
|
+
const filePath = resolve5(rootDir, output.path);
|
|
687
|
+
const dir = dirname4(filePath);
|
|
688
|
+
if (!existsSync3(dir)) {
|
|
689
|
+
mkdirSync2(dir, { recursive: true });
|
|
690
|
+
logger.debug(`Created directory: ${dir}`);
|
|
691
|
+
}
|
|
692
|
+
writeFileSync2(filePath, output.content);
|
|
693
|
+
logger.debug(`Created: ${output.path}`);
|
|
694
|
+
if (output.type === "migration") counts.migrations++;
|
|
695
|
+
else if (output.type === "type") counts.types++;
|
|
696
|
+
else counts.other++;
|
|
697
|
+
}
|
|
698
|
+
return counts;
|
|
699
|
+
}
|
|
700
|
+
async function runPluginGeneration(plugins, schemas, rootDir, verbose) {
|
|
701
|
+
const pluginManager = new PluginManager({
|
|
702
|
+
cwd: rootDir,
|
|
703
|
+
verbose,
|
|
704
|
+
logger: {
|
|
705
|
+
debug: (msg) => logger.debug(msg),
|
|
706
|
+
info: (msg) => logger.info(msg),
|
|
707
|
+
warn: (msg) => logger.warn(msg),
|
|
708
|
+
error: (msg) => logger.error(msg)
|
|
709
|
+
}
|
|
710
|
+
});
|
|
711
|
+
for (const plugin of plugins) {
|
|
712
|
+
await pluginManager.register(plugin);
|
|
713
|
+
}
|
|
714
|
+
const result = await pluginManager.runGenerators(schemas);
|
|
715
|
+
if (!result.success) {
|
|
716
|
+
for (const error of result.errors) {
|
|
717
|
+
logger.error(`Generator ${error.generatorName} failed: ${error.message}`);
|
|
718
|
+
}
|
|
719
|
+
throw new Error("Generator execution failed");
|
|
720
|
+
}
|
|
721
|
+
return writeGeneratorOutputs(result.outputs, rootDir);
|
|
722
|
+
}
|
|
723
|
+
function runDirectGeneration(schemas, config, rootDir, options) {
|
|
724
|
+
let migrationsGenerated = 0;
|
|
725
|
+
let typesGenerated = 0;
|
|
726
|
+
if (!options.typesOnly && config.output.laravel) {
|
|
727
|
+
logger.step("Generating Laravel migrations...");
|
|
728
|
+
const migrationsDir = resolve5(rootDir, config.output.laravel.migrationsPath);
|
|
729
|
+
if (!existsSync3(migrationsDir)) {
|
|
730
|
+
mkdirSync2(migrationsDir, { recursive: true });
|
|
731
|
+
logger.debug(`Created directory: ${migrationsDir}`);
|
|
732
|
+
}
|
|
733
|
+
const migrations = generateMigrations(schemas);
|
|
734
|
+
for (const migration of migrations) {
|
|
735
|
+
const filePath = resolve5(migrationsDir, migration.fileName);
|
|
736
|
+
writeFileSync2(filePath, migration.content);
|
|
737
|
+
logger.debug(`Created: ${migration.fileName}`);
|
|
738
|
+
migrationsGenerated++;
|
|
739
|
+
}
|
|
740
|
+
logger.success(`Generated ${migrationsGenerated} migration(s)`);
|
|
741
|
+
}
|
|
742
|
+
if (!options.migrationsOnly && config.output.typescript) {
|
|
743
|
+
logger.step("Generating TypeScript types...");
|
|
744
|
+
const typesDir = resolve5(rootDir, config.output.typescript.path);
|
|
745
|
+
if (!existsSync3(typesDir)) {
|
|
746
|
+
mkdirSync2(typesDir, { recursive: true });
|
|
747
|
+
logger.debug(`Created directory: ${typesDir}`);
|
|
748
|
+
}
|
|
749
|
+
const typeFiles = generateTypeScript(schemas, {
|
|
750
|
+
singleFile: config.output.typescript.singleFile
|
|
751
|
+
});
|
|
752
|
+
for (const file of typeFiles) {
|
|
753
|
+
const filePath = resolve5(typesDir, file.fileName);
|
|
754
|
+
writeFileSync2(filePath, file.content);
|
|
755
|
+
logger.debug(`Created: ${file.fileName}`);
|
|
756
|
+
typesGenerated++;
|
|
757
|
+
}
|
|
758
|
+
logger.success(`Generated ${typesGenerated} TypeScript file(s)`);
|
|
759
|
+
}
|
|
760
|
+
return { migrations: migrationsGenerated, types: typesGenerated };
|
|
761
|
+
}
|
|
562
762
|
async function runGenerate(options) {
|
|
563
763
|
logger.setVerbose(options.verbose ?? false);
|
|
564
764
|
logger.header("Generating Outputs");
|
|
@@ -601,54 +801,48 @@ async function runGenerate(options) {
|
|
|
601
801
|
}
|
|
602
802
|
let migrationsGenerated = 0;
|
|
603
803
|
let typesGenerated = 0;
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
804
|
+
const usePlugins = hasPluginGenerators(config.plugins);
|
|
805
|
+
if (usePlugins) {
|
|
806
|
+
logger.step("Running plugin generators...");
|
|
807
|
+
const counts = await runPluginGeneration(
|
|
808
|
+
config.plugins,
|
|
809
|
+
schemas,
|
|
810
|
+
rootDir,
|
|
811
|
+
options.verbose ?? false
|
|
812
|
+
);
|
|
813
|
+
migrationsGenerated = counts.migrations;
|
|
814
|
+
typesGenerated = counts.types;
|
|
815
|
+
if (counts.migrations > 0) {
|
|
816
|
+
logger.success(`Generated ${counts.migrations} migration(s)`);
|
|
610
817
|
}
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
const filePath = resolve5(migrationsDir, migration.fileName);
|
|
614
|
-
writeFileSync2(filePath, migration.content);
|
|
615
|
-
logger.debug(`Created: ${migration.fileName}`);
|
|
616
|
-
migrationsGenerated++;
|
|
818
|
+
if (counts.types > 0) {
|
|
819
|
+
logger.success(`Generated ${counts.types} TypeScript file(s)`);
|
|
617
820
|
}
|
|
618
|
-
|
|
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}`);
|
|
821
|
+
if (counts.other > 0) {
|
|
822
|
+
logger.success(`Generated ${counts.other} other file(s)`);
|
|
626
823
|
}
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
const filePath = resolve5(typesDir, file.fileName);
|
|
632
|
-
writeFileSync2(filePath, file.content);
|
|
633
|
-
logger.debug(`Created: ${file.fileName}`);
|
|
634
|
-
typesGenerated++;
|
|
635
|
-
}
|
|
636
|
-
logger.success(`Generated ${typesGenerated} TypeScript file(s)`);
|
|
824
|
+
} else {
|
|
825
|
+
const counts = runDirectGeneration(schemas, config, rootDir, options);
|
|
826
|
+
migrationsGenerated = counts.migrations;
|
|
827
|
+
typesGenerated = counts.types;
|
|
637
828
|
}
|
|
638
829
|
logger.step("Updating lock file...");
|
|
639
|
-
await
|
|
830
|
+
const existingLock = await readLockFile(lockPath);
|
|
831
|
+
const schemaHashes = await buildSchemaHashes(schemas);
|
|
832
|
+
const newLockFile = updateLockFile(existingLock, schemaHashes, config.database.driver);
|
|
833
|
+
await writeLockFile(lockPath, newLockFile);
|
|
640
834
|
logger.debug(`Updated: ${config.lockFilePath}`);
|
|
641
835
|
logger.newline();
|
|
642
836
|
logger.success("Generation complete!");
|
|
643
|
-
if (migrationsGenerated > 0) {
|
|
837
|
+
if (migrationsGenerated > 0 && config.output.laravel) {
|
|
644
838
|
logger.info(` Migrations: ${config.output.laravel.migrationsPath}/`);
|
|
645
839
|
}
|
|
646
|
-
if (typesGenerated > 0) {
|
|
840
|
+
if (typesGenerated > 0 && config.output.typescript) {
|
|
647
841
|
logger.info(` Types: ${config.output.typescript.path}/`);
|
|
648
842
|
}
|
|
649
843
|
}
|
|
650
|
-
function registerGenerateCommand(
|
|
651
|
-
|
|
844
|
+
function registerGenerateCommand(program) {
|
|
845
|
+
program.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) => {
|
|
652
846
|
try {
|
|
653
847
|
await runGenerate(options);
|
|
654
848
|
} catch (error) {
|
|
@@ -663,38 +857,14 @@ function registerGenerateCommand(program2) {
|
|
|
663
857
|
}
|
|
664
858
|
});
|
|
665
859
|
}
|
|
666
|
-
|
|
667
|
-
// src/index.ts
|
|
668
|
-
var VERSION = "0.0.1";
|
|
669
|
-
var program = new Command();
|
|
670
|
-
program.name("omnify").description("Schema-first database migrations for Laravel and TypeScript").version(VERSION);
|
|
671
|
-
registerInitCommand(program);
|
|
672
|
-
registerValidateCommand(program);
|
|
673
|
-
registerDiffCommand(program);
|
|
674
|
-
registerGenerateCommand(program);
|
|
675
|
-
process.on("uncaughtException", (error) => {
|
|
676
|
-
if (error instanceof OmnifyError5) {
|
|
677
|
-
logger.formatError(error);
|
|
678
|
-
process.exit(logger.getExitCode(error));
|
|
679
|
-
} else {
|
|
680
|
-
logger.error(error.message);
|
|
681
|
-
process.exit(1);
|
|
682
|
-
}
|
|
683
|
-
});
|
|
684
|
-
process.on("unhandledRejection", (reason) => {
|
|
685
|
-
if (reason instanceof OmnifyError5) {
|
|
686
|
-
logger.formatError(reason);
|
|
687
|
-
process.exit(logger.getExitCode(reason));
|
|
688
|
-
} else if (reason instanceof Error) {
|
|
689
|
-
logger.error(reason.message);
|
|
690
|
-
} else {
|
|
691
|
-
logger.error(String(reason));
|
|
692
|
-
}
|
|
693
|
-
process.exit(1);
|
|
694
|
-
});
|
|
695
|
-
program.parse();
|
|
696
860
|
export {
|
|
697
861
|
defineConfig,
|
|
698
|
-
loadConfig
|
|
862
|
+
loadConfig,
|
|
863
|
+
logger,
|
|
864
|
+
registerDiffCommand,
|
|
865
|
+
registerGenerateCommand,
|
|
866
|
+
registerInitCommand,
|
|
867
|
+
registerValidateCommand,
|
|
868
|
+
runInit
|
|
699
869
|
};
|
|
700
870
|
//# sourceMappingURL=index.js.map
|