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,8 +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";
|
|
5
|
+
import { confirm, fatal, colors, warning } from "../utils/cli-utils.js";
|
|
6
6
|
function parseMigration(content) {
|
|
7
7
|
const upMatch = content.match(/--\s*UP\s*\n([\s\S]*?)(?=--\s*DOWN|$)/i);
|
|
8
8
|
const downMatch = content.match(/--\s*DOWN\s*\n([\s\S]*?)$/i);
|
|
@@ -11,31 +11,19 @@ function parseMigration(content) {
|
|
|
11
11
|
down: downMatch?.[1]?.trim() || '',
|
|
12
12
|
};
|
|
13
13
|
}
|
|
14
|
-
function askConfirm(question) {
|
|
15
|
-
const rl = readline.createInterface({
|
|
16
|
-
input: process.stdin,
|
|
17
|
-
output: process.stdout,
|
|
18
|
-
});
|
|
19
|
-
return new Promise((resolve) => {
|
|
20
|
-
rl.question(`${question} [y/N]: `, (answer) => {
|
|
21
|
-
rl.close();
|
|
22
|
-
resolve(answer.trim().toLowerCase() === 'y');
|
|
23
|
-
});
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
14
|
export async function migrateCommand(context) {
|
|
27
15
|
const { config, flags } = context;
|
|
28
16
|
if (!config) {
|
|
29
|
-
|
|
30
|
-
|
|
17
|
+
fatal('No configuration found', `run ${colors.cyan('relq init')} to create a configuration file`);
|
|
18
|
+
return;
|
|
31
19
|
}
|
|
32
|
-
requireValidConfig(config);
|
|
20
|
+
await requireValidConfig(config, { calledFrom: 'migrate' });
|
|
33
21
|
const dryRun = flags['dry-run'] === true;
|
|
34
22
|
const force = flags['force'] === true;
|
|
35
23
|
const connection = config.connection;
|
|
36
24
|
const migrationsDir = config.migrations?.directory || './migrations';
|
|
37
25
|
const tableName = config.migrations?.tableName || '_relq_migrations';
|
|
38
|
-
console.log('
|
|
26
|
+
console.log('Running migrations...');
|
|
39
27
|
console.log(` Connection: ${getConnectionDescription(connection)}`);
|
|
40
28
|
console.log(` Migrations: ${migrationsDir}\n`);
|
|
41
29
|
try {
|
|
@@ -69,16 +57,16 @@ export async function migrateCommand(context) {
|
|
|
69
57
|
.sort();
|
|
70
58
|
const pending = migrationFiles.filter(f => !appliedMigrations.has(f));
|
|
71
59
|
if (pending.length === 0) {
|
|
72
|
-
console.log('
|
|
60
|
+
console.log('No pending migrations.');
|
|
73
61
|
return;
|
|
74
62
|
}
|
|
75
|
-
console.log(
|
|
63
|
+
console.log(`Pending migrations (${pending.length}):`);
|
|
76
64
|
for (const file of pending) {
|
|
77
65
|
console.log(` • ${file}`);
|
|
78
66
|
}
|
|
79
67
|
console.log('');
|
|
80
68
|
if (!force && !dryRun) {
|
|
81
|
-
if (!await
|
|
69
|
+
if (!await confirm('Apply these migrations?', false)) {
|
|
82
70
|
console.log('Cancelled.');
|
|
83
71
|
return;
|
|
84
72
|
}
|
|
@@ -88,7 +76,7 @@ export async function migrateCommand(context) {
|
|
|
88
76
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
89
77
|
const { up } = parseMigration(content);
|
|
90
78
|
if (!up) {
|
|
91
|
-
|
|
79
|
+
warning(`Skipping ${file} (no UP section)`);
|
|
92
80
|
continue;
|
|
93
81
|
}
|
|
94
82
|
if (dryRun) {
|
|
@@ -105,7 +93,7 @@ export async function migrateCommand(context) {
|
|
|
105
93
|
await client.query(up);
|
|
106
94
|
await client.query(`INSERT INTO "${tableName}" (name) VALUES ($1)`, [file]);
|
|
107
95
|
await client.query('COMMIT');
|
|
108
|
-
console.log(
|
|
96
|
+
console.log(' Applied');
|
|
109
97
|
}
|
|
110
98
|
catch (error) {
|
|
111
99
|
await client.query('ROLLBACK');
|
|
@@ -117,7 +105,7 @@ export async function migrateCommand(context) {
|
|
|
117
105
|
}
|
|
118
106
|
}
|
|
119
107
|
if (!dryRun) {
|
|
120
|
-
console.log(`\
|
|
108
|
+
console.log(`\nApplied ${pending.length} migration(s).`);
|
|
121
109
|
}
|
|
122
110
|
}
|
|
123
111
|
finally {
|
|
@@ -125,7 +113,6 @@ export async function migrateCommand(context) {
|
|
|
125
113
|
}
|
|
126
114
|
}
|
|
127
115
|
catch (error) {
|
|
128
|
-
|
|
129
|
-
process.exit(1);
|
|
116
|
+
fatal('Migration failed', error instanceof Error ? error.message : String(error));
|
|
130
117
|
}
|
|
131
118
|
}
|
|
@@ -1,16 +1,31 @@
|
|
|
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 { fastIntrospectDatabase } from "../utils/fast-introspect.js";
|
|
6
5
|
import { generateTypeScript } from "../utils/type-generator.js";
|
|
7
6
|
import { getConnectionDescription } from "../utils/env-loader.js";
|
|
8
|
-
import { createSpinner, colors, formatBytes } from "../utils/
|
|
7
|
+
import { createSpinner, colors, formatBytes, formatDuration, fatal, confirm, warning } from "../utils/cli-utils.js";
|
|
9
8
|
import { loadRelqignore, isTableIgnored, isColumnIgnored, isIndexIgnored, isConstraintIgnored, isEnumIgnored, isDomainIgnored, isCompositeTypeIgnored, isFunctionIgnored, } from "../utils/relqignore.js";
|
|
10
|
-
import { isInitialized, initRepository, getHead, saveSnapshot, loadSnapshot, createCommit, shortHash, fetchRemoteCommits, ensureRemoteTable, setFetchHead, getStagedChanges, getUnstagedChanges, } from "../utils/repo-manager.js";
|
|
9
|
+
import { isInitialized, initRepository, getHead, saveSnapshot, loadSnapshot, createCommit, shortHash, fetchRemoteCommits, ensureRemoteTable, setFetchHead, addUnstagedChanges, getStagedChanges, getUnstagedChanges, clearWorkingState, hashFileContent, saveFileHash, } from "../utils/repo-manager.js";
|
|
10
|
+
import { compareSchemas } from "../utils/schema-comparator.js";
|
|
11
11
|
function toCamelCase(str) {
|
|
12
12
|
return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
13
13
|
}
|
|
14
|
+
function normalizePartitionKey(partitionKey) {
|
|
15
|
+
if (!partitionKey)
|
|
16
|
+
return undefined;
|
|
17
|
+
if (Array.isArray(partitionKey)) {
|
|
18
|
+
return partitionKey;
|
|
19
|
+
}
|
|
20
|
+
if (typeof partitionKey === 'string') {
|
|
21
|
+
return partitionKey
|
|
22
|
+
.replace(/^\{|\}$/g, '')
|
|
23
|
+
.split(',')
|
|
24
|
+
.map(k => k.trim())
|
|
25
|
+
.filter(Boolean);
|
|
26
|
+
}
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
14
29
|
function parseSchemaFileForSnapshot(schemaPath) {
|
|
15
30
|
if (!fs.existsSync(schemaPath)) {
|
|
16
31
|
return null;
|
|
@@ -116,36 +131,20 @@ function parseSchemaFileForSnapshot(schemaPath) {
|
|
|
116
131
|
extensions: [],
|
|
117
132
|
};
|
|
118
133
|
}
|
|
119
|
-
function askConfirm(question, defaultYes = true) {
|
|
120
|
-
const suffix = defaultYes ? colors.muted('[Y/n]') : colors.muted('[y/N]');
|
|
121
|
-
const rl = readline.createInterface({
|
|
122
|
-
input: process.stdin,
|
|
123
|
-
output: process.stdout,
|
|
124
|
-
});
|
|
125
|
-
return new Promise((resolve) => {
|
|
126
|
-
rl.question(`${question} ${suffix}: `, (answer) => {
|
|
127
|
-
rl.close();
|
|
128
|
-
const a = answer.trim().toLowerCase();
|
|
129
|
-
if (!a)
|
|
130
|
-
resolve(defaultYes);
|
|
131
|
-
else
|
|
132
|
-
resolve(a === 'y' || a === 'yes');
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
134
|
export async function pullCommand(context) {
|
|
137
|
-
const { config, flags } = context;
|
|
135
|
+
const { config, flags, projectRoot } = context;
|
|
138
136
|
if (!config) {
|
|
139
|
-
|
|
140
|
-
process.exit(1);
|
|
137
|
+
fatal('No configuration found', `Run ${colors.cyan('relq init')} to create one.`);
|
|
141
138
|
}
|
|
142
|
-
requireValidConfig(config);
|
|
139
|
+
await requireValidConfig(config, { calledFrom: 'pull' });
|
|
143
140
|
const connection = config.connection;
|
|
144
|
-
const projectRoot = process.cwd();
|
|
145
141
|
const force = flags['force'] === true;
|
|
142
|
+
const merge = flags['merge'] === true;
|
|
143
|
+
const noCommit = flags['no-commit'] === true;
|
|
146
144
|
const dryRun = flags['dry-run'] === true;
|
|
147
145
|
const author = config.author || 'Relq CLI';
|
|
148
|
-
const
|
|
146
|
+
const schemaPathRaw = typeof config.schema === 'string' ? config.schema : './db/schema.ts';
|
|
147
|
+
const schemaPath = path.resolve(projectRoot, schemaPathRaw);
|
|
149
148
|
const includeFunctions = config.generate?.includeFunctions ?? false;
|
|
150
149
|
const includeTriggers = config.generate?.includeTriggers ?? false;
|
|
151
150
|
const spinner = createSpinner();
|
|
@@ -153,23 +152,27 @@ export async function pullCommand(context) {
|
|
|
153
152
|
console.log('');
|
|
154
153
|
if (!isInitialized(projectRoot)) {
|
|
155
154
|
initRepository(projectRoot);
|
|
156
|
-
console.log(
|
|
155
|
+
console.log('Initialized .relq folder');
|
|
157
156
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
157
|
+
const stagedChanges = getStagedChanges(projectRoot);
|
|
158
|
+
const unstagedChanges = getUnstagedChanges(projectRoot);
|
|
159
|
+
const hasLocalChanges = stagedChanges.length > 0 || unstagedChanges.length > 0;
|
|
160
|
+
let stashedSchemaContent = null;
|
|
161
|
+
const schemaFileExists = fs.existsSync(schemaPath);
|
|
162
|
+
if (hasLocalChanges) {
|
|
163
|
+
if (force) {
|
|
164
|
+
clearWorkingState(projectRoot);
|
|
165
|
+
}
|
|
166
|
+
else if (merge) {
|
|
167
|
+
if (schemaFileExists) {
|
|
168
|
+
stashedSchemaContent = fs.readFileSync(schemaPath, 'utf-8');
|
|
169
|
+
}
|
|
170
|
+
clearWorkingState(projectRoot);
|
|
171
|
+
console.log(`Stashed local file content for restore after pull`);
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
162
174
|
const hasUnstaged = unstagedChanges.length > 0;
|
|
163
175
|
const hasStaged = stagedChanges.length > 0;
|
|
164
|
-
if (hasUnstaged && hasStaged) {
|
|
165
|
-
console.error(colors.red('Error: You have uncommitted and unstaged changes.'));
|
|
166
|
-
}
|
|
167
|
-
else if (hasStaged) {
|
|
168
|
-
console.error(colors.red('Error: You have uncommitted changes.'));
|
|
169
|
-
}
|
|
170
|
-
else {
|
|
171
|
-
console.error(colors.red('Error: You have unstaged changes.'));
|
|
172
|
-
}
|
|
173
176
|
console.log('');
|
|
174
177
|
if (hasStaged) {
|
|
175
178
|
console.log(` ${colors.green('Staged (uncommitted):')} ${stagedChanges.length} change(s)`);
|
|
@@ -177,19 +180,15 @@ export async function pullCommand(context) {
|
|
|
177
180
|
if (hasUnstaged) {
|
|
178
181
|
console.log(` ${colors.red('Unstaged:')} ${unstagedChanges.length} change(s)`);
|
|
179
182
|
}
|
|
180
|
-
|
|
181
|
-
if (hasUnstaged &&
|
|
182
|
-
|
|
183
|
-
console.log(` ${colors.cyan('relq add .')} - stage all changes`);
|
|
184
|
-
console.log(` ${colors.cyan('relq commit -m "message"')} - then commit`);
|
|
183
|
+
let errorMsg = 'You have uncommitted changes';
|
|
184
|
+
if (hasUnstaged && hasStaged) {
|
|
185
|
+
errorMsg = 'You have uncommitted and unstaged changes';
|
|
185
186
|
}
|
|
186
|
-
else {
|
|
187
|
-
|
|
188
|
-
console.log(` ${colors.cyan('relq commit -m "message"')} - commit staged changes`);
|
|
187
|
+
else if (hasUnstaged) {
|
|
188
|
+
errorMsg = 'You have unstaged changes';
|
|
189
189
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
return;
|
|
190
|
+
const hint = `Commit first: ${colors.cyan('relq add . && relq commit -m "message"')}\nOr merge: ${colors.cyan('relq pull --merge')} (preserve local edits)\nOr force: ${colors.cyan('relq pull --force')} (discard & pull)`;
|
|
191
|
+
fatal(errorMsg, hint);
|
|
193
192
|
}
|
|
194
193
|
}
|
|
195
194
|
try {
|
|
@@ -254,6 +253,9 @@ export async function pullCommand(context) {
|
|
|
254
253
|
type: c.type,
|
|
255
254
|
definition: c.definition,
|
|
256
255
|
})),
|
|
256
|
+
isPartitioned: t.isPartitioned,
|
|
257
|
+
partitionType: t.partitionType,
|
|
258
|
+
partitionKey: normalizePartitionKey(t.partitionKey),
|
|
257
259
|
})),
|
|
258
260
|
enums: filteredEnums.map(e => ({
|
|
259
261
|
name: e.name,
|
|
@@ -294,12 +296,7 @@ export async function pullCommand(context) {
|
|
|
294
296
|
console.log('');
|
|
295
297
|
const mergeStatePath = path.join(projectRoot, '.relq', 'MERGE_STATE');
|
|
296
298
|
if (fs.existsSync(mergeStatePath) && !force) {
|
|
297
|
-
|
|
298
|
-
console.log('');
|
|
299
|
-
console.log(`${colors.muted('Use')} ${colors.cyan('relq resolve')} ${colors.muted('to see and resolve conflicts')}`);
|
|
300
|
-
console.log(`${colors.muted('Or use')} ${colors.cyan('relq pull --force')} ${colors.muted('to overwrite local')}`);
|
|
301
|
-
console.log('');
|
|
302
|
-
return;
|
|
299
|
+
fatal('You have unresolved merge conflicts', `Use ${colors.cyan('relq resolve')} to see and resolve conflicts\nOr use ${colors.cyan('relq pull --force')} to overwrite local`);
|
|
303
300
|
}
|
|
304
301
|
if (schemaExists && localSnapshot && !force) {
|
|
305
302
|
const localTables = new Set(localSnapshot.tables.map(t => t.name));
|
|
@@ -314,7 +311,6 @@ export async function pullCommand(context) {
|
|
|
314
311
|
createdAt: new Date().toISOString(),
|
|
315
312
|
};
|
|
316
313
|
fs.writeFileSync(mergeStatePath, JSON.stringify(mergeState, null, 2));
|
|
317
|
-
console.log(`${colors.red('error:')} Merge conflict detected`);
|
|
318
314
|
console.log('');
|
|
319
315
|
console.log(`Both local and remote have modified the same objects:`);
|
|
320
316
|
console.log('');
|
|
@@ -326,17 +322,10 @@ export async function pullCommand(context) {
|
|
|
326
322
|
if (conflicts.length > 10) {
|
|
327
323
|
console.log(` ${colors.muted(`... and ${conflicts.length - 10} more`)}`);
|
|
328
324
|
}
|
|
329
|
-
|
|
330
|
-
console.log('To resolve:');
|
|
331
|
-
console.log(` ${colors.cyan('relq resolve --theirs <name>')} Take remote version`);
|
|
332
|
-
console.log(` ${colors.cyan('relq resolve --ours <name>')} Keep local version`);
|
|
333
|
-
console.log(` ${colors.cyan('relq resolve --all-theirs')} Take all remote`);
|
|
334
|
-
console.log(` ${colors.cyan('relq pull --force')} Force overwrite local`);
|
|
335
|
-
console.log('');
|
|
336
|
-
return;
|
|
325
|
+
fatal('Automatic merge failed; fix conflicts and then commit', `${colors.cyan('relq resolve --theirs <name>')} Take remote version\n${colors.cyan('relq resolve --all-theirs')} Take all remote\n${colors.cyan('relq pull --force')} Force overwrite local`);
|
|
337
326
|
}
|
|
338
327
|
if (added.length === 0 && removed.length === 0) {
|
|
339
|
-
console.log(
|
|
328
|
+
console.log('Already up to date with remote');
|
|
340
329
|
console.log('');
|
|
341
330
|
return;
|
|
342
331
|
}
|
|
@@ -349,40 +338,49 @@ export async function pullCommand(context) {
|
|
|
349
338
|
}
|
|
350
339
|
console.log('');
|
|
351
340
|
if (!dryRun) {
|
|
352
|
-
const proceed = await
|
|
341
|
+
const proceed = await confirm(`${colors.bold('Pull these changes?')}`, true);
|
|
353
342
|
if (!proceed) {
|
|
354
|
-
|
|
355
|
-
console.log(`${colors.muted('Cancelled.')}`);
|
|
356
|
-
return;
|
|
343
|
+
fatal('Operation cancelled by user');
|
|
357
344
|
}
|
|
358
345
|
console.log('');
|
|
359
346
|
}
|
|
360
347
|
}
|
|
361
348
|
else if (schemaExists && !force) {
|
|
362
|
-
|
|
349
|
+
warning('Local schema exists but not tracked');
|
|
363
350
|
console.log('');
|
|
364
351
|
console.log(` ${colors.cyan(schemaPath)}`);
|
|
365
352
|
console.log('');
|
|
366
353
|
if (!dryRun) {
|
|
367
|
-
const proceed = await
|
|
354
|
+
const proceed = await confirm(`${colors.bold('Overwrite local schema?')}`, false);
|
|
368
355
|
if (!proceed) {
|
|
369
|
-
|
|
370
|
-
console.log(`${colors.muted('Cancelled. Run')} ${colors.cyan('relq status')} ${colors.muted('to see current state.')}`);
|
|
371
|
-
return;
|
|
356
|
+
fatal('Operation cancelled by user', `Run ${colors.cyan('relq status')} to see current state.`);
|
|
372
357
|
}
|
|
373
358
|
console.log('');
|
|
374
359
|
}
|
|
375
360
|
}
|
|
376
361
|
else if (!schemaExists) {
|
|
377
|
-
console.log(
|
|
362
|
+
console.log('First pull - creating schema');
|
|
378
363
|
console.log('');
|
|
379
|
-
|
|
364
|
+
const indexCount = filteredTables.reduce((sum, t) => sum + (t.indexes?.filter(i => !i.isPrimary).length || 0), 0);
|
|
365
|
+
const partitionCount = filteredTables.filter(t => t.isPartitioned).length;
|
|
366
|
+
const tableCommentCount = filteredTables.filter(t => t.comment).length;
|
|
367
|
+
const columnCommentCount = filteredTables.reduce((sum, t) => sum + t.columns.filter(c => c.comment).length, 0);
|
|
368
|
+
console.log('Schema Summary:');
|
|
380
369
|
console.log(` ${colors.green(String(filteredTables.length))} tables`);
|
|
381
|
-
|
|
370
|
+
if (indexCount > 0)
|
|
371
|
+
console.log(` ${colors.green(String(indexCount))} indexes`);
|
|
372
|
+
if (partitionCount > 0)
|
|
373
|
+
console.log(` ${colors.green(String(partitionCount))} partitioned tables`);
|
|
374
|
+
if (tableCommentCount > 0)
|
|
375
|
+
console.log(` ${colors.green(String(tableCommentCount))} table comments`);
|
|
376
|
+
if (columnCommentCount > 0)
|
|
377
|
+
console.log(` ${colors.green(String(columnCommentCount))} column comments`);
|
|
378
|
+
if (dbSchema.extensions.length > 0)
|
|
379
|
+
console.log(` ${colors.green(String(dbSchema.extensions.length))} extensions`);
|
|
382
380
|
console.log('');
|
|
383
381
|
}
|
|
384
382
|
if (dryRun) {
|
|
385
|
-
console.log(
|
|
383
|
+
console.log('Dry run - no files written');
|
|
386
384
|
console.log('');
|
|
387
385
|
return;
|
|
388
386
|
}
|
|
@@ -404,26 +402,138 @@ export async function pullCommand(context) {
|
|
|
404
402
|
fs.writeFileSync(schemaPath, typescript, 'utf-8');
|
|
405
403
|
const fileSize = Buffer.byteLength(typescript, 'utf8');
|
|
406
404
|
spinner.succeed(`Written ${colors.cyan(schemaPath)} ${colors.muted(`(${formatBytes(fileSize)})`)}`);
|
|
407
|
-
const
|
|
408
|
-
|
|
409
|
-
|
|
405
|
+
const fileHash = hashFileContent(typescript);
|
|
406
|
+
saveFileHash(fileHash, projectRoot);
|
|
407
|
+
const oldSnapshot = loadSnapshot(projectRoot);
|
|
408
|
+
const beforeSchema = oldSnapshot ? {
|
|
409
|
+
extensions: oldSnapshot.extensions?.map(e => e.name) || [],
|
|
410
|
+
enums: oldSnapshot.enums || [],
|
|
411
|
+
domains: oldSnapshot.domains?.map(d => ({
|
|
412
|
+
name: d.name,
|
|
413
|
+
baseType: d.baseType,
|
|
414
|
+
isNotNull: d.notNull,
|
|
415
|
+
defaultValue: d.default,
|
|
416
|
+
checkExpression: d.check,
|
|
417
|
+
})) || [],
|
|
418
|
+
compositeTypes: oldSnapshot.compositeTypes || [],
|
|
419
|
+
sequences: oldSnapshot.sequences || [],
|
|
420
|
+
tables: oldSnapshot.tables.map(t => ({
|
|
421
|
+
name: t.name,
|
|
422
|
+
schema: t.schema,
|
|
423
|
+
columns: t.columns.map(c => ({
|
|
424
|
+
name: c.name,
|
|
425
|
+
dataType: c.type,
|
|
426
|
+
isNullable: c.nullable,
|
|
427
|
+
defaultValue: c.default,
|
|
428
|
+
isPrimaryKey: c.primaryKey,
|
|
429
|
+
isUnique: c.unique,
|
|
430
|
+
comment: c.comment,
|
|
431
|
+
})),
|
|
432
|
+
indexes: t.indexes.map(i => ({
|
|
433
|
+
name: i.name,
|
|
434
|
+
columns: i.columns,
|
|
435
|
+
isUnique: i.unique,
|
|
436
|
+
type: i.type,
|
|
437
|
+
})),
|
|
438
|
+
constraints: t.constraints || [],
|
|
439
|
+
isPartitioned: t.isPartitioned,
|
|
440
|
+
partitionType: t.partitionType,
|
|
441
|
+
partitionKey: t.partitionKey,
|
|
442
|
+
comment: t.comment,
|
|
443
|
+
})),
|
|
444
|
+
functions: oldSnapshot.functions || [],
|
|
445
|
+
triggers: oldSnapshot.triggers || [],
|
|
446
|
+
} : {
|
|
447
|
+
extensions: [],
|
|
448
|
+
enums: [],
|
|
449
|
+
domains: [],
|
|
450
|
+
compositeTypes: [],
|
|
451
|
+
sequences: [],
|
|
452
|
+
tables: [],
|
|
453
|
+
functions: [],
|
|
454
|
+
triggers: [],
|
|
455
|
+
};
|
|
456
|
+
const afterSchema = {
|
|
457
|
+
extensions: dbSchema.extensions || [],
|
|
458
|
+
enums: filteredEnums || [],
|
|
459
|
+
domains: filteredDomains?.map(d => ({
|
|
460
|
+
name: d.name,
|
|
461
|
+
baseType: d.baseType,
|
|
462
|
+
isNotNull: d.isNotNull,
|
|
463
|
+
defaultValue: d.defaultValue,
|
|
464
|
+
checkExpression: d.checkExpression,
|
|
465
|
+
})) || [],
|
|
466
|
+
compositeTypes: filteredCompositeTypes || [],
|
|
467
|
+
sequences: [],
|
|
468
|
+
tables: filteredTables.map(t => ({
|
|
469
|
+
name: t.name,
|
|
470
|
+
schema: t.schema,
|
|
471
|
+
columns: t.columns.map(c => ({
|
|
472
|
+
name: c.name,
|
|
473
|
+
dataType: c.dataType,
|
|
474
|
+
isNullable: c.isNullable,
|
|
475
|
+
defaultValue: c.defaultValue,
|
|
476
|
+
isPrimaryKey: c.isPrimaryKey,
|
|
477
|
+
isUnique: c.isUnique,
|
|
478
|
+
comment: c.comment,
|
|
479
|
+
})),
|
|
480
|
+
indexes: t.indexes.map(i => ({
|
|
481
|
+
name: i.name,
|
|
482
|
+
columns: i.columns,
|
|
483
|
+
isUnique: i.isUnique,
|
|
484
|
+
type: i.type,
|
|
485
|
+
})),
|
|
486
|
+
constraints: t.constraints || [],
|
|
487
|
+
isPartitioned: t.isPartitioned,
|
|
488
|
+
partitionType: t.partitionType,
|
|
489
|
+
partitionKey: t.partitionKey,
|
|
490
|
+
childPartitions: t.childPartitions,
|
|
491
|
+
comment: t.comment,
|
|
492
|
+
})),
|
|
493
|
+
functions: filteredFunctions || [],
|
|
494
|
+
triggers: filteredTriggers || [],
|
|
495
|
+
};
|
|
496
|
+
const schemaChanges = compareSchemas(beforeSchema, afterSchema);
|
|
497
|
+
saveSnapshot(currentSchema, projectRoot);
|
|
498
|
+
const duration = Date.now() - startTime;
|
|
499
|
+
if (noCommit) {
|
|
500
|
+
if (schemaChanges.length > 0) {
|
|
501
|
+
addUnstagedChanges(schemaChanges, projectRoot);
|
|
502
|
+
spinner.succeed(`Detected ${schemaChanges.length} schema change(s)`);
|
|
503
|
+
}
|
|
504
|
+
console.log('');
|
|
505
|
+
console.log(`Pull completed in ${formatDuration(duration)}`);
|
|
506
|
+
if (schemaChanges.length > 0) {
|
|
507
|
+
console.log('');
|
|
508
|
+
console.log(`${colors.green(String(schemaChanges.length))} change(s) ready to stage`);
|
|
509
|
+
console.log(`hint: run ${colors.cyan("'relq add .'")} to stage all changes`);
|
|
510
|
+
}
|
|
511
|
+
else {
|
|
512
|
+
console.log('Already up to date');
|
|
513
|
+
}
|
|
410
514
|
}
|
|
411
515
|
else {
|
|
412
|
-
|
|
516
|
+
const connectionDesc = getConnectionDescription(connection);
|
|
517
|
+
const commitMessage = `pull: sync from ${connectionDesc}`;
|
|
518
|
+
const commit = createCommit(currentSchema, author, commitMessage, projectRoot);
|
|
519
|
+
clearWorkingState(projectRoot);
|
|
520
|
+
console.log('');
|
|
521
|
+
console.log(`Pull completed in ${formatDuration(duration)}`);
|
|
522
|
+
console.log('');
|
|
523
|
+
console.log(`${colors.yellow('→')} ${shortHash(commit.hash)} ${commitMessage}`);
|
|
524
|
+
console.log(` ${colors.green(String(commit.stats.tables))} tables, ${colors.green(String(commit.stats.columns))} columns`);
|
|
525
|
+
if (stashedSchemaContent) {
|
|
526
|
+
fs.writeFileSync(schemaPath, stashedSchemaContent, 'utf-8');
|
|
527
|
+
console.log('');
|
|
528
|
+
console.log(`${colors.yellow('Restored')} local schema file content`);
|
|
529
|
+
console.log(`hint: run ${colors.cyan("'relq add .'")} to detect changes against the new snapshot`);
|
|
530
|
+
}
|
|
413
531
|
}
|
|
414
|
-
spinner.start('Creating commit...');
|
|
415
|
-
const message = localHead ? 'Pulled schema from database' : 'Initial schema pull';
|
|
416
|
-
const commit = createCommit(currentSchema, author, message, projectRoot);
|
|
417
|
-
spinner.succeed(`Created commit ${colors.yellow(shortHash(commit.hash))}`);
|
|
418
|
-
const duration = Date.now() - startTime;
|
|
419
|
-
console.log('');
|
|
420
|
-
console.log(`${colors.green('✓')} Pull completed in ${colors.cyan(`${duration}ms`)}`);
|
|
421
532
|
console.log('');
|
|
422
533
|
}
|
|
423
|
-
catch (
|
|
534
|
+
catch (err) {
|
|
424
535
|
spinner.fail('Pull failed');
|
|
425
|
-
|
|
426
|
-
process.exit(1);
|
|
536
|
+
fatal(err instanceof Error ? err.message : String(err));
|
|
427
537
|
}
|
|
428
538
|
}
|
|
429
539
|
function detectObjectConflicts(local, remote) {
|