@plyaz/db 0.2.0 → 0.3.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 +474 -28
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +24 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +24 -3
- package/dist/index.mjs.map +1 -1
- package/dist/migrations/MigrationManager.d.ts.map +1 -1
- package/dist/repository/BaseRepository.d.ts +9 -0
- package/dist/repository/BaseRepository.d.ts.map +1 -1
- package/dist/seeds/SeedManager.d.ts.map +1 -1
- package/package.json +2 -2
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()) {
|
|
@@ -347,7 +349,11 @@ var MigrationManager = class {
|
|
|
347
349
|
const isEmptyOrComment = trimmedLine === "" || trimmedLine.startsWith("--");
|
|
348
350
|
current += line + "\n";
|
|
349
351
|
if (isEmptyOrComment) continue;
|
|
350
|
-
const dollarState = this.processDollarDelimiters(
|
|
352
|
+
const dollarState = this.processDollarDelimiters(
|
|
353
|
+
line,
|
|
354
|
+
inDollarBlock,
|
|
355
|
+
dollarTag
|
|
356
|
+
);
|
|
351
357
|
inDollarBlock = dollarState.inDollarBlock;
|
|
352
358
|
dollarTag = dollarState.dollarTag;
|
|
353
359
|
const isEndOfStatement = !inDollarBlock && trimmedLine.endsWith(";");
|
|
@@ -398,7 +404,9 @@ var MigrationManager = class {
|
|
|
398
404
|
await adapter.query(statement);
|
|
399
405
|
const isInterval = (i + 1) % PROGRESS_LOG_INTERVAL === 0;
|
|
400
406
|
const isLast = i === total - 1;
|
|
401
|
-
const isSignificant = Boolean(
|
|
407
|
+
const isSignificant = Boolean(
|
|
408
|
+
description.match(/^(CREATE TABLE|CREATE FUNCTION|CREATE TRIGGER)/i)
|
|
409
|
+
);
|
|
402
410
|
if (isInterval || isLast || isSignificant) {
|
|
403
411
|
console.log(` ✓ [${i + 1}/${total}] ${description}`);
|
|
404
412
|
}
|
|
@@ -420,7 +428,7 @@ var MigrationManager = class {
|
|
|
420
428
|
* Load SQL migration from file
|
|
421
429
|
*/
|
|
422
430
|
loadSqlMigration(migrationFile) {
|
|
423
|
-
const sql2 =
|
|
431
|
+
const sql2 = fs4.readFileSync(migrationFile.filePath, "utf-8");
|
|
424
432
|
const { upSQL, downSQL } = this.parseSqlSections(sql2);
|
|
425
433
|
return {
|
|
426
434
|
version: migrationFile.version,
|
|
@@ -764,10 +772,10 @@ var SeedManager = class {
|
|
|
764
772
|
* Discover seed files from seeds directory
|
|
765
773
|
*/
|
|
766
774
|
async discoverSeeds() {
|
|
767
|
-
if (!
|
|
775
|
+
if (!fs4.existsSync(this.seedsPath)) {
|
|
768
776
|
return [];
|
|
769
777
|
}
|
|
770
|
-
const files =
|
|
778
|
+
const files = fs4.readdirSync(this.seedsPath);
|
|
771
779
|
const seeds = [];
|
|
772
780
|
for (const file of files) {
|
|
773
781
|
const match = file.match(/^(\d+)_(.+)\.(ts|js|sql)$/);
|
|
@@ -821,7 +829,11 @@ var SeedManager = class {
|
|
|
821
829
|
const isEmptyOrComment = trimmedLine === "" || trimmedLine.startsWith("--");
|
|
822
830
|
current += line + "\n";
|
|
823
831
|
if (isEmptyOrComment) continue;
|
|
824
|
-
const dollarState = this.processDollarDelimiters(
|
|
832
|
+
const dollarState = this.processDollarDelimiters(
|
|
833
|
+
line,
|
|
834
|
+
inDollarBlock,
|
|
835
|
+
dollarTag
|
|
836
|
+
);
|
|
825
837
|
inDollarBlock = dollarState.inDollarBlock;
|
|
826
838
|
dollarTag = dollarState.dollarTag;
|
|
827
839
|
if (!inDollarBlock && trimmedLine.endsWith(";") && current.trim()) {
|
|
@@ -893,7 +905,7 @@ var SeedManager = class {
|
|
|
893
905
|
* Load SQL seed from file
|
|
894
906
|
*/
|
|
895
907
|
loadSqlSeed(seedFile) {
|
|
896
|
-
const sql2 =
|
|
908
|
+
const sql2 = fs4.readFileSync(seedFile.filePath, "utf-8");
|
|
897
909
|
return {
|
|
898
910
|
name: seedFile.name,
|
|
899
911
|
run: /* @__PURE__ */ __name(async () => {
|
|
@@ -8257,6 +8269,9 @@ async function createDatabaseService(config) {
|
|
|
8257
8269
|
}
|
|
8258
8270
|
__name(createDatabaseService, "createDatabaseService");
|
|
8259
8271
|
var JSON_INDENT_SPACES = 2;
|
|
8272
|
+
var VERSION_PAD_LENGTH = 3;
|
|
8273
|
+
var DECIMAL_RADIX = 10;
|
|
8274
|
+
var SEPARATOR_WIDTH = 50;
|
|
8260
8275
|
async function getUserSchemas(adapter) {
|
|
8261
8276
|
const result = await adapter.query?.(`
|
|
8262
8277
|
SELECT schema_name FROM information_schema.schemata
|
|
@@ -8317,8 +8332,8 @@ function parseEnvLine(line) {
|
|
|
8317
8332
|
}
|
|
8318
8333
|
__name(parseEnvLine, "parseEnvLine");
|
|
8319
8334
|
function loadEnvFile(envFilePath) {
|
|
8320
|
-
if (!
|
|
8321
|
-
const envContent =
|
|
8335
|
+
if (!fs4.existsSync(envFilePath)) return;
|
|
8336
|
+
const envContent = fs4.readFileSync(envFilePath, "utf-8");
|
|
8322
8337
|
for (const line of envContent.split("\n")) {
|
|
8323
8338
|
const parsed = parseEnvLine(line);
|
|
8324
8339
|
if (parsed) {
|
|
@@ -8351,7 +8366,7 @@ __name(loadConfigFromPath, "loadConfigFromPath");
|
|
|
8351
8366
|
async function findAndLoadConfig() {
|
|
8352
8367
|
for (const configName of CONFIG_PATHS) {
|
|
8353
8368
|
const configFilePath = path4.join(process.cwd(), configName);
|
|
8354
|
-
if (
|
|
8369
|
+
if (fs4.existsSync(configFilePath)) {
|
|
8355
8370
|
return loadConfigFromPath(configFilePath);
|
|
8356
8371
|
}
|
|
8357
8372
|
}
|
|
@@ -8361,7 +8376,7 @@ __name(findAndLoadConfig, "findAndLoadConfig");
|
|
|
8361
8376
|
function warnAboutTypeScriptConfigs() {
|
|
8362
8377
|
for (const tsName of TS_CONFIG_PATHS) {
|
|
8363
8378
|
const tsPath = path4.join(process.cwd(), tsName);
|
|
8364
|
-
if (
|
|
8379
|
+
if (fs4.existsSync(tsPath)) {
|
|
8365
8380
|
console.warn(
|
|
8366
8381
|
`⚠️ Found ${path4.basename(tsPath)} but cannot import TypeScript files directly.`
|
|
8367
8382
|
);
|
|
@@ -8396,7 +8411,7 @@ async function loadConfig(configPath) {
|
|
|
8396
8411
|
const explicitPath = configPath ?? globalConfigPath;
|
|
8397
8412
|
if (explicitPath) {
|
|
8398
8413
|
const resolvedPath = path4.resolve(process.cwd(), explicitPath);
|
|
8399
|
-
if (
|
|
8414
|
+
if (fs4.existsSync(resolvedPath)) {
|
|
8400
8415
|
return loadConfigFromPath(resolvedPath);
|
|
8401
8416
|
}
|
|
8402
8417
|
console.error(`❌ Config file not found: ${resolvedPath}`);
|
|
@@ -8408,8 +8423,28 @@ async function loadConfig(configPath) {
|
|
|
8408
8423
|
return getEnvFallbackConfig();
|
|
8409
8424
|
}
|
|
8410
8425
|
__name(loadConfig, "loadConfig");
|
|
8411
|
-
|
|
8426
|
+
var MIGRATION_POOL_SETTINGS = {
|
|
8427
|
+
max: 3,
|
|
8428
|
+
// Fewer connections, more stable
|
|
8429
|
+
idleTimeoutMillis: 0,
|
|
8430
|
+
// Never timeout idle connections during migrations
|
|
8431
|
+
connectionTimeoutMillis: 3e4,
|
|
8432
|
+
// 30s to establish connection
|
|
8433
|
+
keepAlive: true,
|
|
8434
|
+
keepAliveInitialDelayMillis: 5e3,
|
|
8435
|
+
// Start keepalive after 5s
|
|
8436
|
+
allowExitOnIdle: false
|
|
8437
|
+
// Don't exit while migrations running
|
|
8438
|
+
};
|
|
8439
|
+
async function initDatabase(configPath, forMigration = false) {
|
|
8412
8440
|
const config = await loadConfig(configPath);
|
|
8441
|
+
if (forMigration && config.config) {
|
|
8442
|
+
config.pool = {
|
|
8443
|
+
...MIGRATION_POOL_SETTINGS,
|
|
8444
|
+
...config.pool
|
|
8445
|
+
// User config can still override
|
|
8446
|
+
};
|
|
8447
|
+
}
|
|
8413
8448
|
const db = await createDatabaseService(config);
|
|
8414
8449
|
let adapter = db.adapter;
|
|
8415
8450
|
while (adapter.baseAdapter) {
|
|
@@ -8437,7 +8472,7 @@ program.name("plyaz-db").description("Database management CLI for @plyaz/db").ve
|
|
|
8437
8472
|
var migrateCommand = program.command("migrate").description("Database migration commands");
|
|
8438
8473
|
migrateCommand.command("up").description("Run pending migrations").option("-t, --target <version>", "Target migration version").action(async (options) => {
|
|
8439
8474
|
try {
|
|
8440
|
-
const { adapter, config } = await initDatabase();
|
|
8475
|
+
const { adapter, config } = await initDatabase(void 0, true);
|
|
8441
8476
|
const migrationManager = new MigrationManager({
|
|
8442
8477
|
adapter,
|
|
8443
8478
|
migrationsPath: config.migrationsPath ?? "./migrations",
|
|
@@ -8459,7 +8494,7 @@ migrateCommand.command("up").description("Run pending migrations").option("-t, -
|
|
|
8459
8494
|
});
|
|
8460
8495
|
migrateCommand.command("down").description("Rollback migrations").option("-s, --steps <number>", "Number of migrations to rollback", "1").action(async (options) => {
|
|
8461
8496
|
try {
|
|
8462
|
-
const { adapter, config } = await initDatabase();
|
|
8497
|
+
const { adapter, config } = await initDatabase(void 0, true);
|
|
8463
8498
|
const migrationManager = new MigrationManager({
|
|
8464
8499
|
adapter,
|
|
8465
8500
|
migrationsPath: config.migrationsPath ?? "./migrations",
|
|
@@ -8522,7 +8557,7 @@ migrateCommand.command("reset").description("Rollback all migrations").option("-
|
|
|
8522
8557
|
process.exit(1);
|
|
8523
8558
|
}
|
|
8524
8559
|
try {
|
|
8525
|
-
const { adapter, config } = await initDatabase();
|
|
8560
|
+
const { adapter, config } = await initDatabase(void 0, true);
|
|
8526
8561
|
const migrationManager = new MigrationManager({
|
|
8527
8562
|
adapter,
|
|
8528
8563
|
migrationsPath: config.migrationsPath ?? "./migrations",
|
|
@@ -8565,7 +8600,7 @@ migrateCommand.command("generate-down").description("Generate DOWN sections for
|
|
|
8565
8600
|
var seedCommand = program.command("seed").description("Database seeding commands");
|
|
8566
8601
|
seedCommand.command("run").description("Run seeds").option("-n, --name <name>", "Specific seed to run").option("--skip-existing", "Skip seeds that have already been run").action(async (options) => {
|
|
8567
8602
|
try {
|
|
8568
|
-
const { adapter, config } = await initDatabase();
|
|
8603
|
+
const { adapter, config } = await initDatabase(void 0, true);
|
|
8569
8604
|
const seedManager = new SeedManager({
|
|
8570
8605
|
adapter,
|
|
8571
8606
|
seedsPath: config.seedsPath ?? "./seeds",
|
|
@@ -8625,7 +8660,7 @@ seedCommand.command("reset").description("Reset seeds (run cleanup functions)").
|
|
|
8625
8660
|
process.exit(1);
|
|
8626
8661
|
}
|
|
8627
8662
|
try {
|
|
8628
|
-
const { adapter, config } = await initDatabase();
|
|
8663
|
+
const { adapter, config } = await initDatabase(void 0, true);
|
|
8629
8664
|
const seedManager = new SeedManager({
|
|
8630
8665
|
adapter,
|
|
8631
8666
|
seedsPath: config.seedsPath ?? "./seeds",
|
|
@@ -9196,6 +9231,417 @@ program.command("reset").description(
|
|
|
9196
9231
|
process.exit(1);
|
|
9197
9232
|
}
|
|
9198
9233
|
});
|
|
9234
|
+
function createReadlineInterface() {
|
|
9235
|
+
return readline.createInterface({
|
|
9236
|
+
input: process.stdin,
|
|
9237
|
+
output: process.stdout
|
|
9238
|
+
});
|
|
9239
|
+
}
|
|
9240
|
+
__name(createReadlineInterface, "createReadlineInterface");
|
|
9241
|
+
async function promptInput(message, defaultValue) {
|
|
9242
|
+
const rl = createReadlineInterface();
|
|
9243
|
+
const prompt = defaultValue ? `${message} (${defaultValue}): ` : `${message}: `;
|
|
9244
|
+
return new Promise((resolve4) => {
|
|
9245
|
+
rl.question(prompt, (answer) => {
|
|
9246
|
+
rl.close();
|
|
9247
|
+
const trimmed = answer.trim();
|
|
9248
|
+
resolve4(trimmed !== "" ? trimmed : defaultValue ?? "");
|
|
9249
|
+
});
|
|
9250
|
+
});
|
|
9251
|
+
}
|
|
9252
|
+
__name(promptInput, "promptInput");
|
|
9253
|
+
async function promptSelect(message, options) {
|
|
9254
|
+
const rl = createReadlineInterface();
|
|
9255
|
+
console.log(message);
|
|
9256
|
+
options.forEach((opt, i) => console.log(` ${i + 1}) ${opt}`));
|
|
9257
|
+
return new Promise((resolve4) => {
|
|
9258
|
+
rl.question("Select (number): ", (answer) => {
|
|
9259
|
+
rl.close();
|
|
9260
|
+
const index = Number.parseInt(answer, DECIMAL_RADIX) - 1;
|
|
9261
|
+
resolve4(options[index] ?? options[0]);
|
|
9262
|
+
});
|
|
9263
|
+
});
|
|
9264
|
+
}
|
|
9265
|
+
__name(promptSelect, "promptSelect");
|
|
9266
|
+
async function promptConfirm(message) {
|
|
9267
|
+
const rl = createReadlineInterface();
|
|
9268
|
+
return new Promise((resolve4) => {
|
|
9269
|
+
rl.question(`${message} (y/N): `, (answer) => {
|
|
9270
|
+
rl.close();
|
|
9271
|
+
resolve4(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
|
|
9272
|
+
});
|
|
9273
|
+
});
|
|
9274
|
+
}
|
|
9275
|
+
__name(promptConfirm, "promptConfirm");
|
|
9276
|
+
async function runInteractiveMigration(config) {
|
|
9277
|
+
console.log();
|
|
9278
|
+
console.log("📦 Create Migration - Interactive Mode");
|
|
9279
|
+
console.log("─".repeat(SEPARATOR_WIDTH));
|
|
9280
|
+
console.log();
|
|
9281
|
+
const name = await promptInput("Migration name (e.g., add_users_table)");
|
|
9282
|
+
if (!name) {
|
|
9283
|
+
console.error("❌ Migration name is required");
|
|
9284
|
+
process.exit(1);
|
|
9285
|
+
}
|
|
9286
|
+
const snakeName = toSnakeCase(name);
|
|
9287
|
+
const tableName = await promptInput("Table name", snakeName);
|
|
9288
|
+
const schema = await promptInput("Database schema", "public");
|
|
9289
|
+
const typeChoice = await promptSelect("File type:", [
|
|
9290
|
+
"SQL (.sql)",
|
|
9291
|
+
"TypeScript (.ts)"
|
|
9292
|
+
]);
|
|
9293
|
+
const type = typeChoice.includes("TypeScript") ? "ts" : "sql";
|
|
9294
|
+
const gitUser = getGitUsername();
|
|
9295
|
+
const includeAuthor = gitUser ? await promptConfirm(`Include author (${gitUser})?`) : false;
|
|
9296
|
+
const author = includeAuthor ? gitUser : "";
|
|
9297
|
+
console.log();
|
|
9298
|
+
console.log("─".repeat(SEPARATOR_WIDTH));
|
|
9299
|
+
console.log(`Migration: ${name}`);
|
|
9300
|
+
console.log(`Table: ${schema}.${tableName}`);
|
|
9301
|
+
console.log(`Type: ${type.toUpperCase()}`);
|
|
9302
|
+
if (author) console.log(`Author: ${author}`);
|
|
9303
|
+
console.log("─".repeat(SEPARATOR_WIDTH));
|
|
9304
|
+
const confirm = await promptConfirm("Create migration?");
|
|
9305
|
+
if (!confirm) {
|
|
9306
|
+
console.log("Cancelled.");
|
|
9307
|
+
process.exit(0);
|
|
9308
|
+
}
|
|
9309
|
+
const migrationsPath = config.migrationsPath ?? "./migrations";
|
|
9310
|
+
if (!fs4.existsSync(migrationsPath)) {
|
|
9311
|
+
fs4.mkdirSync(migrationsPath, { recursive: true });
|
|
9312
|
+
}
|
|
9313
|
+
const version = getNextMigrationVersion(migrationsPath);
|
|
9314
|
+
const filename = `${version}_${snakeName}.${type}`;
|
|
9315
|
+
const filepath = path4.join(migrationsPath, filename);
|
|
9316
|
+
const content = getMigrationTemplate({
|
|
9317
|
+
name,
|
|
9318
|
+
version,
|
|
9319
|
+
tableName,
|
|
9320
|
+
schema,
|
|
9321
|
+
author,
|
|
9322
|
+
type
|
|
9323
|
+
});
|
|
9324
|
+
fs4.writeFileSync(filepath, content, "utf-8");
|
|
9325
|
+
console.log(`✅ Created: ${filepath}`);
|
|
9326
|
+
}
|
|
9327
|
+
__name(runInteractiveMigration, "runInteractiveMigration");
|
|
9328
|
+
async function runInteractiveSeed(config) {
|
|
9329
|
+
console.log();
|
|
9330
|
+
console.log("🌱 Create Seed - Interactive Mode");
|
|
9331
|
+
console.log("─".repeat(SEPARATOR_WIDTH));
|
|
9332
|
+
console.log();
|
|
9333
|
+
const name = await promptInput("Seed name (e.g., admin_users)");
|
|
9334
|
+
if (!name) {
|
|
9335
|
+
console.error("❌ Seed name is required");
|
|
9336
|
+
process.exit(1);
|
|
9337
|
+
}
|
|
9338
|
+
const snakeName = toSnakeCase(name);
|
|
9339
|
+
const tableName = await promptInput("Table name", snakeName);
|
|
9340
|
+
const schema = await promptInput("Database schema", "public");
|
|
9341
|
+
const typeChoice = await promptSelect("File type:", [
|
|
9342
|
+
"TypeScript (.ts)",
|
|
9343
|
+
"SQL (.sql)",
|
|
9344
|
+
"CSV (.csv)"
|
|
9345
|
+
]);
|
|
9346
|
+
const typeMap = {
|
|
9347
|
+
"TypeScript (.ts)": "ts",
|
|
9348
|
+
"SQL (.sql)": "sql",
|
|
9349
|
+
"CSV (.csv)": "csv"
|
|
9350
|
+
};
|
|
9351
|
+
const type = typeMap[typeChoice] ?? "ts";
|
|
9352
|
+
let author = "";
|
|
9353
|
+
if (type !== "csv") {
|
|
9354
|
+
const gitUser = getGitUsername();
|
|
9355
|
+
const includeAuthor = gitUser ? await promptConfirm(`Include author (${gitUser})?`) : false;
|
|
9356
|
+
author = includeAuthor ? gitUser : "";
|
|
9357
|
+
}
|
|
9358
|
+
console.log();
|
|
9359
|
+
console.log("─".repeat(SEPARATOR_WIDTH));
|
|
9360
|
+
console.log(`Seed: ${name}`);
|
|
9361
|
+
console.log(`Table: ${schema}.${tableName}`);
|
|
9362
|
+
console.log(`Type: ${type.toUpperCase()}`);
|
|
9363
|
+
if (author) console.log(`Author: ${author}`);
|
|
9364
|
+
console.log("─".repeat(SEPARATOR_WIDTH));
|
|
9365
|
+
const confirm = await promptConfirm("Create seed?");
|
|
9366
|
+
if (!confirm) {
|
|
9367
|
+
console.log("Cancelled.");
|
|
9368
|
+
process.exit(0);
|
|
9369
|
+
}
|
|
9370
|
+
const seedsPath = config.seedsPath ?? "./seeds";
|
|
9371
|
+
if (!fs4.existsSync(seedsPath)) {
|
|
9372
|
+
fs4.mkdirSync(seedsPath, { recursive: true });
|
|
9373
|
+
}
|
|
9374
|
+
const order = getNextSeedOrder(seedsPath);
|
|
9375
|
+
const filename = `${order}_${snakeName}.${type}`;
|
|
9376
|
+
const filepath = path4.join(seedsPath, filename);
|
|
9377
|
+
let content;
|
|
9378
|
+
if (type === "csv") {
|
|
9379
|
+
content = getCsvSeedTemplate({ name, tableName, schema });
|
|
9380
|
+
} else {
|
|
9381
|
+
content = getSeedTemplate({
|
|
9382
|
+
name,
|
|
9383
|
+
order,
|
|
9384
|
+
tableName,
|
|
9385
|
+
schema,
|
|
9386
|
+
author,
|
|
9387
|
+
type
|
|
9388
|
+
});
|
|
9389
|
+
}
|
|
9390
|
+
fs4.writeFileSync(filepath, content, "utf-8");
|
|
9391
|
+
console.log(`✅ Created: ${filepath}`);
|
|
9392
|
+
}
|
|
9393
|
+
__name(runInteractiveSeed, "runInteractiveSeed");
|
|
9394
|
+
function getNextMigrationVersion(migrationsPath) {
|
|
9395
|
+
if (!fs4.existsSync(migrationsPath)) {
|
|
9396
|
+
return "001";
|
|
9397
|
+
}
|
|
9398
|
+
const files = fs4.readdirSync(migrationsPath);
|
|
9399
|
+
const versions = files.map((f) => {
|
|
9400
|
+
const match = f.match(/^(\d+)_/);
|
|
9401
|
+
return match ? Number.parseInt(match[1], DECIMAL_RADIX) : 0;
|
|
9402
|
+
}).filter((v) => v > 0);
|
|
9403
|
+
const maxVersion = versions.length > 0 ? Math.max(...versions) : 0;
|
|
9404
|
+
return String(maxVersion + 1).padStart(VERSION_PAD_LENGTH, "0");
|
|
9405
|
+
}
|
|
9406
|
+
__name(getNextMigrationVersion, "getNextMigrationVersion");
|
|
9407
|
+
function getNextSeedOrder(seedsPath) {
|
|
9408
|
+
if (!fs4.existsSync(seedsPath)) {
|
|
9409
|
+
return "001";
|
|
9410
|
+
}
|
|
9411
|
+
const files = fs4.readdirSync(seedsPath);
|
|
9412
|
+
const orders = files.map((f) => {
|
|
9413
|
+
const match = f.match(/^(\d+)_/);
|
|
9414
|
+
return match ? Number.parseInt(match[1], DECIMAL_RADIX) : 0;
|
|
9415
|
+
}).filter((v) => v > 0);
|
|
9416
|
+
const maxOrder = orders.length > 0 ? Math.max(...orders) : 0;
|
|
9417
|
+
return String(maxOrder + 1).padStart(VERSION_PAD_LENGTH, "0");
|
|
9418
|
+
}
|
|
9419
|
+
__name(getNextSeedOrder, "getNextSeedOrder");
|
|
9420
|
+
function toSnakeCase(name) {
|
|
9421
|
+
return name.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "").replace(/[^a-z0-9_]/g, "_").replace(/_+/g, "_");
|
|
9422
|
+
}
|
|
9423
|
+
__name(toSnakeCase, "toSnakeCase");
|
|
9424
|
+
function toPascalCase(name) {
|
|
9425
|
+
return name.split(/[_\s-]+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
|
|
9426
|
+
}
|
|
9427
|
+
__name(toPascalCase, "toPascalCase");
|
|
9428
|
+
function getTemplateDir() {
|
|
9429
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
9430
|
+
const currentDir = path4.dirname(currentFile);
|
|
9431
|
+
const packageRoot = path4.resolve(currentDir, "..", "..");
|
|
9432
|
+
return path4.join(packageRoot, "template");
|
|
9433
|
+
}
|
|
9434
|
+
__name(getTemplateDir, "getTemplateDir");
|
|
9435
|
+
function loadTemplate(templatePath, vars) {
|
|
9436
|
+
const fullPath = path4.join(getTemplateDir(), templatePath);
|
|
9437
|
+
if (!fs4.existsSync(fullPath)) {
|
|
9438
|
+
throw new Error(`Template not found: ${fullPath}`);
|
|
9439
|
+
}
|
|
9440
|
+
let content = fs4.readFileSync(fullPath, "utf-8");
|
|
9441
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
9442
|
+
const regex = new RegExp(`\\{\\{${key}\\}\\}`, "g");
|
|
9443
|
+
content = content.replace(regex, value ?? "");
|
|
9444
|
+
}
|
|
9445
|
+
return content;
|
|
9446
|
+
}
|
|
9447
|
+
__name(loadTemplate, "loadTemplate");
|
|
9448
|
+
function getGitUsername() {
|
|
9449
|
+
try {
|
|
9450
|
+
return execSync("git config user.name", { encoding: "utf-8" }).trim();
|
|
9451
|
+
} catch {
|
|
9452
|
+
return "";
|
|
9453
|
+
}
|
|
9454
|
+
}
|
|
9455
|
+
__name(getGitUsername, "getGitUsername");
|
|
9456
|
+
function getMigrationTemplate(options) {
|
|
9457
|
+
const { name, version, tableName, schema, author, type } = options;
|
|
9458
|
+
const description = toPascalCase(name);
|
|
9459
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
9460
|
+
const vars = {
|
|
9461
|
+
VERSION: version,
|
|
9462
|
+
DESCRIPTION: description,
|
|
9463
|
+
TABLE_NAME: tableName,
|
|
9464
|
+
SCHEMA: schema,
|
|
9465
|
+
CREATED_DATE: date,
|
|
9466
|
+
AUTHOR: author
|
|
9467
|
+
};
|
|
9468
|
+
const templateFile = type === "ts" ? "migrations/migration.ts.template" : "migrations/migration.sql.template";
|
|
9469
|
+
return loadTemplate(templateFile, vars);
|
|
9470
|
+
}
|
|
9471
|
+
__name(getMigrationTemplate, "getMigrationTemplate");
|
|
9472
|
+
function getSeedTemplate(options) {
|
|
9473
|
+
const { name, order, tableName, schema, author, type } = options;
|
|
9474
|
+
const description = toPascalCase(name);
|
|
9475
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
9476
|
+
const vars = {
|
|
9477
|
+
ORDER: order,
|
|
9478
|
+
DESCRIPTION: description,
|
|
9479
|
+
TABLE_NAME: tableName,
|
|
9480
|
+
SCHEMA: schema,
|
|
9481
|
+
CREATED_DATE: date,
|
|
9482
|
+
AUTHOR: author,
|
|
9483
|
+
UUID_1: randomUUID(),
|
|
9484
|
+
UUID_2: randomUUID(),
|
|
9485
|
+
UUID_3: randomUUID()
|
|
9486
|
+
};
|
|
9487
|
+
const templateFile = type === "ts" ? "seeds/seed.ts.template" : "seeds/seed.sql.template";
|
|
9488
|
+
return loadTemplate(templateFile, vars);
|
|
9489
|
+
}
|
|
9490
|
+
__name(getSeedTemplate, "getSeedTemplate");
|
|
9491
|
+
function getCsvSeedTemplate(options) {
|
|
9492
|
+
const { name, tableName, schema } = options;
|
|
9493
|
+
const description = toPascalCase(name);
|
|
9494
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
9495
|
+
const vars = {
|
|
9496
|
+
DESCRIPTION: description,
|
|
9497
|
+
TABLE_NAME: tableName,
|
|
9498
|
+
SCHEMA: schema,
|
|
9499
|
+
CREATED_DATE: date,
|
|
9500
|
+
AUTHOR: "",
|
|
9501
|
+
UUID_1: randomUUID(),
|
|
9502
|
+
UUID_2: randomUUID(),
|
|
9503
|
+
UUID_3: randomUUID()
|
|
9504
|
+
};
|
|
9505
|
+
try {
|
|
9506
|
+
return loadTemplate("seeds/seed.csv.template", vars);
|
|
9507
|
+
} catch {
|
|
9508
|
+
return `id,name,created_at
|
|
9509
|
+
${randomUUID()},Example 1,${(/* @__PURE__ */ new Date()).toISOString()}
|
|
9510
|
+
${randomUUID()},Example 2,${(/* @__PURE__ */ new Date()).toISOString()}
|
|
9511
|
+
`;
|
|
9512
|
+
}
|
|
9513
|
+
}
|
|
9514
|
+
__name(getCsvSeedTemplate, "getCsvSeedTemplate");
|
|
9515
|
+
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(
|
|
9517
|
+
"-T, --table <table>",
|
|
9518
|
+
"Table name (default: derived from migration name)"
|
|
9519
|
+
).option(
|
|
9520
|
+
"-s, --schema <schema>",
|
|
9521
|
+
"Database schema (default: public)",
|
|
9522
|
+
"public"
|
|
9523
|
+
).option("--no-author", "Skip adding git username as author").option("-i, --interactive", "Force interactive mode").action(
|
|
9524
|
+
async (name, options) => {
|
|
9525
|
+
try {
|
|
9526
|
+
const config = await loadConfig();
|
|
9527
|
+
if (!name || options.interactive) {
|
|
9528
|
+
await runInteractiveMigration(config);
|
|
9529
|
+
process.exit(0);
|
|
9530
|
+
}
|
|
9531
|
+
const migrationsPath = config.migrationsPath ?? "./migrations";
|
|
9532
|
+
if (!fs4.existsSync(migrationsPath)) {
|
|
9533
|
+
fs4.mkdirSync(migrationsPath, { recursive: true });
|
|
9534
|
+
console.log(`📁 Created directory: ${migrationsPath}`);
|
|
9535
|
+
}
|
|
9536
|
+
const version = getNextMigrationVersion(migrationsPath);
|
|
9537
|
+
const snakeName = toSnakeCase(name);
|
|
9538
|
+
const tableName = options.table ?? snakeName;
|
|
9539
|
+
const schema = options.schema;
|
|
9540
|
+
const author = options.author ? getGitUsername() : "";
|
|
9541
|
+
const extension = options.type === "ts" ? "ts" : "sql";
|
|
9542
|
+
const filename = `${version}_${snakeName}.${extension}`;
|
|
9543
|
+
const filepath = path4.join(migrationsPath, filename);
|
|
9544
|
+
if (fs4.existsSync(filepath)) {
|
|
9545
|
+
console.error(`❌ File already exists: ${filepath}`);
|
|
9546
|
+
process.exit(1);
|
|
9547
|
+
}
|
|
9548
|
+
const content = getMigrationTemplate({
|
|
9549
|
+
name,
|
|
9550
|
+
version,
|
|
9551
|
+
tableName,
|
|
9552
|
+
schema,
|
|
9553
|
+
author,
|
|
9554
|
+
type: options.type === "ts" ? "ts" : "sql"
|
|
9555
|
+
});
|
|
9556
|
+
fs4.writeFileSync(filepath, content, "utf-8");
|
|
9557
|
+
console.log(`✅ Created migration: ${filename}`);
|
|
9558
|
+
console.log(` Path: ${filepath}`);
|
|
9559
|
+
console.log(` Type: ${options.type.toUpperCase()}`);
|
|
9560
|
+
console.log(` Table: ${schema}.${tableName}`);
|
|
9561
|
+
if (author) {
|
|
9562
|
+
console.log(` Author: ${author}`);
|
|
9563
|
+
}
|
|
9564
|
+
console.log("");
|
|
9565
|
+
console.log("📝 Next steps:");
|
|
9566
|
+
console.log(` 1. Edit the migration file to add your schema changes`);
|
|
9567
|
+
console.log(` 2. Run 'plyaz-db migrate up' to apply the migration`);
|
|
9568
|
+
console.log(
|
|
9569
|
+
` 3. Test rollback with 'plyaz-db migrate down' before deploying`
|
|
9570
|
+
);
|
|
9571
|
+
process.exit(0);
|
|
9572
|
+
} catch (error) {
|
|
9573
|
+
console.error("❌ Error:", error.message);
|
|
9574
|
+
process.exit(1);
|
|
9575
|
+
}
|
|
9576
|
+
}
|
|
9577
|
+
);
|
|
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(
|
|
9579
|
+
"-s, --schema <schema>",
|
|
9580
|
+
"Database schema (default: public)",
|
|
9581
|
+
"public"
|
|
9582
|
+
).option("--no-author", "Skip adding git username as author").option("-i, --interactive", "Force interactive mode").action(
|
|
9583
|
+
async (name, options) => {
|
|
9584
|
+
try {
|
|
9585
|
+
const config = await loadConfig();
|
|
9586
|
+
if (!name || options.interactive) {
|
|
9587
|
+
await runInteractiveSeed(config);
|
|
9588
|
+
process.exit(0);
|
|
9589
|
+
}
|
|
9590
|
+
const seedsPath = config.seedsPath ?? "./seeds";
|
|
9591
|
+
if (!fs4.existsSync(seedsPath)) {
|
|
9592
|
+
fs4.mkdirSync(seedsPath, { recursive: true });
|
|
9593
|
+
console.log(`📁 Created directory: ${seedsPath}`);
|
|
9594
|
+
}
|
|
9595
|
+
const order = getNextSeedOrder(seedsPath);
|
|
9596
|
+
const snakeName = toSnakeCase(name);
|
|
9597
|
+
const tableName = options.table ?? snakeName;
|
|
9598
|
+
const schema = options.schema;
|
|
9599
|
+
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
|
+
const filename = `${order}_${snakeName}.${fileType}`;
|
|
9607
|
+
const filepath = path4.join(seedsPath, filename);
|
|
9608
|
+
if (fs4.existsSync(filepath)) {
|
|
9609
|
+
console.error(`❌ File already exists: ${filepath}`);
|
|
9610
|
+
process.exit(1);
|
|
9611
|
+
}
|
|
9612
|
+
let content;
|
|
9613
|
+
if (fileType === "csv") {
|
|
9614
|
+
content = getCsvSeedTemplate({ name, tableName, schema });
|
|
9615
|
+
} else {
|
|
9616
|
+
content = getSeedTemplate({
|
|
9617
|
+
name,
|
|
9618
|
+
order,
|
|
9619
|
+
tableName,
|
|
9620
|
+
schema,
|
|
9621
|
+
author,
|
|
9622
|
+
type: fileType
|
|
9623
|
+
});
|
|
9624
|
+
}
|
|
9625
|
+
fs4.writeFileSync(filepath, content, "utf-8");
|
|
9626
|
+
console.log(`✅ Created seed: ${filename}`);
|
|
9627
|
+
console.log(` Path: ${filepath}`);
|
|
9628
|
+
console.log(` Type: ${fileType.toUpperCase()}`);
|
|
9629
|
+
console.log(` Table: ${schema}.${tableName}`);
|
|
9630
|
+
if (author && fileType !== "csv") {
|
|
9631
|
+
console.log(` Author: ${author}`);
|
|
9632
|
+
}
|
|
9633
|
+
console.log("");
|
|
9634
|
+
console.log("📝 Next steps:");
|
|
9635
|
+
console.log(` 1. Edit the seed file to add your data`);
|
|
9636
|
+
console.log(` 2. Run 'plyaz-db seed run' to execute seeds`);
|
|
9637
|
+
console.log(` 3. Use 'plyaz-db seed status' to check seed status`);
|
|
9638
|
+
process.exit(0);
|
|
9639
|
+
} catch (error) {
|
|
9640
|
+
console.error("❌ Error:", error.message);
|
|
9641
|
+
process.exit(1);
|
|
9642
|
+
}
|
|
9643
|
+
}
|
|
9644
|
+
);
|
|
9199
9645
|
program.parse();
|
|
9200
9646
|
//# sourceMappingURL=index.js.map
|
|
9201
9647
|
//# sourceMappingURL=index.js.map
|