@plyaz/db 0.4.0 → 0.4.1

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
@@ -15,7 +15,7 @@ import { PgColumn } from 'drizzle-orm/pg-core';
15
15
  import { createClient } from '@supabase/supabase-js';
16
16
  import { randomBytes, createCipheriv, createDecipheriv, randomUUID } from 'crypto';
17
17
  import * as readline from 'readline';
18
- import { pathToFileURL, fileURLToPath } from 'url';
18
+ import { fileURLToPath, pathToFileURL } from 'url';
19
19
  import { execSync } from 'child_process';
20
20
 
21
21
  var __defProp = Object.defineProperty;
@@ -9796,6 +9796,207 @@ createCommand.command("seed").description("Create a new seed file (interactive i
9796
9796
  }
9797
9797
  }
9798
9798
  );
9799
+ program.command("init").description("Create a db.config.mjs configuration file interactively").option("-f, --force", "Overwrite existing config file").option("-y, --yes", "Use default values without prompting").action(async (options) => {
9800
+ const configPath = path4.join(process.cwd(), "db.config.mjs");
9801
+ if (fs4.existsSync(configPath) && !options.force) {
9802
+ console.error("āŒ db.config.mjs already exists");
9803
+ console.error(" Use --force to overwrite");
9804
+ process.exit(1);
9805
+ }
9806
+ let adapter = "sql";
9807
+ let connectionString = "postgresql://user:password@localhost:5432/database";
9808
+ let migrationsPath = "./migrations";
9809
+ let seedsPath = "./seeds";
9810
+ let migrationsTable = "schema_migrations";
9811
+ let seedsTable = "seed_history";
9812
+ if (!options.yes) {
9813
+ const rl = readline.createInterface({
9814
+ input: process.stdin,
9815
+ output: process.stdout
9816
+ });
9817
+ const question = /* @__PURE__ */ __name((prompt) => new Promise((resolve4) => rl.question(prompt, resolve4)), "question");
9818
+ console.log("\nšŸ“¦ Initialize Database Configuration");
9819
+ console.log("─".repeat(SEPARATOR_WIDTH));
9820
+ console.log("");
9821
+ console.log("Database adapter:");
9822
+ console.log(" 1) sql (PostgreSQL - recommended)");
9823
+ console.log(" 2) drizzle (Drizzle ORM)");
9824
+ console.log(" 3) supabase (Supabase client)");
9825
+ const adapterChoice = await question("Select (number) [1]: ");
9826
+ if (adapterChoice === "2") adapter = "drizzle";
9827
+ else if (adapterChoice === "3") adapter = "supabase";
9828
+ const connInput = await question(
9829
+ `Connection string [${connectionString}]: `
9830
+ );
9831
+ if (connInput.trim()) connectionString = connInput.trim();
9832
+ const migrInput = await question(`Migrations path [${migrationsPath}]: `);
9833
+ if (migrInput.trim()) migrationsPath = migrInput.trim();
9834
+ const seedInput = await question(`Seeds path [${seedsPath}]: `);
9835
+ if (seedInput.trim()) seedsPath = seedInput.trim();
9836
+ const migrTableInput = await question(
9837
+ `Migrations table [${migrationsTable}]: `
9838
+ );
9839
+ if (migrTableInput.trim()) migrationsTable = migrTableInput.trim();
9840
+ const seedTableInput = await question(`Seeds table [${seedsTable}]: `);
9841
+ if (seedTableInput.trim()) seedsTable = seedTableInput.trim();
9842
+ rl.close();
9843
+ console.log("");
9844
+ }
9845
+ const templateDir = getTemplateDir();
9846
+ const templatePath = path4.join(
9847
+ templateDir,
9848
+ "config",
9849
+ "db.config.mjs.template"
9850
+ );
9851
+ if (!fs4.existsSync(templatePath)) {
9852
+ console.error(`āŒ Template not found: ${templatePath}`);
9853
+ process.exit(1);
9854
+ }
9855
+ let configContent = fs4.readFileSync(templatePath, "utf-8");
9856
+ configContent = configContent.replace(/\{\{ADAPTER\}\}/g, adapter).replace(/\{\{CONNECTION_STRING\}\}/g, connectionString).replace(/\{\{MIGRATIONS_PATH\}\}/g, migrationsPath).replace(/\{\{SEEDS_PATH\}\}/g, seedsPath).replace(/\{\{MIGRATIONS_TABLE\}\}/g, migrationsTable).replace(/\{\{SEEDS_TABLE\}\}/g, seedsTable);
9857
+ fs4.writeFileSync(configPath, configContent, "utf-8");
9858
+ console.log("āœ… Created db.config.mjs");
9859
+ console.log("");
9860
+ console.log("šŸ“ Next steps:");
9861
+ console.log(" 1. Update DATABASE_URL in your .env file");
9862
+ console.log(" 2. Run 'plyaz-db health' to test connection");
9863
+ console.log(
9864
+ " 3. Run 'plyaz-db create migration <name>' to create migrations"
9865
+ );
9866
+ process.exit(0);
9867
+ });
9868
+ program.command("version").description("Show current applied migration version").action(async () => {
9869
+ try {
9870
+ const { db, adapter, config } = await initDatabase();
9871
+ const migrationsTable = config.migrationsTable ?? "schema_migrations";
9872
+ const tableCheck = await adapter.query(
9873
+ `SELECT EXISTS (
9874
+ SELECT FROM information_schema.tables
9875
+ WHERE table_name = '${migrationsTable}'
9876
+ )::text as exists`
9877
+ );
9878
+ if (tableCheck[0]?.exists !== "true") {
9879
+ console.log("šŸ“Š Migration Status");
9880
+ console.log("─".repeat(SEPARATOR_WIDTH));
9881
+ console.log(" No migrations have been applied yet");
9882
+ console.log(` (Table '${migrationsTable}' does not exist)`);
9883
+ await db.close();
9884
+ process.exit(0);
9885
+ }
9886
+ const result = await adapter.query(
9887
+ `SELECT name, applied_at FROM ${migrationsTable} ORDER BY applied_at DESC LIMIT 1`
9888
+ );
9889
+ const countResult = await adapter.query(
9890
+ `SELECT COUNT(*) as count FROM ${migrationsTable}`
9891
+ );
9892
+ console.log("šŸ“Š Migration Status");
9893
+ console.log("─".repeat(SEPARATOR_WIDTH));
9894
+ if (result.length === 0) {
9895
+ console.log(" No migrations have been applied yet");
9896
+ } else {
9897
+ const latest = result[0];
9898
+ const total = Number.parseInt(countResult[0].count, DECIMAL_RADIX);
9899
+ console.log(` Current version: ${latest.name}`);
9900
+ console.log(
9901
+ ` Applied at: ${new Date(latest.applied_at).toLocaleString()}`
9902
+ );
9903
+ console.log(` Total applied: ${total} migration(s)`);
9904
+ }
9905
+ await db.close();
9906
+ process.exit(0);
9907
+ } catch (error) {
9908
+ console.error("āŒ Error:", error.message);
9909
+ process.exit(1);
9910
+ }
9911
+ });
9912
+ migrateCommand.command("verify").description("Verify all SQL migrations have valid UP and DOWN sections").option("--fix", "Show suggestions for fixing issues").action(async (options) => {
9913
+ try {
9914
+ const config = await loadConfig(program.opts().config);
9915
+ const migrationsPath = config.migrationsPath ?? "./migrations";
9916
+ if (!fs4.existsSync(migrationsPath)) {
9917
+ console.log("šŸ“ No migrations directory found");
9918
+ process.exit(0);
9919
+ }
9920
+ const files = fs4.readdirSync(migrationsPath).filter((f) => f.endsWith(".sql"));
9921
+ if (files.length === 0) {
9922
+ console.log("šŸ“ No SQL migration files found");
9923
+ process.exit(0);
9924
+ }
9925
+ console.log("šŸ” Verifying SQL migrations...\n");
9926
+ let hasErrors = false;
9927
+ const issues = [];
9928
+ for (const file of files.sort()) {
9929
+ const filepath = path4.join(migrationsPath, file);
9930
+ const content = fs4.readFileSync(filepath, "utf-8");
9931
+ const hasDown = /^--\s*DOWN\s*$/im.test(content);
9932
+ const hasDownContent = hasDown && content.split(/^--\s*DOWN\s*$/im)[1]?.trim().length > 0;
9933
+ if (!hasDown) {
9934
+ issues.push({
9935
+ file,
9936
+ type: "error",
9937
+ message: "Missing -- DOWN section"
9938
+ });
9939
+ hasErrors = true;
9940
+ } else if (!hasDownContent) {
9941
+ issues.push({
9942
+ file,
9943
+ type: "warning",
9944
+ message: "DOWN section is empty"
9945
+ });
9946
+ }
9947
+ if (content.includes("DROP TABLE") && !content.includes("IF EXISTS")) {
9948
+ issues.push({
9949
+ file,
9950
+ type: "warning",
9951
+ message: "DROP TABLE without IF EXISTS (may fail on rollback)"
9952
+ });
9953
+ }
9954
+ if (content.includes("CREATE TABLE") && !content.includes("IF NOT EXISTS")) {
9955
+ issues.push({
9956
+ file,
9957
+ type: "warning",
9958
+ message: "CREATE TABLE without IF NOT EXISTS (not idempotent)"
9959
+ });
9960
+ }
9961
+ }
9962
+ if (issues.length === 0) {
9963
+ console.log(
9964
+ `āœ… All ${files.length} SQL migration(s) verified successfully`
9965
+ );
9966
+ } else {
9967
+ const byFile = /* @__PURE__ */ new Map();
9968
+ for (const issue of issues) {
9969
+ if (!byFile.has(issue.file)) byFile.set(issue.file, []);
9970
+ byFile.get(issue.file).push(issue);
9971
+ }
9972
+ for (const [file, fileIssues] of byFile) {
9973
+ console.log(`šŸ“„ ${file}`);
9974
+ for (const issue of fileIssues) {
9975
+ const icon = issue.type === "error" ? "āŒ" : "āš ļø";
9976
+ console.log(` ${icon} ${issue.message}`);
9977
+ }
9978
+ console.log("");
9979
+ }
9980
+ console.log("─".repeat(SEPARATOR_WIDTH));
9981
+ console.log(
9982
+ `Summary: ${files.length} file(s), ${issues.filter((i) => i.type === "error").length} error(s), ${issues.filter((i) => i.type === "warning").length} warning(s)`
9983
+ );
9984
+ if (options.fix) {
9985
+ console.log("\nšŸ’” Suggestions:");
9986
+ console.log(" - Add '-- DOWN' marker followed by rollback SQL");
9987
+ console.log(" - Use 'IF EXISTS' with DROP statements");
9988
+ console.log(" - Use 'IF NOT EXISTS' with CREATE statements");
9989
+ console.log(
9990
+ " - Run 'plyaz-db migrate generate-down' to auto-generate DOWN sections"
9991
+ );
9992
+ }
9993
+ }
9994
+ process.exit(hasErrors ? 1 : 0);
9995
+ } catch (error) {
9996
+ console.error("āŒ Error:", error.message);
9997
+ process.exit(1);
9998
+ }
9999
+ });
9799
10000
  program.parse();
9800
10001
  //# sourceMappingURL=index.js.map
9801
10002
  //# sourceMappingURL=index.js.map