@plyaz/db 0.3.0 → 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 +593 -23
- package/dist/cli/index.js.map +1 -1
- package/package.json +3 -2
- package/template/migrations/migration.sql.template +29 -0
- package/template/migrations/migration.ts.template +54 -0
- package/template/seeds/seed.csv.template +22 -0
- package/template/seeds/seed.sql.template +19 -0
- package/template/seeds/seed.ts.template +63 -0
package/dist/cli/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import * as
|
|
2
|
+
import * as fs4 from 'fs';
|
|
3
3
|
import * as path4 from 'path';
|
|
4
4
|
import { Command } from 'commander';
|
|
5
5
|
import { DatabaseError } from '@plyaz/errors';
|
|
@@ -13,8 +13,10 @@ import { Pool } from 'pg';
|
|
|
13
13
|
import { eq, sql, isNotNull, isNull, between, like, not, inArray, lte, lt, gte, gt, asc, desc } from 'drizzle-orm';
|
|
14
14
|
import { PgColumn } from 'drizzle-orm/pg-core';
|
|
15
15
|
import { createClient } from '@supabase/supabase-js';
|
|
16
|
-
import { randomBytes, createCipheriv, createDecipheriv } from 'crypto';
|
|
17
|
-
import
|
|
16
|
+
import { randomBytes, createCipheriv, createDecipheriv, randomUUID } from 'crypto';
|
|
17
|
+
import * as readline from 'readline';
|
|
18
|
+
import { pathToFileURL, fileURLToPath } from 'url';
|
|
19
|
+
import { execSync } from 'child_process';
|
|
18
20
|
|
|
19
21
|
var __defProp = Object.defineProperty;
|
|
20
22
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -59,7 +61,7 @@ function suggestDownMigration(sqlContent, filename) {
|
|
|
59
61
|
};
|
|
60
62
|
}
|
|
61
63
|
function addDownSection(filePath) {
|
|
62
|
-
const content =
|
|
64
|
+
const content = fs4.readFileSync(filePath, "utf-8");
|
|
63
65
|
if (content.includes("-- DOWN")) {
|
|
64
66
|
return content;
|
|
65
67
|
}
|
|
@@ -68,16 +70,16 @@ function addDownSection(filePath) {
|
|
|
68
70
|
return content + downSection;
|
|
69
71
|
}
|
|
70
72
|
function processDirectory(migrationsPath, dryRun = true) {
|
|
71
|
-
if (!
|
|
73
|
+
if (!fs4.existsSync(migrationsPath)) {
|
|
72
74
|
console.error(`Migration directory not found: ${migrationsPath}`);
|
|
73
75
|
return;
|
|
74
76
|
}
|
|
75
|
-
const files =
|
|
77
|
+
const files = fs4.readdirSync(migrationsPath).filter((f) => f.endsWith(".sql"));
|
|
76
78
|
console.log(`Found ${files.length} SQL migration files
|
|
77
79
|
`);
|
|
78
80
|
for (const file of files) {
|
|
79
81
|
const filePath = path4.join(migrationsPath, file);
|
|
80
|
-
const content =
|
|
82
|
+
const content = fs4.readFileSync(filePath, "utf-8");
|
|
81
83
|
if (content.includes("-- DOWN")) {
|
|
82
84
|
console.log(`✓ ${file} - Already has DOWN section`);
|
|
83
85
|
continue;
|
|
@@ -91,7 +93,7 @@ function processDirectory(migrationsPath, dryRun = true) {
|
|
|
91
93
|
const downSection = newContent.split("-- DOWN")[1];
|
|
92
94
|
console.log(downSection);
|
|
93
95
|
} else {
|
|
94
|
-
|
|
96
|
+
fs4.writeFileSync(filePath, newContent, "utf-8");
|
|
95
97
|
console.log(`✓ ${file} - Added DOWN section`);
|
|
96
98
|
}
|
|
97
99
|
}
|
|
@@ -258,12 +260,12 @@ var MigrationManager = class {
|
|
|
258
260
|
* Discover migration files from migrations directory (including subdirectories)
|
|
259
261
|
*/
|
|
260
262
|
async discoverMigrations() {
|
|
261
|
-
if (!
|
|
263
|
+
if (!fs4.existsSync(this.migrationsPath)) {
|
|
262
264
|
return [];
|
|
263
265
|
}
|
|
264
266
|
const migrations = [];
|
|
265
267
|
const scanDirectory = /* @__PURE__ */ __name((dir) => {
|
|
266
|
-
const entries =
|
|
268
|
+
const entries = fs4.readdirSync(dir, { withFileTypes: true });
|
|
267
269
|
for (const entry of entries) {
|
|
268
270
|
const fullPath = path4.join(dir, entry.name);
|
|
269
271
|
if (entry.isDirectory()) {
|
|
@@ -426,7 +428,7 @@ var MigrationManager = class {
|
|
|
426
428
|
* Load SQL migration from file
|
|
427
429
|
*/
|
|
428
430
|
loadSqlMigration(migrationFile) {
|
|
429
|
-
const sql2 =
|
|
431
|
+
const sql2 = fs4.readFileSync(migrationFile.filePath, "utf-8");
|
|
430
432
|
const { upSQL, downSQL } = this.parseSqlSections(sql2);
|
|
431
433
|
return {
|
|
432
434
|
version: migrationFile.version,
|
|
@@ -770,10 +772,10 @@ var SeedManager = class {
|
|
|
770
772
|
* Discover seed files from seeds directory
|
|
771
773
|
*/
|
|
772
774
|
async discoverSeeds() {
|
|
773
|
-
if (!
|
|
775
|
+
if (!fs4.existsSync(this.seedsPath)) {
|
|
774
776
|
return [];
|
|
775
777
|
}
|
|
776
|
-
const files =
|
|
778
|
+
const files = fs4.readdirSync(this.seedsPath);
|
|
777
779
|
const seeds = [];
|
|
778
780
|
for (const file of files) {
|
|
779
781
|
const match = file.match(/^(\d+)_(.+)\.(ts|js|sql)$/);
|
|
@@ -903,7 +905,7 @@ var SeedManager = class {
|
|
|
903
905
|
* Load SQL seed from file
|
|
904
906
|
*/
|
|
905
907
|
loadSqlSeed(seedFile) {
|
|
906
|
-
const sql2 =
|
|
908
|
+
const sql2 = fs4.readFileSync(seedFile.filePath, "utf-8");
|
|
907
909
|
return {
|
|
908
910
|
name: seedFile.name,
|
|
909
911
|
run: /* @__PURE__ */ __name(async () => {
|
|
@@ -8267,6 +8269,77 @@ async function createDatabaseService(config) {
|
|
|
8267
8269
|
}
|
|
8268
8270
|
__name(createDatabaseService, "createDatabaseService");
|
|
8269
8271
|
var JSON_INDENT_SPACES = 2;
|
|
8272
|
+
var VERSION_PAD_LENGTH = 3;
|
|
8273
|
+
var DECIMAL_RADIX = 10;
|
|
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");
|
|
8270
8343
|
async function getUserSchemas(adapter) {
|
|
8271
8344
|
const result = await adapter.query?.(`
|
|
8272
8345
|
SELECT schema_name FROM information_schema.schemata
|
|
@@ -8327,8 +8400,8 @@ function parseEnvLine(line) {
|
|
|
8327
8400
|
}
|
|
8328
8401
|
__name(parseEnvLine, "parseEnvLine");
|
|
8329
8402
|
function loadEnvFile(envFilePath) {
|
|
8330
|
-
if (!
|
|
8331
|
-
const envContent =
|
|
8403
|
+
if (!fs4.existsSync(envFilePath)) return;
|
|
8404
|
+
const envContent = fs4.readFileSync(envFilePath, "utf-8");
|
|
8332
8405
|
for (const line of envContent.split("\n")) {
|
|
8333
8406
|
const parsed = parseEnvLine(line);
|
|
8334
8407
|
if (parsed) {
|
|
@@ -8361,7 +8434,7 @@ __name(loadConfigFromPath, "loadConfigFromPath");
|
|
|
8361
8434
|
async function findAndLoadConfig() {
|
|
8362
8435
|
for (const configName of CONFIG_PATHS) {
|
|
8363
8436
|
const configFilePath = path4.join(process.cwd(), configName);
|
|
8364
|
-
if (
|
|
8437
|
+
if (fs4.existsSync(configFilePath)) {
|
|
8365
8438
|
return loadConfigFromPath(configFilePath);
|
|
8366
8439
|
}
|
|
8367
8440
|
}
|
|
@@ -8371,7 +8444,7 @@ __name(findAndLoadConfig, "findAndLoadConfig");
|
|
|
8371
8444
|
function warnAboutTypeScriptConfigs() {
|
|
8372
8445
|
for (const tsName of TS_CONFIG_PATHS) {
|
|
8373
8446
|
const tsPath = path4.join(process.cwd(), tsName);
|
|
8374
|
-
if (
|
|
8447
|
+
if (fs4.existsSync(tsPath)) {
|
|
8375
8448
|
console.warn(
|
|
8376
8449
|
`⚠️ Found ${path4.basename(tsPath)} but cannot import TypeScript files directly.`
|
|
8377
8450
|
);
|
|
@@ -8406,7 +8479,7 @@ async function loadConfig(configPath) {
|
|
|
8406
8479
|
const explicitPath = configPath ?? globalConfigPath;
|
|
8407
8480
|
if (explicitPath) {
|
|
8408
8481
|
const resolvedPath = path4.resolve(process.cwd(), explicitPath);
|
|
8409
|
-
if (
|
|
8482
|
+
if (fs4.existsSync(resolvedPath)) {
|
|
8410
8483
|
return loadConfigFromPath(resolvedPath);
|
|
8411
8484
|
}
|
|
8412
8485
|
console.error(`❌ Config file not found: ${resolvedPath}`);
|
|
@@ -8489,16 +8562,15 @@ migrateCommand.command("up").description("Run pending migrations").option("-t, -
|
|
|
8489
8562
|
});
|
|
8490
8563
|
migrateCommand.command("down").description("Rollback migrations").option("-s, --steps <number>", "Number of migrations to rollback", "1").action(async (options) => {
|
|
8491
8564
|
try {
|
|
8565
|
+
const steps = validateSteps(options.steps);
|
|
8492
8566
|
const { adapter, config } = await initDatabase(void 0, true);
|
|
8493
8567
|
const migrationManager = new MigrationManager({
|
|
8494
8568
|
adapter,
|
|
8495
8569
|
migrationsPath: config.migrationsPath ?? "./migrations",
|
|
8496
8570
|
tableName: config.migrationsTable ?? "schema_migrations"
|
|
8497
8571
|
});
|
|
8498
|
-
console.log(`🔄 Rolling back ${
|
|
8499
|
-
const result = await migrationManager.down(
|
|
8500
|
-
Number.parseInt(options.steps, 10)
|
|
8501
|
-
);
|
|
8572
|
+
console.log(`🔄 Rolling back ${steps} migration(s)...`);
|
|
8573
|
+
const result = await migrationManager.down(steps);
|
|
8502
8574
|
if (result.success) {
|
|
8503
8575
|
console.log(`✅ Rolled back ${result.value} migration(s)`);
|
|
8504
8576
|
process.exit(0);
|
|
@@ -9226,6 +9298,504 @@ program.command("reset").description(
|
|
|
9226
9298
|
process.exit(1);
|
|
9227
9299
|
}
|
|
9228
9300
|
});
|
|
9301
|
+
function createReadlineInterface() {
|
|
9302
|
+
return readline.createInterface({
|
|
9303
|
+
input: process.stdin,
|
|
9304
|
+
output: process.stdout
|
|
9305
|
+
});
|
|
9306
|
+
}
|
|
9307
|
+
__name(createReadlineInterface, "createReadlineInterface");
|
|
9308
|
+
async function promptInput(message, defaultValue) {
|
|
9309
|
+
const rl = createReadlineInterface();
|
|
9310
|
+
const prompt = defaultValue ? `${message} (${defaultValue}): ` : `${message}: `;
|
|
9311
|
+
return new Promise((resolve4) => {
|
|
9312
|
+
rl.question(prompt, (answer) => {
|
|
9313
|
+
rl.close();
|
|
9314
|
+
const trimmed = answer.trim();
|
|
9315
|
+
resolve4(trimmed !== "" ? trimmed : defaultValue ?? "");
|
|
9316
|
+
});
|
|
9317
|
+
});
|
|
9318
|
+
}
|
|
9319
|
+
__name(promptInput, "promptInput");
|
|
9320
|
+
async function promptSelect(message, options) {
|
|
9321
|
+
const rl = createReadlineInterface();
|
|
9322
|
+
console.log(message);
|
|
9323
|
+
options.forEach((opt, i) => console.log(` ${i + 1}) ${opt}`));
|
|
9324
|
+
return new Promise((resolve4) => {
|
|
9325
|
+
rl.question("Select (number): ", (answer) => {
|
|
9326
|
+
rl.close();
|
|
9327
|
+
const index = Number.parseInt(answer, DECIMAL_RADIX) - 1;
|
|
9328
|
+
resolve4(options[index] ?? options[0]);
|
|
9329
|
+
});
|
|
9330
|
+
});
|
|
9331
|
+
}
|
|
9332
|
+
__name(promptSelect, "promptSelect");
|
|
9333
|
+
async function promptConfirm(message) {
|
|
9334
|
+
const rl = createReadlineInterface();
|
|
9335
|
+
return new Promise((resolve4) => {
|
|
9336
|
+
rl.question(`${message} (y/N): `, (answer) => {
|
|
9337
|
+
rl.close();
|
|
9338
|
+
resolve4(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
|
|
9339
|
+
});
|
|
9340
|
+
});
|
|
9341
|
+
}
|
|
9342
|
+
__name(promptConfirm, "promptConfirm");
|
|
9343
|
+
async function runInteractiveMigration(config) {
|
|
9344
|
+
console.log();
|
|
9345
|
+
console.log("📦 Create Migration - Interactive Mode");
|
|
9346
|
+
console.log("─".repeat(SEPARATOR_WIDTH));
|
|
9347
|
+
console.log();
|
|
9348
|
+
const name = await promptInput("Migration name (e.g., add_users_table)");
|
|
9349
|
+
if (!name) {
|
|
9350
|
+
console.error("❌ Migration name is required");
|
|
9351
|
+
process.exit(1);
|
|
9352
|
+
}
|
|
9353
|
+
validateName(name, "migration");
|
|
9354
|
+
const snakeName = toSnakeCase(name);
|
|
9355
|
+
const tableName = await promptInput("Table name", snakeName);
|
|
9356
|
+
const schema = await promptInput("Database schema", "public");
|
|
9357
|
+
validateSchemaName(schema);
|
|
9358
|
+
const typeChoice = await promptSelect("File type:", [
|
|
9359
|
+
"SQL (.sql)",
|
|
9360
|
+
"TypeScript (.ts)"
|
|
9361
|
+
]);
|
|
9362
|
+
const type = typeChoice.includes("TypeScript") ? "ts" : "sql";
|
|
9363
|
+
const gitUser = getGitUsername();
|
|
9364
|
+
const includeAuthor = gitUser ? await promptConfirm(`Include author (${gitUser})?`) : false;
|
|
9365
|
+
const author = includeAuthor ? gitUser : "";
|
|
9366
|
+
console.log();
|
|
9367
|
+
console.log("─".repeat(SEPARATOR_WIDTH));
|
|
9368
|
+
console.log(`Migration: ${name}`);
|
|
9369
|
+
console.log(`Table: ${schema}.${tableName}`);
|
|
9370
|
+
console.log(`Type: ${type.toUpperCase()}`);
|
|
9371
|
+
if (author) console.log(`Author: ${author}`);
|
|
9372
|
+
console.log("─".repeat(SEPARATOR_WIDTH));
|
|
9373
|
+
const confirm = await promptConfirm("Create migration?");
|
|
9374
|
+
if (!confirm) {
|
|
9375
|
+
console.log("Cancelled.");
|
|
9376
|
+
process.exit(0);
|
|
9377
|
+
}
|
|
9378
|
+
const migrationsPath = config.migrationsPath ?? "./migrations";
|
|
9379
|
+
if (!fs4.existsSync(migrationsPath)) {
|
|
9380
|
+
fs4.mkdirSync(migrationsPath, { recursive: true });
|
|
9381
|
+
console.log(`📁 Created directory: ${migrationsPath}`);
|
|
9382
|
+
}
|
|
9383
|
+
const version = getNextMigrationVersion(migrationsPath);
|
|
9384
|
+
const filename = `${version}_${snakeName}.${type}`;
|
|
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
|
+
}
|
|
9395
|
+
const content = getMigrationTemplate({
|
|
9396
|
+
name,
|
|
9397
|
+
version,
|
|
9398
|
+
tableName,
|
|
9399
|
+
schema,
|
|
9400
|
+
author,
|
|
9401
|
+
type
|
|
9402
|
+
});
|
|
9403
|
+
fs4.writeFileSync(filepath, content, "utf-8");
|
|
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
|
+
);
|
|
9419
|
+
}
|
|
9420
|
+
__name(runInteractiveMigration, "runInteractiveMigration");
|
|
9421
|
+
async function runInteractiveSeed(config) {
|
|
9422
|
+
console.log();
|
|
9423
|
+
console.log("🌱 Create Seed - Interactive Mode");
|
|
9424
|
+
console.log("─".repeat(SEPARATOR_WIDTH));
|
|
9425
|
+
console.log();
|
|
9426
|
+
const name = await promptInput("Seed name (e.g., admin_users)");
|
|
9427
|
+
if (!name) {
|
|
9428
|
+
console.error("❌ Seed name is required");
|
|
9429
|
+
process.exit(1);
|
|
9430
|
+
}
|
|
9431
|
+
validateName(name, "seed");
|
|
9432
|
+
const snakeName = toSnakeCase(name);
|
|
9433
|
+
const tableName = await promptInput("Table name", snakeName);
|
|
9434
|
+
const schema = await promptInput("Database schema", "public");
|
|
9435
|
+
validateSchemaName(schema);
|
|
9436
|
+
const typeChoice = await promptSelect("File type:", [
|
|
9437
|
+
"TypeScript (.ts)",
|
|
9438
|
+
"SQL (.sql)",
|
|
9439
|
+
"CSV (.csv)"
|
|
9440
|
+
]);
|
|
9441
|
+
const typeMap = {
|
|
9442
|
+
"TypeScript (.ts)": "ts",
|
|
9443
|
+
"SQL (.sql)": "sql",
|
|
9444
|
+
"CSV (.csv)": "csv"
|
|
9445
|
+
};
|
|
9446
|
+
const type = typeMap[typeChoice] ?? "ts";
|
|
9447
|
+
let author = "";
|
|
9448
|
+
if (type !== "csv") {
|
|
9449
|
+
const gitUser = getGitUsername();
|
|
9450
|
+
const includeAuthor = gitUser ? await promptConfirm(`Include author (${gitUser})?`) : false;
|
|
9451
|
+
author = includeAuthor ? gitUser : "";
|
|
9452
|
+
}
|
|
9453
|
+
console.log();
|
|
9454
|
+
console.log("─".repeat(SEPARATOR_WIDTH));
|
|
9455
|
+
console.log(`Seed: ${name}`);
|
|
9456
|
+
console.log(`Table: ${schema}.${tableName}`);
|
|
9457
|
+
console.log(`Type: ${type.toUpperCase()}`);
|
|
9458
|
+
if (author) console.log(`Author: ${author}`);
|
|
9459
|
+
console.log("─".repeat(SEPARATOR_WIDTH));
|
|
9460
|
+
const confirm = await promptConfirm("Create seed?");
|
|
9461
|
+
if (!confirm) {
|
|
9462
|
+
console.log("Cancelled.");
|
|
9463
|
+
process.exit(0);
|
|
9464
|
+
}
|
|
9465
|
+
const seedsPath = config.seedsPath ?? "./seeds";
|
|
9466
|
+
if (!fs4.existsSync(seedsPath)) {
|
|
9467
|
+
fs4.mkdirSync(seedsPath, { recursive: true });
|
|
9468
|
+
console.log(`📁 Created directory: ${seedsPath}`);
|
|
9469
|
+
}
|
|
9470
|
+
const order = getNextSeedOrder(seedsPath);
|
|
9471
|
+
const filename = `${order}_${snakeName}.${type}`;
|
|
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
|
+
}
|
|
9482
|
+
let content;
|
|
9483
|
+
if (type === "csv") {
|
|
9484
|
+
content = getCsvSeedTemplate({ name, tableName, schema });
|
|
9485
|
+
} else {
|
|
9486
|
+
content = getSeedTemplate({
|
|
9487
|
+
name,
|
|
9488
|
+
order,
|
|
9489
|
+
tableName,
|
|
9490
|
+
schema,
|
|
9491
|
+
author,
|
|
9492
|
+
type
|
|
9493
|
+
});
|
|
9494
|
+
}
|
|
9495
|
+
fs4.writeFileSync(filepath, content, "utf-8");
|
|
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`);
|
|
9509
|
+
}
|
|
9510
|
+
__name(runInteractiveSeed, "runInteractiveSeed");
|
|
9511
|
+
function getNextMigrationVersion(migrationsPath) {
|
|
9512
|
+
if (!fs4.existsSync(migrationsPath)) {
|
|
9513
|
+
return "001";
|
|
9514
|
+
}
|
|
9515
|
+
const files = fs4.readdirSync(migrationsPath);
|
|
9516
|
+
const versions = files.map((f) => {
|
|
9517
|
+
const match = f.match(/^(\d+)_/);
|
|
9518
|
+
return match ? Number.parseInt(match[1], DECIMAL_RADIX) : 0;
|
|
9519
|
+
}).filter((v) => v > 0);
|
|
9520
|
+
const maxVersion = versions.length > 0 ? Math.max(...versions) : 0;
|
|
9521
|
+
return String(maxVersion + 1).padStart(VERSION_PAD_LENGTH, "0");
|
|
9522
|
+
}
|
|
9523
|
+
__name(getNextMigrationVersion, "getNextMigrationVersion");
|
|
9524
|
+
function getNextSeedOrder(seedsPath) {
|
|
9525
|
+
if (!fs4.existsSync(seedsPath)) {
|
|
9526
|
+
return "001";
|
|
9527
|
+
}
|
|
9528
|
+
const files = fs4.readdirSync(seedsPath);
|
|
9529
|
+
const orders = files.map((f) => {
|
|
9530
|
+
const match = f.match(/^(\d+)_/);
|
|
9531
|
+
return match ? Number.parseInt(match[1], DECIMAL_RADIX) : 0;
|
|
9532
|
+
}).filter((v) => v > 0);
|
|
9533
|
+
const maxOrder = orders.length > 0 ? Math.max(...orders) : 0;
|
|
9534
|
+
return String(maxOrder + 1).padStart(VERSION_PAD_LENGTH, "0");
|
|
9535
|
+
}
|
|
9536
|
+
__name(getNextSeedOrder, "getNextSeedOrder");
|
|
9537
|
+
function toSnakeCase(name) {
|
|
9538
|
+
return name.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "").replace(/[^a-z0-9_]/g, "_").replace(/_+/g, "_");
|
|
9539
|
+
}
|
|
9540
|
+
__name(toSnakeCase, "toSnakeCase");
|
|
9541
|
+
function toPascalCase(name) {
|
|
9542
|
+
return name.split(/[_\s-]+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
|
|
9543
|
+
}
|
|
9544
|
+
__name(toPascalCase, "toPascalCase");
|
|
9545
|
+
function getTemplateDir() {
|
|
9546
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
9547
|
+
const currentDir = path4.dirname(currentFile);
|
|
9548
|
+
const packageRoot = path4.resolve(currentDir, "..", "..");
|
|
9549
|
+
return path4.join(packageRoot, "template");
|
|
9550
|
+
}
|
|
9551
|
+
__name(getTemplateDir, "getTemplateDir");
|
|
9552
|
+
function loadTemplate(templatePath, vars) {
|
|
9553
|
+
const fullPath = path4.join(getTemplateDir(), templatePath);
|
|
9554
|
+
if (!fs4.existsSync(fullPath)) {
|
|
9555
|
+
throw new Error(`Template not found: ${fullPath}`);
|
|
9556
|
+
}
|
|
9557
|
+
let content = fs4.readFileSync(fullPath, "utf-8");
|
|
9558
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
9559
|
+
const regex = new RegExp(`\\{\\{${key}\\}\\}`, "g");
|
|
9560
|
+
content = content.replace(regex, value ?? "");
|
|
9561
|
+
}
|
|
9562
|
+
return content;
|
|
9563
|
+
}
|
|
9564
|
+
__name(loadTemplate, "loadTemplate");
|
|
9565
|
+
function getGitUsername() {
|
|
9566
|
+
try {
|
|
9567
|
+
return execSync("git config user.name", { encoding: "utf-8" }).trim();
|
|
9568
|
+
} catch {
|
|
9569
|
+
return "";
|
|
9570
|
+
}
|
|
9571
|
+
}
|
|
9572
|
+
__name(getGitUsername, "getGitUsername");
|
|
9573
|
+
function getMigrationTemplate(options) {
|
|
9574
|
+
const { name, version, tableName, schema, author, type } = options;
|
|
9575
|
+
const description = toPascalCase(name);
|
|
9576
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
9577
|
+
const vars = {
|
|
9578
|
+
VERSION: version,
|
|
9579
|
+
DESCRIPTION: description,
|
|
9580
|
+
TABLE_NAME: tableName,
|
|
9581
|
+
SCHEMA: schema,
|
|
9582
|
+
CREATED_DATE: date,
|
|
9583
|
+
AUTHOR: author
|
|
9584
|
+
};
|
|
9585
|
+
const templateFile = type === "ts" ? "migrations/migration.ts.template" : "migrations/migration.sql.template";
|
|
9586
|
+
return loadTemplate(templateFile, vars);
|
|
9587
|
+
}
|
|
9588
|
+
__name(getMigrationTemplate, "getMigrationTemplate");
|
|
9589
|
+
function getSeedTemplate(options) {
|
|
9590
|
+
const { name, order, tableName, schema, author, type } = options;
|
|
9591
|
+
const description = toPascalCase(name);
|
|
9592
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
9593
|
+
const vars = {
|
|
9594
|
+
ORDER: order,
|
|
9595
|
+
DESCRIPTION: description,
|
|
9596
|
+
TABLE_NAME: tableName,
|
|
9597
|
+
SCHEMA: schema,
|
|
9598
|
+
CREATED_DATE: date,
|
|
9599
|
+
AUTHOR: author,
|
|
9600
|
+
UUID_1: randomUUID(),
|
|
9601
|
+
UUID_2: randomUUID(),
|
|
9602
|
+
UUID_3: randomUUID()
|
|
9603
|
+
};
|
|
9604
|
+
const templateFile = type === "ts" ? "seeds/seed.ts.template" : "seeds/seed.sql.template";
|
|
9605
|
+
return loadTemplate(templateFile, vars);
|
|
9606
|
+
}
|
|
9607
|
+
__name(getSeedTemplate, "getSeedTemplate");
|
|
9608
|
+
function getCsvSeedTemplate(options) {
|
|
9609
|
+
const { name, tableName, schema } = options;
|
|
9610
|
+
const description = toPascalCase(name);
|
|
9611
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
9612
|
+
const vars = {
|
|
9613
|
+
DESCRIPTION: description,
|
|
9614
|
+
TABLE_NAME: tableName,
|
|
9615
|
+
SCHEMA: schema,
|
|
9616
|
+
CREATED_DATE: date,
|
|
9617
|
+
AUTHOR: "",
|
|
9618
|
+
UUID_1: randomUUID(),
|
|
9619
|
+
UUID_2: randomUUID(),
|
|
9620
|
+
UUID_3: randomUUID()
|
|
9621
|
+
};
|
|
9622
|
+
try {
|
|
9623
|
+
return loadTemplate("seeds/seed.csv.template", vars);
|
|
9624
|
+
} catch {
|
|
9625
|
+
return `id,name,created_at
|
|
9626
|
+
${randomUUID()},Example 1,${(/* @__PURE__ */ new Date()).toISOString()}
|
|
9627
|
+
${randomUUID()},Example 2,${(/* @__PURE__ */ new Date()).toISOString()}
|
|
9628
|
+
`;
|
|
9629
|
+
}
|
|
9630
|
+
}
|
|
9631
|
+
__name(getCsvSeedTemplate, "getCsvSeedTemplate");
|
|
9632
|
+
var createCommand = program.command("create").description("Create new migration or seed files");
|
|
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(
|
|
9634
|
+
"-T, --table <table>",
|
|
9635
|
+
"Table name (default: derived from migration name)"
|
|
9636
|
+
).option(
|
|
9637
|
+
"-s, --schema <schema>",
|
|
9638
|
+
"Database schema (default: public)",
|
|
9639
|
+
"public"
|
|
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(
|
|
9644
|
+
async (name, options) => {
|
|
9645
|
+
try {
|
|
9646
|
+
const config = await loadConfig();
|
|
9647
|
+
if (!name || options.interactive) {
|
|
9648
|
+
await runInteractiveMigration(config);
|
|
9649
|
+
process.exit(0);
|
|
9650
|
+
}
|
|
9651
|
+
validateName(name, "migration");
|
|
9652
|
+
validateSchemaName(options.schema);
|
|
9653
|
+
const fileType = validateMigrationType(options.type);
|
|
9654
|
+
const migrationsPath = config.migrationsPath ?? "./migrations";
|
|
9655
|
+
if (!options.dryRun && !fs4.existsSync(migrationsPath)) {
|
|
9656
|
+
fs4.mkdirSync(migrationsPath, { recursive: true });
|
|
9657
|
+
console.log(`📁 Created directory: ${migrationsPath}`);
|
|
9658
|
+
}
|
|
9659
|
+
const version = getNextMigrationVersion(migrationsPath);
|
|
9660
|
+
const snakeName = toSnakeCase(name);
|
|
9661
|
+
const tableName = options.table ?? snakeName;
|
|
9662
|
+
const schema = options.schema;
|
|
9663
|
+
const author = options.author ? getGitUsername() : "";
|
|
9664
|
+
const filename = `${version}_${snakeName}.${fileType}`;
|
|
9665
|
+
const filepath = path4.join(migrationsPath, filename);
|
|
9666
|
+
if (fs4.existsSync(filepath) && !options.force) {
|
|
9667
|
+
console.error(`❌ File already exists: ${filepath}`);
|
|
9668
|
+
console.error(" Use --force to overwrite");
|
|
9669
|
+
process.exit(1);
|
|
9670
|
+
}
|
|
9671
|
+
const content = getMigrationTemplate({
|
|
9672
|
+
name,
|
|
9673
|
+
version,
|
|
9674
|
+
tableName,
|
|
9675
|
+
schema,
|
|
9676
|
+
author,
|
|
9677
|
+
type: fileType
|
|
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
|
+
}
|
|
9694
|
+
fs4.writeFileSync(filepath, content, "utf-8");
|
|
9695
|
+
console.log(`✅ Created migration: ${filename}`);
|
|
9696
|
+
console.log(` Path: ${filepath}`);
|
|
9697
|
+
console.log(` Type: ${fileType.toUpperCase()}`);
|
|
9698
|
+
console.log(` Table: ${schema}.${tableName}`);
|
|
9699
|
+
if (author) {
|
|
9700
|
+
console.log(` Author: ${author}`);
|
|
9701
|
+
}
|
|
9702
|
+
console.log("");
|
|
9703
|
+
console.log("📝 Next steps:");
|
|
9704
|
+
console.log(` 1. Edit the migration file to add your schema changes`);
|
|
9705
|
+
console.log(` 2. Run 'plyaz-db migrate up' to apply the migration`);
|
|
9706
|
+
console.log(
|
|
9707
|
+
` 3. Test rollback with 'plyaz-db migrate down' before deploying`
|
|
9708
|
+
);
|
|
9709
|
+
process.exit(0);
|
|
9710
|
+
} catch (error) {
|
|
9711
|
+
console.error("❌ Error:", error.message);
|
|
9712
|
+
process.exit(1);
|
|
9713
|
+
}
|
|
9714
|
+
}
|
|
9715
|
+
);
|
|
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(
|
|
9717
|
+
"-s, --schema <schema>",
|
|
9718
|
+
"Database schema (default: public)",
|
|
9719
|
+
"public"
|
|
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(
|
|
9724
|
+
async (name, options) => {
|
|
9725
|
+
try {
|
|
9726
|
+
const config = await loadConfig();
|
|
9727
|
+
if (!name || options.interactive) {
|
|
9728
|
+
await runInteractiveSeed(config);
|
|
9729
|
+
process.exit(0);
|
|
9730
|
+
}
|
|
9731
|
+
validateName(name, "seed");
|
|
9732
|
+
validateSchemaName(options.schema);
|
|
9733
|
+
const fileType = validateSeedType(options.type);
|
|
9734
|
+
const seedsPath = config.seedsPath ?? "./seeds";
|
|
9735
|
+
if (!options.dryRun && !fs4.existsSync(seedsPath)) {
|
|
9736
|
+
fs4.mkdirSync(seedsPath, { recursive: true });
|
|
9737
|
+
console.log(`📁 Created directory: ${seedsPath}`);
|
|
9738
|
+
}
|
|
9739
|
+
const order = getNextSeedOrder(seedsPath);
|
|
9740
|
+
const snakeName = toSnakeCase(name);
|
|
9741
|
+
const tableName = options.table ?? snakeName;
|
|
9742
|
+
const schema = options.schema;
|
|
9743
|
+
const author = options.author ? getGitUsername() : "";
|
|
9744
|
+
const filename = `${order}_${snakeName}.${fileType}`;
|
|
9745
|
+
const filepath = path4.join(seedsPath, filename);
|
|
9746
|
+
if (fs4.existsSync(filepath) && !options.force) {
|
|
9747
|
+
console.error(`❌ File already exists: ${filepath}`);
|
|
9748
|
+
console.error(" Use --force to overwrite");
|
|
9749
|
+
process.exit(1);
|
|
9750
|
+
}
|
|
9751
|
+
let content;
|
|
9752
|
+
if (fileType === "csv") {
|
|
9753
|
+
content = getCsvSeedTemplate({ name, tableName, schema });
|
|
9754
|
+
} else {
|
|
9755
|
+
content = getSeedTemplate({
|
|
9756
|
+
name,
|
|
9757
|
+
order,
|
|
9758
|
+
tableName,
|
|
9759
|
+
schema,
|
|
9760
|
+
author,
|
|
9761
|
+
type: fileType
|
|
9762
|
+
});
|
|
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
|
+
}
|
|
9779
|
+
fs4.writeFileSync(filepath, content, "utf-8");
|
|
9780
|
+
console.log(`✅ Created seed: ${filename}`);
|
|
9781
|
+
console.log(` Path: ${filepath}`);
|
|
9782
|
+
console.log(` Type: ${fileType.toUpperCase()}`);
|
|
9783
|
+
console.log(` Table: ${schema}.${tableName}`);
|
|
9784
|
+
if (author && fileType !== "csv") {
|
|
9785
|
+
console.log(` Author: ${author}`);
|
|
9786
|
+
}
|
|
9787
|
+
console.log("");
|
|
9788
|
+
console.log("📝 Next steps:");
|
|
9789
|
+
console.log(` 1. Edit the seed file to add your data`);
|
|
9790
|
+
console.log(` 2. Run 'plyaz-db seed run' to execute seeds`);
|
|
9791
|
+
console.log(` 3. Use 'plyaz-db seed status' to check seed status`);
|
|
9792
|
+
process.exit(0);
|
|
9793
|
+
} catch (error) {
|
|
9794
|
+
console.error("❌ Error:", error.message);
|
|
9795
|
+
process.exit(1);
|
|
9796
|
+
}
|
|
9797
|
+
}
|
|
9798
|
+
);
|
|
9229
9799
|
program.parse();
|
|
9230
9800
|
//# sourceMappingURL=index.js.map
|
|
9231
9801
|
//# sourceMappingURL=index.js.map
|