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,21 +1,19 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
|
-
import * as readline from 'readline';
|
|
4
3
|
import { requireValidConfig } from "../utils/config-loader.js";
|
|
5
4
|
import { getConnectionDescription } from "../utils/env-loader.js";
|
|
6
|
-
import { colors, createSpinner } from "../utils/
|
|
5
|
+
import { colors, createSpinner, fatal, confirm, warning } from "../utils/cli-utils.js";
|
|
7
6
|
import { fastIntrospectDatabase } from "../utils/fast-introspect.js";
|
|
8
7
|
import { loadRelqignore, isTableIgnored, isColumnIgnored, isEnumIgnored, isDomainIgnored, isFunctionIgnored, } from "../utils/relqignore.js";
|
|
9
8
|
import { isInitialized, getHead, shortHash, fetchRemoteCommits, pushCommit, ensureRemoteTable, getAllCommits, loadSnapshot, } from "../utils/repo-manager.js";
|
|
10
9
|
export async function pushCommand(context) {
|
|
11
10
|
const { config, flags } = context;
|
|
12
11
|
if (!config) {
|
|
13
|
-
|
|
14
|
-
process.exit(1);
|
|
12
|
+
fatal('No configuration found', `Run ${colors.cyan('relq init')} to create one.`);
|
|
15
13
|
}
|
|
16
|
-
requireValidConfig(config);
|
|
14
|
+
await requireValidConfig(config, { calledFrom: 'push' });
|
|
17
15
|
const connection = config.connection;
|
|
18
|
-
const projectRoot =
|
|
16
|
+
const { projectRoot } = context;
|
|
19
17
|
const force = flags['force'] === true;
|
|
20
18
|
const dryRun = flags['dry-run'] === true;
|
|
21
19
|
const applySQL = flags['apply'] === true;
|
|
@@ -26,17 +24,11 @@ export async function pushCommand(context) {
|
|
|
26
24
|
const includeViews = config.includeViews ?? false;
|
|
27
25
|
console.log('');
|
|
28
26
|
if (!isInitialized(projectRoot)) {
|
|
29
|
-
|
|
30
|
-
console.log('');
|
|
31
|
-
console.log(`${colors.muted('Run')} ${colors.cyan('relq init')} ${colors.muted('to initialize.')}`);
|
|
32
|
-
return;
|
|
27
|
+
fatal('not a relq repository (or any parent directories): .relq', `Run ${colors.cyan('relq init')} to initialize.`);
|
|
33
28
|
}
|
|
34
29
|
const localHead = getHead(projectRoot);
|
|
35
30
|
if (!localHead) {
|
|
36
|
-
|
|
37
|
-
console.log('');
|
|
38
|
-
console.log(`${colors.muted('Run')} ${colors.cyan('relq commit -m "message"')} ${colors.muted('to create a commit.')}`);
|
|
39
|
-
return;
|
|
31
|
+
fatal('no commits to push', `Run ${colors.cyan('relq commit -m "message"')} to create a commit.`);
|
|
40
32
|
}
|
|
41
33
|
const spinner = createSpinner();
|
|
42
34
|
try {
|
|
@@ -60,9 +52,8 @@ export async function pushCommand(context) {
|
|
|
60
52
|
spinner.succeed(`Found ${remoteDb.tables.length} tables in remote`);
|
|
61
53
|
const localSnapshot = loadSnapshot(projectRoot);
|
|
62
54
|
if (!localSnapshot) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
return;
|
|
55
|
+
spinner.stop();
|
|
56
|
+
fatal('No local snapshot found', `Run ${colors.cyan('relq pull')} or ${colors.cyan('relq import')} first.`);
|
|
66
57
|
}
|
|
67
58
|
const ignorePatterns = loadRelqignore(projectRoot);
|
|
68
59
|
const analysis = analyzeSync(localSnapshot, remoteDb, ignorePatterns, { includeFunctions, includeTriggers, includeViews });
|
|
@@ -70,8 +61,7 @@ export async function pushCommand(context) {
|
|
|
70
61
|
const hasObjectsToDrop = analysis.objectsToDrop.length > 0;
|
|
71
62
|
const hasSchemaDrift = analysis.schemaDrift.length > 0;
|
|
72
63
|
if (hasRemoteAhead && !force) {
|
|
73
|
-
|
|
74
|
-
console.log(`${colors.red('error:')} Remote has ${remoteMissing.length} commit(s) you don't have locally`);
|
|
64
|
+
spinner.stop();
|
|
75
65
|
console.log('');
|
|
76
66
|
for (const commit of remoteMissing.slice(0, 3)) {
|
|
77
67
|
console.log(` ${colors.yellow(shortHash(commit.hash))} ${commit.message}`);
|
|
@@ -79,15 +69,11 @@ export async function pushCommand(context) {
|
|
|
79
69
|
if (remoteMissing.length > 3) {
|
|
80
70
|
console.log(` ${colors.muted(`... and ${remoteMissing.length - 3} more`)}`);
|
|
81
71
|
}
|
|
82
|
-
|
|
83
|
-
console.log(`${colors.yellow('hint:')} Run ${colors.cyan('relq pull')} to integrate remote changes.`);
|
|
84
|
-
console.log(`${colors.yellow('hint:')} Use ${colors.cyan('relq push --force')} to override (may cause data loss).`);
|
|
85
|
-
console.log('');
|
|
86
|
-
return;
|
|
72
|
+
fatal(`Remote has ${remoteMissing.length} commit(s) you don't have locally`, `Run ${colors.cyan('relq pull')} to integrate remote changes.\nUse ${colors.cyan('relq push --force')} to override (may cause data loss).`);
|
|
87
73
|
}
|
|
88
74
|
if (hasObjectsToDrop) {
|
|
89
75
|
console.log('');
|
|
90
|
-
|
|
76
|
+
warning(`Remote has ${analysis.objectsToDrop.length} object(s) not in your local schema:`);
|
|
91
77
|
console.log('');
|
|
92
78
|
for (const obj of analysis.objectsToDrop.slice(0, 10)) {
|
|
93
79
|
console.log(` ${colors.red('DROP')} ${obj.type.toLowerCase()}: ${obj.name}`);
|
|
@@ -97,39 +83,27 @@ export async function pushCommand(context) {
|
|
|
97
83
|
}
|
|
98
84
|
console.log('');
|
|
99
85
|
if (!force) {
|
|
100
|
-
|
|
101
|
-
console.log('');
|
|
102
|
-
console.log(`${colors.yellow('hint:')} Run ${colors.cyan('relq pull')} first to sync your local schema.`);
|
|
103
|
-
console.log(`${colors.yellow('hint:')} Use ${colors.cyan('relq push --force')} to DROP these objects from remote.`);
|
|
104
|
-
console.log('');
|
|
105
|
-
return;
|
|
86
|
+
fatal('Cannot push - remote has objects not in your history', `Run ${colors.cyan('relq pull')} first to sync your local schema.\nUse ${colors.cyan('relq push --force')} to DROP these objects from remote.`);
|
|
106
87
|
}
|
|
107
88
|
const dependencyErrors = checkDropDependencies(analysis.objectsToDrop, remoteDb, ignorePatterns);
|
|
108
89
|
if (dependencyErrors.length > 0) {
|
|
109
|
-
console.log(`${colors.red('error:')} Cannot drop objects due to dependencies:`);
|
|
110
90
|
console.log('');
|
|
111
91
|
for (const err of dependencyErrors) {
|
|
112
|
-
console.log(` ${
|
|
92
|
+
console.log(` ${err}`);
|
|
113
93
|
}
|
|
114
|
-
|
|
115
|
-
console.log(`${colors.muted('These objects are used by non-ignored objects in your schema.')}`);
|
|
116
|
-
console.log(`${colors.muted('Either add them to .relqignore or import them with')} ${colors.cyan('relq pull')}`);
|
|
117
|
-
console.log('');
|
|
118
|
-
return;
|
|
94
|
+
fatal('Cannot drop objects due to dependencies', `These objects are used by non-ignored objects in your schema.\nEither add them to .relqignore or import them with ${colors.cyan('relq pull')}`);
|
|
119
95
|
}
|
|
120
96
|
if (!skipPrompt && !dryRun) {
|
|
121
|
-
|
|
97
|
+
warning('This will DROP data from your database!');
|
|
122
98
|
console.log('');
|
|
123
|
-
const confirmed = await
|
|
99
|
+
const confirmed = await confirm(`Drop ${analysis.objectsToDrop.length} object(s) from remote?`, false);
|
|
124
100
|
if (!confirmed) {
|
|
125
|
-
|
|
126
|
-
console.log('');
|
|
127
|
-
return;
|
|
101
|
+
fatal('Operation cancelled by user');
|
|
128
102
|
}
|
|
129
103
|
}
|
|
130
104
|
}
|
|
131
105
|
if (toPush.length === 0 && !hasObjectsToDrop) {
|
|
132
|
-
console.log(
|
|
106
|
+
console.log('Everything up-to-date');
|
|
133
107
|
console.log('');
|
|
134
108
|
return;
|
|
135
109
|
}
|
|
@@ -219,7 +193,7 @@ export async function pushCommand(context) {
|
|
|
219
193
|
console.log(` ${oldHash}..${newHash} ${colors.muted('main -> main')}`);
|
|
220
194
|
if (hasObjectsToDrop && force && applySQL) {
|
|
221
195
|
console.log('');
|
|
222
|
-
|
|
196
|
+
warning(`Dropped ${analysis.objectsToDrop.length} object(s) from remote`);
|
|
223
197
|
}
|
|
224
198
|
if (!applySQL) {
|
|
225
199
|
console.log('');
|
|
@@ -227,10 +201,9 @@ export async function pushCommand(context) {
|
|
|
227
201
|
}
|
|
228
202
|
console.log('');
|
|
229
203
|
}
|
|
230
|
-
catch (
|
|
204
|
+
catch (err) {
|
|
231
205
|
spinner.fail('Push failed');
|
|
232
|
-
|
|
233
|
-
process.exit(1);
|
|
206
|
+
fatal(err instanceof Error ? err.message : String(err));
|
|
234
207
|
}
|
|
235
208
|
}
|
|
236
209
|
function analyzeSync(local, remote, patterns, options) {
|
|
@@ -329,17 +302,4 @@ function generateDropSQL(obj) {
|
|
|
329
302
|
return '';
|
|
330
303
|
}
|
|
331
304
|
}
|
|
332
|
-
function askConfirmation(question) {
|
|
333
|
-
const rl = readline.createInterface({
|
|
334
|
-
input: process.stdin,
|
|
335
|
-
output: process.stdout,
|
|
336
|
-
});
|
|
337
|
-
return new Promise((resolve) => {
|
|
338
|
-
rl.question(question, (answer) => {
|
|
339
|
-
rl.close();
|
|
340
|
-
const a = answer.trim().toLowerCase();
|
|
341
|
-
resolve(a === 'y' || a === 'yes');
|
|
342
|
-
});
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
305
|
export default pushCommand;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { warning } from "../utils/spinner.js";
|
|
1
2
|
export async function remoteCommand(context) {
|
|
2
3
|
console.log('');
|
|
3
|
-
|
|
4
|
+
warning('Remote tracking is coming soon.');
|
|
4
5
|
console.log('');
|
|
5
6
|
console.log('This will allow you to:');
|
|
6
7
|
console.log(' • Track multiple remote databases');
|
|
@@ -1,40 +1,26 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import * as readline from 'readline';
|
|
4
|
-
import { colors, createSpinner } from "../utils/
|
|
4
|
+
import { colors, createSpinner, fatal, warning } from "../utils/cli-utils.js";
|
|
5
5
|
import { isInitialized, getHead, setHead, loadCommit, saveSnapshot, shortHash, getAllCommits, } from "../utils/repo-manager.js";
|
|
6
6
|
export async function resetCommand(context) {
|
|
7
|
-
const { flags, args } = context;
|
|
8
|
-
const projectRoot = process.cwd();
|
|
7
|
+
const { flags, args, projectRoot } = context;
|
|
9
8
|
console.log('');
|
|
10
9
|
if (!isInitialized(projectRoot)) {
|
|
11
|
-
|
|
12
|
-
return;
|
|
10
|
+
fatal('not a relq repository (or any parent directories): .relq', `Run ${colors.cyan('relq init')} to initialize.`);
|
|
13
11
|
}
|
|
14
12
|
const hard = flags['hard'] === true;
|
|
15
13
|
const soft = flags['soft'] === true;
|
|
16
14
|
const target = args[0];
|
|
17
15
|
if (!target) {
|
|
18
|
-
|
|
19
|
-
console.log('');
|
|
20
|
-
console.log('Usage:');
|
|
21
|
-
console.log(` ${colors.cyan('relq reset --hard HEAD~1')} Reset to previous commit`);
|
|
22
|
-
console.log(` ${colors.cyan('relq reset --hard <hash>')} Reset to specific commit`);
|
|
23
|
-
console.log('');
|
|
24
|
-
return;
|
|
16
|
+
fatal('Please specify a target', `Usage:\n ${colors.cyan('relq reset --hard HEAD~1')} Reset to previous commit\n ${colors.cyan('relq reset --hard <hash>')} Reset to specific commit`);
|
|
25
17
|
}
|
|
26
18
|
if (!hard && !soft) {
|
|
27
|
-
|
|
28
|
-
console.log('');
|
|
29
|
-
console.log(` ${colors.cyan('--hard')} Discard all changes (DANGEROUS)`);
|
|
30
|
-
console.log(` ${colors.cyan('--soft')} Keep changes unstaged`);
|
|
31
|
-
console.log('');
|
|
32
|
-
return;
|
|
19
|
+
fatal('Please specify --hard or --soft', ` ${colors.cyan('--hard')} Discard all changes (DANGEROUS)\n ${colors.cyan('--soft')} Keep changes unstaged`);
|
|
33
20
|
}
|
|
34
21
|
const currentHead = getHead(projectRoot);
|
|
35
22
|
if (!currentHead) {
|
|
36
|
-
|
|
37
|
-
return;
|
|
23
|
+
fatal('No commits yet', `Run ${colors.cyan('relq pull')} or ${colors.cyan('relq import')} first.`);
|
|
38
24
|
}
|
|
39
25
|
const allCommits = getAllCommits(projectRoot);
|
|
40
26
|
let targetHash = null;
|
|
@@ -55,21 +41,15 @@ export async function resetCommand(context) {
|
|
|
55
41
|
}
|
|
56
42
|
}
|
|
57
43
|
if (!targetHash) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
console.log('Available commits:');
|
|
61
|
-
for (const c of allCommits.slice(0, 5)) {
|
|
62
|
-
console.log(` ${colors.yellow(shortHash(c.hash))} ${c.message}`);
|
|
63
|
-
}
|
|
64
|
-
return;
|
|
44
|
+
const availableList = allCommits.slice(0, 5).map(c => ` ${colors.yellow(shortHash(c.hash))} ${c.message}`).join('\n');
|
|
45
|
+
fatal(`Cannot find commit: ${target}`, `Available commits:\n${availableList}`);
|
|
65
46
|
}
|
|
66
47
|
const targetCommit = loadCommit(targetHash, projectRoot);
|
|
67
48
|
if (!targetCommit) {
|
|
68
|
-
|
|
69
|
-
return;
|
|
49
|
+
fatal(`Cannot load commit: ${targetHash}`);
|
|
70
50
|
}
|
|
71
51
|
if (hard) {
|
|
72
|
-
|
|
52
|
+
warning('This will discard all local changes!');
|
|
73
53
|
console.log('');
|
|
74
54
|
console.log(`Reset to: ${colors.yellow(shortHash(targetHash))} ${targetCommit.message}`);
|
|
75
55
|
console.log('');
|
|
@@ -94,14 +74,14 @@ export async function resetCommand(context) {
|
|
|
94
74
|
spinner.start(`Resetting to ${shortHash(targetHash)}...`);
|
|
95
75
|
const commitPath = path.join(projectRoot, '.relq', 'commits', `${targetHash}.json`);
|
|
96
76
|
if (!fs.existsSync(commitPath)) {
|
|
97
|
-
spinner.
|
|
98
|
-
|
|
77
|
+
spinner.stop();
|
|
78
|
+
fatal('Cannot find commit data - repository may be corrupt');
|
|
99
79
|
}
|
|
100
80
|
const commitData = JSON.parse(fs.readFileSync(commitPath, 'utf-8'));
|
|
101
81
|
const targetSnapshot = commitData.snapshot;
|
|
102
82
|
if (!targetSnapshot) {
|
|
103
|
-
spinner.
|
|
104
|
-
|
|
83
|
+
spinner.stop();
|
|
84
|
+
fatal('Commit has no snapshot data - repository may be corrupt');
|
|
105
85
|
}
|
|
106
86
|
if (hard) {
|
|
107
87
|
saveSnapshot(targetSnapshot, projectRoot);
|
|
@@ -124,10 +104,9 @@ export async function resetCommand(context) {
|
|
|
124
104
|
console.log(`${colors.muted('Changes are unstaged. Use')} ${colors.cyan('relq status')} ${colors.muted('to see.')}`);
|
|
125
105
|
}
|
|
126
106
|
}
|
|
127
|
-
catch (
|
|
107
|
+
catch (err) {
|
|
128
108
|
spinner.fail('Reset failed');
|
|
129
|
-
|
|
130
|
-
process.exit(1);
|
|
109
|
+
fatal(err instanceof Error ? err.message : String(err));
|
|
131
110
|
}
|
|
132
111
|
}
|
|
133
112
|
export default resetCommand;
|
|
@@ -1,25 +1,23 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
|
-
import { colors } from "../utils/spinner.js";
|
|
3
|
+
import { colors, fatal, warning } from "../utils/spinner.js";
|
|
4
4
|
import { isInitialized, loadSnapshot, saveSnapshot, } from "../utils/repo-manager.js";
|
|
5
5
|
export async function resolveCommand(context) {
|
|
6
|
-
const { flags, args } = context;
|
|
7
|
-
const projectRoot = process.cwd();
|
|
6
|
+
const { flags, args, projectRoot } = context;
|
|
8
7
|
console.log('');
|
|
9
8
|
if (!isInitialized(projectRoot)) {
|
|
10
|
-
|
|
11
|
-
return;
|
|
9
|
+
fatal('not a relq repository (or any parent directories): .relq', `Run ${colors.cyan('relq init')} to initialize.`);
|
|
12
10
|
}
|
|
13
11
|
const mergeStatePath = path.join(projectRoot, '.relq', 'MERGE_STATE');
|
|
14
12
|
if (!fs.existsSync(mergeStatePath)) {
|
|
15
|
-
console.log(
|
|
13
|
+
console.log('No conflicts to resolve');
|
|
16
14
|
console.log('');
|
|
17
15
|
return;
|
|
18
16
|
}
|
|
19
17
|
const mergeState = JSON.parse(fs.readFileSync(mergeStatePath, 'utf-8'));
|
|
20
18
|
if (mergeState.conflicts.length === 0) {
|
|
21
19
|
fs.unlinkSync(mergeStatePath);
|
|
22
|
-
console.log(
|
|
20
|
+
console.log('All conflicts resolved');
|
|
23
21
|
console.log('');
|
|
24
22
|
return;
|
|
25
23
|
}
|
|
@@ -33,9 +31,9 @@ export async function resolveCommand(context) {
|
|
|
33
31
|
console.log('');
|
|
34
32
|
saveSnapshot(mergeState.remoteSnapshot, projectRoot);
|
|
35
33
|
fs.unlinkSync(mergeStatePath);
|
|
36
|
-
console.log(
|
|
34
|
+
console.log('Applied remote versions for all conflicts');
|
|
37
35
|
console.log('');
|
|
38
|
-
console.log(
|
|
36
|
+
console.log(`hint: run 'relq commit -m "Merge remote changes"' to complete`);
|
|
39
37
|
console.log('');
|
|
40
38
|
return;
|
|
41
39
|
}
|
|
@@ -43,9 +41,9 @@ export async function resolveCommand(context) {
|
|
|
43
41
|
console.log(`Resolving ${mergeState.conflicts.length} conflict(s) with --all-ours`);
|
|
44
42
|
console.log('');
|
|
45
43
|
fs.unlinkSync(mergeStatePath);
|
|
46
|
-
console.log(
|
|
44
|
+
console.log('Kept local versions for all conflicts');
|
|
47
45
|
console.log('');
|
|
48
|
-
console.log(
|
|
46
|
+
console.log(`hint: run 'relq commit -m "Keep local changes"' to complete`);
|
|
49
47
|
console.log('');
|
|
50
48
|
return;
|
|
51
49
|
}
|
|
@@ -75,17 +73,17 @@ export async function resolveCommand(context) {
|
|
|
75
73
|
mergeState.conflicts = mergeState.conflicts.filter(c => c !== conflict);
|
|
76
74
|
if (mergeState.conflicts.length === 0) {
|
|
77
75
|
fs.unlinkSync(mergeStatePath);
|
|
78
|
-
console.log(
|
|
76
|
+
console.log('All conflicts resolved');
|
|
79
77
|
}
|
|
80
78
|
else {
|
|
81
79
|
fs.writeFileSync(mergeStatePath, JSON.stringify(mergeState, null, 2));
|
|
82
|
-
console.log(
|
|
83
|
-
console.log(`${
|
|
80
|
+
console.log(`Resolved: ${conflict.objectType} ${objectName}`);
|
|
81
|
+
console.log(`${mergeState.conflicts.length} conflict(s) remaining`);
|
|
84
82
|
}
|
|
85
83
|
console.log('');
|
|
86
84
|
return;
|
|
87
85
|
}
|
|
88
|
-
|
|
86
|
+
warning(`You have ${mergeState.conflicts.length} unresolved conflict(s):`);
|
|
89
87
|
console.log('');
|
|
90
88
|
for (const conflict of mergeState.conflicts) {
|
|
91
89
|
const name = conflict.parentName
|
|
@@ -1,17 +1,8 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
|
-
import * as readline from 'readline';
|
|
4
3
|
import { requireValidConfig } from "../utils/config-loader.js";
|
|
5
4
|
import { getConnectionDescription } from "../utils/env-loader.js";
|
|
6
|
-
|
|
7
|
-
reset: '\x1b[0m',
|
|
8
|
-
bold: '\x1b[1m',
|
|
9
|
-
dim: '\x1b[2m',
|
|
10
|
-
red: '\x1b[31m',
|
|
11
|
-
green: '\x1b[32m',
|
|
12
|
-
yellow: '\x1b[33m',
|
|
13
|
-
cyan: '\x1b[36m',
|
|
14
|
-
};
|
|
5
|
+
import { colors, confirm, fatal, warning } from "../utils/cli-utils.js";
|
|
15
6
|
function parseMigration(content) {
|
|
16
7
|
const upMatch = content.match(/--\s*UP\s*\n([\s\S]*?)(?=--\s*DOWN|$)/i);
|
|
17
8
|
const downMatch = content.match(/--\s*DOWN\s*\n([\s\S]*?)$/i);
|
|
@@ -20,32 +11,20 @@ function parseMigration(content) {
|
|
|
20
11
|
down: downMatch?.[1]?.trim() || '',
|
|
21
12
|
};
|
|
22
13
|
}
|
|
23
|
-
function askConfirm(question) {
|
|
24
|
-
const rl = readline.createInterface({
|
|
25
|
-
input: process.stdin,
|
|
26
|
-
output: process.stdout,
|
|
27
|
-
});
|
|
28
|
-
return new Promise((resolve) => {
|
|
29
|
-
rl.question(`${question} [y/N]: `, (answer) => {
|
|
30
|
-
rl.close();
|
|
31
|
-
resolve(answer.trim().toLowerCase() === 'y');
|
|
32
|
-
});
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
14
|
export async function rollbackCommand(context) {
|
|
36
15
|
const { config, args, flags } = context;
|
|
37
16
|
if (!config) {
|
|
38
|
-
|
|
39
|
-
|
|
17
|
+
fatal('No configuration found', `run ${colors.cyan('relq init')} to create a configuration file`);
|
|
18
|
+
return;
|
|
40
19
|
}
|
|
41
|
-
requireValidConfig(config);
|
|
20
|
+
await requireValidConfig(config, { calledFrom: 'rollback' });
|
|
42
21
|
const connection = config.connection;
|
|
43
22
|
const migrationsDir = config.migrations?.directory || './migrations';
|
|
44
23
|
const tableName = config.migrations?.tableName || '_relq_migrations';
|
|
45
24
|
const count = parseInt(args[0]) || 1;
|
|
46
25
|
const dryRun = flags['dry-run'] === true;
|
|
47
26
|
const force = flags['force'] === true;
|
|
48
|
-
console.log(
|
|
27
|
+
console.log(`Rolling back ${count} migration(s)...`);
|
|
49
28
|
console.log(` Connection: ${getConnectionDescription(connection)}`);
|
|
50
29
|
console.log('');
|
|
51
30
|
try {
|
|
@@ -65,17 +44,17 @@ export async function rollbackCommand(context) {
|
|
|
65
44
|
LIMIT $1;
|
|
66
45
|
`, [count]);
|
|
67
46
|
if (result.rows.length === 0) {
|
|
68
|
-
console.log(
|
|
47
|
+
console.log('No migrations to rollback.');
|
|
69
48
|
return;
|
|
70
49
|
}
|
|
71
50
|
const toRollback = result.rows.map(r => r.name);
|
|
72
|
-
console.log(
|
|
51
|
+
console.log('Migrations to rollback:');
|
|
73
52
|
for (const name of toRollback) {
|
|
74
|
-
console.log(`
|
|
53
|
+
console.log(` - ${name}`);
|
|
75
54
|
}
|
|
76
55
|
console.log('');
|
|
77
56
|
if (!force && !dryRun) {
|
|
78
|
-
const proceed = await
|
|
57
|
+
const proceed = await confirm(`${colors.red('This will undo ' + toRollback.length + ' migration(s). Continue?')}`, false);
|
|
79
58
|
if (!proceed) {
|
|
80
59
|
console.log('Cancelled.');
|
|
81
60
|
return;
|
|
@@ -84,18 +63,18 @@ export async function rollbackCommand(context) {
|
|
|
84
63
|
for (const name of toRollback) {
|
|
85
64
|
const filePath = path.join(migrationsDir, name);
|
|
86
65
|
if (!fs.existsSync(filePath)) {
|
|
87
|
-
|
|
66
|
+
warning(`Migration file not found: ${name}`);
|
|
88
67
|
continue;
|
|
89
68
|
}
|
|
90
69
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
91
70
|
const { down } = parseMigration(content);
|
|
92
71
|
if (!down) {
|
|
93
|
-
|
|
72
|
+
warning(`No DOWN section in: ${name}`);
|
|
94
73
|
continue;
|
|
95
74
|
}
|
|
96
75
|
if (dryRun) {
|
|
97
|
-
console.log(
|
|
98
|
-
console.log(
|
|
76
|
+
console.log(`[dry-run] Would rollback: ${name}`);
|
|
77
|
+
console.log(down);
|
|
99
78
|
console.log('');
|
|
100
79
|
}
|
|
101
80
|
else {
|
|
@@ -106,7 +85,7 @@ export async function rollbackCommand(context) {
|
|
|
106
85
|
await client.query(down);
|
|
107
86
|
await client.query(`DELETE FROM "${tableName}" WHERE name = $1`, [name]);
|
|
108
87
|
await client.query('COMMIT');
|
|
109
|
-
console.log(
|
|
88
|
+
console.log(' Rolled back');
|
|
110
89
|
}
|
|
111
90
|
catch (error) {
|
|
112
91
|
await client.query('ROLLBACK');
|
|
@@ -119,7 +98,7 @@ export async function rollbackCommand(context) {
|
|
|
119
98
|
}
|
|
120
99
|
if (!dryRun) {
|
|
121
100
|
console.log('');
|
|
122
|
-
console.log(
|
|
101
|
+
console.log(`Rolled back ${toRollback.length} migration(s).`);
|
|
123
102
|
}
|
|
124
103
|
}
|
|
125
104
|
finally {
|
|
@@ -127,7 +106,6 @@ export async function rollbackCommand(context) {
|
|
|
127
106
|
}
|
|
128
107
|
}
|
|
129
108
|
catch (error) {
|
|
130
|
-
|
|
131
|
-
process.exit(1);
|
|
109
|
+
fatal('Rollback failed', error instanceof Error ? error.message : String(error));
|
|
132
110
|
}
|
|
133
111
|
}
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
|
-
import { colors } from "../utils/spinner.js";
|
|
3
|
+
import { colors, fatal } from "../utils/spinner.js";
|
|
4
4
|
import { isInitialized, getStagedChanges, getUnstagedChanges, } from "../utils/repo-manager.js";
|
|
5
5
|
export async function stashCommand(context) {
|
|
6
|
-
const { args, flags } = context;
|
|
7
|
-
const projectRoot = process.cwd();
|
|
6
|
+
const { args, flags, projectRoot } = context;
|
|
8
7
|
const subcommand = args[0] || 'push';
|
|
9
8
|
console.log('');
|
|
10
9
|
if (!isInitialized(projectRoot)) {
|
|
11
|
-
|
|
12
|
-
return;
|
|
10
|
+
fatal('not a relq repository (or any parent directories): .relq', `Run ${colors.cyan('relq init')} to initialize.`);
|
|
13
11
|
}
|
|
14
12
|
const stashDir = path.join(projectRoot, '.relq', 'stash');
|
|
15
13
|
switch (subcommand) {
|
|
@@ -56,7 +54,7 @@ async function stashPush(projectRoot, stashDir, message) {
|
|
|
56
54
|
fs.writeFileSync(stagedPath, '[]');
|
|
57
55
|
if (fs.existsSync(unstagedPath))
|
|
58
56
|
fs.writeFileSync(unstagedPath, '[]');
|
|
59
|
-
console.log(
|
|
57
|
+
console.log('Saved working directory');
|
|
60
58
|
console.log(` stash@{${stashIdx}}: ${message}`);
|
|
61
59
|
console.log('');
|
|
62
60
|
}
|
|
@@ -81,7 +79,7 @@ async function stashPop(projectRoot, stashDir) {
|
|
|
81
79
|
fs.writeFileSync(unstagedPath, JSON.stringify(stash.unstaged, null, 2));
|
|
82
80
|
}
|
|
83
81
|
fs.unlinkSync(stashPath);
|
|
84
|
-
console.log(
|
|
82
|
+
console.log('Applied stash and dropped');
|
|
85
83
|
console.log(` ${stash.message}`);
|
|
86
84
|
console.log('');
|
|
87
85
|
}
|
|
@@ -112,7 +110,7 @@ async function stashDrop(stashDir) {
|
|
|
112
110
|
return;
|
|
113
111
|
}
|
|
114
112
|
fs.unlinkSync(path.join(stashDir, stashFiles[0]));
|
|
115
|
-
console.log(
|
|
113
|
+
console.log('Dropped stash');
|
|
116
114
|
console.log('');
|
|
117
115
|
}
|
|
118
116
|
export default stashCommand;
|
|
@@ -1,20 +1,15 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { getConnectionDescription } from "../utils/env-loader.js";
|
|
4
|
-
import { colors } from "../utils/spinner.js";
|
|
4
|
+
import { colors, fatal } from "../utils/spinner.js";
|
|
5
5
|
import { loadRelqignore, } from "../utils/relqignore.js";
|
|
6
6
|
import { isInitialized, getHead, loadCommit, shortHash, getStagedChanges, getUnstagedChanges, } from "../utils/repo-manager.js";
|
|
7
7
|
export async function statusCommand(context) {
|
|
8
|
-
const { config, flags } = context;
|
|
9
|
-
const projectRoot = process.cwd();
|
|
8
|
+
const { config, flags, projectRoot } = context;
|
|
10
9
|
const connection = config?.connection;
|
|
11
10
|
console.log('');
|
|
12
11
|
if (!isInitialized(projectRoot)) {
|
|
13
|
-
|
|
14
|
-
console.log('');
|
|
15
|
-
console.log(`${colors.muted('Run')} ${colors.cyan('relq init')} ${colors.muted('to initialize.')}`);
|
|
16
|
-
console.log('');
|
|
17
|
-
return;
|
|
12
|
+
fatal('not a relq repository (or any parent directories): .relq', `Run ${colors.cyan('relq init')} to initialize.`);
|
|
18
13
|
}
|
|
19
14
|
const head = getHead(projectRoot);
|
|
20
15
|
const staged = getStagedChanges(projectRoot);
|
|
@@ -37,7 +32,7 @@ export async function statusCommand(context) {
|
|
|
37
32
|
console.log(`${colors.green('Changes to be committed:')}`);
|
|
38
33
|
console.log(` ${colors.muted('(use "relq restore --staged <name>..." to unstage)')}`);
|
|
39
34
|
console.log('');
|
|
40
|
-
displayChanges(staged, '
|
|
35
|
+
displayChanges(staged, ' ');
|
|
41
36
|
console.log('');
|
|
42
37
|
}
|
|
43
38
|
if (unstaged.length > 0) {
|
|
@@ -45,7 +40,7 @@ export async function statusCommand(context) {
|
|
|
45
40
|
console.log(` ${colors.muted('(use "relq add <name>..." to stage)')}`);
|
|
46
41
|
console.log(` ${colors.muted('(use "relq restore <name>..." to discard)')}`);
|
|
47
42
|
console.log('');
|
|
48
|
-
displayChanges(unstaged, '
|
|
43
|
+
displayChanges(unstaged, ' ');
|
|
49
44
|
console.log('');
|
|
50
45
|
}
|
|
51
46
|
const ignorePatterns = loadRelqignore(projectRoot);
|
|
@@ -56,7 +51,7 @@ export async function statusCommand(context) {
|
|
|
56
51
|
!p.raw.startsWith('_temp_') &&
|
|
57
52
|
!p.raw.startsWith('tmp_'));
|
|
58
53
|
if (userPatterns.length > 0) {
|
|
59
|
-
console.log(`${colors.muted(
|
|
54
|
+
console.log(`${colors.muted(`${userPatterns.length} pattern(s) active from .relqignore`)}`);
|
|
60
55
|
console.log('');
|
|
61
56
|
}
|
|
62
57
|
}
|