relq 1.0.2 → 1.0.4
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/cjs/cli/commands/add.cjs +403 -27
- package/dist/cjs/cli/commands/branch.cjs +13 -23
- package/dist/cjs/cli/commands/checkout.cjs +16 -29
- package/dist/cjs/cli/commands/cherry-pick.cjs +3 -4
- package/dist/cjs/cli/commands/commit.cjs +21 -29
- package/dist/cjs/cli/commands/diff.cjs +28 -32
- package/dist/cjs/cli/commands/export.cjs +7 -7
- package/dist/cjs/cli/commands/fetch.cjs +15 -21
- package/dist/cjs/cli/commands/generate.cjs +28 -54
- package/dist/cjs/cli/commands/history.cjs +19 -40
- package/dist/cjs/cli/commands/import.cjs +34 -41
- package/dist/cjs/cli/commands/init.cjs +69 -59
- package/dist/cjs/cli/commands/introspect.cjs +4 -8
- package/dist/cjs/cli/commands/log.cjs +26 -32
- package/dist/cjs/cli/commands/merge.cjs +24 -41
- package/dist/cjs/cli/commands/migrate.cjs +12 -25
- package/dist/cjs/cli/commands/pull.cjs +216 -106
- package/dist/cjs/cli/commands/push.cjs +35 -75
- package/dist/cjs/cli/commands/remote.cjs +2 -1
- package/dist/cjs/cli/commands/reset.cjs +22 -43
- package/dist/cjs/cli/commands/resolve.cjs +12 -14
- package/dist/cjs/cli/commands/rollback.cjs +16 -38
- package/dist/cjs/cli/commands/stash.cjs +5 -7
- package/dist/cjs/cli/commands/status.cjs +5 -10
- package/dist/cjs/cli/commands/sync.cjs +30 -50
- package/dist/cjs/cli/commands/tag.cjs +3 -4
- package/dist/cjs/cli/index.cjs +72 -9
- package/dist/cjs/cli/utils/change-tracker.cjs +107 -3
- package/dist/cjs/cli/utils/cli-utils.cjs +217 -0
- package/dist/cjs/cli/utils/config-loader.cjs +34 -8
- package/dist/cjs/cli/utils/fast-introspect.cjs +109 -3
- package/dist/cjs/cli/utils/git-utils.cjs +42 -161
- package/dist/cjs/cli/utils/pool-manager.cjs +156 -0
- package/dist/cjs/cli/utils/project-root.cjs +56 -5
- package/dist/cjs/cli/utils/relqignore.cjs +1 -0
- package/dist/cjs/cli/utils/repo-manager.cjs +47 -0
- package/dist/cjs/cli/utils/schema-comparator.cjs +301 -11
- package/dist/cjs/cli/utils/schema-diff.cjs +202 -1
- package/dist/cjs/cli/utils/schema-hash.cjs +2 -1
- package/dist/cjs/cli/utils/schema-introspect.cjs +7 -3
- package/dist/cjs/cli/utils/snapshot-manager.cjs +1 -0
- package/dist/cjs/cli/utils/spinner.cjs +14 -106
- package/dist/cjs/cli/utils/sql-generator.cjs +10 -2
- package/dist/cjs/cli/utils/type-generator.cjs +28 -16
- package/dist/config.d.ts +16 -6
- package/dist/esm/cli/commands/add.js +372 -29
- package/dist/esm/cli/commands/branch.js +14 -24
- package/dist/esm/cli/commands/checkout.js +16 -29
- package/dist/esm/cli/commands/cherry-pick.js +3 -4
- package/dist/esm/cli/commands/commit.js +22 -30
- package/dist/esm/cli/commands/diff.js +6 -10
- package/dist/esm/cli/commands/export.js +8 -8
- package/dist/esm/cli/commands/fetch.js +14 -20
- package/dist/esm/cli/commands/generate.js +28 -54
- package/dist/esm/cli/commands/history.js +11 -32
- package/dist/esm/cli/commands/import.js +35 -42
- package/dist/esm/cli/commands/init.js +65 -55
- package/dist/esm/cli/commands/introspect.js +4 -8
- package/dist/esm/cli/commands/log.js +6 -12
- package/dist/esm/cli/commands/merge.js +20 -37
- package/dist/esm/cli/commands/migrate.js +12 -25
- package/dist/esm/cli/commands/pull.js +204 -94
- package/dist/esm/cli/commands/push.js +21 -61
- package/dist/esm/cli/commands/remote.js +2 -1
- package/dist/esm/cli/commands/reset.js +16 -37
- package/dist/esm/cli/commands/resolve.js +13 -15
- package/dist/esm/cli/commands/rollback.js +16 -38
- package/dist/esm/cli/commands/stash.js +6 -8
- package/dist/esm/cli/commands/status.js +6 -11
- package/dist/esm/cli/commands/sync.js +30 -50
- package/dist/esm/cli/commands/tag.js +3 -4
- package/dist/esm/cli/index.js +72 -9
- package/dist/esm/cli/utils/change-tracker.js +107 -3
- package/dist/esm/cli/utils/cli-utils.js +169 -0
- package/dist/esm/cli/utils/config-loader.js +34 -8
- package/dist/esm/cli/utils/fast-introspect.js +109 -3
- package/dist/esm/cli/utils/git-utils.js +2 -124
- package/dist/esm/cli/utils/pool-manager.js +114 -0
- package/dist/esm/cli/utils/project-root.js +55 -5
- package/dist/esm/cli/utils/relqignore.js +1 -0
- package/dist/esm/cli/utils/repo-manager.js +42 -0
- package/dist/esm/cli/utils/schema-comparator.js +301 -11
- package/dist/esm/cli/utils/schema-diff.js +202 -1
- package/dist/esm/cli/utils/schema-hash.js +2 -1
- package/dist/esm/cli/utils/schema-introspect.js +7 -3
- package/dist/esm/cli/utils/snapshot-manager.js +1 -0
- package/dist/esm/cli/utils/spinner.js +1 -101
- package/dist/esm/cli/utils/sql-generator.js +10 -2
- package/dist/esm/cli/utils/type-generator.js +28 -16
- package/dist/index.d.ts +25 -8
- package/dist/schema-builder.d.ts +18 -7
- package/package.json +1 -1
|
@@ -1,31 +1,27 @@
|
|
|
1
1
|
import * as crypto from 'crypto';
|
|
2
|
-
import {
|
|
3
|
-
import { isInitialized, getHead, getStagedChanges, getUnstagedChanges, shortHash, } from "../utils/repo-manager.js";
|
|
2
|
+
import { fatal, hint } from "../utils/cli-utils.js";
|
|
3
|
+
import { isInitialized, getHead, getStagedChanges, getUnstagedChanges, shortHash, hashFileContent, saveFileHash, } from "../utils/repo-manager.js";
|
|
4
|
+
import { loadConfig } from "../../config/config.js";
|
|
4
5
|
import { sortChangesByDependency, generateCombinedSQL, } from "../utils/change-tracker.js";
|
|
5
6
|
import * as fs from 'fs';
|
|
6
7
|
import * as path from 'path';
|
|
7
8
|
export async function commitCommand(context) {
|
|
8
|
-
const { config, flags, args } = context;
|
|
9
|
-
const projectRoot = process.cwd();
|
|
9
|
+
const { config, flags, args, projectRoot } = context;
|
|
10
10
|
const author = config?.author || 'Developer <dev@example.com>';
|
|
11
11
|
console.log('');
|
|
12
12
|
if (!isInitialized(projectRoot)) {
|
|
13
|
-
|
|
14
|
-
console.log('');
|
|
15
|
-
console.log(`${colors.muted('Run')} ${colors.cyan('relq init')} ${colors.muted('first.')}`);
|
|
16
|
-
return;
|
|
13
|
+
fatal('not a relq repository (or any parent directories): .relq', "run 'relq init' to initialize");
|
|
17
14
|
}
|
|
18
15
|
const staged = getStagedChanges(projectRoot);
|
|
19
16
|
if (staged.length === 0) {
|
|
20
|
-
console.log(
|
|
21
|
-
console.log('');
|
|
17
|
+
console.log('nothing to commit, working tree clean');
|
|
22
18
|
const unstaged = getUnstagedChanges(projectRoot);
|
|
23
19
|
if (unstaged.length > 0) {
|
|
24
|
-
console.log(`${
|
|
25
|
-
|
|
20
|
+
console.log(`${unstaged.length} unstaged change(s).`);
|
|
21
|
+
hint("run 'relq add .' to stage all changes");
|
|
26
22
|
}
|
|
27
23
|
else {
|
|
28
|
-
|
|
24
|
+
hint("run 'relq add <table>' to stage changes");
|
|
29
25
|
}
|
|
30
26
|
return;
|
|
31
27
|
}
|
|
@@ -35,10 +31,7 @@ export async function commitCommand(context) {
|
|
|
35
31
|
message = args.join(' ');
|
|
36
32
|
}
|
|
37
33
|
else {
|
|
38
|
-
|
|
39
|
-
console.log('');
|
|
40
|
-
console.log(`${colors.muted('Usage:')} relq commit -m "message"`);
|
|
41
|
-
return;
|
|
34
|
+
fatal('commit message required', "usage: relq commit -m '<message>'");
|
|
42
35
|
}
|
|
43
36
|
}
|
|
44
37
|
const sortedChanges = sortChangesByDependency(staged);
|
|
@@ -77,7 +70,7 @@ export async function commitCommand(context) {
|
|
|
77
70
|
fs.writeFileSync(path.join(commitsDir, `${hash}.json`), JSON.stringify(commit, null, 2), 'utf-8');
|
|
78
71
|
fs.writeFileSync(path.join(projectRoot, '.relq', 'HEAD'), hash, 'utf-8');
|
|
79
72
|
const workingPath = path.join(projectRoot, '.relq', 'working.json');
|
|
80
|
-
const unstaged = getUnstagedChanges(projectRoot);
|
|
73
|
+
const unstaged = getUnstagedChanges(projectRoot).filter(c => c.objectType !== 'SCHEMA_FILE');
|
|
81
74
|
if (unstaged.length > 0) {
|
|
82
75
|
fs.writeFileSync(workingPath, JSON.stringify({
|
|
83
76
|
timestamp: new Date().toISOString(),
|
|
@@ -90,20 +83,19 @@ export async function commitCommand(context) {
|
|
|
90
83
|
fs.unlinkSync(workingPath);
|
|
91
84
|
}
|
|
92
85
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
if (drops > 0) {
|
|
102
|
-
console.log(` ${colors.red(`${drops} dropped`)}`);
|
|
86
|
+
const commitConfig = await loadConfig();
|
|
87
|
+
const schemaPathRaw = typeof commitConfig.schema === 'string' ? commitConfig.schema : './db/schema.ts';
|
|
88
|
+
const schemaFilePath = path.resolve(projectRoot, schemaPathRaw);
|
|
89
|
+
if (fs.existsSync(schemaFilePath)) {
|
|
90
|
+
const currentContent = fs.readFileSync(schemaFilePath, 'utf-8');
|
|
91
|
+
const currentHash = hashFileContent(currentContent);
|
|
92
|
+
saveFileHash(currentHash, projectRoot);
|
|
103
93
|
}
|
|
94
|
+
console.log(`[${shortHash(hash)}] ${message}`);
|
|
95
|
+
console.log(` ${creates} create(s), ${alters} alter(s), ${drops} drop(s)`);
|
|
104
96
|
console.log('');
|
|
105
|
-
|
|
106
|
-
|
|
97
|
+
hint("run 'relq push' to apply changes to database");
|
|
98
|
+
hint("run 'relq export' to export as SQL file");
|
|
107
99
|
console.log('');
|
|
108
100
|
}
|
|
109
101
|
export default commitCommand;
|
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
import { requireValidConfig } from "../utils/config-loader.js";
|
|
2
2
|
import { fastIntrospectDatabase } from "../utils/fast-introspect.js";
|
|
3
3
|
import { getConnectionDescription } from "../utils/env-loader.js";
|
|
4
|
-
import { colors, createSpinner } from "../utils/
|
|
4
|
+
import { colors, createSpinner, fatal } from "../utils/cli-utils.js";
|
|
5
5
|
import { isInitialized, loadSnapshot, getStagedChanges, getUnstagedChanges, } from "../utils/repo-manager.js";
|
|
6
6
|
import { getChangeDisplayName, generateChangeSQL, sortChangesByDependency } from "../utils/change-tracker.js";
|
|
7
7
|
export async function diffCommand(context) {
|
|
8
|
-
const { config, args, flags } = context;
|
|
9
|
-
const projectRoot = process.cwd();
|
|
8
|
+
const { config, args, flags, projectRoot } = context;
|
|
10
9
|
console.log('');
|
|
11
10
|
if (!isInitialized(projectRoot)) {
|
|
12
|
-
|
|
13
|
-
console.log(`${colors.muted('Run')} ${colors.cyan('relq init')} ${colors.muted('to initialize.')}`);
|
|
14
|
-
return;
|
|
11
|
+
fatal('not a relq repository (or any parent directories): .relq', `Run ${colors.cyan('relq init')} to initialize.`);
|
|
15
12
|
}
|
|
16
13
|
const showSQL = flags['sql'] === true;
|
|
17
14
|
const staged = flags['staged'] === true;
|
|
@@ -22,10 +19,9 @@ export async function diffCommand(context) {
|
|
|
22
19
|
}
|
|
23
20
|
if (target === 'remote/live' || target === 'remote' || target === 'live' || target === 'origin') {
|
|
24
21
|
if (!config) {
|
|
25
|
-
|
|
26
|
-
process.exit(1);
|
|
22
|
+
fatal('No configuration found', `Run ${colors.cyan('relq init')} to create one.`);
|
|
27
23
|
}
|
|
28
|
-
requireValidConfig(config);
|
|
24
|
+
await requireValidConfig(config, { calledFrom: 'diff' });
|
|
29
25
|
await showOriginDiff(config, projectRoot, showSQL);
|
|
30
26
|
return;
|
|
31
27
|
}
|
|
@@ -111,7 +107,7 @@ async function showOriginDiff(config, projectRoot, showSQL) {
|
|
|
111
107
|
const diffs = compareSchemas(snapshot, remoteDb);
|
|
112
108
|
if (diffs.length === 0) {
|
|
113
109
|
console.log('');
|
|
114
|
-
console.log(
|
|
110
|
+
console.log('Local and remote are in sync.');
|
|
115
111
|
console.log('');
|
|
116
112
|
return;
|
|
117
113
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { introspectDatabase } from "../utils/schema-introspect.js";
|
|
4
|
-
import { createSpinner, colors } from "../utils/spinner.js";
|
|
4
|
+
import { createSpinner, colors, warning } from "../utils/spinner.js";
|
|
5
5
|
import { isInitialized, getStagedChanges, getUnstagedChanges, loadSnapshot, } from "../utils/repo-manager.js";
|
|
6
6
|
import { generateCombinedSQL, sortChangesByDependency, getChangeDisplayName, } from "../utils/change-tracker.js";
|
|
7
7
|
import { generateFullSchemaSQL } from "../utils/sql-generator.js";
|
|
@@ -9,8 +9,7 @@ import { loadRelqignore, isTableIgnored, isColumnIgnored, isIndexIgnored, isCons
|
|
|
9
9
|
import { loadConfig } from "../../config/config.js";
|
|
10
10
|
export async function exportCommand(context) {
|
|
11
11
|
const spinner = createSpinner();
|
|
12
|
-
const { args, flags } = context;
|
|
13
|
-
const projectRoot = process.cwd();
|
|
12
|
+
const { args, flags, projectRoot } = context;
|
|
14
13
|
const changesOnly = Boolean(flags['changes']);
|
|
15
14
|
const stagedOnly = Boolean(flags['staged']);
|
|
16
15
|
const fromDb = Boolean(flags['db']);
|
|
@@ -75,7 +74,7 @@ async function exportFromSnapshot(projectRoot, absoluteOutputPath, options) {
|
|
|
75
74
|
console.log(` ${colors.green('•')} Triggers: ${filteredSnapshot.triggers.length}`);
|
|
76
75
|
}
|
|
77
76
|
if (ignoredCount > 0) {
|
|
78
|
-
console.log(` ${colors.muted(
|
|
77
|
+
console.log(` ${colors.muted(`${ignoredCount} object(s) filtered by .relqignore`)}`);
|
|
79
78
|
}
|
|
80
79
|
spinner.start('Generating SQL statements');
|
|
81
80
|
const sqlContent = generateFullSQL(schema, options);
|
|
@@ -87,7 +86,7 @@ async function exportFromSnapshot(projectRoot, absoluteOutputPath, options) {
|
|
|
87
86
|
fs.writeFileSync(absoluteOutputPath, sqlContent, 'utf-8');
|
|
88
87
|
spinner.succeed(`Written ${options.output} (${(sqlContent.length / 1024).toFixed(1)} KB)`);
|
|
89
88
|
console.log('');
|
|
90
|
-
console.log(
|
|
89
|
+
console.log('Export completed');
|
|
91
90
|
console.log(` Source: ${colors.muted('snapshot (local)')}`);
|
|
92
91
|
console.log(` Output: ${colors.muted(options.output || '')}`);
|
|
93
92
|
}
|
|
@@ -127,7 +126,7 @@ async function exportFromDatabase(context, absoluteOutputPath, options) {
|
|
|
127
126
|
fs.writeFileSync(absoluteOutputPath, sqlContent, 'utf-8');
|
|
128
127
|
spinner.succeed(`Written ${options.output} (${(sqlContent.length / 1024).toFixed(1)} KB)`);
|
|
129
128
|
console.log('');
|
|
130
|
-
console.log(
|
|
129
|
+
console.log('Export completed');
|
|
131
130
|
console.log(` Source: ${colors.muted('database (live)')}`);
|
|
132
131
|
console.log(` Output: ${colors.muted(options.output || '')}`);
|
|
133
132
|
}
|
|
@@ -213,6 +212,7 @@ function normalizedToDbSchema(normalized) {
|
|
|
213
212
|
cycle: s.cycle,
|
|
214
213
|
ownedBy: s.ownedBy || undefined,
|
|
215
214
|
})),
|
|
215
|
+
collations: [],
|
|
216
216
|
extensions: normalized.extensions.map(e => e.name),
|
|
217
217
|
functions: (normalized.functions || []).map(f => ({
|
|
218
218
|
name: f.name,
|
|
@@ -260,7 +260,7 @@ async function exportChanges(projectRoot, flags, args, stagedOnly) {
|
|
|
260
260
|
modeLabel = 'uncommitted';
|
|
261
261
|
}
|
|
262
262
|
if (changes.length === 0) {
|
|
263
|
-
|
|
263
|
+
warning(`No ${modeLabel} changes to export`);
|
|
264
264
|
console.log('');
|
|
265
265
|
if (stagedOnly) {
|
|
266
266
|
console.log(`${colors.muted('Run')} ${colors.cyan('relq add .')} ${colors.muted('to stage changes.')}`);
|
|
@@ -289,7 +289,7 @@ async function exportChanges(projectRoot, flags, args, stagedOnly) {
|
|
|
289
289
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
290
290
|
}
|
|
291
291
|
fs.writeFileSync(absoluteOutputPath, sqlContent, 'utf-8');
|
|
292
|
-
console.log(
|
|
292
|
+
console.log(`Exported ${changes.length} ${modeLabel} change(s) to ${outputPath}`);
|
|
293
293
|
console.log(`${colors.muted(`${(sqlContent.length / 1024).toFixed(1)} KB`)}`);
|
|
294
294
|
console.log('');
|
|
295
295
|
}
|
|
@@ -1,22 +1,18 @@
|
|
|
1
1
|
import { requireValidConfig } from "../utils/config-loader.js";
|
|
2
2
|
import { getConnectionDescription } from "../utils/env-loader.js";
|
|
3
|
-
import { colors, createSpinner } from "../utils/
|
|
3
|
+
import { colors, createSpinner, fatal, hint } from "../utils/cli-utils.js";
|
|
4
4
|
import { isInitialized, getHead, loadCommit, saveCommit, setFetchHead, fetchRemoteCommits, ensureRemoteTable, } from "../utils/repo-manager.js";
|
|
5
5
|
export async function fetchCommand(context) {
|
|
6
6
|
const { config, flags } = context;
|
|
7
7
|
if (!config) {
|
|
8
|
-
|
|
9
|
-
process.exit(1);
|
|
8
|
+
fatal('No configuration found', `run ${colors.cyan('relq init')} to create a configuration file`);
|
|
10
9
|
}
|
|
11
|
-
requireValidConfig(config);
|
|
10
|
+
await requireValidConfig(config, { calledFrom: 'fetch' });
|
|
12
11
|
const connection = config.connection;
|
|
13
|
-
const projectRoot =
|
|
12
|
+
const { projectRoot } = context;
|
|
14
13
|
console.log('');
|
|
15
14
|
if (!isInitialized(projectRoot)) {
|
|
16
|
-
|
|
17
|
-
console.log('');
|
|
18
|
-
console.log(`${colors.muted('Run')} ${colors.cyan('relq init')} ${colors.muted('to initialize.')}`);
|
|
19
|
-
return;
|
|
15
|
+
fatal('not a relq repository (or any parent directories): .relq', "run 'relq init' to initialize");
|
|
20
16
|
}
|
|
21
17
|
const spinner = createSpinner();
|
|
22
18
|
spinner.start(`Fetching from ${getConnectionDescription(connection)}...`);
|
|
@@ -25,8 +21,7 @@ export async function fetchCommand(context) {
|
|
|
25
21
|
const remoteCommits = await fetchRemoteCommits(connection, 100);
|
|
26
22
|
if (remoteCommits.length === 0) {
|
|
27
23
|
spinner.succeed('No remote commits found');
|
|
28
|
-
|
|
29
|
-
console.log(`${colors.muted('Run')} ${colors.cyan('relq push')} ${colors.muted('to push your commits.')}`);
|
|
24
|
+
hint("run 'relq push' to push your commits");
|
|
30
25
|
return;
|
|
31
26
|
}
|
|
32
27
|
let newCommits = 0;
|
|
@@ -47,25 +42,24 @@ export async function fetchCommand(context) {
|
|
|
47
42
|
const ahead = [...localCommits].filter(h => !remoteHashes.has(h)).length;
|
|
48
43
|
console.log('');
|
|
49
44
|
if (behind > 0 && ahead > 0) {
|
|
50
|
-
console.log(`Your branch and 'origin/main' have diverged
|
|
51
|
-
console.log(`
|
|
45
|
+
console.log(`Your branch and 'origin/main' have diverged,`);
|
|
46
|
+
console.log(`and have ${ahead} and ${behind} different commits each, respectively.`);
|
|
52
47
|
}
|
|
53
48
|
else if (behind > 0) {
|
|
54
|
-
console.log(`Your branch is ${
|
|
55
|
-
|
|
49
|
+
console.log(`Your branch is behind 'origin/main' by ${behind} commit(s).`);
|
|
50
|
+
hint("run 'relq pull' to update your local branch");
|
|
56
51
|
}
|
|
57
52
|
else if (ahead > 0) {
|
|
58
|
-
console.log(`Your branch is ${
|
|
59
|
-
|
|
53
|
+
console.log(`Your branch is ahead of 'origin/main' by ${ahead} commit(s).`);
|
|
54
|
+
hint("run 'relq push' to publish your local commits");
|
|
60
55
|
}
|
|
61
56
|
else {
|
|
62
|
-
console.log(
|
|
57
|
+
console.log("Your branch is up to date with 'origin/main'.");
|
|
63
58
|
}
|
|
64
59
|
}
|
|
65
60
|
catch (error) {
|
|
66
61
|
spinner.fail('Fetch failed');
|
|
67
|
-
|
|
68
|
-
process.exit(1);
|
|
62
|
+
fatal('Fetch failed', error instanceof Error ? error.message : String(error));
|
|
69
63
|
}
|
|
70
64
|
console.log('');
|
|
71
65
|
}
|
|
@@ -8,32 +8,7 @@ import { diffSchemas, filterDiff, hasDestructiveChanges, getDestructiveTables }
|
|
|
8
8
|
import { normalizeSchema } from "../utils/schema-hash.js";
|
|
9
9
|
import { generateMigrationFile, getNextMigrationNumber, generateTimestampedName } from "../utils/migration-generator.js";
|
|
10
10
|
import { getConnectionDescription } from "../utils/env-loader.js";
|
|
11
|
-
|
|
12
|
-
reset: '\x1b[0m',
|
|
13
|
-
bold: '\x1b[1m',
|
|
14
|
-
dim: '\x1b[2m',
|
|
15
|
-
red: '\x1b[31m',
|
|
16
|
-
green: '\x1b[32m',
|
|
17
|
-
yellow: '\x1b[33m',
|
|
18
|
-
cyan: '\x1b[36m',
|
|
19
|
-
};
|
|
20
|
-
function askConfirm(question, defaultYes = false) {
|
|
21
|
-
const rl = readline.createInterface({
|
|
22
|
-
input: process.stdin,
|
|
23
|
-
output: process.stdout,
|
|
24
|
-
});
|
|
25
|
-
const suffix = defaultYes ? '[Y/n]' : '[y/N]';
|
|
26
|
-
return new Promise((resolve) => {
|
|
27
|
-
rl.question(`${question} ${suffix}: `, (answer) => {
|
|
28
|
-
rl.close();
|
|
29
|
-
const a = answer.trim().toLowerCase();
|
|
30
|
-
if (!a)
|
|
31
|
-
resolve(defaultYes);
|
|
32
|
-
else
|
|
33
|
-
resolve(a === 'y' || a === 'yes');
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
}
|
|
11
|
+
import { colors, confirm, fatal, warning } from "../utils/cli-utils.js";
|
|
37
12
|
function askInput(question) {
|
|
38
13
|
const rl = readline.createInterface({
|
|
39
14
|
input: process.stdin,
|
|
@@ -49,10 +24,10 @@ function askInput(question) {
|
|
|
49
24
|
export async function generateCommand(context) {
|
|
50
25
|
const { config, args, flags } = context;
|
|
51
26
|
if (!config) {
|
|
52
|
-
|
|
53
|
-
|
|
27
|
+
fatal('No configuration found', `run ${colors.cyan('relq init')} to create a configuration file`);
|
|
28
|
+
return;
|
|
54
29
|
}
|
|
55
|
-
requireValidConfig(config);
|
|
30
|
+
await requireValidConfig(config, { calledFrom: 'generate' });
|
|
56
31
|
const connection = config.connection;
|
|
57
32
|
const migrationsDir = config.migrations?.directory || './migrations';
|
|
58
33
|
const snapshotPath = config.sync?.snapshot || '.relq/snapshot.json';
|
|
@@ -75,10 +50,10 @@ export async function generateCommand(context) {
|
|
|
75
50
|
migrationName = message.replace(/\s+/g, '_').toLowerCase();
|
|
76
51
|
}
|
|
77
52
|
if (!migrationName && !isEmpty) {
|
|
78
|
-
migrationName = await askInput(
|
|
53
|
+
migrationName = await askInput('Migration name: ');
|
|
79
54
|
if (!migrationName) {
|
|
80
|
-
|
|
81
|
-
|
|
55
|
+
fatal('Migration name is required');
|
|
56
|
+
return;
|
|
82
57
|
}
|
|
83
58
|
}
|
|
84
59
|
if (isEmpty) {
|
|
@@ -86,48 +61,48 @@ export async function generateCommand(context) {
|
|
|
86
61
|
await createEmptyMigration(migrationsDir, migrationName, format, dryRun);
|
|
87
62
|
return;
|
|
88
63
|
}
|
|
89
|
-
console.log(
|
|
64
|
+
console.log('Generating migration...');
|
|
90
65
|
console.log(` Connection: ${getConnectionDescription(connection)}`);
|
|
91
66
|
console.log('');
|
|
92
67
|
try {
|
|
93
68
|
const dbSchema = await introspectDatabase(connection);
|
|
94
69
|
const snapshot = loadSnapshot(snapshotPath);
|
|
95
70
|
if (!snapshot) {
|
|
96
|
-
|
|
97
|
-
console.log(
|
|
71
|
+
warning('No snapshot found.');
|
|
72
|
+
console.log('Run "relq pull" first to create initial snapshot.');
|
|
98
73
|
return;
|
|
99
74
|
}
|
|
100
75
|
const localSchema = snapshotToDatabaseSchema(snapshot);
|
|
101
76
|
const diff = diffSchemas(normalizeSchema(localSchema), normalizeSchema(dbSchema));
|
|
102
77
|
const filteredDiff = filterDiff(diff, ignorePatterns);
|
|
103
78
|
if (!filteredDiff.hasChanges) {
|
|
104
|
-
console.log(
|
|
79
|
+
console.log('No changes to generate.');
|
|
105
80
|
return;
|
|
106
81
|
}
|
|
107
82
|
const s = filteredDiff.summary;
|
|
108
|
-
console.log(
|
|
83
|
+
console.log('Changes to include:');
|
|
109
84
|
if (s.tablesAdded > 0)
|
|
110
|
-
console.log(`
|
|
85
|
+
console.log(` + ${s.tablesAdded} table(s)`);
|
|
111
86
|
if (s.tablesRemoved > 0)
|
|
112
|
-
console.log(`
|
|
87
|
+
console.log(` - ${s.tablesRemoved} table(s)`);
|
|
113
88
|
if (s.tablesModified > 0)
|
|
114
|
-
console.log(`
|
|
89
|
+
console.log(` ~ ${s.tablesModified} table(s) modified`);
|
|
115
90
|
if (s.columnsAdded > 0)
|
|
116
|
-
console.log(`
|
|
91
|
+
console.log(` + ${s.columnsAdded} column(s)`);
|
|
117
92
|
if (s.columnsRemoved > 0)
|
|
118
|
-
console.log(`
|
|
93
|
+
console.log(` - ${s.columnsRemoved} column(s)`);
|
|
119
94
|
if (s.columnsModified > 0)
|
|
120
|
-
console.log(`
|
|
95
|
+
console.log(` ~ ${s.columnsModified} column(s)`);
|
|
121
96
|
console.log('');
|
|
122
97
|
if (hasDestructiveChanges(filteredDiff)) {
|
|
123
98
|
const tables = getDestructiveTables(filteredDiff);
|
|
124
|
-
|
|
99
|
+
warning('Destructive changes:');
|
|
125
100
|
for (const t of tables) {
|
|
126
|
-
console.log(`
|
|
101
|
+
console.log(` - ${t}`);
|
|
127
102
|
}
|
|
128
103
|
console.log('');
|
|
129
104
|
if (!autoStage) {
|
|
130
|
-
const proceed = await
|
|
105
|
+
const proceed = await confirm('Include destructive changes?', false);
|
|
131
106
|
if (!proceed) {
|
|
132
107
|
console.log('Cancelled.');
|
|
133
108
|
return;
|
|
@@ -149,7 +124,7 @@ export async function generateCommand(context) {
|
|
|
149
124
|
}
|
|
150
125
|
const filePath = path.join(migrationsDir, fileName);
|
|
151
126
|
if (dryRun) {
|
|
152
|
-
console.log(
|
|
127
|
+
console.log(`[dry-run] Would create: ${filePath}`);
|
|
153
128
|
console.log('');
|
|
154
129
|
console.log('--- Generated SQL ---');
|
|
155
130
|
console.log(migrationFile.content);
|
|
@@ -160,16 +135,15 @@ export async function generateCommand(context) {
|
|
|
160
135
|
fs.mkdirSync(migrationsDir, { recursive: true });
|
|
161
136
|
}
|
|
162
137
|
fs.writeFileSync(filePath, migrationFile.content, 'utf-8');
|
|
163
|
-
console.log(
|
|
138
|
+
console.log(`Created: ${filePath}`);
|
|
164
139
|
saveSnapshot(dbSchema, snapshotPath, connection.database);
|
|
165
|
-
console.log(
|
|
140
|
+
console.log('Updated snapshot.');
|
|
166
141
|
}
|
|
167
142
|
console.log('');
|
|
168
|
-
console.log(
|
|
143
|
+
console.log('Run "relq push" to apply this migration.');
|
|
169
144
|
}
|
|
170
145
|
catch (error) {
|
|
171
|
-
|
|
172
|
-
process.exit(1);
|
|
146
|
+
fatal('Generation failed', error instanceof Error ? error.message : String(error));
|
|
173
147
|
}
|
|
174
148
|
}
|
|
175
149
|
async function createEmptyMigration(migrationsDir, name, format, dryRun) {
|
|
@@ -194,13 +168,13 @@ async function createEmptyMigration(migrationsDir, name, format, dryRun) {
|
|
|
194
168
|
|
|
195
169
|
`;
|
|
196
170
|
if (dryRun) {
|
|
197
|
-
console.log(
|
|
171
|
+
console.log(`[dry-run] Would create: ${filePath}`);
|
|
198
172
|
}
|
|
199
173
|
else {
|
|
200
174
|
if (!fs.existsSync(migrationsDir)) {
|
|
201
175
|
fs.mkdirSync(migrationsDir, { recursive: true });
|
|
202
176
|
}
|
|
203
177
|
fs.writeFileSync(filePath, content, 'utf-8');
|
|
204
|
-
console.log(
|
|
178
|
+
console.log(`Created empty migration: ${filePath}`);
|
|
205
179
|
}
|
|
206
180
|
}
|
|
@@ -1,27 +1,19 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import { requireValidConfig } from "../utils/config-loader.js";
|
|
3
3
|
import { getConnectionDescription } from "../utils/env-loader.js";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
bold: '\x1b[1m',
|
|
7
|
-
dim: '\x1b[2m',
|
|
8
|
-
red: '\x1b[31m',
|
|
9
|
-
green: '\x1b[32m',
|
|
10
|
-
yellow: '\x1b[33m',
|
|
11
|
-
cyan: '\x1b[36m',
|
|
12
|
-
};
|
|
4
|
+
import { colors, fatal } from "../utils/cli-utils.js";
|
|
5
|
+
import { withPool } from "../utils/pool-manager.js";
|
|
13
6
|
export async function historyCommand(context) {
|
|
14
7
|
const { config, flags } = context;
|
|
15
8
|
if (!config) {
|
|
16
|
-
|
|
17
|
-
process.exit(1);
|
|
9
|
+
fatal('No configuration found', `run ${colors.cyan('relq init')} to create a configuration file`);
|
|
18
10
|
}
|
|
19
|
-
requireValidConfig(config);
|
|
11
|
+
await requireValidConfig(config, { calledFrom: 'history' });
|
|
20
12
|
const connection = config.connection;
|
|
21
13
|
const migrationsDir = config.migrations?.directory || './migrations';
|
|
22
14
|
const tableName = config.migrations?.tableName || '_relq_migrations';
|
|
23
15
|
const limit = parseInt(flags['n']) || 20;
|
|
24
|
-
console.log(
|
|
16
|
+
console.log('Migration History');
|
|
25
17
|
console.log(`Database: ${getConnectionDescription(connection)}`);
|
|
26
18
|
console.log('');
|
|
27
19
|
try {
|
|
@@ -40,8 +32,8 @@ export async function historyCommand(context) {
|
|
|
40
32
|
const toShow = history.slice(0, limit);
|
|
41
33
|
const pendingCount = history.filter(h => h.pending).length;
|
|
42
34
|
const appliedCount = history.filter(h => !h.pending).length;
|
|
43
|
-
console.log(
|
|
44
|
-
console.log(`${
|
|
35
|
+
console.log(`Showing ${toShow.length} of ${history.length} migrations`);
|
|
36
|
+
console.log(`${appliedCount} applied, ${pendingCount} pending`);
|
|
45
37
|
console.log('');
|
|
46
38
|
for (const record of toShow) {
|
|
47
39
|
if (record.pending) {
|
|
@@ -74,8 +66,7 @@ export async function historyCommand(context) {
|
|
|
74
66
|
}
|
|
75
67
|
return;
|
|
76
68
|
}
|
|
77
|
-
|
|
78
|
-
process.exit(1);
|
|
69
|
+
fatal('Failed to load history', error instanceof Error ? error.message : String(error));
|
|
79
70
|
}
|
|
80
71
|
}
|
|
81
72
|
function getMigrationFiles(migrationsDir) {
|
|
@@ -87,29 +78,17 @@ function getMigrationFiles(migrationsDir) {
|
|
|
87
78
|
.sort();
|
|
88
79
|
}
|
|
89
80
|
async function getAppliedMigrations(connection, tableName) {
|
|
90
|
-
|
|
91
|
-
const pool = new Pool({
|
|
92
|
-
host: connection.host,
|
|
93
|
-
port: connection.port || 5432,
|
|
94
|
-
database: connection.database,
|
|
95
|
-
user: connection.user,
|
|
96
|
-
password: connection.password,
|
|
97
|
-
connectionString: connection.url,
|
|
98
|
-
});
|
|
99
|
-
try {
|
|
81
|
+
return withPool(connection, async (pool) => {
|
|
100
82
|
const result = await pool.query(`
|
|
101
83
|
SELECT name, applied_at
|
|
102
84
|
FROM "${tableName}"
|
|
103
85
|
ORDER BY id DESC;
|
|
104
86
|
`);
|
|
105
|
-
return result.rows.map(r => ({
|
|
87
|
+
return result.rows.map((r) => ({
|
|
106
88
|
name: r.name,
|
|
107
89
|
appliedAt: new Date(r.applied_at),
|
|
108
90
|
}));
|
|
109
|
-
}
|
|
110
|
-
finally {
|
|
111
|
-
await pool.end();
|
|
112
|
-
}
|
|
91
|
+
});
|
|
113
92
|
}
|
|
114
93
|
function formatDate(date) {
|
|
115
94
|
const now = new Date();
|