@famgia/omnify-cli 2.0.14 → 2.0.16

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 CHANGED
@@ -122,6 +122,58 @@ omnify generate --force
122
122
  omnify generate -v
123
123
  ```
124
124
 
125
+ ### `omnify ai-guides`
126
+
127
+ Generate AI assistant guides (Cursor rules, Claude guides/rules, Antigravity rules).
128
+
129
+ > This command loads `@famgia/omnify-ai-guides`. If it’s not installed, install it first:
130
+ >
131
+ > ```bash
132
+ > pnpm add -D @famgia/omnify-ai-guides
133
+ > ```
134
+
135
+ ```bash
136
+ omnify ai-guides [options]
137
+ ```
138
+
139
+ This command reads from `omnify.config.ts` (if present). Add `aiGuides` section:
140
+
141
+ ```typescript
142
+ export default defineConfig({
143
+ // ... other config
144
+ aiGuides: {
145
+ typescriptBase: 'resources/ts', // or 'resources/js', 'src', etc.
146
+ laravelBase: 'app',
147
+ categories: ['omnify', 'laravel', 'react'], // optional
148
+ adapters: ['cursor', 'claude', 'antigravity'], // optional
149
+ },
150
+ });
151
+ ```
152
+
153
+ Options (override config):
154
+
155
+ ```bash
156
+ -v, --verbose Enable verbose output
157
+ -c, --categories <categories> Categories to generate (comma-separated: omnify,laravel,react)
158
+ -a, --adapters <adapters> Adapters to use (comma-separated: cursor,claude,antigravity)
159
+ --laravel-base <path> Laravel base path for placeholders
160
+ --typescript-base <path> TypeScript base path for placeholders
161
+ --dry-run Show what would be generated without writing files
162
+ ```
163
+
164
+ Common examples:
165
+
166
+ ```bash
167
+ # API-only project (skip React)
168
+ omnify ai-guides --categories omnify,laravel
169
+
170
+ # Frontend-only (skip Laravel)
171
+ omnify ai-guides --categories omnify,react
172
+
173
+ # Only Cursor rules
174
+ omnify ai-guides --adapters cursor
175
+ ```
176
+
125
177
  ### `omnify create-laravel-project`
126
178
 
127
179
  Create a new Laravel project from the boilerplate template.
@@ -187,13 +239,13 @@ export default defineConfig({
187
239
 
188
240
  ### Configuration Options
189
241
 
190
- | Option | Type | Required | Description |
191
- |--------|------|----------|-------------|
192
- | `schemasDir` | `string` | Yes | Directory containing schema files |
193
- | `lockFilePath` | `string` | Yes | Path to lock file for change tracking |
194
- | `database.driver` | `string` | Yes | Database driver: `mysql`, `postgres`, `sqlite` |
195
- | `database.devUrl` | `string` | Yes* | Development database URL for Atlas (*required for generate) |
196
- | `plugins` | `Plugin[]` | No | Array of generator plugins |
242
+ | Option | Type | Required | Description |
243
+ | ----------------- | ---------- | -------- | ----------------------------------------------------------- |
244
+ | `schemasDir` | `string` | Yes | Directory containing schema files |
245
+ | `lockFilePath` | `string` | Yes | Path to lock file for change tracking |
246
+ | `database.driver` | `string` | Yes | Database driver: `mysql`, `postgres`, `sqlite` |
247
+ | `database.devUrl` | `string` | Yes* | Development database URL for Atlas (*required for generate) |
248
+ | `plugins` | `Plugin[]` | No | Array of generator plugins |
197
249
 
198
250
  ### Database URL Format
199
251
 
@@ -301,18 +353,18 @@ values:
301
353
 
302
354
  ## Exit Codes
303
355
 
304
- | Code | Meaning |
305
- |------|---------|
306
- | 0 | Success |
307
- | 1 | General error |
308
- | 2 | Validation error |
356
+ | Code | Meaning |
357
+ | ---- | ---------------- |
358
+ | 0 | Success |
359
+ | 1 | General error |
360
+ | 2 | Validation error |
309
361
 
310
362
  ## Environment Variables
311
363
 
312
- | Variable | Description |
313
- |----------|-------------|
364
+ | Variable | Description |
365
+ | ---------------- | ------------------------------------ |
314
366
  | `OMNIFY_DEV_URL` | Override database.devUrl from config |
315
- | `DEBUG` | Set to `omnify:*` for debug output |
367
+ | `DEBUG` | Set to `omnify:*` for debug output |
316
368
 
317
369
  ## Troubleshooting
318
370
 
package/dist/cli.js CHANGED
@@ -839,7 +839,8 @@ async function resolveConfig(userConfig, configPath2) {
839
839
  lockFilePath: userConfig.lockFilePath ?? ".omnify.lock",
840
840
  discovery,
841
841
  ...userConfig.locale && { locale: userConfig.locale },
842
- ...allSchemaPaths.length > 0 && { additionalSchemaPaths: allSchemaPaths }
842
+ ...allSchemaPaths.length > 0 && { additionalSchemaPaths: allSchemaPaths },
843
+ ...userConfig.aiGuides && { aiGuides: userConfig.aiGuides }
843
844
  };
844
845
  return result;
845
846
  }
@@ -1127,6 +1128,9 @@ import {
1127
1128
  isLockFileV2,
1128
1129
  validateMigrations,
1129
1130
  getMigrationsToRegenerate,
1131
+ addEnhancedMigrationRecord,
1132
+ extractTimestampFromFilename,
1133
+ extractTableNameFromFilename,
1130
1134
  VERSION_CHAIN_FILE,
1131
1135
  readVersionChain,
1132
1136
  checkBulkLockViolation
@@ -1590,6 +1594,7 @@ function schemaChangeToVersionChange(change) {
1590
1594
  }
1591
1595
  function writeGeneratorOutputs(outputs, rootDir) {
1592
1596
  const counts = { migrations: 0, types: 0, models: 0, factories: 0, other: 0 };
1597
+ const migrationRecords = [];
1593
1598
  for (const output of outputs) {
1594
1599
  const filePath = resolve9(rootDir, output.path);
1595
1600
  const dir = dirname6(filePath);
@@ -1603,13 +1608,34 @@ function writeGeneratorOutputs(outputs, rootDir) {
1603
1608
  }
1604
1609
  writeFileSync5(filePath, output.content);
1605
1610
  logger.debug(`Created: ${output.path}`);
1606
- if (output.type === "migration") counts.migrations++;
1607
- else if (output.type === "type") counts.types++;
1611
+ if (output.type === "migration") {
1612
+ counts.migrations++;
1613
+ const fileName = output.path.split("/").pop() ?? output.path;
1614
+ const timestamp = extractTimestampFromFilename(fileName);
1615
+ const metaTableName = output.metadata?.tableName;
1616
+ const tableName = metaTableName ?? extractTableNameFromFilename(fileName);
1617
+ const migrationType = output.metadata?.migrationType;
1618
+ const schemaName = output.metadata?.schemaName;
1619
+ const pathParts = output.path.split("/");
1620
+ pathParts.pop();
1621
+ const outputPath = pathParts.join("/");
1622
+ if (timestamp && tableName) {
1623
+ migrationRecords.push({
1624
+ fileName,
1625
+ timestamp,
1626
+ tableName,
1627
+ type: migrationType ?? "create",
1628
+ schemas: schemaName ? [schemaName] : [],
1629
+ content: output.content,
1630
+ outputPath
1631
+ });
1632
+ }
1633
+ } else if (output.type === "type") counts.types++;
1608
1634
  else if (output.type === "model") counts.models++;
1609
1635
  else if (output.type === "factory") counts.factories++;
1610
1636
  else counts.other++;
1611
1637
  }
1612
- return counts;
1638
+ return { ...counts, migrationRecords };
1613
1639
  }
1614
1640
  async function runPluginGeneration(plugins, schemas, rootDir, verbose, changes, localeConfig) {
1615
1641
  const pluginManager = new PluginManager({
@@ -1640,6 +1666,7 @@ function runDirectGeneration(schemas, config, rootDir, options, changes) {
1640
1666
  let typesGenerated = 0;
1641
1667
  let modelsGenerated = 0;
1642
1668
  let factoriesGenerated = 0;
1669
+ const migrationRecords = [];
1643
1670
  const customTypesMap = /* @__PURE__ */ new Map();
1644
1671
  for (const plugin of config.plugins) {
1645
1672
  if (plugin.types) {
@@ -1685,6 +1712,17 @@ function runDirectGeneration(schemas, config, rootDir, options, changes) {
1685
1712
  writeFileSync5(filePath, migration.content);
1686
1713
  logger.debug(`Created: ${migration.fileName}`);
1687
1714
  migrationsGenerated++;
1715
+ const timestamp = extractTimestampFromFilename(migration.fileName);
1716
+ if (timestamp && tableName) {
1717
+ migrationRecords.push({
1718
+ fileName: migration.fileName,
1719
+ timestamp,
1720
+ tableName,
1721
+ type: "create",
1722
+ schemas: migration.schemaName ? [migration.schemaName] : [],
1723
+ content: migration.content
1724
+ });
1725
+ }
1688
1726
  }
1689
1727
  }
1690
1728
  if (alterChanges.length > 0) {
@@ -1694,6 +1732,18 @@ function runDirectGeneration(schemas, config, rootDir, options, changes) {
1694
1732
  writeFileSync5(filePath, migration.content);
1695
1733
  logger.debug(`Created: ${migration.fileName}`);
1696
1734
  migrationsGenerated++;
1735
+ const timestamp = extractTimestampFromFilename(migration.fileName);
1736
+ const tableName = migration.tables[0];
1737
+ if (timestamp && tableName) {
1738
+ migrationRecords.push({
1739
+ fileName: migration.fileName,
1740
+ timestamp,
1741
+ tableName,
1742
+ type: migration.type,
1743
+ schemas: [],
1744
+ content: migration.content
1745
+ });
1746
+ }
1697
1747
  }
1698
1748
  }
1699
1749
  logger.success(`Generated ${migrationsGenerated} migration(s)`);
@@ -1854,7 +1904,7 @@ function runDirectGeneration(schemas, config, rootDir, options, changes) {
1854
1904
  logger.success("Auto-configured @omnify-base/* path in tsconfig.json");
1855
1905
  }
1856
1906
  }
1857
- return { migrations: migrationsGenerated, types: typesGenerated, models: modelsGenerated, factories: factoriesGenerated };
1907
+ return { migrations: migrationsGenerated, types: typesGenerated, models: modelsGenerated, factories: factoriesGenerated, migrationRecords };
1858
1908
  }
1859
1909
  async function runGenerate(options) {
1860
1910
  logger.setVerbose(options.verbose ?? false);
@@ -2014,7 +2064,7 @@ async function runGenerate(options) {
2014
2064
  }
2015
2065
  if (existingLock && config.output.laravel?.migrationsPath) {
2016
2066
  const migrationsDir = resolve9(rootDir, config.output.laravel.migrationsPath);
2017
- const migrationValidation = await validateMigrations(existingLock, migrationsDir);
2067
+ const migrationValidation = await validateMigrations(existingLock, migrationsDir, rootDir);
2018
2068
  if (!migrationValidation.valid) {
2019
2069
  logger.newline();
2020
2070
  logger.warn("Migration file issues detected:");
@@ -2073,7 +2123,7 @@ async function runGenerate(options) {
2073
2123
  const alterMigrations = toRegenerate.filter((m) => m.type === "alter" || m.type === "drop");
2074
2124
  if (createMigrations.length > 0) {
2075
2125
  logger.info(`Regenerating ${createMigrations.length} missing CREATE migration(s) with original timestamps...`);
2076
- const migrationsDir2 = resolve9(rootDir, config.output.laravel.migrationsPath);
2126
+ const defaultMigrationsDir = resolve9(rootDir, config.output.laravel.migrationsPath);
2077
2127
  const customTypesMap2 = /* @__PURE__ */ new Map();
2078
2128
  for (const plugin of config.plugins) {
2079
2129
  if (plugin.types) {
@@ -2094,11 +2144,20 @@ async function runGenerate(options) {
2094
2144
  timestamp: migData.timestamp,
2095
2145
  customTypes: customTypesMap2
2096
2146
  });
2097
- for (const mig of regenerated) {
2098
- const filePath = resolve9(migrationsDir2, migData.fileName);
2099
- writeFileSync5(filePath, mig.content);
2100
- logger.success(` Regenerated: ${migData.fileName}`);
2147
+ const matchingMig = regenerated.find((mig) => {
2148
+ return mig.tables.includes(migData.tableName);
2149
+ });
2150
+ if (!matchingMig) {
2151
+ logger.warn(` Cannot regenerate ${migData.fileName}: migration for table '${migData.tableName}' not found`);
2152
+ continue;
2101
2153
  }
2154
+ const targetDir = migData.outputPath ? resolve9(rootDir, migData.outputPath) : defaultMigrationsDir;
2155
+ if (!existsSync9(targetDir)) {
2156
+ mkdirSync3(targetDir, { recursive: true });
2157
+ }
2158
+ const filePath = resolve9(targetDir, migData.fileName);
2159
+ writeFileSync5(filePath, matchingMig.content);
2160
+ logger.success(` Regenerated: ${migData.fileName}`);
2102
2161
  }
2103
2162
  }
2104
2163
  if (alterMigrations.length > 0) {
@@ -2128,6 +2187,7 @@ async function runGenerate(options) {
2128
2187
  let typesGenerated = 0;
2129
2188
  let modelsGenerated = 0;
2130
2189
  let factoriesGenerated = 0;
2190
+ let allMigrationRecords = [];
2131
2191
  const usePlugins = hasPluginGenerators(config.plugins);
2132
2192
  const customTypesMap = /* @__PURE__ */ new Map();
2133
2193
  for (const plugin of config.plugins) {
@@ -2157,6 +2217,7 @@ async function runGenerate(options) {
2157
2217
  );
2158
2218
  migrationsGenerated = counts.migrations;
2159
2219
  typesGenerated = counts.types;
2220
+ allMigrationRecords = counts.migrationRecords;
2160
2221
  if (counts.migrations > 0) {
2161
2222
  logger.success(`Generated ${counts.migrations} migration(s)`);
2162
2223
  }
@@ -2282,9 +2343,24 @@ async function runGenerate(options) {
2282
2343
  typesGenerated = counts.types;
2283
2344
  modelsGenerated = counts.models;
2284
2345
  factoriesGenerated = counts.factories;
2346
+ allMigrationRecords = counts.migrationRecords;
2285
2347
  }
2286
2348
  logger.step("Updating lock file...");
2287
- const newLockFile = updateLockFile(existingLock, currentSnapshots, config.database.driver);
2349
+ let newLockFile = updateLockFile(existingLock, currentSnapshots, config.database.driver);
2350
+ if (allMigrationRecords.length > 0) {
2351
+ logger.debug(`Adding ${allMigrationRecords.length} migration(s) to lock file...`);
2352
+ for (const record of allMigrationRecords) {
2353
+ newLockFile = addEnhancedMigrationRecord(newLockFile, {
2354
+ fileName: record.fileName,
2355
+ timestamp: record.timestamp,
2356
+ tableName: record.tableName,
2357
+ type: record.type,
2358
+ schemas: record.schemas,
2359
+ content: record.content,
2360
+ outputPath: record.outputPath
2361
+ });
2362
+ }
2363
+ }
2288
2364
  await writeLockFile(lockPath, newLockFile);
2289
2365
  logger.debug(`Updated: ${config.lockFilePath}`);
2290
2366
  if (comparison.hasChanges) {
@@ -3252,6 +3328,110 @@ function registerVerifyCommand(program2) {
3252
3328
  });
3253
3329
  }
3254
3330
 
3331
+ // src/commands/ai-guides.ts
3332
+ async function runAIGuides(options) {
3333
+ logger.setVerbose(options.verbose ?? false);
3334
+ logger.header("Generating AI Guides");
3335
+ let generateAIGuides2;
3336
+ let getAvailableCategories;
3337
+ try {
3338
+ const aiGuidesModule = await import("@famgia/omnify-ai-guides");
3339
+ generateAIGuides2 = aiGuidesModule.generateAIGuides;
3340
+ getAvailableCategories = aiGuidesModule.getAvailableCategories;
3341
+ } catch (error) {
3342
+ logger.error("Failed to load @famgia/omnify-ai-guides package");
3343
+ logger.error("Please install it: pnpm add @famgia/omnify-ai-guides");
3344
+ throw error;
3345
+ }
3346
+ const rootDir = process.cwd();
3347
+ let configAiGuides;
3348
+ try {
3349
+ const { config } = await loadConfig();
3350
+ configAiGuides = config.aiGuides;
3351
+ if (configAiGuides) {
3352
+ logger.debug("Loaded aiGuides config from omnify.config.ts");
3353
+ }
3354
+ } catch {
3355
+ logger.debug("No omnify.config.ts found, using defaults");
3356
+ }
3357
+ let categories = getAvailableCategories();
3358
+ if (options.categories) {
3359
+ categories = options.categories.split(",").map((c) => c.trim());
3360
+ logger.debug(`Categories (from CLI): ${categories.join(", ")}`);
3361
+ } else if (configAiGuides?.categories) {
3362
+ categories = [...configAiGuides.categories];
3363
+ logger.debug(`Categories (from config): ${categories.join(", ")}`);
3364
+ }
3365
+ let adapters;
3366
+ if (options.adapters) {
3367
+ adapters = options.adapters.split(",").map((a) => a.trim());
3368
+ logger.debug(`Adapters (from CLI): ${adapters.join(", ")}`);
3369
+ } else if (configAiGuides?.adapters) {
3370
+ adapters = [...configAiGuides.adapters];
3371
+ logger.debug(`Adapters (from config): ${adapters.join(", ")}`);
3372
+ }
3373
+ const placeholders = {
3374
+ LARAVEL_BASE: options.laravelBase ?? configAiGuides?.laravelBase ?? "app",
3375
+ LARAVEL_ROOT: "",
3376
+ TYPESCRIPT_BASE: options.typescriptBase ?? configAiGuides?.typescriptBase ?? "resources/ts"
3377
+ };
3378
+ logger.step("Generating guides...");
3379
+ logger.debug(`Root directory: ${rootDir}`);
3380
+ logger.debug(`Laravel base: ${placeholders.LARAVEL_BASE}`);
3381
+ logger.debug(`TypeScript base: ${placeholders.TYPESCRIPT_BASE}`);
3382
+ logger.debug(`Categories: ${JSON.stringify(categories)}`);
3383
+ logger.debug(`Adapters: ${JSON.stringify(adapters)}`);
3384
+ const result = generateAIGuides2(rootDir, {
3385
+ categories,
3386
+ adapters,
3387
+ placeholders,
3388
+ dryRun: options.dryRun
3389
+ });
3390
+ const claudeCount = result.counts["claude"] || 0;
3391
+ const cursorCount = result.counts["cursor"] || 0;
3392
+ const antigravityCount = result.counts["antigravity"] || 0;
3393
+ const totalCount = claudeCount + cursorCount + antigravityCount;
3394
+ if (options.dryRun) {
3395
+ logger.info("[DRY RUN] Would generate:");
3396
+ }
3397
+ if (claudeCount > 0) {
3398
+ logger.success(` ${claudeCount} Claude files (.claude/)`);
3399
+ }
3400
+ if (cursorCount > 0) {
3401
+ logger.success(` ${cursorCount} Cursor rules (.cursor/rules/)`);
3402
+ }
3403
+ if (antigravityCount > 0) {
3404
+ logger.success(` ${antigravityCount} Antigravity rules (.agent/rules/)`);
3405
+ }
3406
+ if (result.errors?.length) {
3407
+ logger.newline();
3408
+ logger.warn("Warnings:");
3409
+ for (const error of result.errors) {
3410
+ logger.warn(` ${error}`);
3411
+ }
3412
+ }
3413
+ logger.newline();
3414
+ logger.success(`Generated ${totalCount} AI guide files!`);
3415
+ }
3416
+ function registerAIGuidesCommand(program2) {
3417
+ program2.command("ai-guides").description("Generate AI assistant guides (Cursor, Claude, Antigravity). Reads from omnify.config.ts").option("-v, --verbose", "Enable verbose output").option(
3418
+ "-c, --categories <categories>",
3419
+ "Categories to generate (comma-separated: omnify,laravel,react). Overrides config"
3420
+ ).option(
3421
+ "-a, --adapters <adapters>",
3422
+ "Adapters to use (comma-separated: cursor,claude,antigravity). Overrides config"
3423
+ ).option("--laravel-base <path>", "Laravel base path for placeholders. Overrides config").option("--typescript-base <path>", "TypeScript base path for placeholders. Overrides config").option("--dry-run", "Show what would be generated without writing files").action(async (opts) => {
3424
+ try {
3425
+ await runAIGuides(opts);
3426
+ } catch (error) {
3427
+ if (error instanceof Error) {
3428
+ logger.error(error.message);
3429
+ }
3430
+ process.exit(1);
3431
+ }
3432
+ });
3433
+ }
3434
+
3255
3435
  // src/cli.ts
3256
3436
  var VERSION = "0.0.5";
3257
3437
  var program = new Command();
@@ -3264,6 +3444,7 @@ registerResetCommand(program);
3264
3444
  registerCreateProjectCommand(program);
3265
3445
  registerDeployCommand(program);
3266
3446
  registerVerifyCommand(program);
3447
+ registerAIGuidesCommand(program);
3267
3448
  process.on("uncaughtException", (error) => {
3268
3449
  if (error instanceof OmnifyError7) {
3269
3450
  logger.formatError(error);