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
|
@@ -6,27 +6,23 @@ import { saveSnapshot, loadSnapshot, isInitialized, initRepository, stageChanges
|
|
|
6
6
|
import { compareSchemas } from "../utils/schema-comparator.js";
|
|
7
7
|
import { getChangeDisplayName } from "../utils/change-tracker.js";
|
|
8
8
|
import { loadRelqignore, validateIgnoreDependencies, isTableIgnored, isColumnIgnored, isIndexIgnored, isConstraintIgnored, isEnumIgnored, isDomainIgnored, isSequenceIgnored, isCompositeTypeIgnored, isFunctionIgnored, } from "../utils/relqignore.js";
|
|
9
|
-
import { colors,
|
|
10
|
-
export async function importCommand(sqlFilePath, options = {}) {
|
|
9
|
+
import { colors, fatal, warning, hint, getWorkingTreeStatus, printDirtyWorkingTreeError, printMergeStrategyHelp, readSQLFile, createSpinner, formatBytes, } from "../utils/git-utils.js";
|
|
10
|
+
export async function importCommand(sqlFilePath, options = {}, projectRoot = process.cwd()) {
|
|
11
11
|
const { includeFunctions = false, includeTriggers = false, force = false, dryRun = false } = options;
|
|
12
|
-
const projectRoot = process.cwd();
|
|
13
12
|
const spinner = createSpinner();
|
|
14
13
|
console.log('');
|
|
15
14
|
if (!sqlFilePath) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
console.log(' --include-functions Include functions in import');
|
|
28
|
-
console.log(' --include-triggers Include triggers in import');
|
|
29
|
-
process.exit(1);
|
|
15
|
+
fatal('No SQL file specified', 'usage: relq import <sql-file> [options]\n\n' +
|
|
16
|
+
'Options:\n' +
|
|
17
|
+
' --output <path> Output schema file path\n' +
|
|
18
|
+
' --force Force import, overwrite local changes\n' +
|
|
19
|
+
' --dry-run Preview changes without applying\n' +
|
|
20
|
+
' --theirs Accept all incoming changes\n' +
|
|
21
|
+
' --ours Keep all local changes (reject incoming)\n' +
|
|
22
|
+
' --abort Abort the import operation\n' +
|
|
23
|
+
' --include-functions Include functions in import\n' +
|
|
24
|
+
' --include-triggers Include triggers in import');
|
|
25
|
+
return;
|
|
30
26
|
}
|
|
31
27
|
if (options.abort) {
|
|
32
28
|
console.log('Aborting import...');
|
|
@@ -49,11 +45,8 @@ export async function importCommand(sqlFilePath, options = {}) {
|
|
|
49
45
|
warning(warn);
|
|
50
46
|
}
|
|
51
47
|
if (!validation.valid) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
console.log(` - ${err}`);
|
|
55
|
-
}
|
|
56
|
-
process.exit(1);
|
|
48
|
+
fatal('Invalid PostgreSQL SQL file', validation.errors.join('\n - '));
|
|
49
|
+
return;
|
|
57
50
|
}
|
|
58
51
|
console.log(`Importing ${colors.cyan(path.basename(sqlFilePath))} ${colors.gray(`(${formatBytes(sqlContent.length)})`)}`);
|
|
59
52
|
console.log('');
|
|
@@ -68,7 +61,8 @@ export async function importCommand(sqlFilePath, options = {}) {
|
|
|
68
61
|
printDirtyWorkingTreeError(status, 'import');
|
|
69
62
|
console.log('');
|
|
70
63
|
printMergeStrategyHelp();
|
|
71
|
-
|
|
64
|
+
fatal('Working tree is not clean', 'Commit or stash your changes before importing.');
|
|
65
|
+
return;
|
|
72
66
|
}
|
|
73
67
|
}
|
|
74
68
|
spinner.start('Parsing SQL schema');
|
|
@@ -196,7 +190,7 @@ export async function importCommand(sqlFilePath, options = {}) {
|
|
|
196
190
|
partitions: parsedSchema.partitions,
|
|
197
191
|
};
|
|
198
192
|
if (ignoredCount > 0) {
|
|
199
|
-
console.log(`${
|
|
193
|
+
console.log(`${ignoredCount} object(s) ignored by .relqignore`);
|
|
200
194
|
}
|
|
201
195
|
const dependencyErrors = validateIgnoreDependencies({
|
|
202
196
|
tables: filteredTables.map(t => ({
|
|
@@ -214,14 +208,9 @@ export async function importCommand(sqlFilePath, options = {}) {
|
|
|
214
208
|
}, ignorePatterns);
|
|
215
209
|
if (dependencyErrors.length > 0) {
|
|
216
210
|
spinner.stop();
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
console.log(` ${colors.red('✗')} ${depError.message}`);
|
|
221
|
-
}
|
|
222
|
-
console.log('');
|
|
223
|
-
hint('Either un-ignore the type or add the column to .relqignore');
|
|
224
|
-
process.exit(1);
|
|
211
|
+
const errorMessages = dependencyErrors.map(e => e.message).join('\n ');
|
|
212
|
+
fatal('Dependency validation failed', `${errorMessages}\n\nEither un-ignore the type or add the column to .relqignore`);
|
|
213
|
+
return;
|
|
225
214
|
}
|
|
226
215
|
spinner.start('Generating TypeScript schema');
|
|
227
216
|
const dbSchema = convertToDbSchema(filteredSchema, filteredFunctions, triggers, comments);
|
|
@@ -277,7 +266,7 @@ export async function importCommand(sqlFilePath, options = {}) {
|
|
|
277
266
|
}
|
|
278
267
|
else {
|
|
279
268
|
console.log('');
|
|
280
|
-
console.log(`${
|
|
269
|
+
console.log(`${drops.length} object(s) only in existing schema (preserved)`);
|
|
281
270
|
}
|
|
282
271
|
}
|
|
283
272
|
console.log('');
|
|
@@ -304,8 +293,10 @@ export async function importCommand(sqlFilePath, options = {}) {
|
|
|
304
293
|
console.log(`${colors.yellow('Dry run mode')} - no files written`);
|
|
305
294
|
console.log('');
|
|
306
295
|
console.log('Would write:');
|
|
307
|
-
|
|
308
|
-
|
|
296
|
+
const dryRunOutputPath = options.output || './db/schema.ts';
|
|
297
|
+
const dryRunAbsPath = path.resolve(projectRoot, dryRunOutputPath);
|
|
298
|
+
console.log(` ${colors.cyan(dryRunAbsPath)} ${colors.gray(`(${formatBytes(finalTypescriptContent.length)})`)}`);
|
|
299
|
+
console.log(` ${colors.cyan(path.join(projectRoot, '.relq/snapshot.json'))}`);
|
|
309
300
|
if (changes.length > 0) {
|
|
310
301
|
console.log(` Stage ${changes.length} change(s)`);
|
|
311
302
|
}
|
|
@@ -313,29 +304,29 @@ export async function importCommand(sqlFilePath, options = {}) {
|
|
|
313
304
|
return;
|
|
314
305
|
}
|
|
315
306
|
const outputPath = options.output || './db/schema.ts';
|
|
316
|
-
const absoluteOutputPath = path.resolve(outputPath);
|
|
307
|
+
const absoluteOutputPath = path.resolve(projectRoot, outputPath);
|
|
317
308
|
const outputDir = path.dirname(absoluteOutputPath);
|
|
318
309
|
if (!fs.existsSync(outputDir)) {
|
|
319
310
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
320
311
|
}
|
|
321
312
|
fs.writeFileSync(absoluteOutputPath, finalTypescriptContent, 'utf-8');
|
|
322
|
-
console.log(`Written ${colors.cyan(
|
|
313
|
+
console.log(`Written ${colors.cyan(absoluteOutputPath)} ${colors.gray(`(${formatBytes(finalTypescriptContent.length)})`)}`);
|
|
323
314
|
saveSnapshot(mergedSchema, projectRoot);
|
|
324
315
|
if (changes.length > 0) {
|
|
325
316
|
addUnstagedChanges(changes, projectRoot);
|
|
326
317
|
stageChanges(['.'], projectRoot);
|
|
327
318
|
console.log('');
|
|
328
|
-
console.log(`${
|
|
319
|
+
console.log(`${changes.length} change(s) staged for commit`);
|
|
329
320
|
}
|
|
330
321
|
console.log('');
|
|
331
|
-
console.log(
|
|
322
|
+
console.log('Import successful.');
|
|
332
323
|
console.log('');
|
|
333
324
|
if (changes.length > 0) {
|
|
334
|
-
hint('
|
|
335
|
-
hint('
|
|
325
|
+
hint("run 'relq status' to see staged changes");
|
|
326
|
+
hint("run 'relq commit -m <message>' to commit");
|
|
336
327
|
}
|
|
337
328
|
else {
|
|
338
|
-
hint('
|
|
329
|
+
hint("run 'relq status' to see current state");
|
|
339
330
|
}
|
|
340
331
|
console.log('');
|
|
341
332
|
}
|
|
@@ -432,6 +423,7 @@ function convertToDbSchema(parsed, functions = [], triggers = [], comments = [])
|
|
|
432
423
|
cycle: s.cycle,
|
|
433
424
|
ownedBy: s.ownedBy,
|
|
434
425
|
})) || [],
|
|
426
|
+
collations: [],
|
|
435
427
|
extensions: parsed.extensions,
|
|
436
428
|
partitions: parsed.partitions,
|
|
437
429
|
functions: functions.map(f => ({
|
|
@@ -700,6 +692,7 @@ function snapshotToDbSchemaForGeneration(snapshot) {
|
|
|
700
692
|
cycle: s.cycle,
|
|
701
693
|
ownedBy: s.ownedBy ?? undefined,
|
|
702
694
|
})) || [],
|
|
695
|
+
collations: [],
|
|
703
696
|
extensions: (snapshot.extensions || []).map(e => typeof e === 'string' ? e : e.name),
|
|
704
697
|
partitions: [],
|
|
705
698
|
functions: snapshot.functions?.map(f => ({
|
|
@@ -4,7 +4,7 @@ import * as readline from 'readline';
|
|
|
4
4
|
import { loadEnvConfig } from "../utils/env-loader.js";
|
|
5
5
|
import { initRepository, ensureRemoteTable, isInitialized } from "../utils/repo-manager.js";
|
|
6
6
|
import { createDefaultRelqignore } from "../utils/relqignore.js";
|
|
7
|
-
import { colors, createSpinner } from "../utils/
|
|
7
|
+
import { colors, createSpinner, fatal, warning, hint } from "../utils/cli-utils.js";
|
|
8
8
|
function ask(rl, question, defaultValue) {
|
|
9
9
|
const suffix = defaultValue ? ` ${colors.muted(`[${defaultValue}]`)}` : '';
|
|
10
10
|
return new Promise((resolve) => {
|
|
@@ -120,11 +120,9 @@ export async function initCommand(context) {
|
|
|
120
120
|
const cwd = process.cwd();
|
|
121
121
|
const spinner = createSpinner();
|
|
122
122
|
console.log('');
|
|
123
|
-
console.log(`${colors.bold('Relq')} - Git-like schema versioning for PostgreSQL`);
|
|
124
|
-
console.log('');
|
|
125
123
|
const { findConfigFileRecursive } = await import("../utils/config-loader.js");
|
|
124
|
+
const { findProjectRoot } = await import("../utils/project-root.js");
|
|
126
125
|
const existingConfig = findConfigFileRecursive(cwd);
|
|
127
|
-
const localRelqExists = isInitialized(cwd);
|
|
128
126
|
let projectRoot = cwd;
|
|
129
127
|
let existingConfigValues = null;
|
|
130
128
|
if (existingConfig) {
|
|
@@ -136,28 +134,35 @@ export async function initCommand(context) {
|
|
|
136
134
|
catch {
|
|
137
135
|
}
|
|
138
136
|
if (existingConfig.directory !== cwd) {
|
|
139
|
-
|
|
140
|
-
console.log(`
|
|
137
|
+
hint(`Found relq.config.ts in: ${colors.cyan(existingConfig.directory)}`);
|
|
138
|
+
console.log(` Initializing at project root...`);
|
|
141
139
|
console.log('');
|
|
142
|
-
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
const packageRoot = findProjectRoot(cwd);
|
|
144
|
+
if (packageRoot && packageRoot !== cwd) {
|
|
145
|
+
projectRoot = packageRoot;
|
|
146
|
+
hint(`Found package.json in: ${colors.cyan(packageRoot)}`);
|
|
147
|
+
console.log(` Initializing at project root...`);
|
|
143
148
|
console.log('');
|
|
144
|
-
return;
|
|
145
149
|
}
|
|
150
|
+
}
|
|
151
|
+
if (existingConfig && existingConfig.directory === projectRoot) {
|
|
146
152
|
const relqFolderExists = isInitialized(projectRoot);
|
|
147
153
|
if (relqFolderExists) {
|
|
148
|
-
console.log(`${colors.green('
|
|
154
|
+
console.log(`${colors.green('Relq is already initialized.')}`);
|
|
149
155
|
console.log('');
|
|
150
|
-
console.log(` ${colors.
|
|
151
|
-
console.log(` ${colors.
|
|
156
|
+
console.log(` ${colors.dim('-')} relq.config.ts`);
|
|
157
|
+
console.log(` ${colors.dim('-')} .relq/ folder`);
|
|
152
158
|
console.log('');
|
|
153
|
-
|
|
154
|
-
|
|
159
|
+
hint(`Use ${colors.cyan('relq status')} to see current state`);
|
|
160
|
+
hint(`Use ${colors.cyan('relq pull')} to sync with database`);
|
|
155
161
|
return;
|
|
156
162
|
}
|
|
157
163
|
else {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
console.log(`${colors.muted('This could mean the setup was incomplete.')}`);
|
|
164
|
+
warning('Found relq.config.ts but .relq/ folder is missing');
|
|
165
|
+
hint('This could mean the setup was incomplete');
|
|
161
166
|
console.log('');
|
|
162
167
|
const rl = readline.createInterface({
|
|
163
168
|
input: process.stdin,
|
|
@@ -181,12 +186,12 @@ export async function initCommand(context) {
|
|
|
181
186
|
}
|
|
182
187
|
if (missingFields.length > 0) {
|
|
183
188
|
console.log('');
|
|
184
|
-
|
|
189
|
+
warning('Your config is missing required fields:');
|
|
185
190
|
for (const field of missingFields) {
|
|
186
|
-
console.log(` ${colors.
|
|
191
|
+
console.log(` ${colors.dim('-')} ${field}`);
|
|
187
192
|
}
|
|
188
193
|
console.log('');
|
|
189
|
-
console.log(`${colors.
|
|
194
|
+
console.log(`${colors.dim("Let's fill in the missing fields:")}`);
|
|
190
195
|
console.log('');
|
|
191
196
|
let authorValue = existingConfigValues?.author;
|
|
192
197
|
let schemaValue = existingConfigValues?.schema;
|
|
@@ -198,9 +203,9 @@ export async function initCommand(context) {
|
|
|
198
203
|
}
|
|
199
204
|
if (missingFields.includes('connection')) {
|
|
200
205
|
console.log('');
|
|
201
|
-
console.log(
|
|
202
|
-
console.log(
|
|
203
|
-
console.log(
|
|
206
|
+
console.log('Connection not configured.');
|
|
207
|
+
console.log(' Set DATABASE_* environment variables in .env');
|
|
208
|
+
console.log(' or update relq.config.ts with connection details.');
|
|
204
209
|
rl.close();
|
|
205
210
|
return;
|
|
206
211
|
}
|
|
@@ -226,18 +231,18 @@ export async function initCommand(context) {
|
|
|
226
231
|
}
|
|
227
232
|
rl.close();
|
|
228
233
|
console.log('');
|
|
229
|
-
console.log(
|
|
234
|
+
console.log('Completing setup...');
|
|
230
235
|
console.log('');
|
|
231
236
|
initRepository(projectRoot);
|
|
232
|
-
console.log(
|
|
237
|
+
console.log(' Created .relq/ folder');
|
|
233
238
|
createDefaultRelqignore(projectRoot);
|
|
234
|
-
console.log(
|
|
239
|
+
console.log(' Created .relqignore');
|
|
235
240
|
const gitignorePath = path.join(projectRoot, '.gitignore');
|
|
236
241
|
if (fs.existsSync(gitignorePath)) {
|
|
237
242
|
const gitignore = fs.readFileSync(gitignorePath, 'utf-8');
|
|
238
243
|
if (!gitignore.includes('.relq')) {
|
|
239
244
|
fs.appendFileSync(gitignorePath, '\n# Relq local state\n.relq/\n');
|
|
240
|
-
console.log(
|
|
245
|
+
console.log(' Added .relq/ to .gitignore');
|
|
241
246
|
}
|
|
242
247
|
}
|
|
243
248
|
if (existingConfigValues?.connection) {
|
|
@@ -251,9 +256,15 @@ export async function initCommand(context) {
|
|
|
251
256
|
}
|
|
252
257
|
}
|
|
253
258
|
console.log('');
|
|
254
|
-
console.log(
|
|
259
|
+
console.log('Setup complete!');
|
|
255
260
|
console.log('');
|
|
256
|
-
|
|
261
|
+
const calledFrom = context.flags['called-from'];
|
|
262
|
+
if (calledFrom) {
|
|
263
|
+
console.log(`Continuing with ${calledFrom}...`);
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
console.log('Run "relq pull" to sync with database.');
|
|
267
|
+
}
|
|
257
268
|
return;
|
|
258
269
|
}
|
|
259
270
|
rl.close();
|
|
@@ -272,7 +283,7 @@ export async function initCommand(context) {
|
|
|
272
283
|
output: process.stdout,
|
|
273
284
|
});
|
|
274
285
|
try {
|
|
275
|
-
console.log(
|
|
286
|
+
console.log('Checking for database connection...');
|
|
276
287
|
console.log('');
|
|
277
288
|
const envCheck = checkEnvVars();
|
|
278
289
|
let useEnv = false;
|
|
@@ -282,28 +293,28 @@ export async function initCommand(context) {
|
|
|
282
293
|
let user = 'postgres';
|
|
283
294
|
let password = '';
|
|
284
295
|
if (envCheck.found) {
|
|
285
|
-
console.log(
|
|
296
|
+
console.log('Found database environment variables:');
|
|
286
297
|
if (envCheck.vars.RELQ_PG_CONN_URL) {
|
|
287
|
-
console.log(
|
|
298
|
+
console.log(' RELQ_PG_CONN_URL');
|
|
288
299
|
}
|
|
289
300
|
else {
|
|
290
301
|
if (envCheck.vars.DATABASE_HOST)
|
|
291
|
-
console.log(`
|
|
302
|
+
console.log(` DATABASE_HOST: ${envCheck.vars.DATABASE_HOST}`);
|
|
292
303
|
if (envCheck.vars.DATABASE_PORT)
|
|
293
|
-
console.log(`
|
|
304
|
+
console.log(` DATABASE_PORT: ${envCheck.vars.DATABASE_PORT}`);
|
|
294
305
|
if (envCheck.vars.DATABASE_NAME)
|
|
295
|
-
console.log(`
|
|
306
|
+
console.log(` DATABASE_NAME: ${envCheck.vars.DATABASE_NAME}`);
|
|
296
307
|
if (envCheck.vars.DATABASE_USER)
|
|
297
|
-
console.log(`
|
|
308
|
+
console.log(` DATABASE_USER: ${envCheck.vars.DATABASE_USER}`);
|
|
298
309
|
if (envCheck.vars.DATABASE_PASSWORD)
|
|
299
|
-
console.log(
|
|
310
|
+
console.log(' DATABASE_PASSWORD: ***');
|
|
300
311
|
}
|
|
301
312
|
console.log('');
|
|
302
313
|
useEnv = await askYesNo(rl, 'Use these environment variables?', true);
|
|
303
314
|
console.log('');
|
|
304
315
|
}
|
|
305
316
|
if (!useEnv) {
|
|
306
|
-
console.log(
|
|
317
|
+
console.log('Database Connection');
|
|
307
318
|
console.log('');
|
|
308
319
|
host = await ask(rl, ' Host', 'localhost');
|
|
309
320
|
port = await ask(rl, ' Port', '5432');
|
|
@@ -313,21 +324,21 @@ export async function initCommand(context) {
|
|
|
313
324
|
console.log('');
|
|
314
325
|
}
|
|
315
326
|
const detectedPath = detectSchemaPath();
|
|
316
|
-
console.log(
|
|
327
|
+
console.log('Schema Configuration');
|
|
317
328
|
console.log('');
|
|
318
329
|
const schemaPath = await ask(rl, ' Schema file path', detectedPath);
|
|
319
330
|
console.log('');
|
|
320
|
-
console.log(
|
|
331
|
+
console.log('Author (for commit history)');
|
|
321
332
|
console.log('');
|
|
322
333
|
const author = await ask(rl, ' Name <email>', 'Developer <dev@example.com>');
|
|
323
334
|
console.log('');
|
|
324
|
-
console.log(
|
|
335
|
+
console.log('Features to track');
|
|
325
336
|
console.log('');
|
|
326
337
|
const includeFunctions = await askYesNo(rl, ' Include functions?', false);
|
|
327
338
|
const includeTriggers = await askYesNo(rl, ' Include triggers?', false);
|
|
328
339
|
console.log('');
|
|
329
340
|
rl.close();
|
|
330
|
-
console.log(
|
|
341
|
+
console.log('Creating files...');
|
|
331
342
|
console.log('');
|
|
332
343
|
const configPath = path.join(cwd, 'relq.config.ts');
|
|
333
344
|
if (!fs.existsSync(configPath)) {
|
|
@@ -344,26 +355,26 @@ export async function initCommand(context) {
|
|
|
344
355
|
includeTriggers,
|
|
345
356
|
});
|
|
346
357
|
fs.writeFileSync(configPath, configContent, 'utf-8');
|
|
347
|
-
console.log(
|
|
358
|
+
console.log(' Created relq.config.ts');
|
|
348
359
|
}
|
|
349
360
|
else {
|
|
350
|
-
console.log(
|
|
361
|
+
console.log(' relq.config.ts already exists');
|
|
351
362
|
}
|
|
352
363
|
initRepository(cwd);
|
|
353
|
-
console.log(
|
|
364
|
+
console.log(' Created .relq/ folder');
|
|
354
365
|
createDefaultRelqignore(cwd);
|
|
355
|
-
console.log(
|
|
366
|
+
console.log(' Created .relqignore');
|
|
356
367
|
const schemaDir = path.dirname(schemaPath);
|
|
357
368
|
if (!fs.existsSync(schemaDir)) {
|
|
358
369
|
fs.mkdirSync(schemaDir, { recursive: true });
|
|
359
|
-
console.log(`
|
|
370
|
+
console.log(` Created ${schemaDir}/`);
|
|
360
371
|
}
|
|
361
372
|
const gitignorePath = path.join(cwd, '.gitignore');
|
|
362
373
|
if (fs.existsSync(gitignorePath)) {
|
|
363
374
|
const gitignore = fs.readFileSync(gitignorePath, 'utf-8');
|
|
364
375
|
if (!gitignore.includes('.relq')) {
|
|
365
376
|
fs.appendFileSync(gitignorePath, '\n# Relq local state\n.relq/\n');
|
|
366
|
-
console.log(
|
|
377
|
+
console.log(' Added .relq/ to .gitignore');
|
|
367
378
|
}
|
|
368
379
|
}
|
|
369
380
|
console.log('');
|
|
@@ -382,20 +393,19 @@ export async function initCommand(context) {
|
|
|
382
393
|
}
|
|
383
394
|
catch (error) {
|
|
384
395
|
spinner.fail('Could not connect to database');
|
|
385
|
-
|
|
396
|
+
hint("run 'relq pull' after fixing connection");
|
|
386
397
|
}
|
|
387
398
|
console.log('');
|
|
388
|
-
console.log(
|
|
399
|
+
console.log('Relq initialized successfully!');
|
|
389
400
|
console.log('');
|
|
390
|
-
console.log(
|
|
391
|
-
console.log(
|
|
392
|
-
console.log(
|
|
393
|
-
console.log(
|
|
401
|
+
console.log('Next steps:');
|
|
402
|
+
console.log(" 1. Review 'relq.config.ts'");
|
|
403
|
+
console.log(" 2. Run 'relq pull' to sync with database");
|
|
404
|
+
console.log(" 3. Run 'relq status' to see current state");
|
|
394
405
|
console.log('');
|
|
395
406
|
}
|
|
396
407
|
catch (error) {
|
|
397
408
|
rl.close();
|
|
398
|
-
|
|
399
|
-
process.exit(1);
|
|
409
|
+
fatal('Initialization failed', error instanceof Error ? error.message : String(error));
|
|
400
410
|
}
|
|
401
411
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as readline from 'readline';
|
|
3
3
|
import { parseSqlToDefineTable } from "../../introspect/index.js";
|
|
4
|
+
import { fatal } from "../utils/cli-utils.js";
|
|
4
5
|
function readStdin() {
|
|
5
6
|
return new Promise((resolve) => {
|
|
6
7
|
let data = '';
|
|
@@ -43,8 +44,7 @@ export async function introspectCommand(context) {
|
|
|
43
44
|
let sql;
|
|
44
45
|
if (filePath) {
|
|
45
46
|
if (!fs.existsSync(filePath)) {
|
|
46
|
-
|
|
47
|
-
process.exit(1);
|
|
47
|
+
fatal(`File not found: ${filePath}`, 'Check the file path and try again.');
|
|
48
48
|
}
|
|
49
49
|
sql = fs.readFileSync(filePath, 'utf-8');
|
|
50
50
|
console.log(`📄 Reading from: ${filePath}\n`);
|
|
@@ -56,10 +56,7 @@ export async function introspectCommand(context) {
|
|
|
56
56
|
sql = await readInteractive();
|
|
57
57
|
}
|
|
58
58
|
if (!sql.trim()) {
|
|
59
|
-
|
|
60
|
-
console.error('Usage: relq introspect --file schema.sql');
|
|
61
|
-
console.error(' or: cat schema.sql | relq introspect');
|
|
62
|
-
process.exit(1);
|
|
59
|
+
fatal('No SQL input provided', 'Usage: relq introspect --file schema.sql\n or: cat schema.sql | relq introspect');
|
|
63
60
|
}
|
|
64
61
|
console.log('🔍 Parsing SQL...\n');
|
|
65
62
|
try {
|
|
@@ -80,8 +77,7 @@ export async function introspectCommand(context) {
|
|
|
80
77
|
console.log(output.join('\n'));
|
|
81
78
|
}
|
|
82
79
|
catch (error) {
|
|
83
|
-
|
|
84
|
-
process.exit(1);
|
|
80
|
+
fatal('Error parsing SQL', error instanceof Error ? error.message : String(error));
|
|
85
81
|
}
|
|
86
82
|
}
|
|
87
83
|
function generateDefineTableCode(table) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { colors } from "../utils/
|
|
1
|
+
import { colors, fatal, hint } from "../utils/cli-utils.js";
|
|
2
2
|
import { isInitialized, getHead, getCommitHistory, shortHash, } from "../utils/repo-manager.js";
|
|
3
3
|
function formatDate(timestamp) {
|
|
4
4
|
const date = new Date(timestamp);
|
|
@@ -12,23 +12,18 @@ function formatDate(timestamp) {
|
|
|
12
12
|
return `${dayName} ${monthName} ${day} ${time} ${year}`;
|
|
13
13
|
}
|
|
14
14
|
export async function logCommand(context) {
|
|
15
|
-
const { flags } = context;
|
|
16
|
-
const projectRoot = process.cwd();
|
|
15
|
+
const { flags, projectRoot } = context;
|
|
17
16
|
const limit = parseInt(flags['n']) || 10;
|
|
18
17
|
const oneline = flags['oneline'] === true;
|
|
19
18
|
console.log('');
|
|
20
19
|
if (!isInitialized(projectRoot)) {
|
|
21
|
-
|
|
22
|
-
console.log('');
|
|
23
|
-
console.log(`${colors.muted('Run')} ${colors.cyan('relq init')} ${colors.muted('to initialize.')}`);
|
|
24
|
-
return;
|
|
20
|
+
fatal('not a relq repository (or any parent directories): .relq', "run 'relq init' to initialize");
|
|
25
21
|
}
|
|
26
22
|
const head = getHead(projectRoot);
|
|
27
23
|
const commits = getCommitHistory(limit, projectRoot);
|
|
28
24
|
if (commits.length === 0) {
|
|
29
|
-
console.log(
|
|
30
|
-
|
|
31
|
-
console.log(`${colors.muted('Run')} ${colors.cyan('relq commit -m "message"')} ${colors.muted('to create first commit.')}`);
|
|
25
|
+
console.log('No commits yet.');
|
|
26
|
+
hint("run 'relq commit -m <message>' to create first commit");
|
|
32
27
|
console.log('');
|
|
33
28
|
return;
|
|
34
29
|
}
|
|
@@ -57,8 +52,7 @@ export async function logCommand(context) {
|
|
|
57
52
|
}
|
|
58
53
|
}
|
|
59
54
|
export async function showCommand(context) {
|
|
60
|
-
const { args } = context;
|
|
61
|
-
const projectRoot = process.cwd();
|
|
55
|
+
const { args, projectRoot } = context;
|
|
62
56
|
const target = args[0];
|
|
63
57
|
console.log('');
|
|
64
58
|
if (!isInitialized(projectRoot)) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
|
-
import { colors, createSpinner } from "../utils/
|
|
3
|
+
import { colors, createSpinner, fatal } from "../utils/cli-utils.js";
|
|
4
4
|
import { isInitialized, getHead, loadCommit, loadSnapshot, saveSnapshot, createCommit, shortHash, } from "../utils/repo-manager.js";
|
|
5
5
|
function loadBranchState(projectRoot) {
|
|
6
6
|
const branchPath = path.join(projectRoot, '.relq', 'branches.json');
|
|
@@ -15,12 +15,10 @@ function saveBranchState(state, projectRoot) {
|
|
|
15
15
|
fs.writeFileSync(branchPath, JSON.stringify(state, null, 2));
|
|
16
16
|
}
|
|
17
17
|
export async function mergeCommand(context) {
|
|
18
|
-
const { config, args, flags } = context;
|
|
19
|
-
const projectRoot = process.cwd();
|
|
18
|
+
const { config, args, flags, projectRoot } = context;
|
|
20
19
|
console.log('');
|
|
21
20
|
if (!isInitialized(projectRoot)) {
|
|
22
|
-
|
|
23
|
-
return;
|
|
21
|
+
fatal('not a relq repository (or any parent directories): .relq', `Run ${colors.cyan('relq init')} to initialize.`);
|
|
24
22
|
}
|
|
25
23
|
const branchName = args[0];
|
|
26
24
|
const abort = flags['abort'] === true;
|
|
@@ -28,38 +26,27 @@ export async function mergeCommand(context) {
|
|
|
28
26
|
if (abort) {
|
|
29
27
|
if (fs.existsSync(mergeStatePath)) {
|
|
30
28
|
fs.unlinkSync(mergeStatePath);
|
|
31
|
-
console.log(
|
|
29
|
+
console.log('Merge aborted');
|
|
30
|
+
console.log('');
|
|
31
|
+
return;
|
|
32
32
|
}
|
|
33
33
|
else {
|
|
34
|
-
|
|
34
|
+
fatal('There is no merge to abort (MERGE_STATE missing)');
|
|
35
35
|
}
|
|
36
|
-
console.log('');
|
|
37
|
-
return;
|
|
38
36
|
}
|
|
39
37
|
if (fs.existsSync(mergeStatePath)) {
|
|
40
38
|
const mergeState = JSON.parse(fs.readFileSync(mergeStatePath, 'utf-8'));
|
|
41
|
-
|
|
42
|
-
console.log('');
|
|
43
|
-
console.log(`${colors.muted('Use')} ${colors.cyan('relq resolve')} ${colors.muted('to resolve conflicts')}`);
|
|
44
|
-
console.log(`${colors.muted('Or')} ${colors.cyan('relq merge --abort')} ${colors.muted('to cancel')}`);
|
|
45
|
-
console.log('');
|
|
46
|
-
return;
|
|
39
|
+
fatal(`Merge in progress from '${mergeState.fromBranch}'`, `Use ${colors.cyan('relq resolve')} to resolve conflicts\nOr ${colors.cyan('relq merge --abort')} to cancel`);
|
|
47
40
|
}
|
|
48
41
|
if (!branchName) {
|
|
49
|
-
|
|
50
|
-
console.log('');
|
|
51
|
-
console.log(`Usage: ${colors.cyan('relq merge <branch>')}`);
|
|
52
|
-
console.log('');
|
|
53
|
-
return;
|
|
42
|
+
fatal('Please specify a branch to merge', `Usage: ${colors.cyan('relq merge <branch>')}`);
|
|
54
43
|
}
|
|
55
44
|
const state = loadBranchState(projectRoot);
|
|
56
45
|
if (!state.branches[branchName]) {
|
|
57
|
-
|
|
58
|
-
return;
|
|
46
|
+
fatal(`Branch not found: ${branchName}`, `Use ${colors.cyan('relq branch')} to list available branches.`);
|
|
59
47
|
}
|
|
60
48
|
if (branchName === state.current) {
|
|
61
|
-
|
|
62
|
-
return;
|
|
49
|
+
fatal('Cannot merge branch into itself');
|
|
63
50
|
}
|
|
64
51
|
const spinner = createSpinner();
|
|
65
52
|
spinner.start(`Merging '${branchName}' into '${state.current}'...`);
|
|
@@ -67,8 +54,8 @@ export async function mergeCommand(context) {
|
|
|
67
54
|
const currentHash = getHead(projectRoot);
|
|
68
55
|
const incomingHash = state.branches[branchName];
|
|
69
56
|
if (!currentHash || !incomingHash) {
|
|
70
|
-
spinner.
|
|
71
|
-
|
|
57
|
+
spinner.stop();
|
|
58
|
+
fatal('No commits to merge', `Run ${colors.cyan('relq pull')} first.`);
|
|
72
59
|
}
|
|
73
60
|
if (currentHash === incomingHash) {
|
|
74
61
|
spinner.succeed('Already up to date');
|
|
@@ -78,14 +65,14 @@ export async function mergeCommand(context) {
|
|
|
78
65
|
const currentCommit = loadCommit(currentHash, projectRoot);
|
|
79
66
|
const incomingCommit = loadCommit(incomingHash, projectRoot);
|
|
80
67
|
if (!currentCommit || !incomingCommit) {
|
|
81
|
-
spinner.
|
|
82
|
-
|
|
68
|
+
spinner.stop();
|
|
69
|
+
fatal('Cannot load commit data - repository may be corrupt');
|
|
83
70
|
}
|
|
84
71
|
const currentSnapshot = loadSnapshot(projectRoot) || currentCommit.schema;
|
|
85
72
|
const incomingSnapshot = incomingCommit.schema;
|
|
86
73
|
if (!currentSnapshot || !incomingSnapshot) {
|
|
87
|
-
spinner.
|
|
88
|
-
|
|
74
|
+
spinner.stop();
|
|
75
|
+
fatal('No snapshot data - repository may be corrupt');
|
|
89
76
|
}
|
|
90
77
|
const conflicts = detectMergeConflicts(currentSnapshot, incomingSnapshot);
|
|
91
78
|
if (conflicts.length > 0) {
|
|
@@ -106,11 +93,7 @@ export async function mergeCommand(context) {
|
|
|
106
93
|
if (conflicts.length > 5) {
|
|
107
94
|
console.log(` ${colors.muted(`... and ${conflicts.length - 5} more`)}`);
|
|
108
95
|
}
|
|
109
|
-
|
|
110
|
-
console.log(`${colors.muted('Use')} ${colors.cyan('relq resolve --all-theirs')} ${colors.muted('to accept incoming')}`);
|
|
111
|
-
console.log(`${colors.muted('Or')} ${colors.cyan('relq merge --abort')} ${colors.muted('to cancel')}`);
|
|
112
|
-
console.log('');
|
|
113
|
-
return;
|
|
96
|
+
fatal(`Automatic merge failed; fix conflicts and then commit`, `Use ${colors.cyan('relq resolve --all-theirs')} to accept incoming\nOr ${colors.cyan('relq merge --abort')} to cancel`);
|
|
114
97
|
}
|
|
115
98
|
const mergedSnapshot = mergeSnapshots(currentSnapshot, incomingSnapshot);
|
|
116
99
|
saveSnapshot(mergedSnapshot, projectRoot);
|
|
@@ -121,9 +104,9 @@ export async function mergeCommand(context) {
|
|
|
121
104
|
console.log(` ${colors.yellow(shortHash(commit.hash))} ${message}`);
|
|
122
105
|
console.log('');
|
|
123
106
|
}
|
|
124
|
-
catch (
|
|
107
|
+
catch (err) {
|
|
125
108
|
spinner.fail('Merge failed');
|
|
126
|
-
|
|
109
|
+
fatal(err instanceof Error ? err.message : String(err));
|
|
127
110
|
}
|
|
128
111
|
}
|
|
129
112
|
function detectMergeConflicts(current, incoming) {
|