pecunia-cli 0.2.2 → 0.2.3

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/api.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { a as generateKyselySchema, n as generateSchema, o as generateDrizzleSchema, r as generatePrismaSchema, t as adapters } from "./generators-DMu0BKgN.mjs";
1
+ import { a as generateKyselySchema, n as generateSchema, o as generateDrizzleSchema, r as generatePrismaSchema, t as adapters } from "./generators-CMgruX4S.mjs";
2
2
 
3
3
  export { adapters, generateDrizzleSchema, generateKyselySchema, generatePrismaSchema, generateSchema };
@@ -1,4 +1,4 @@
1
- import fs, { existsSync } from "node:fs";
1
+ import fs, { existsSync, statSync } from "node:fs";
2
2
  import fs$1 from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { getMigrations, getPaymentTables } from "pecunia-root";
@@ -306,6 +306,101 @@ CREATE TRIGGER ${triggerName}
306
306
  }
307
307
  }
308
308
 
309
+ //#endregion
310
+ //#region src/utils/drizzle-migrations.ts
311
+ /**
312
+ * Find or create the Drizzle migrations directory.
313
+ * Never places migrations under src/ to avoid drizzle-kit crashes.
314
+ *
315
+ * @param projectRoot - The project root directory (where package.json typically lives)
316
+ * @returns The path to the drizzle migrations directory
317
+ */
318
+ async function getDrizzleMigrationsDir(projectRoot) {
319
+ const drizzleDir = path.resolve(projectRoot, "drizzle");
320
+ if (existsSync(drizzleDir)) {
321
+ if (!statSync(drizzleDir).isDirectory()) throw new Error(`"drizzle" exists but is not a directory. Please remove it or rename it.`);
322
+ return drizzleDir;
323
+ }
324
+ await fs$1.mkdir(drizzleDir, { recursive: true });
325
+ return drizzleDir;
326
+ }
327
+ /**
328
+ * Find existing invariants migration file if it exists.
329
+ *
330
+ * @param migrationsDir - Path to the drizzle migrations directory
331
+ * @returns The migration number if found, or null
332
+ */
333
+ async function findExistingInvariantsMigration(migrationsDir) {
334
+ if (!existsSync(migrationsDir)) return null;
335
+ const invariantsFile = (await fs$1.readdir(migrationsDir)).find((file) => file.endsWith("_pecunia_invariants.sql") && /^\d{4}_/.test(file));
336
+ if (!invariantsFile) return null;
337
+ const match = invariantsFile.match(/^(\d{4})_/);
338
+ return match ? match[1] : null;
339
+ }
340
+ /**
341
+ * Determine the next migration number by scanning existing migration files.
342
+ * Migration files are expected to follow the pattern: `NNNN_description.sql`
343
+ * where NNNN is a 4-digit number.
344
+ *
345
+ * @param migrationsDir - Path to the drizzle migrations directory
346
+ * @returns The next migration number (4-digit string, e.g., "0001")
347
+ */
348
+ async function getNextMigrationNumber(migrationsDir) {
349
+ const existingInvariantsNumber = await findExistingInvariantsMigration(migrationsDir);
350
+ if (existingInvariantsNumber) return existingInvariantsNumber;
351
+ if (!existsSync(migrationsDir)) return "0001";
352
+ const migrationFiles = (await fs$1.readdir(migrationsDir)).filter((file) => file.endsWith(".sql") && /^\d{4}_/.test(file));
353
+ if (migrationFiles.length === 0) return "0001";
354
+ const numbers = migrationFiles.map((file) => {
355
+ const match = file.match(/^(\d{4})_/);
356
+ return match ? parseInt(match[1], 10) : 0;
357
+ }).filter((n) => n > 0);
358
+ if (numbers.length === 0) return "0001";
359
+ const nextNumber = Math.max(...numbers) + 1;
360
+ if (nextNumber >= 9999) return "9999";
361
+ return String(nextNumber).padStart(4, "0");
362
+ }
363
+ /**
364
+ * Get the path for the invariants migration file.
365
+ * Uses a deterministic name based on migration number to ensure it runs after
366
+ * base table creation migrations.
367
+ *
368
+ * @param migrationsDir - Path to the drizzle migrations directory
369
+ * @param migrationNumber - The migration number to use (4-digit string)
370
+ * @returns The full path to the invariants migration file
371
+ */
372
+ function getInvariantsMigrationPath(migrationsDir, migrationNumber) {
373
+ return path.join(migrationsDir, `${migrationNumber}_pecunia_invariants.sql`);
374
+ }
375
+ /**
376
+ * Write the invariants SQL file to the drizzle migrations directory.
377
+ * Handles idempotency by checking if the file exists and has the same content.
378
+ *
379
+ * @param projectRoot - The project root directory
380
+ * @param sqlContent - The SQL content to write
381
+ * @returns The path to the written file, or null if no changes were needed
382
+ */
383
+ async function writeInvariantsMigration(projectRoot, sqlContent) {
384
+ const migrationsDir = await getDrizzleMigrationsDir(projectRoot);
385
+ const migrationPath = getInvariantsMigrationPath(migrationsDir, await getNextMigrationNumber(migrationsDir));
386
+ let created = false;
387
+ let updated = false;
388
+ if (existsSync(migrationPath)) {
389
+ if (await fs$1.readFile(migrationPath, "utf-8") === sqlContent) return {
390
+ path: migrationPath,
391
+ created: false,
392
+ updated: false
393
+ };
394
+ updated = true;
395
+ } else created = true;
396
+ await fs$1.writeFile(migrationPath, sqlContent, "utf-8");
397
+ return {
398
+ path: migrationPath,
399
+ created,
400
+ updated
401
+ };
402
+ }
403
+
309
404
  //#endregion
310
405
  //#region src/generators/drizzle.ts
311
406
  function convertToSnakeCase(str, camelCase) {
@@ -314,10 +409,14 @@ function convertToSnakeCase(str, camelCase) {
314
409
  }
315
410
  const generateDrizzleSchema = async ({ options, file, adapter }) => {
316
411
  const tables = getPaymentTables$1(options);
317
- const filePath = file || "./payment-schema.ts";
412
+ const filePath = file || "./src/db/schema.ts";
318
413
  const databaseType = adapter.options?.provider;
414
+ const projectRoot = process.cwd();
319
415
  if (!databaseType) throw new Error("Database provider type is undefined during Drizzle schema generation. Please define a `provider` in the Drizzle adapter config.");
320
- const fileExist = existsSync(filePath);
416
+ const resolvedSchemaPath = path.isAbsolute(filePath) ? filePath : path.resolve(projectRoot, filePath);
417
+ const schemaDir = path.dirname(resolvedSchemaPath);
418
+ if (!existsSync(schemaDir)) await fs$1.mkdir(schemaDir, { recursive: true });
419
+ const fileExist = existsSync(resolvedSchemaPath);
321
420
  let code = generateImport({
322
421
  databaseType,
323
422
  tables,
@@ -646,19 +745,17 @@ const generateDrizzleSchema = async ({ options, file, adapter }) => {
646
745
  if (typeHints) code += `\n\n${typeHints}`;
647
746
  const formattedCode = await prettier.format(code, { parser: "typescript" });
648
747
  if (databaseType === "pg") {
649
- const sql = emitPostgresInvariantSql(normalizeInvariants(tables, {
748
+ const result = await writeInvariantsMigration(projectRoot, emitPostgresInvariantSql(normalizeInvariants(tables, {
650
749
  getModelName,
651
750
  getFieldName
652
- }), "public");
653
- const sqlFilePath = filePath.replace(/\.ts$/, "-invariants.sql");
654
- const sqlDir = path.dirname(path.resolve(process.cwd(), sqlFilePath));
655
- await fs$1.mkdir(sqlDir, { recursive: true });
656
- await fs$1.writeFile(path.resolve(process.cwd(), sqlFilePath), sql);
657
- console.log(`📝 Generated invariant SQL: ${sqlFilePath}`);
751
+ }), "public"));
752
+ if (result.created) console.log(`Generated invariants migration: ${path.relative(projectRoot, result.path)}\nNote: Created ./drizzle directory. Invariants SQL is placed in Drizzle migrations.`);
753
+ else if (result.updated) console.log(`Updated invariants migration: ${path.relative(projectRoot, result.path)}`);
754
+ else console.log(`Invariants migration up to date: ${path.relative(projectRoot, result.path)}`);
658
755
  }
659
756
  return {
660
757
  code: formattedCode,
661
- fileName: filePath,
758
+ fileName: path.relative(projectRoot, resolvedSchemaPath),
662
759
  overwrite: fileExist
663
760
  };
664
761
  };
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { i as getPackageInfo, n as generateSchema } from "./generators-DMu0BKgN.mjs";
2
+ import { i as getPackageInfo, n as generateSchema } from "./generators-CMgruX4S.mjs";
3
3
  import { Command } from "commander";
4
4
  import fs, { existsSync, readFileSync } from "node:fs";
5
5
  import fs$1 from "node:fs/promises";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pecunia-cli",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "type": "module",
5
5
  "module": "dist/index.mjs",
6
6
  "main": "./dist/index.mjs",