@plyaz/db 0.3.1 → 0.4.0

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/cli/index.js CHANGED
@@ -8272,6 +8272,74 @@ var JSON_INDENT_SPACES = 2;
8272
8272
  var VERSION_PAD_LENGTH = 3;
8273
8273
  var DECIMAL_RADIX = 10;
8274
8274
  var SEPARATOR_WIDTH = 50;
8275
+ var VALID_MIGRATION_TYPES = ["sql", "ts"];
8276
+ var VALID_SEED_TYPES = ["ts", "sql", "csv"];
8277
+ var MIN_ROLLBACK_STEPS = 1;
8278
+ function validateName(name, type) {
8279
+ if (!name || name.trim() === "") {
8280
+ console.error(`❌ ${type} name cannot be empty`);
8281
+ process.exit(1);
8282
+ }
8283
+ if (!/[a-zA-Z]/.test(name)) {
8284
+ console.error(`❌ ${type} name must contain at least one letter`);
8285
+ process.exit(1);
8286
+ }
8287
+ if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
8288
+ console.error(
8289
+ `❌ ${type} name can only contain letters, numbers, underscores, and hyphens`
8290
+ );
8291
+ process.exit(1);
8292
+ }
8293
+ }
8294
+ __name(validateName, "validateName");
8295
+ function validateMigrationType(type) {
8296
+ if (!VALID_MIGRATION_TYPES.includes(type)) {
8297
+ console.warn(
8298
+ `⚠ Invalid type "${type}". Valid options: ${VALID_MIGRATION_TYPES.join(", ")}. Using default: sql`
8299
+ );
8300
+ return "sql";
8301
+ }
8302
+ return type;
8303
+ }
8304
+ __name(validateMigrationType, "validateMigrationType");
8305
+ function validateSeedType(type) {
8306
+ if (!VALID_SEED_TYPES.includes(type)) {
8307
+ console.warn(
8308
+ `⚠ Invalid type "${type}". Valid options: ${VALID_SEED_TYPES.join(", ")}. Using default: ts`
8309
+ );
8310
+ return "ts";
8311
+ }
8312
+ return type;
8313
+ }
8314
+ __name(validateSeedType, "validateSeedType");
8315
+ function validateSteps(stepsStr) {
8316
+ const steps = Number.parseInt(stepsStr, DECIMAL_RADIX);
8317
+ if (Number.isNaN(steps)) {
8318
+ console.error(`❌ Steps must be a number, got: "${stepsStr}"`);
8319
+ process.exit(1);
8320
+ }
8321
+ if (steps < MIN_ROLLBACK_STEPS) {
8322
+ console.error(
8323
+ `❌ Steps must be at least ${MIN_ROLLBACK_STEPS}, got: ${steps}`
8324
+ );
8325
+ process.exit(1);
8326
+ }
8327
+ return steps;
8328
+ }
8329
+ __name(validateSteps, "validateSteps");
8330
+ function validateSchemaName(schema) {
8331
+ if (!schema || schema.trim() === "") {
8332
+ console.error("❌ Schema name cannot be empty");
8333
+ process.exit(1);
8334
+ }
8335
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(schema)) {
8336
+ console.error(
8337
+ "❌ Invalid schema name. Must start with letter or underscore, contain only alphanumeric and underscores"
8338
+ );
8339
+ process.exit(1);
8340
+ }
8341
+ }
8342
+ __name(validateSchemaName, "validateSchemaName");
8275
8343
  async function getUserSchemas(adapter) {
8276
8344
  const result = await adapter.query?.(`
8277
8345
  SELECT schema_name FROM information_schema.schemata
@@ -8494,16 +8562,15 @@ migrateCommand.command("up").description("Run pending migrations").option("-t, -
8494
8562
  });
8495
8563
  migrateCommand.command("down").description("Rollback migrations").option("-s, --steps <number>", "Number of migrations to rollback", "1").action(async (options) => {
8496
8564
  try {
8565
+ const steps = validateSteps(options.steps);
8497
8566
  const { adapter, config } = await initDatabase(void 0, true);
8498
8567
  const migrationManager = new MigrationManager({
8499
8568
  adapter,
8500
8569
  migrationsPath: config.migrationsPath ?? "./migrations",
8501
8570
  tableName: config.migrationsTable ?? "schema_migrations"
8502
8571
  });
8503
- console.log(`🔄 Rolling back ${options.steps} migration(s)...`);
8504
- const result = await migrationManager.down(
8505
- Number.parseInt(options.steps, 10)
8506
- );
8572
+ console.log(`🔄 Rolling back ${steps} migration(s)...`);
8573
+ const result = await migrationManager.down(steps);
8507
8574
  if (result.success) {
8508
8575
  console.log(`✅ Rolled back ${result.value} migration(s)`);
8509
8576
  process.exit(0);
@@ -9283,9 +9350,11 @@ async function runInteractiveMigration(config) {
9283
9350
  console.error("❌ Migration name is required");
9284
9351
  process.exit(1);
9285
9352
  }
9353
+ validateName(name, "migration");
9286
9354
  const snakeName = toSnakeCase(name);
9287
9355
  const tableName = await promptInput("Table name", snakeName);
9288
9356
  const schema = await promptInput("Database schema", "public");
9357
+ validateSchemaName(schema);
9289
9358
  const typeChoice = await promptSelect("File type:", [
9290
9359
  "SQL (.sql)",
9291
9360
  "TypeScript (.ts)"
@@ -9309,10 +9378,20 @@ async function runInteractiveMigration(config) {
9309
9378
  const migrationsPath = config.migrationsPath ?? "./migrations";
9310
9379
  if (!fs4.existsSync(migrationsPath)) {
9311
9380
  fs4.mkdirSync(migrationsPath, { recursive: true });
9381
+ console.log(`📁 Created directory: ${migrationsPath}`);
9312
9382
  }
9313
9383
  const version = getNextMigrationVersion(migrationsPath);
9314
9384
  const filename = `${version}_${snakeName}.${type}`;
9315
9385
  const filepath = path4.join(migrationsPath, filename);
9386
+ if (fs4.existsSync(filepath)) {
9387
+ const overwrite = await promptConfirm(
9388
+ `File exists. Overwrite ${filename}?`
9389
+ );
9390
+ if (!overwrite) {
9391
+ console.log("Cancelled.");
9392
+ process.exit(0);
9393
+ }
9394
+ }
9316
9395
  const content = getMigrationTemplate({
9317
9396
  name,
9318
9397
  version,
@@ -9322,7 +9401,21 @@ async function runInteractiveMigration(config) {
9322
9401
  type
9323
9402
  });
9324
9403
  fs4.writeFileSync(filepath, content, "utf-8");
9325
- console.log(`✅ Created: ${filepath}`);
9404
+ console.log();
9405
+ console.log(`✅ Created migration: ${filename}`);
9406
+ console.log(` Path: ${filepath}`);
9407
+ console.log(` Type: ${type.toUpperCase()}`);
9408
+ console.log(` Table: ${schema}.${tableName}`);
9409
+ if (author) {
9410
+ console.log(` Author: ${author}`);
9411
+ }
9412
+ console.log("");
9413
+ console.log("📝 Next steps:");
9414
+ console.log(` 1. Edit the migration file to add your schema changes`);
9415
+ console.log(` 2. Run 'plyaz-db migrate up' to apply the migration`);
9416
+ console.log(
9417
+ ` 3. Test rollback with 'plyaz-db migrate down' before deploying`
9418
+ );
9326
9419
  }
9327
9420
  __name(runInteractiveMigration, "runInteractiveMigration");
9328
9421
  async function runInteractiveSeed(config) {
@@ -9335,9 +9428,11 @@ async function runInteractiveSeed(config) {
9335
9428
  console.error("❌ Seed name is required");
9336
9429
  process.exit(1);
9337
9430
  }
9431
+ validateName(name, "seed");
9338
9432
  const snakeName = toSnakeCase(name);
9339
9433
  const tableName = await promptInput("Table name", snakeName);
9340
9434
  const schema = await promptInput("Database schema", "public");
9435
+ validateSchemaName(schema);
9341
9436
  const typeChoice = await promptSelect("File type:", [
9342
9437
  "TypeScript (.ts)",
9343
9438
  "SQL (.sql)",
@@ -9370,10 +9465,20 @@ async function runInteractiveSeed(config) {
9370
9465
  const seedsPath = config.seedsPath ?? "./seeds";
9371
9466
  if (!fs4.existsSync(seedsPath)) {
9372
9467
  fs4.mkdirSync(seedsPath, { recursive: true });
9468
+ console.log(`📁 Created directory: ${seedsPath}`);
9373
9469
  }
9374
9470
  const order = getNextSeedOrder(seedsPath);
9375
9471
  const filename = `${order}_${snakeName}.${type}`;
9376
9472
  const filepath = path4.join(seedsPath, filename);
9473
+ if (fs4.existsSync(filepath)) {
9474
+ const overwrite = await promptConfirm(
9475
+ `File exists. Overwrite ${filename}?`
9476
+ );
9477
+ if (!overwrite) {
9478
+ console.log("Cancelled.");
9479
+ process.exit(0);
9480
+ }
9481
+ }
9377
9482
  let content;
9378
9483
  if (type === "csv") {
9379
9484
  content = getCsvSeedTemplate({ name, tableName, schema });
@@ -9388,7 +9493,19 @@ async function runInteractiveSeed(config) {
9388
9493
  });
9389
9494
  }
9390
9495
  fs4.writeFileSync(filepath, content, "utf-8");
9391
- console.log(`✅ Created: ${filepath}`);
9496
+ console.log();
9497
+ console.log(`✅ Created seed: ${filename}`);
9498
+ console.log(` Path: ${filepath}`);
9499
+ console.log(` Type: ${type.toUpperCase()}`);
9500
+ console.log(` Table: ${schema}.${tableName}`);
9501
+ if (author && type !== "csv") {
9502
+ console.log(` Author: ${author}`);
9503
+ }
9504
+ console.log("");
9505
+ console.log("📝 Next steps:");
9506
+ console.log(` 1. Edit the seed file to add your data`);
9507
+ console.log(` 2. Run 'plyaz-db seed run' to execute seeds`);
9508
+ console.log(` 3. Use 'plyaz-db seed status' to check seed status`);
9392
9509
  }
9393
9510
  __name(runInteractiveSeed, "runInteractiveSeed");
9394
9511
  function getNextMigrationVersion(migrationsPath) {
@@ -9513,14 +9630,17 @@ ${randomUUID()},Example 2,${(/* @__PURE__ */ new Date()).toISOString()}
9513
9630
  }
9514
9631
  __name(getCsvSeedTemplate, "getCsvSeedTemplate");
9515
9632
  var createCommand = program.command("create").description("Create new migration or seed files");
9516
- createCommand.command("migration").description("Create a new migration file (interactive if no name provided)").argument("[name]", "Migration name (e.g., add_users_table)").option("-t, --type <type>", "File type: sql or ts (default: sql)", "sql").option(
9633
+ createCommand.command("migration").description("Create a new migration file (interactive if no name provided)").argument("[name]", "Migration name (e.g., add_users_table)").option("-t, --type <type>", "File type: {sql, ts} (default: sql)", "sql").option(
9517
9634
  "-T, --table <table>",
9518
9635
  "Table name (default: derived from migration name)"
9519
9636
  ).option(
9520
9637
  "-s, --schema <schema>",
9521
9638
  "Database schema (default: public)",
9522
9639
  "public"
9523
- ).option("--no-author", "Skip adding git username as author").option("-i, --interactive", "Force interactive mode").action(
9640
+ ).option("--no-author", "Skip adding git username as author").option("-i, --interactive", "Force interactive mode").option(
9641
+ "-d, --dry-run",
9642
+ "Preview what would be created without writing files"
9643
+ ).option("-f, --force", "Overwrite existing file if it exists").action(
9524
9644
  async (name, options) => {
9525
9645
  try {
9526
9646
  const config = await loadConfig();
@@ -9528,8 +9648,11 @@ createCommand.command("migration").description("Create a new migration file (int
9528
9648
  await runInteractiveMigration(config);
9529
9649
  process.exit(0);
9530
9650
  }
9651
+ validateName(name, "migration");
9652
+ validateSchemaName(options.schema);
9653
+ const fileType = validateMigrationType(options.type);
9531
9654
  const migrationsPath = config.migrationsPath ?? "./migrations";
9532
- if (!fs4.existsSync(migrationsPath)) {
9655
+ if (!options.dryRun && !fs4.existsSync(migrationsPath)) {
9533
9656
  fs4.mkdirSync(migrationsPath, { recursive: true });
9534
9657
  console.log(`📁 Created directory: ${migrationsPath}`);
9535
9658
  }
@@ -9538,11 +9661,11 @@ createCommand.command("migration").description("Create a new migration file (int
9538
9661
  const tableName = options.table ?? snakeName;
9539
9662
  const schema = options.schema;
9540
9663
  const author = options.author ? getGitUsername() : "";
9541
- const extension = options.type === "ts" ? "ts" : "sql";
9542
- const filename = `${version}_${snakeName}.${extension}`;
9664
+ const filename = `${version}_${snakeName}.${fileType}`;
9543
9665
  const filepath = path4.join(migrationsPath, filename);
9544
- if (fs4.existsSync(filepath)) {
9666
+ if (fs4.existsSync(filepath) && !options.force) {
9545
9667
  console.error(`❌ File already exists: ${filepath}`);
9668
+ console.error(" Use --force to overwrite");
9546
9669
  process.exit(1);
9547
9670
  }
9548
9671
  const content = getMigrationTemplate({
@@ -9551,12 +9674,27 @@ createCommand.command("migration").description("Create a new migration file (int
9551
9674
  tableName,
9552
9675
  schema,
9553
9676
  author,
9554
- type: options.type === "ts" ? "ts" : "sql"
9677
+ type: fileType
9555
9678
  });
9679
+ if (options.dryRun) {
9680
+ console.log("🔍 Dry-run mode - no files will be created\n");
9681
+ console.log(`Would create: ${filename}`);
9682
+ console.log(` Path: ${filepath}`);
9683
+ console.log(` Type: ${fileType.toUpperCase()}`);
9684
+ console.log(` Table: ${schema}.${tableName}`);
9685
+ if (author) {
9686
+ console.log(` Author: ${author}`);
9687
+ }
9688
+ console.log("\n📄 File content preview:");
9689
+ console.log("─".repeat(SEPARATOR_WIDTH));
9690
+ console.log(content);
9691
+ console.log("─".repeat(SEPARATOR_WIDTH));
9692
+ process.exit(0);
9693
+ }
9556
9694
  fs4.writeFileSync(filepath, content, "utf-8");
9557
9695
  console.log(`✅ Created migration: ${filename}`);
9558
9696
  console.log(` Path: ${filepath}`);
9559
- console.log(` Type: ${options.type.toUpperCase()}`);
9697
+ console.log(` Type: ${fileType.toUpperCase()}`);
9560
9698
  console.log(` Table: ${schema}.${tableName}`);
9561
9699
  if (author) {
9562
9700
  console.log(` Author: ${author}`);
@@ -9575,11 +9713,14 @@ createCommand.command("migration").description("Create a new migration file (int
9575
9713
  }
9576
9714
  }
9577
9715
  );
9578
- createCommand.command("seed").description("Create a new seed file (interactive if no name provided)").argument("[name]", "Seed name (e.g., admin_users)").option("-t, --type <type>", "File type: ts, sql, or csv (default: ts)", "ts").option("-T, --table <table>", "Table name (default: derived from seed name)").option(
9716
+ createCommand.command("seed").description("Create a new seed file (interactive if no name provided)").argument("[name]", "Seed name (e.g., admin_users)").option("-t, --type <type>", "File type: {ts, sql, csv} (default: ts)", "ts").option("-T, --table <table>", "Table name (default: derived from seed name)").option(
9579
9717
  "-s, --schema <schema>",
9580
9718
  "Database schema (default: public)",
9581
9719
  "public"
9582
- ).option("--no-author", "Skip adding git username as author").option("-i, --interactive", "Force interactive mode").action(
9720
+ ).option("--no-author", "Skip adding git username as author").option("-i, --interactive", "Force interactive mode").option(
9721
+ "-d, --dry-run",
9722
+ "Preview what would be created without writing files"
9723
+ ).option("-f, --force", "Overwrite existing file if it exists").action(
9583
9724
  async (name, options) => {
9584
9725
  try {
9585
9726
  const config = await loadConfig();
@@ -9587,8 +9728,11 @@ createCommand.command("seed").description("Create a new seed file (interactive i
9587
9728
  await runInteractiveSeed(config);
9588
9729
  process.exit(0);
9589
9730
  }
9731
+ validateName(name, "seed");
9732
+ validateSchemaName(options.schema);
9733
+ const fileType = validateSeedType(options.type);
9590
9734
  const seedsPath = config.seedsPath ?? "./seeds";
9591
- if (!fs4.existsSync(seedsPath)) {
9735
+ if (!options.dryRun && !fs4.existsSync(seedsPath)) {
9592
9736
  fs4.mkdirSync(seedsPath, { recursive: true });
9593
9737
  console.log(`📁 Created directory: ${seedsPath}`);
9594
9738
  }
@@ -9597,16 +9741,11 @@ createCommand.command("seed").description("Create a new seed file (interactive i
9597
9741
  const tableName = options.table ?? snakeName;
9598
9742
  const schema = options.schema;
9599
9743
  const author = options.author ? getGitUsername() : "";
9600
- const typeMap = {
9601
- ts: "ts",
9602
- sql: "sql",
9603
- csv: "csv"
9604
- };
9605
- const fileType = typeMap[options.type] ?? "ts";
9606
9744
  const filename = `${order}_${snakeName}.${fileType}`;
9607
9745
  const filepath = path4.join(seedsPath, filename);
9608
- if (fs4.existsSync(filepath)) {
9746
+ if (fs4.existsSync(filepath) && !options.force) {
9609
9747
  console.error(`❌ File already exists: ${filepath}`);
9748
+ console.error(" Use --force to overwrite");
9610
9749
  process.exit(1);
9611
9750
  }
9612
9751
  let content;
@@ -9622,6 +9761,21 @@ createCommand.command("seed").description("Create a new seed file (interactive i
9622
9761
  type: fileType
9623
9762
  });
9624
9763
  }
9764
+ if (options.dryRun) {
9765
+ console.log("🔍 Dry-run mode - no files will be created\n");
9766
+ console.log(`Would create: ${filename}`);
9767
+ console.log(` Path: ${filepath}`);
9768
+ console.log(` Type: ${fileType.toUpperCase()}`);
9769
+ console.log(` Table: ${schema}.${tableName}`);
9770
+ if (author && fileType !== "csv") {
9771
+ console.log(` Author: ${author}`);
9772
+ }
9773
+ console.log("\n📄 File content preview:");
9774
+ console.log("─".repeat(SEPARATOR_WIDTH));
9775
+ console.log(content);
9776
+ console.log("─".repeat(SEPARATOR_WIDTH));
9777
+ process.exit(0);
9778
+ }
9625
9779
  fs4.writeFileSync(filepath, content, "utf-8");
9626
9780
  console.log(`✅ Created seed: ${filename}`);
9627
9781
  console.log(` Path: ${filepath}`);