turbine-orm 0.39.0 → 0.40.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/README.md +9 -3
- package/dist/cjs/cli/index.js +500 -130
- package/dist/cjs/cli/migrate.js +79 -0
- package/dist/cjs/client.js +1 -0
- package/dist/cjs/powql.js +7 -3
- package/dist/cjs/query/aggregates.js +11 -1
- package/dist/cjs/query/builder.js +35 -8
- package/dist/cjs/query/filters.js +33 -0
- package/dist/cjs/query/relations.js +7 -7
- package/dist/cli/index.d.ts +61 -1
- package/dist/cli/index.js +500 -131
- package/dist/cli/migrate.d.ts +33 -0
- package/dist/cli/migrate.js +78 -0
- package/dist/client.d.ts +10 -0
- package/dist/client.js +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/powql.js +8 -4
- package/dist/query/aggregates.d.ts +1 -1
- package/dist/query/aggregates.js +12 -2
- package/dist/query/builder.d.ts +15 -3
- package/dist/query/builder.js +36 -9
- package/dist/query/deferred.d.ts +11 -0
- package/dist/query/filters.d.ts +17 -0
- package/dist/query/filters.js +32 -0
- package/dist/query/index.d.ts +1 -1
- package/dist/query/relations.js +8 -8
- package/dist/query/types.d.ts +86 -4
- package/package.json +3 -3
package/dist/cjs/cli/index.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* turbine init — Initialize a Turbine project
|
|
8
8
|
* turbine generate | pull — Introspect database and generate TypeScript types
|
|
9
9
|
* turbine push - Apply schema-builder definitions to database (destructive ops gated)
|
|
10
|
-
* turbine migrate create <name> - Create a new SQL migration file (--auto | --recipe <name>)
|
|
10
|
+
* turbine migrate create <name> - Create a new SQL migration file (--auto | --from-diff | --recipe <name>)
|
|
11
11
|
* turbine migrate up — Apply pending migrations
|
|
12
12
|
* turbine migrate deploy — Apply pending migrations without prompts
|
|
13
13
|
* turbine migrate down — Rollback last migration
|
|
@@ -62,6 +62,7 @@ exports.parseArgs = parseArgs;
|
|
|
62
62
|
exports.loadDotEnvForCli = loadDotEnvForCli;
|
|
63
63
|
exports.dotEnvUrlConflictWarning = dotEnvUrlConflictWarning;
|
|
64
64
|
exports.detectConsumerModuleType = detectConsumerModuleType;
|
|
65
|
+
exports.planInitSteps = planInitSteps;
|
|
65
66
|
exports.buildMigrateDeployOptions = buildMigrateDeployOptions;
|
|
66
67
|
exports.getSeedExecutionPlan = getSeedExecutionPlan;
|
|
67
68
|
exports.isLoopbackHost = isLoopbackHost;
|
|
@@ -129,6 +130,25 @@ function parseArgs(argv = process.argv.slice(2)) {
|
|
|
129
130
|
case '--auto':
|
|
130
131
|
result.auto = true;
|
|
131
132
|
break;
|
|
133
|
+
case '--from-diff':
|
|
134
|
+
result.fromDiff = true;
|
|
135
|
+
break;
|
|
136
|
+
case '--yes':
|
|
137
|
+
case '-y':
|
|
138
|
+
result.yes = true;
|
|
139
|
+
break;
|
|
140
|
+
case '--skip-schema':
|
|
141
|
+
result.skipSchema = true;
|
|
142
|
+
break;
|
|
143
|
+
case '--skip-seed':
|
|
144
|
+
result.skipSeed = true;
|
|
145
|
+
break;
|
|
146
|
+
case '--skip-push':
|
|
147
|
+
result.skipPush = true;
|
|
148
|
+
break;
|
|
149
|
+
case '--skip-generate':
|
|
150
|
+
result.skipGenerate = true;
|
|
151
|
+
break;
|
|
132
152
|
case '--allow-drift':
|
|
133
153
|
result.allowDrift = true;
|
|
134
154
|
break;
|
|
@@ -422,90 +442,103 @@ function detectConsumerModuleType(cwd = process.cwd()) {
|
|
|
422
442
|
return 'none';
|
|
423
443
|
}
|
|
424
444
|
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
(
|
|
458
|
-
//
|
|
459
|
-
//
|
|
460
|
-
|
|
461
|
-
//
|
|
462
|
-
const
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
(
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
445
|
+
/**
|
|
446
|
+
* Pure step planner for `turbine init`. Given the detected project state and the
|
|
447
|
+
* effective flags, decide for each step whether to run it, prompt for it, or
|
|
448
|
+
* skip it (and why). No IO: every input is precomputed by the caller: so the
|
|
449
|
+
* whole decision matrix is unit-testable without a TTY or a database.
|
|
450
|
+
*
|
|
451
|
+
* Three modes:
|
|
452
|
+
* - `prompt` (interactive TTY, no `--yes`): scaffold + DB steps are prompted.
|
|
453
|
+
* - `auto-yes` (`--yes`): accept each step's default; the yes-defaults run.
|
|
454
|
+
* - `auto-legacy` (non-TTY, no `--yes`): reproduce the pre-existing init
|
|
455
|
+
* behavior. Scaffold files + generate run; push + seed-run do not.
|
|
456
|
+
*
|
|
457
|
+
* Steps that create files (config, schema, seed) are skipped when the file
|
|
458
|
+
* already exists, so re-runs are safe. DB steps (push, generate, seed-run) are
|
|
459
|
+
* skipped when there is no URL or the database is unreachable.
|
|
460
|
+
*/
|
|
461
|
+
function planInitSteps(state, flags) {
|
|
462
|
+
const mode = flags.interactive && !flags.yes ? 'prompt' : flags.yes ? 'auto-yes' : 'auto-legacy';
|
|
463
|
+
const steps = [];
|
|
464
|
+
// config: core scaffold, never prompted.
|
|
465
|
+
steps.push(state.configExists && !flags.force
|
|
466
|
+
? { id: 'config', action: 'skip', defaultYes: true, skipReason: 'exists' }
|
|
467
|
+
: { id: 'config', action: 'run', defaultYes: true });
|
|
468
|
+
// Scaffold files (schema, seed): created by default; prompted interactively.
|
|
469
|
+
const scaffold = (id, exists, skipFlag) => {
|
|
470
|
+
if (skipFlag)
|
|
471
|
+
return { id, action: 'skip', defaultYes: true, skipReason: 'flag' };
|
|
472
|
+
if (exists)
|
|
473
|
+
return { id, action: 'skip', defaultYes: true, skipReason: 'exists' };
|
|
474
|
+
return { id, action: mode === 'prompt' ? 'prompt' : 'run', defaultYes: true };
|
|
475
|
+
};
|
|
476
|
+
steps.push(scaffold('schema', state.schemaExists, flags.skipSchema));
|
|
477
|
+
steps.push(scaffold('seed-file', state.seedFileExists, flags.skipSeed));
|
|
478
|
+
// seed-run needs a seed file: either one that already exists, or one this run
|
|
479
|
+
// is about to create.
|
|
480
|
+
const seedWillExist = state.seedFileExists || steps.find((s) => s.id === 'seed-file')?.action !== 'skip';
|
|
481
|
+
// DB steps: need a reachable database.
|
|
482
|
+
const dbStep = (id, skipFlag, opts) => {
|
|
483
|
+
const { defaultYes } = opts;
|
|
484
|
+
if (skipFlag)
|
|
485
|
+
return { id, action: 'skip', defaultYes, skipReason: 'flag' };
|
|
486
|
+
if (!state.hasUrl)
|
|
487
|
+
return { id, action: 'skip', defaultYes, skipReason: 'no-url' };
|
|
488
|
+
if (!state.dbReachable)
|
|
489
|
+
return { id, action: 'skip', defaultYes, skipReason: 'unreachable' };
|
|
490
|
+
if (opts.extraSkip)
|
|
491
|
+
return { id, action: 'skip', defaultYes, skipReason: opts.extraSkip };
|
|
492
|
+
if (mode === 'prompt')
|
|
493
|
+
return { id, action: 'prompt', defaultYes };
|
|
494
|
+
if (mode === 'auto-yes') {
|
|
495
|
+
return defaultYes
|
|
496
|
+
? { id, action: 'run', defaultYes }
|
|
497
|
+
: { id, action: 'skip', defaultYes, skipReason: 'default-no' };
|
|
498
|
+
}
|
|
499
|
+
// auto-legacy
|
|
500
|
+
return opts.legacyRun
|
|
501
|
+
? { id, action: 'run', defaultYes }
|
|
502
|
+
: { id, action: 'skip', defaultYes, skipReason: 'non-interactive' };
|
|
503
|
+
};
|
|
504
|
+
steps.push(dbStep('push', flags.skipPush, { legacyRun: false, defaultYes: true }));
|
|
505
|
+
steps.push(dbStep('generate', flags.skipGenerate, { legacyRun: true, defaultYes: true }));
|
|
506
|
+
steps.push(dbStep('seed-run', flags.skipSeed, {
|
|
507
|
+
legacyRun: false,
|
|
508
|
+
defaultYes: false,
|
|
509
|
+
extraSkip: seedWillExist ? undefined : 'no-seed-file',
|
|
510
|
+
}));
|
|
511
|
+
return steps;
|
|
512
|
+
}
|
|
513
|
+
/** Prompt for a yes/no answer on an interactive TTY. */
|
|
514
|
+
async function promptYesNo(question, defaultYes) {
|
|
515
|
+
const { createInterface } = await Promise.resolve().then(() => __importStar(require('node:readline/promises')));
|
|
516
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
517
|
+
try {
|
|
518
|
+
const suffix = defaultYes ? '[Y/n]' : '[y/N]';
|
|
519
|
+
const raw = (await rl.question(` ${question} ${(0, ui_js_1.dim)(suffix)} `)).trim().toLowerCase();
|
|
520
|
+
if (raw === '')
|
|
521
|
+
return defaultYes;
|
|
522
|
+
return raw === 'y' || raw === 'yes';
|
|
484
523
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
if (!(0, node_fs_1.existsSync)(migrDir)) {
|
|
488
|
-
(0, node_fs_1.mkdirSync)(migrDir, { recursive: true });
|
|
489
|
-
// Create .gitkeep
|
|
490
|
-
(0, node_fs_1.writeFileSync)(`${migrDir}/.gitkeep`, '', 'utf-8');
|
|
491
|
-
(0, ui_js_1.success)(`Created ${(0, ui_js_1.cyan)(`${migrDir}/`)}`);
|
|
524
|
+
finally {
|
|
525
|
+
rl.close();
|
|
492
526
|
}
|
|
493
|
-
|
|
494
|
-
|
|
527
|
+
}
|
|
528
|
+
/** Probe database reachability with a short-lived connection. Never throws. */
|
|
529
|
+
async function probeDatabase(url) {
|
|
530
|
+
try {
|
|
531
|
+
const { default: pg } = await Promise.resolve().then(() => __importStar(require('pg')));
|
|
532
|
+
const client = new pg.Client({ connectionString: url });
|
|
533
|
+
await client.connect();
|
|
534
|
+
await client.end();
|
|
535
|
+
return true;
|
|
495
536
|
}
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
(0, node_fs_1.mkdirSync)(config.out, { recursive: true });
|
|
499
|
-
(0, ui_js_1.success)(`Created ${(0, ui_js_1.cyan)(`${config.out}/`)}`);
|
|
537
|
+
catch {
|
|
538
|
+
return false;
|
|
500
539
|
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
const seedDir = (0, node_path_1.dirname)(initSeedFile);
|
|
504
|
-
if (!(0, node_fs_1.existsSync)(initSeedFile)) {
|
|
505
|
-
if (!(0, node_fs_1.existsSync)(seedDir)) {
|
|
506
|
-
(0, node_fs_1.mkdirSync)(seedDir, { recursive: true });
|
|
507
|
-
}
|
|
508
|
-
(0, node_fs_1.writeFileSync)(initSeedFile, `/**
|
|
540
|
+
}
|
|
541
|
+
const INIT_SEED_TEMPLATE = `/**
|
|
509
542
|
* Turbine seed file
|
|
510
543
|
*
|
|
511
544
|
* Run with: npx turbine seed
|
|
@@ -521,16 +554,8 @@ export default defineSeed(async (db) => {
|
|
|
521
554
|
|
|
522
555
|
console.log('Done!');
|
|
523
556
|
});
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
}
|
|
527
|
-
// Create schema builder template
|
|
528
|
-
if (!(0, node_fs_1.existsSync)(config.schemaFile)) {
|
|
529
|
-
const schemaDir = config.schemaFile.substring(0, config.schemaFile.lastIndexOf('/'));
|
|
530
|
-
if (!(0, node_fs_1.existsSync)(schemaDir)) {
|
|
531
|
-
(0, node_fs_1.mkdirSync)(schemaDir, { recursive: true });
|
|
532
|
-
}
|
|
533
|
-
(0, node_fs_1.writeFileSync)(config.schemaFile, `/**
|
|
557
|
+
`;
|
|
558
|
+
const INIT_SCHEMA_TEMPLATE = `/**
|
|
534
559
|
* Turbine schema definition
|
|
535
560
|
*
|
|
536
561
|
* Define your database schema in TypeScript.
|
|
@@ -550,54 +575,298 @@ export default defineSchema({
|
|
|
550
575
|
// created_at: { type: 'timestamp', default: 'NOW()' },
|
|
551
576
|
// },
|
|
552
577
|
});
|
|
553
|
-
|
|
554
|
-
|
|
578
|
+
`;
|
|
579
|
+
/** Create supporting directories + .gitignore entries (unconditional, unprompted). */
|
|
580
|
+
function ensureInitScaffoldDirs(config) {
|
|
581
|
+
const migrDir = config.migrationsDir;
|
|
582
|
+
if (!(0, node_fs_1.existsSync)(migrDir)) {
|
|
583
|
+
(0, node_fs_1.mkdirSync)(migrDir, { recursive: true });
|
|
584
|
+
(0, node_fs_1.writeFileSync)(`${migrDir}/.gitkeep`, '', 'utf-8');
|
|
585
|
+
(0, ui_js_1.success)(`Created ${(0, ui_js_1.cyan)(`${migrDir}/`)}`);
|
|
586
|
+
}
|
|
587
|
+
if (!(0, node_fs_1.existsSync)(config.out)) {
|
|
588
|
+
(0, node_fs_1.mkdirSync)(config.out, { recursive: true });
|
|
589
|
+
(0, ui_js_1.success)(`Created ${(0, ui_js_1.cyan)(`${config.out}/`)}`);
|
|
555
590
|
}
|
|
556
|
-
// Add .gitignore entries for generated output and config (may contain connection strings)
|
|
557
591
|
const gitignorePath = '.gitignore';
|
|
558
592
|
if ((0, node_fs_1.existsSync)(gitignorePath)) {
|
|
559
593
|
const gitignoreContent = (0, node_fs_1.readFileSync)(gitignorePath, 'utf-8');
|
|
560
594
|
const additions = [];
|
|
561
|
-
if (!gitignoreContent.includes('generated/turbine'))
|
|
595
|
+
if (!gitignoreContent.includes('generated/turbine'))
|
|
562
596
|
additions.push('generated/turbine/');
|
|
563
|
-
|
|
564
|
-
if (!gitignoreContent.includes('turbine.config.ts')) {
|
|
597
|
+
if (!gitignoreContent.includes('turbine.config.ts'))
|
|
565
598
|
additions.push('turbine.config.ts');
|
|
566
|
-
}
|
|
567
599
|
if (additions.length > 0) {
|
|
568
600
|
(0, node_fs_1.appendFileSync)(gitignorePath, `\n# Turbine generated client & config\n${additions.join('\n')}\n`);
|
|
569
601
|
(0, ui_js_1.success)(`Added ${(0, ui_js_1.cyan)(additions.join(', '))} to ${(0, ui_js_1.cyan)('.gitignore')}`);
|
|
570
602
|
}
|
|
571
603
|
}
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
(0,
|
|
604
|
+
}
|
|
605
|
+
function writeInitSchemaTemplate(config) {
|
|
606
|
+
const schemaDir = (0, node_path_1.dirname)(config.schemaFile);
|
|
607
|
+
if (schemaDir && !(0, node_fs_1.existsSync)(schemaDir))
|
|
608
|
+
(0, node_fs_1.mkdirSync)(schemaDir, { recursive: true });
|
|
609
|
+
(0, node_fs_1.writeFileSync)(config.schemaFile, INIT_SCHEMA_TEMPLATE, 'utf-8');
|
|
610
|
+
(0, ui_js_1.success)(`Created ${(0, ui_js_1.cyan)(config.schemaFile)}`);
|
|
611
|
+
}
|
|
612
|
+
function writeInitSeedTemplate(seedFilePath) {
|
|
613
|
+
const seedDir = (0, node_path_1.dirname)(seedFilePath);
|
|
614
|
+
if (seedDir && !(0, node_fs_1.existsSync)(seedDir))
|
|
615
|
+
(0, node_fs_1.mkdirSync)(seedDir, { recursive: true });
|
|
616
|
+
(0, node_fs_1.writeFileSync)(seedFilePath, INIT_SEED_TEMPLATE, 'utf-8');
|
|
617
|
+
(0, ui_js_1.success)(`Created ${(0, ui_js_1.cyan)(seedFilePath)}`);
|
|
618
|
+
}
|
|
619
|
+
/** Push the code-first schema to the database, preserving the destructive typed confirm. */
|
|
620
|
+
async function runInitPush(config, url) {
|
|
621
|
+
if (!(0, node_fs_1.existsSync)(config.schemaFile)) {
|
|
622
|
+
(0, ui_js_1.warn)(`No schema file at ${(0, ui_js_1.cyan)(config.schemaFile)}: skipping push.`);
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
const schemaDef = await loadSchemaFile(config.schemaFile);
|
|
626
|
+
const spinner = new ui_js_1.Spinner('Computing schema diff').start();
|
|
627
|
+
const diff = await (0, schema_sql_js_1.schemaDiff)(schemaDef, url);
|
|
628
|
+
if (diff.statements.length === 0) {
|
|
629
|
+
spinner.succeed('Database already in sync: nothing to push');
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
spinner.succeed(`Found ${(0, ui_js_1.bold)(String(diff.statements.length))} change(s) to apply`);
|
|
633
|
+
const pushSpinner = new ui_js_1.Spinner('Applying schema').start();
|
|
634
|
+
try {
|
|
635
|
+
const result = await (0, schema_sql_js_1.schemaPush)(schemaDef, url, { precomputedDiff: diff });
|
|
636
|
+
pushSpinner.succeed(`Applied ${(0, ui_js_1.bold)(String(result.statementsExecuted))} statement(s)`);
|
|
637
|
+
}
|
|
638
|
+
catch (err) {
|
|
639
|
+
if (!(err instanceof schema_sql_js_1.DestructivePushRefusal))
|
|
640
|
+
throw err;
|
|
641
|
+
pushSpinner.stop();
|
|
642
|
+
if (!(await confirmDestructive(err.message))) {
|
|
643
|
+
(0, ui_js_1.warn)('Push aborted: no schema changes were applied.');
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
const result = await (0, schema_sql_js_1.schemaPush)(schemaDef, url, { allowDestructive: true, precomputedDiff: diff });
|
|
647
|
+
(0, ui_js_1.success)(`Applied ${(0, ui_js_1.bold)(String(result.statementsExecuted))} statement(s)`);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
/** Introspect the database and generate the typed client. */
|
|
651
|
+
async function runInitGenerate(config, url) {
|
|
652
|
+
const spinner = new ui_js_1.Spinner('Introspecting database').start();
|
|
653
|
+
try {
|
|
654
|
+
const schema = await (0, introspect_js_1.introspect)({
|
|
655
|
+
connectionString: url,
|
|
656
|
+
schema: config.schema,
|
|
657
|
+
include: config.include.length ? config.include : undefined,
|
|
658
|
+
exclude: config.exclude.length ? config.exclude : undefined,
|
|
659
|
+
});
|
|
660
|
+
spinner.succeed(`Found ${(0, ui_js_1.bold)(String(Object.keys(schema.tables).length))} tables`);
|
|
661
|
+
const genSpinner = new ui_js_1.Spinner('Generating TypeScript client').start();
|
|
662
|
+
const result = (0, generate_js_1.generate)({ schema, outDir: config.out, connectionString: url });
|
|
663
|
+
genSpinner.succeed(`Generated ${(0, ui_js_1.bold)(String(result.files.length))} files to ${(0, ui_js_1.cyan)(`${config.out}/`)}`);
|
|
664
|
+
}
|
|
665
|
+
catch (err) {
|
|
666
|
+
spinner.fail('Could not generate client');
|
|
667
|
+
if (err instanceof Error)
|
|
668
|
+
console.log(` ${(0, ui_js_1.dim)((0, ui_js_1.redactUrl)(err.message))}`);
|
|
669
|
+
(0, ui_js_1.info)(`Run generation later with: ${(0, ui_js_1.cyan)('npx turbine generate')}`);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
/** Run the seed file. */
|
|
673
|
+
async function runInitSeed(config) {
|
|
674
|
+
const seedFile = (0, config_js_1.resolveSeedFile)(config);
|
|
675
|
+
if (!seedFile || !(0, node_fs_1.existsSync)(seedFile)) {
|
|
676
|
+
(0, ui_js_1.warn)('No seed file found: skipping seed run.');
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
const spinner = new ui_js_1.Spinner('Running seed file').start();
|
|
680
|
+
try {
|
|
681
|
+
await runSeedPlan(getSeedExecutionPlan(seedFile), config);
|
|
682
|
+
spinner.succeed('Seed completed');
|
|
683
|
+
}
|
|
684
|
+
catch (err) {
|
|
685
|
+
spinner.fail('Seed failed');
|
|
686
|
+
if (err instanceof Error)
|
|
687
|
+
console.log(` ${(0, ui_js_1.dim)((0, ui_js_1.redactUrl)(err.message))}`);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
/** Human label for a step, used in prompts and skip messages. */
|
|
691
|
+
function initStepLabel(id) {
|
|
692
|
+
switch (id) {
|
|
693
|
+
case 'config':
|
|
694
|
+
return 'config file';
|
|
695
|
+
case 'schema':
|
|
696
|
+
return 'schema file';
|
|
697
|
+
case 'seed-file':
|
|
698
|
+
return 'seed file';
|
|
699
|
+
case 'push':
|
|
700
|
+
return 'schema push';
|
|
701
|
+
case 'generate':
|
|
702
|
+
return 'client generation';
|
|
703
|
+
case 'seed-run':
|
|
704
|
+
return 'seed run';
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
/** Report a skipped step with its reason. Existence skips are the quiet common case. */
|
|
708
|
+
function reportInitSkip(step) {
|
|
709
|
+
const label = initStepLabel(step.id);
|
|
710
|
+
switch (step.skipReason) {
|
|
711
|
+
case 'exists':
|
|
712
|
+
(0, ui_js_1.info)(`${label} already exists ${(0, ui_js_1.dim)('- skipped')}`);
|
|
713
|
+
break;
|
|
714
|
+
case 'flag':
|
|
715
|
+
console.log(` ${(0, ui_js_1.dim)(`${ui_js_1.symbols.dot} ${label} skipped (flag)`)}`);
|
|
716
|
+
break;
|
|
717
|
+
case 'no-url':
|
|
718
|
+
console.log(` ${(0, ui_js_1.dim)(`${ui_js_1.symbols.dot} ${label} skipped (no database URL)`)}`);
|
|
719
|
+
break;
|
|
720
|
+
case 'unreachable':
|
|
721
|
+
console.log(` ${(0, ui_js_1.dim)(`${ui_js_1.symbols.dot} ${label} skipped (database unreachable)`)}`);
|
|
722
|
+
break;
|
|
723
|
+
case 'no-seed-file':
|
|
724
|
+
console.log(` ${(0, ui_js_1.dim)(`${ui_js_1.symbols.dot} ${label} skipped (no seed file)`)}`);
|
|
725
|
+
break;
|
|
726
|
+
case 'non-interactive':
|
|
727
|
+
console.log(` ${(0, ui_js_1.dim)(`${ui_js_1.symbols.dot} ${label} skipped (non-interactive: pass --yes to run it)`)}`);
|
|
728
|
+
break;
|
|
729
|
+
case 'default-no':
|
|
730
|
+
console.log(` ${(0, ui_js_1.dim)(`${ui_js_1.symbols.dot} ${label} skipped (default no)`)}`);
|
|
731
|
+
break;
|
|
732
|
+
default:
|
|
733
|
+
console.log(` ${(0, ui_js_1.dim)(`${ui_js_1.symbols.dot} ${label} skipped`)}`);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
/** The interactive prompt question for a promptable step. */
|
|
737
|
+
function initPromptQuestion(step, config, seedFilePath) {
|
|
738
|
+
switch (step.id) {
|
|
739
|
+
case 'schema':
|
|
740
|
+
return `Create a starter schema file (${config.schemaFile})?`;
|
|
741
|
+
case 'seed-file':
|
|
742
|
+
return `Create a starter seed file (${seedFilePath})?`;
|
|
743
|
+
case 'push':
|
|
744
|
+
return 'Push your schema to the database now?';
|
|
745
|
+
case 'generate':
|
|
746
|
+
return 'Generate the typed client from the database now?';
|
|
747
|
+
case 'seed-run':
|
|
748
|
+
return 'Run the seed file now?';
|
|
749
|
+
default:
|
|
750
|
+
return `Run ${initStepLabel(step.id)}?`;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
async function cmdInit(args, config) {
|
|
754
|
+
(0, ui_js_1.banner)();
|
|
755
|
+
(0, ui_js_1.header)('Initializing Turbine project');
|
|
756
|
+
// Detect environment. main() has already auto-loaded a local `.env` into
|
|
757
|
+
// process.env (when DATABASE_URL was not otherwise set), so these messages
|
|
758
|
+
// describe the real, post-load state, no more "if set" hand-waving.
|
|
759
|
+
const envUrl = process.env.DATABASE_URL;
|
|
760
|
+
const hasEnvFile = (0, node_fs_1.existsSync)('.env');
|
|
761
|
+
const hasEnvLocal = (0, node_fs_1.existsSync)('.env.local');
|
|
762
|
+
// On Node < 20.12 (no process.loadEnvFile) main() could not auto-load .env, so
|
|
763
|
+
// we cannot claim it "has no DATABASE_URL"; we simply could not read it.
|
|
764
|
+
const canAutoLoadEnv = typeof process.loadEnvFile === 'function';
|
|
765
|
+
if (envUrl) {
|
|
766
|
+
(0, ui_js_1.success)(`Detected ${(0, ui_js_1.cyan)('DATABASE_URL')} in the environment`);
|
|
767
|
+
}
|
|
768
|
+
else if (hasEnvFile && !canAutoLoadEnv) {
|
|
769
|
+
(0, ui_js_1.info)(`Found ${(0, ui_js_1.cyan)('.env')} ${(0, ui_js_1.dim)('(this Node version cannot auto-load it. Upgrade to Node 20.12+ or export')} ${(0, ui_js_1.cyan)('DATABASE_URL')}${(0, ui_js_1.dim)(')')}`);
|
|
770
|
+
}
|
|
771
|
+
else if (hasEnvFile) {
|
|
772
|
+
// .env exists but did not provide DATABASE_URL; if it had, the auto-load
|
|
773
|
+
// in main() would have populated envUrl above.
|
|
774
|
+
(0, ui_js_1.info)(`Found ${(0, ui_js_1.cyan)('.env')} ${(0, ui_js_1.dim)('(no')} ${(0, ui_js_1.cyan)('DATABASE_URL')} ${(0, ui_js_1.dim)('set in it yet)')}`);
|
|
775
|
+
}
|
|
776
|
+
else if (hasEnvLocal) {
|
|
777
|
+
(0, ui_js_1.info)(`Found ${(0, ui_js_1.cyan)('.env.local')} ${(0, ui_js_1.dim)('(note: Turbine only auto-loads')} ${(0, ui_js_1.cyan)('.env')}${(0, ui_js_1.dim)(')')}`);
|
|
778
|
+
}
|
|
779
|
+
else {
|
|
780
|
+
(0, ui_js_1.info)(`No ${(0, ui_js_1.cyan)('DATABASE_URL')} found in environment`);
|
|
781
|
+
}
|
|
782
|
+
(0, ui_js_1.newline)();
|
|
783
|
+
// Heads-up (not an edit) about the consumer's module system. A CommonJS
|
|
784
|
+
// project (`npm init -y` default, or no "type" field) works fine now that the
|
|
785
|
+
// config loader unwraps the CJS-interop double-wrapped default, but ESM is the
|
|
786
|
+
// smoother path for a TypeScript config file.
|
|
787
|
+
const moduleType = detectConsumerModuleType();
|
|
788
|
+
if (moduleType === 'commonjs') {
|
|
789
|
+
(0, ui_js_1.info)(`Your ${(0, ui_js_1.cyan)('package.json')} is a CommonJS project ${(0, ui_js_1.dim)('(no')} ${(0, ui_js_1.cyan)('"type": "module"')}${(0, ui_js_1.dim)(').')}`);
|
|
790
|
+
console.log(` ${(0, ui_js_1.dim)('Turbine works either way. For the smoothest TypeScript config experience, consider adding')} ${(0, ui_js_1.cyan)('"type": "module"')}${(0, ui_js_1.dim)('.')}`);
|
|
577
791
|
(0, ui_js_1.newline)();
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
792
|
+
}
|
|
793
|
+
// Resolve the effective URL and detect current project state.
|
|
794
|
+
const url = args.url ?? envUrl ?? config.url;
|
|
795
|
+
const hasUrl = Boolean(url);
|
|
796
|
+
const interactive = Boolean(process.stdin.isTTY);
|
|
797
|
+
const yes = args.yes === true;
|
|
798
|
+
const seedFilePath = config.seedFile ?? './seed.ts';
|
|
799
|
+
const configPath = (0, config_js_1.findConfigFile)();
|
|
800
|
+
const state = {
|
|
801
|
+
configExists: Boolean(configPath),
|
|
802
|
+
schemaExists: (0, node_fs_1.existsSync)(config.schemaFile),
|
|
803
|
+
seedFileExists: (0, node_fs_1.existsSync)(seedFilePath),
|
|
804
|
+
hasUrl,
|
|
805
|
+
dbReachable: false,
|
|
806
|
+
};
|
|
807
|
+
// Probe the database once, up front, so the planner can decide the DB steps.
|
|
808
|
+
// Skip the probe entirely when every DB step is already flag-skipped.
|
|
809
|
+
const anyDbStepPossible = !(args.skipPush && args.skipGenerate && args.skipSeed);
|
|
810
|
+
if (hasUrl && anyDbStepPossible) {
|
|
811
|
+
const probe = new ui_js_1.Spinner('Checking database connection').start();
|
|
812
|
+
state.dbReachable = await probeDatabase(url);
|
|
813
|
+
if (state.dbReachable)
|
|
814
|
+
probe.succeed('Database is reachable');
|
|
815
|
+
else
|
|
816
|
+
probe.info('Database not reachable: push / generate / seed steps will be skipped');
|
|
817
|
+
}
|
|
818
|
+
const flags = {
|
|
819
|
+
yes,
|
|
820
|
+
force: args.force === true,
|
|
821
|
+
interactive,
|
|
822
|
+
skipSchema: args.skipSchema === true,
|
|
823
|
+
skipSeed: args.skipSeed === true,
|
|
824
|
+
skipPush: args.skipPush === true,
|
|
825
|
+
skipGenerate: args.skipGenerate === true,
|
|
826
|
+
};
|
|
827
|
+
const plan = planInitSteps(state, flags);
|
|
828
|
+
const degraded = !interactive && !yes;
|
|
829
|
+
// Supporting dirs + .gitignore, regardless of prompts (unchanged behavior).
|
|
830
|
+
ensureInitScaffoldDirs(config);
|
|
831
|
+
(0, ui_js_1.newline)();
|
|
832
|
+
// Execute the plan in order. `run` proceeds; `prompt` asks; `skip` reports.
|
|
833
|
+
for (const step of plan) {
|
|
834
|
+
if (step.action === 'skip') {
|
|
835
|
+
reportInitSkip(step);
|
|
836
|
+
continue;
|
|
837
|
+
}
|
|
838
|
+
const shouldRun = step.action === 'run' ? true : await promptYesNo(initPromptQuestion(step, config, seedFilePath), step.defaultYes);
|
|
839
|
+
if (!shouldRun) {
|
|
840
|
+
(0, ui_js_1.info)(`Skipped ${initStepLabel(step.id)}`);
|
|
841
|
+
continue;
|
|
842
|
+
}
|
|
843
|
+
switch (step.id) {
|
|
844
|
+
case 'config':
|
|
845
|
+
(0, node_fs_1.writeFileSync)('turbine.config.ts', (0, config_js_1.configTemplate)(args.url ?? undefined), 'utf-8');
|
|
846
|
+
(0, ui_js_1.success)(state.configExists ? `Overwrote ${(0, ui_js_1.cyan)('turbine.config.ts')}` : `Created ${(0, ui_js_1.cyan)('turbine.config.ts')}`);
|
|
847
|
+
break;
|
|
848
|
+
case 'schema':
|
|
849
|
+
writeInitSchemaTemplate(config);
|
|
850
|
+
break;
|
|
851
|
+
case 'seed-file':
|
|
852
|
+
writeInitSeedTemplate(seedFilePath);
|
|
853
|
+
break;
|
|
854
|
+
case 'push':
|
|
855
|
+
await runInitPush(config, url);
|
|
856
|
+
break;
|
|
857
|
+
case 'generate':
|
|
858
|
+
await runInitGenerate(config, url);
|
|
859
|
+
break;
|
|
860
|
+
case 'seed-run':
|
|
861
|
+
await runInitSeed(config);
|
|
862
|
+
break;
|
|
599
863
|
}
|
|
600
864
|
}
|
|
865
|
+
if (degraded) {
|
|
866
|
+
(0, ui_js_1.newline)();
|
|
867
|
+
(0, ui_js_1.info)('Non-interactive shell: interactive prompts were skipped.');
|
|
868
|
+
console.log(` ${(0, ui_js_1.dim)('Re-run in a terminal, pass')} ${(0, ui_js_1.cyan)('--yes')} ${(0, ui_js_1.dim)('to accept defaults, or use')} ${(0, ui_js_1.cyan)('--skip-*')} ${(0, ui_js_1.dim)('flags to control each step.')}`);
|
|
869
|
+
}
|
|
601
870
|
// Next steps
|
|
602
871
|
(0, ui_js_1.newline)();
|
|
603
872
|
(0, ui_js_1.divider)();
|
|
@@ -850,9 +1119,10 @@ async function cmdMigrate(args, config) {
|
|
|
850
1119
|
console.log(` ${(0, ui_js_1.bold)('turbine migrate')} ${(0, ui_js_1.dim)('— SQL-first migration system')}`);
|
|
851
1120
|
(0, ui_js_1.newline)();
|
|
852
1121
|
console.log(` ${(0, ui_js_1.bold)('Commands:')}`);
|
|
853
|
-
console.log(` ${(0, ui_js_1.cyan)('create <name>')}
|
|
854
|
-
console.log(` ${(0, ui_js_1.cyan)('create <name> --auto')}
|
|
855
|
-
console.log(` ${(0, ui_js_1.cyan)('create <name> --
|
|
1122
|
+
console.log(` ${(0, ui_js_1.cyan)('create <name>')} Create a new migration file`);
|
|
1123
|
+
console.log(` ${(0, ui_js_1.cyan)('create <name> --auto')} Auto-generate from schema diff`);
|
|
1124
|
+
console.log(` ${(0, ui_js_1.cyan)('create <name> --from-diff')} Generate from schema diff, destructive statements flagged`);
|
|
1125
|
+
console.log(` ${(0, ui_js_1.cyan)('create <name> --recipe')} Scaffold a named recipe (e.g. backfill)`);
|
|
856
1126
|
console.log(` ${(0, ui_js_1.cyan)('up')} Apply pending migrations`);
|
|
857
1127
|
console.log(` ${(0, ui_js_1.cyan)('deploy')} Apply pending migrations without prompts`);
|
|
858
1128
|
console.log(` ${(0, ui_js_1.cyan)('down')} Rollback last migration`);
|
|
@@ -860,6 +1130,7 @@ async function cmdMigrate(args, config) {
|
|
|
860
1130
|
(0, ui_js_1.newline)();
|
|
861
1131
|
console.log(` ${(0, ui_js_1.bold)('Options:')}`);
|
|
862
1132
|
console.log(` ${(0, ui_js_1.cyan)('--auto')} Auto-generate UP/DOWN SQL from schema diff`);
|
|
1133
|
+
console.log(` ${(0, ui_js_1.cyan)('--from-diff')} Like --auto, but flags destructive statements in the file`);
|
|
863
1134
|
console.log(` ${(0, ui_js_1.cyan)('--recipe <name>')} Scaffold a sanctioned migration pattern`);
|
|
864
1135
|
console.log(` ${(0, ui_js_1.cyan)('--step, -n')} Number of migrations to apply/rollback`);
|
|
865
1136
|
console.log(` ${(0, ui_js_1.cyan)('--dry-run')} Show SQL without executing`);
|
|
@@ -873,6 +1144,7 @@ async function cmdMigrate(args, config) {
|
|
|
873
1144
|
console.log(` ${(0, ui_js_1.bold)('Examples:')}`);
|
|
874
1145
|
console.log(` ${(0, ui_js_1.dim)('npx turbine migrate create add_users_table')}`);
|
|
875
1146
|
console.log(` ${(0, ui_js_1.dim)('npx turbine migrate create add_email_index --auto')}`);
|
|
1147
|
+
console.log(` ${(0, ui_js_1.dim)('npx turbine migrate create sync_schema --from-diff')}`);
|
|
876
1148
|
console.log(` ${(0, ui_js_1.dim)('npx turbine migrate create backfill_full_name --recipe backfill')}`);
|
|
877
1149
|
console.log(` ${(0, ui_js_1.dim)('npx turbine migrate up')}`);
|
|
878
1150
|
console.log(` ${(0, ui_js_1.dim)('npx turbine migrate deploy --dry-run')}`);
|
|
@@ -915,6 +1187,20 @@ async function cmdMigrateCreate(args, config) {
|
|
|
915
1187
|
(0, ui_js_1.newline)();
|
|
916
1188
|
process.exit(1);
|
|
917
1189
|
}
|
|
1190
|
+
// The scaffold strategies each own the file body, so combining them is
|
|
1191
|
+
// ambiguous. Refuse up front (before any of the strategy blocks run).
|
|
1192
|
+
if (args.fromDiff && args.recipe) {
|
|
1193
|
+
(0, ui_js_1.error)('--from-diff cannot be combined with --recipe.');
|
|
1194
|
+
console.log(` ${(0, ui_js_1.dim)('Pick one: --from-diff generates from the schema diff, --recipe scaffolds a pattern.')}`);
|
|
1195
|
+
(0, ui_js_1.newline)();
|
|
1196
|
+
process.exit(1);
|
|
1197
|
+
}
|
|
1198
|
+
if (args.fromDiff && args.auto) {
|
|
1199
|
+
(0, ui_js_1.error)('--from-diff cannot be combined with --auto.');
|
|
1200
|
+
console.log(` ${(0, ui_js_1.dim)('Both generate from the schema diff; --from-diff also flags destructive statements.')}`);
|
|
1201
|
+
(0, ui_js_1.newline)();
|
|
1202
|
+
process.exit(1);
|
|
1203
|
+
}
|
|
918
1204
|
if (args.auto) {
|
|
919
1205
|
// Auto-generate migration from schema diff
|
|
920
1206
|
const url = requireUrl(config);
|
|
@@ -925,7 +1211,7 @@ async function cmdMigrateCreate(args, config) {
|
|
|
925
1211
|
const diffSpinner = new ui_js_1.Spinner('Computing schema diff').start();
|
|
926
1212
|
const diff = await (0, schema_sql_js_1.schemaDiff)(schemaDef, url);
|
|
927
1213
|
if (diff.statements.length === 0) {
|
|
928
|
-
diffSpinner.succeed('Database is already in sync
|
|
1214
|
+
diffSpinner.succeed('Database is already in sync: nothing to migrate');
|
|
929
1215
|
(0, ui_js_1.newline)();
|
|
930
1216
|
return;
|
|
931
1217
|
}
|
|
@@ -966,6 +1252,78 @@ async function cmdMigrateCreate(args, config) {
|
|
|
966
1252
|
(0, ui_js_1.newline)();
|
|
967
1253
|
return;
|
|
968
1254
|
}
|
|
1255
|
+
if (args.fromDiff) {
|
|
1256
|
+
// Load the code-first schema (same file `push` uses). loadSchemaFile exits
|
|
1257
|
+
// with a clear message when the project has no schema-builder file.
|
|
1258
|
+
const url = requireUrl(config);
|
|
1259
|
+
(0, ui_js_1.label)('Database', (0, ui_js_1.redactUrl)(url));
|
|
1260
|
+
(0, ui_js_1.label)('Schema file', config.schemaFile);
|
|
1261
|
+
(0, ui_js_1.newline)();
|
|
1262
|
+
const schemaDef = await loadSchemaFile(config.schemaFile);
|
|
1263
|
+
const diffSpinner = new ui_js_1.Spinner('Computing schema diff').start();
|
|
1264
|
+
const diff = await (0, schema_sql_js_1.schemaDiff)(schemaDef, url);
|
|
1265
|
+
if (diff.statements.length === 0) {
|
|
1266
|
+
diffSpinner.succeed('Database is already in sync: nothing to migrate');
|
|
1267
|
+
(0, ui_js_1.newline)();
|
|
1268
|
+
return;
|
|
1269
|
+
}
|
|
1270
|
+
diffSpinner.succeed(`Found ${(0, ui_js_1.bold)(String(diff.statements.length))} change(s)`);
|
|
1271
|
+
(0, ui_js_1.newline)();
|
|
1272
|
+
const body = (0, migrate_js_1.buildDiffMigrationBody)(diff);
|
|
1273
|
+
const file = (0, migrate_js_1.createMigration)(config.migrationsDir, name, { up: body.up, down: body.down });
|
|
1274
|
+
const relPath = (0, node_path_1.relative)(process.cwd(), file.path);
|
|
1275
|
+
(0, ui_js_1.success)(`Created diff migration: ${(0, ui_js_1.bold)(file.filename)}`);
|
|
1276
|
+
(0, ui_js_1.newline)();
|
|
1277
|
+
console.log(` ${(0, ui_js_1.dim)('File:')} ${(0, ui_js_1.cyan)(relPath)}`);
|
|
1278
|
+
(0, ui_js_1.newline)();
|
|
1279
|
+
// Summarize the changes, mirroring --auto's output.
|
|
1280
|
+
if (diff.create.length > 0) {
|
|
1281
|
+
console.log(` ${(0, ui_js_1.green)('+ Create')} ${diff.create.length} table(s): ${diff.create.map((t) => t.name).join(', ')}`);
|
|
1282
|
+
}
|
|
1283
|
+
if (diff.alter.length > 0) {
|
|
1284
|
+
console.log(` ${(0, ui_js_1.yellow)('~ Alter')} ${diff.alter.length} table(s):`);
|
|
1285
|
+
for (const a of diff.alter) {
|
|
1286
|
+
for (const col of a.columns) {
|
|
1287
|
+
const actionLabel = col.action === 'add'
|
|
1288
|
+
? (0, ui_js_1.green)('+ add')
|
|
1289
|
+
: col.action === 'drop'
|
|
1290
|
+
? (0, ui_js_1.red)('- drop')
|
|
1291
|
+
: col.action === 'add_unique'
|
|
1292
|
+
? (0, ui_js_1.green)('+ unique')
|
|
1293
|
+
: col.action === 'drop_unique'
|
|
1294
|
+
? (0, ui_js_1.red)('- unique')
|
|
1295
|
+
: (0, ui_js_1.yellow)(`~ ${col.action.replace(/_/g, ' ')}`);
|
|
1296
|
+
console.log(` ${actionLabel} ${a.table}.${col.column}`);
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
(0, ui_js_1.newline)();
|
|
1301
|
+
// Loudly flag destructive statements written into the file. They stay in the
|
|
1302
|
+
// migration (so `migrate up`'s gate still refuses them by default) but the
|
|
1303
|
+
// operator must know they are there before running.
|
|
1304
|
+
const destructiveCount = body.destructiveUp.length + body.destructiveDown.length;
|
|
1305
|
+
if (destructiveCount > 0) {
|
|
1306
|
+
(0, ui_js_1.warn)(`This migration contains ${(0, ui_js_1.bold)(String(destructiveCount))} DESTRUCTIVE statement(s), flagged in the file.`);
|
|
1307
|
+
for (const h of body.destructiveUp) {
|
|
1308
|
+
console.log(` ${(0, ui_js_1.red)(ui_js_1.symbols.warning)} ${(0, ui_js_1.dim)('UP')} [${h.kind}] ${h.target}`);
|
|
1309
|
+
}
|
|
1310
|
+
for (const h of body.destructiveDown) {
|
|
1311
|
+
console.log(` ${(0, ui_js_1.red)(ui_js_1.symbols.warning)} ${(0, ui_js_1.dim)('DOWN')} [${h.kind}] ${h.target}`);
|
|
1312
|
+
}
|
|
1313
|
+
(0, ui_js_1.newline)();
|
|
1314
|
+
console.log(` ${(0, ui_js_1.dim)('`migrate up` refuses destructive statements by default: confirm interactively or pass')} ${(0, ui_js_1.cyan)('--allow-destructive')}${(0, ui_js_1.dim)('.')}`);
|
|
1315
|
+
(0, ui_js_1.newline)();
|
|
1316
|
+
}
|
|
1317
|
+
if (diff.warnings && diff.warnings.length > 0) {
|
|
1318
|
+
for (const w of diff.warnings)
|
|
1319
|
+
(0, ui_js_1.warn)(w);
|
|
1320
|
+
(0, ui_js_1.newline)();
|
|
1321
|
+
}
|
|
1322
|
+
console.log(` ${(0, ui_js_1.dim)('Review the migration, then run:')}`);
|
|
1323
|
+
console.log(` ${(0, ui_js_1.cyan)('npx turbine migrate up')}`);
|
|
1324
|
+
(0, ui_js_1.newline)();
|
|
1325
|
+
return;
|
|
1326
|
+
}
|
|
969
1327
|
if (args.recipe) {
|
|
970
1328
|
if (!migrate_js_1.MIGRATION_RECIPES[args.recipe]) {
|
|
971
1329
|
(0, ui_js_1.error)(`Unknown migration recipe: ${args.recipe}`);
|
|
@@ -1724,12 +2082,22 @@ function showInitHelp() {
|
|
|
1724
2082
|
console.log(` ${(0, ui_js_1.bold)('Usage:')}`);
|
|
1725
2083
|
console.log(` npx turbine init ${(0, ui_js_1.dim)('[options]')}`);
|
|
1726
2084
|
(0, ui_js_1.newline)();
|
|
1727
|
-
console.log(`
|
|
1728
|
-
console.log(`
|
|
2085
|
+
console.log(` Sequenced, interactive bootstrap. Detects project state and runs only`);
|
|
2086
|
+
console.log(` the needed steps: writes ${(0, ui_js_1.cyan)('turbine.config.ts')}, offers a starter schema +`);
|
|
2087
|
+
console.log(` seed file, and (when a reachable database is configured) offers to push`);
|
|
2088
|
+
console.log(` the schema, generate the typed client, and run the seed file.`);
|
|
2089
|
+
(0, ui_js_1.newline)();
|
|
2090
|
+
console.log(` ${(0, ui_js_1.dim)('Re-runs skip completed steps. A non-interactive shell runs the safe')}`);
|
|
2091
|
+
console.log(` ${(0, ui_js_1.dim)('scaffold + generate steps only; pass --yes to accept every default.')}`);
|
|
1729
2092
|
(0, ui_js_1.newline)();
|
|
1730
2093
|
console.log(` ${(0, ui_js_1.bold)('Options:')}`);
|
|
1731
2094
|
console.log(` ${(0, ui_js_1.cyan)('--url, -u')} ${(0, ui_js_1.dim)('<url>')} Postgres connection string to embed in config`);
|
|
1732
2095
|
console.log(` ${(0, ui_js_1.cyan)('--force, -f')} Overwrite existing config file`);
|
|
2096
|
+
console.log(` ${(0, ui_js_1.cyan)('--yes, -y')} Accept every step's default (non-interactive)`);
|
|
2097
|
+
console.log(` ${(0, ui_js_1.cyan)('--skip-schema')} Don't create the starter schema file`);
|
|
2098
|
+
console.log(` ${(0, ui_js_1.cyan)('--skip-seed')} Don't create the seed file or run the seed`);
|
|
2099
|
+
console.log(` ${(0, ui_js_1.cyan)('--skip-push')} Don't push the schema to the database`);
|
|
2100
|
+
console.log(` ${(0, ui_js_1.cyan)('--skip-generate')} Don't generate the typed client`);
|
|
1733
2101
|
(0, ui_js_1.newline)();
|
|
1734
2102
|
}
|
|
1735
2103
|
function showGenerateHelp() {
|
|
@@ -1791,6 +2159,7 @@ function showMigrateHelp() {
|
|
|
1791
2159
|
console.log(` ${(0, ui_js_1.bold)('Options:')}`);
|
|
1792
2160
|
console.log(` ${(0, ui_js_1.cyan)('--url, -u')} ${(0, ui_js_1.dim)('<url>')} Postgres connection string`);
|
|
1793
2161
|
console.log(` ${(0, ui_js_1.cyan)('--auto')} Auto-generate UP/DOWN SQL from schema diff ${(0, ui_js_1.dim)('(create only)')}`);
|
|
2162
|
+
console.log(` ${(0, ui_js_1.cyan)('--from-diff')} Generate from schema diff, flagging destructive statements ${(0, ui_js_1.dim)('(create only)')}`);
|
|
1794
2163
|
console.log(` ${(0, ui_js_1.cyan)('--recipe')} ${(0, ui_js_1.dim)('<name>')} Scaffold a sanctioned migration pattern ${(0, ui_js_1.dim)('(create only, e.g. backfill)')}`);
|
|
1795
2164
|
console.log(` ${(0, ui_js_1.cyan)('--step, -n')} ${(0, ui_js_1.dim)('<N>')} Number of migrations to apply/rollback`);
|
|
1796
2165
|
console.log(` ${(0, ui_js_1.cyan)('--dry-run')} Show SQL without executing`);
|
|
@@ -1801,6 +2170,7 @@ function showMigrateHelp() {
|
|
|
1801
2170
|
console.log(` ${(0, ui_js_1.bold)('Examples:')}`);
|
|
1802
2171
|
console.log(` ${(0, ui_js_1.dim)('$')} npx turbine migrate create add_users_table`);
|
|
1803
2172
|
console.log(` ${(0, ui_js_1.dim)('$')} npx turbine migrate create add_email_index --auto`);
|
|
2173
|
+
console.log(` ${(0, ui_js_1.dim)('$')} npx turbine migrate create sync_schema --from-diff`);
|
|
1804
2174
|
console.log(` ${(0, ui_js_1.dim)('$')} npx turbine migrate create backfill_full_name --recipe backfill`);
|
|
1805
2175
|
console.log(` ${(0, ui_js_1.dim)('$')} npx turbine migrate up`);
|
|
1806
2176
|
console.log(` ${(0, ui_js_1.dim)('$')} npx turbine migrate deploy --dry-run`);
|