relq 1.0.1 → 1.0.2
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/addon/buffer/index.cjs +1881 -0
- package/dist/cjs/addon/pg/index.cjs +4812 -0
- package/dist/cjs/addon/pg-cursor/index.cjs +1451 -0
- package/dist/cjs/addon/pg-format/index.cjs +2270 -0
- package/dist/cjs/cli/commands/add.cjs +30 -1
- package/dist/cjs/cli/commands/branch.cjs +141 -0
- package/dist/cjs/cli/commands/checkout.cjs +134 -0
- package/dist/cjs/cli/commands/cherry-pick.cjs +283 -0
- package/dist/cjs/cli/commands/diff.cjs +148 -69
- package/dist/cjs/cli/commands/export.cjs +64 -5
- package/dist/cjs/cli/commands/fetch.cjs +34 -4
- package/dist/cjs/cli/commands/history.cjs +1 -1
- package/dist/cjs/cli/commands/import.cjs +283 -12
- package/dist/cjs/cli/commands/log.cjs +75 -0
- package/dist/cjs/cli/commands/merge.cjs +224 -0
- package/dist/cjs/cli/commands/migrate.cjs +1 -1
- package/dist/cjs/cli/commands/pull.cjs +123 -7
- package/dist/cjs/cli/commands/push.cjs +245 -29
- package/dist/cjs/cli/commands/remote.cjs +16 -0
- package/dist/cjs/cli/commands/reset.cjs +169 -0
- package/dist/cjs/cli/commands/resolve.cjs +193 -0
- package/dist/cjs/cli/commands/rollback.cjs +1 -1
- package/dist/cjs/cli/commands/stash.cjs +154 -0
- package/dist/cjs/cli/commands/status.cjs +48 -0
- package/dist/cjs/cli/commands/tag.cjs +147 -0
- package/dist/cjs/cli/index.cjs +46 -2
- package/dist/cjs/cli/utils/commit-manager.cjs +3 -3
- package/dist/cjs/cli/utils/env-loader.cjs +3 -2
- package/dist/cjs/cli/utils/fast-introspect.cjs +1 -1
- package/dist/cjs/cli/utils/project-root.cjs +56 -0
- package/dist/cjs/cli/utils/relqignore.cjs +296 -38
- package/dist/cjs/cli/utils/repo-manager.cjs +45 -3
- package/dist/cjs/cli/utils/schema-introspect.cjs +2 -2
- package/dist/cjs/cli/utils/sql-generator.cjs +1 -1
- package/dist/cjs/cli/utils/sql-parser.cjs +94 -7
- package/dist/cjs/condition/array-condition-builder.cjs +1 -1
- package/dist/cjs/condition/condition-collector.cjs +1 -1
- package/dist/cjs/condition/fulltext-condition-builder.cjs +1 -1
- package/dist/cjs/condition/geometric-condition-builder.cjs +1 -1
- package/dist/cjs/condition/jsonb-condition-builder.cjs +1 -1
- package/dist/cjs/condition/network-condition-builder.cjs +1 -1
- package/dist/cjs/condition/range-condition-builder.cjs +1 -1
- package/dist/cjs/copy/copy-builder.cjs +1 -1
- package/dist/cjs/core/query-builder.cjs +1 -1
- package/dist/cjs/core/relq-client.cjs +2 -2
- package/dist/cjs/count/count-builder.cjs +1 -1
- package/dist/cjs/cte/cte-builder.cjs +1 -1
- package/dist/cjs/delete/delete-builder.cjs +1 -1
- package/dist/cjs/function/create-function-builder.cjs +1 -1
- package/dist/cjs/functions/advanced-functions.cjs +1 -1
- package/dist/cjs/functions/case-builder.cjs +1 -1
- package/dist/cjs/functions/geometric-functions.cjs +1 -1
- package/dist/cjs/functions/network-functions.cjs +1 -1
- package/dist/cjs/functions/sql-functions.cjs +1 -1
- package/dist/cjs/indexing/create-index-builder.cjs +1 -1
- package/dist/cjs/indexing/drop-index-builder.cjs +1 -1
- package/dist/cjs/insert/conflict-builder.cjs +1 -1
- package/dist/cjs/insert/insert-builder.cjs +1 -1
- package/dist/cjs/maintenance/vacuum-builder.cjs +1 -1
- package/dist/cjs/pubsub/listen-notify-builder.cjs +1 -1
- package/dist/cjs/pubsub/listener-connection.cjs +2 -2
- package/dist/cjs/raw/raw-query-builder.cjs +1 -1
- package/dist/cjs/schema/schema-builder.cjs +1 -1
- package/dist/cjs/schema-definition/table-definition.cjs +1 -1
- package/dist/cjs/select/aggregate-builder.cjs +1 -1
- package/dist/cjs/select/select-builder.cjs +1 -1
- package/dist/cjs/sequence/sequence-builder.cjs +1 -1
- package/dist/cjs/table/alter-table-builder.cjs +1 -1
- package/dist/cjs/table/constraint-builder.cjs +1 -1
- package/dist/cjs/table/create-table-builder.cjs +1 -1
- package/dist/cjs/table/partition-builder.cjs +1 -1
- package/dist/cjs/table/truncate-builder.cjs +1 -1
- package/dist/cjs/transaction/transaction-builder.cjs +1 -1
- package/dist/cjs/trigger/create-trigger-builder.cjs +1 -1
- package/dist/cjs/update/array-update-builder.cjs +1 -1
- package/dist/cjs/update/update-builder.cjs +1 -1
- package/dist/cjs/utils/index.cjs +1 -1
- package/dist/cjs/view/create-view-builder.cjs +1 -1
- package/dist/cjs/window/window-builder.cjs +1 -1
- package/dist/esm/cli/commands/add.js +30 -1
- package/dist/esm/cli/commands/branch.js +105 -0
- package/dist/esm/cli/commands/checkout.js +98 -0
- package/dist/esm/cli/commands/cherry-pick.js +247 -0
- package/dist/esm/cli/commands/diff.js +148 -69
- package/dist/esm/cli/commands/export.js +64 -5
- package/dist/esm/cli/commands/fetch.js +35 -5
- package/dist/esm/cli/commands/history.js +1 -1
- package/dist/esm/cli/commands/import.js +283 -12
- package/dist/esm/cli/commands/log.js +74 -0
- package/dist/esm/cli/commands/merge.js +188 -0
- package/dist/esm/cli/commands/migrate.js +1 -1
- package/dist/esm/cli/commands/pull.js +124 -8
- package/dist/esm/cli/commands/push.js +246 -30
- package/dist/esm/cli/commands/remote.js +13 -0
- package/dist/esm/cli/commands/reset.js +133 -0
- package/dist/esm/cli/commands/resolve.js +157 -0
- package/dist/esm/cli/commands/rollback.js +1 -1
- package/dist/esm/cli/commands/stash.js +118 -0
- package/dist/esm/cli/commands/status.js +15 -0
- package/dist/esm/cli/commands/tag.js +111 -0
- package/dist/esm/cli/index.js +47 -3
- package/dist/esm/cli/utils/commit-manager.js +3 -3
- package/dist/esm/cli/utils/env-loader.js +3 -2
- package/dist/esm/cli/utils/fast-introspect.js +1 -1
- package/dist/esm/cli/utils/project-root.js +19 -0
- package/dist/esm/cli/utils/relqignore.js +277 -37
- package/dist/esm/cli/utils/repo-manager.js +41 -3
- package/dist/esm/cli/utils/schema-introspect.js +2 -2
- package/dist/esm/cli/utils/sql-generator.js +1 -1
- package/dist/esm/cli/utils/sql-parser.js +94 -7
- package/dist/esm/condition/array-condition-builder.js +1 -1
- package/dist/esm/condition/condition-collector.js +1 -1
- package/dist/esm/condition/fulltext-condition-builder.js +1 -1
- package/dist/esm/condition/geometric-condition-builder.js +1 -1
- package/dist/esm/condition/jsonb-condition-builder.js +1 -1
- package/dist/esm/condition/network-condition-builder.js +1 -1
- package/dist/esm/condition/range-condition-builder.js +1 -1
- package/dist/esm/copy/copy-builder.js +1 -1
- package/dist/esm/core/query-builder.js +1 -1
- package/dist/esm/core/relq-client.js +2 -2
- package/dist/esm/count/count-builder.js +1 -1
- package/dist/esm/cte/cte-builder.js +1 -1
- package/dist/esm/delete/delete-builder.js +1 -1
- package/dist/esm/function/create-function-builder.js +1 -1
- package/dist/esm/functions/advanced-functions.js +1 -1
- package/dist/esm/functions/case-builder.js +1 -1
- package/dist/esm/functions/geometric-functions.js +1 -1
- package/dist/esm/functions/network-functions.js +1 -1
- package/dist/esm/functions/sql-functions.js +1 -1
- package/dist/esm/indexing/create-index-builder.js +1 -1
- package/dist/esm/indexing/drop-index-builder.js +1 -1
- package/dist/esm/insert/conflict-builder.js +1 -1
- package/dist/esm/insert/insert-builder.js +1 -1
- package/dist/esm/maintenance/vacuum-builder.js +1 -1
- package/dist/esm/pubsub/listen-notify-builder.js +1 -1
- package/dist/esm/pubsub/listener-connection.js +2 -2
- package/dist/esm/raw/raw-query-builder.js +1 -1
- package/dist/esm/schema/schema-builder.js +1 -1
- package/dist/esm/schema-definition/table-definition.js +1 -1
- package/dist/esm/select/aggregate-builder.js +1 -1
- package/dist/esm/select/select-builder.js +1 -1
- package/dist/esm/sequence/sequence-builder.js +1 -1
- package/dist/esm/table/alter-table-builder.js +1 -1
- package/dist/esm/table/constraint-builder.js +1 -1
- package/dist/esm/table/create-table-builder.js +1 -1
- package/dist/esm/table/partition-builder.js +1 -1
- package/dist/esm/table/truncate-builder.js +1 -1
- package/dist/esm/transaction/transaction-builder.js +1 -1
- package/dist/esm/trigger/create-trigger-builder.js +1 -1
- package/dist/esm/update/array-update-builder.js +1 -1
- package/dist/esm/update/update-builder.js +1 -1
- package/dist/esm/utils/index.js +1 -1
- package/dist/esm/view/create-view-builder.js +1 -1
- package/dist/esm/window/window-builder.js +1 -1
- package/package.json +1 -1
- /package/dist/{addons/buffer.js → esm/addon/buffer/index.js} +0 -0
- /package/dist/{addons/pg.js → esm/addon/pg/index.js} +0 -0
- /package/dist/{addons/pg-cursor.js → esm/addon/pg-cursor/index.js} +0 -0
- /package/dist/{addons/pg-format.js → esm/addon/pg-format/index.js} +0 -0
|
@@ -245,8 +245,24 @@ async function pullCommand(context) {
|
|
|
245
245
|
includeTriggers,
|
|
246
246
|
});
|
|
247
247
|
spinner.succeed(`Found ${dbSchema.tables.length} tables`);
|
|
248
|
-
const ignorePatterns = (0, relqignore_1.
|
|
249
|
-
const filteredTables =
|
|
248
|
+
const ignorePatterns = (0, relqignore_1.loadRelqignore)(projectRoot);
|
|
249
|
+
const filteredTables = dbSchema.tables
|
|
250
|
+
.filter(t => !(0, relqignore_1.isTableIgnored)(t.name, ignorePatterns).ignored)
|
|
251
|
+
.map(t => ({
|
|
252
|
+
...t,
|
|
253
|
+
columns: t.columns.filter(c => !(0, relqignore_1.isColumnIgnored)(t.name, c.name, ignorePatterns).ignored),
|
|
254
|
+
indexes: t.indexes.filter(i => !(0, relqignore_1.isIndexIgnored)(t.name, i.name, ignorePatterns).ignored),
|
|
255
|
+
constraints: t.constraints.filter(c => !(0, relqignore_1.isConstraintIgnored)(t.name, c.name, ignorePatterns).ignored),
|
|
256
|
+
}));
|
|
257
|
+
const filteredEnums = dbSchema.enums.filter(e => !(0, relqignore_1.isEnumIgnored)(e.name, ignorePatterns).ignored);
|
|
258
|
+
const filteredDomains = dbSchema.domains.filter(d => !(0, relqignore_1.isDomainIgnored)(d.name, ignorePatterns).ignored);
|
|
259
|
+
const filteredCompositeTypes = dbSchema.compositeTypes.filter(c => !(0, relqignore_1.isCompositeTypeIgnored)(c.name, ignorePatterns).ignored);
|
|
260
|
+
const filteredFunctions = includeFunctions
|
|
261
|
+
? dbSchema.functions.filter(f => !(0, relqignore_1.isFunctionIgnored)(f.name, ignorePatterns).ignored)
|
|
262
|
+
: [];
|
|
263
|
+
const filteredTriggers = includeTriggers
|
|
264
|
+
? dbSchema.triggers
|
|
265
|
+
: [];
|
|
250
266
|
const localHead = (0, repo_manager_1.getHead)(projectRoot);
|
|
251
267
|
const localSnapshot = (0, repo_manager_1.loadSnapshot)(projectRoot);
|
|
252
268
|
const schemaExists = fs.existsSync(schemaPath);
|
|
@@ -275,12 +291,12 @@ async function pullCommand(context) {
|
|
|
275
291
|
definition: c.definition,
|
|
276
292
|
})),
|
|
277
293
|
})),
|
|
278
|
-
enums:
|
|
294
|
+
enums: filteredEnums.map(e => ({
|
|
279
295
|
name: e.name,
|
|
280
296
|
schema: 'public',
|
|
281
297
|
values: e.values,
|
|
282
298
|
})),
|
|
283
|
-
domains:
|
|
299
|
+
domains: filteredDomains.map(d => ({
|
|
284
300
|
name: d.name,
|
|
285
301
|
schema: 'public',
|
|
286
302
|
baseType: d.baseType,
|
|
@@ -288,20 +304,20 @@ async function pullCommand(context) {
|
|
|
288
304
|
default: d.defaultValue || null,
|
|
289
305
|
check: d.checkExpression || null,
|
|
290
306
|
})),
|
|
291
|
-
compositeTypes:
|
|
307
|
+
compositeTypes: filteredCompositeTypes.map(c => ({
|
|
292
308
|
name: c.name,
|
|
293
309
|
schema: 'public',
|
|
294
310
|
attributes: c.attributes,
|
|
295
311
|
})),
|
|
296
312
|
sequences: [],
|
|
297
313
|
collations: [],
|
|
298
|
-
functions:
|
|
314
|
+
functions: filteredFunctions.map(f => ({
|
|
299
315
|
name: f.name,
|
|
300
316
|
returnType: f.returnType,
|
|
301
317
|
argTypes: f.argTypes,
|
|
302
318
|
language: f.language,
|
|
303
319
|
})),
|
|
304
|
-
triggers:
|
|
320
|
+
triggers: filteredTriggers.map(t => ({
|
|
305
321
|
name: t.name,
|
|
306
322
|
table: t.tableName,
|
|
307
323
|
events: [t.event],
|
|
@@ -312,11 +328,49 @@ async function pullCommand(context) {
|
|
|
312
328
|
extensions: dbSchema.extensions.map(ext => ({ name: ext })),
|
|
313
329
|
};
|
|
314
330
|
console.log('');
|
|
331
|
+
const mergeStatePath = path.join(projectRoot, '.relq', 'MERGE_STATE');
|
|
332
|
+
if (fs.existsSync(mergeStatePath) && !force) {
|
|
333
|
+
console.log(`${spinner_1.colors.red('error:')} You have unresolved merge conflicts`);
|
|
334
|
+
console.log('');
|
|
335
|
+
console.log(`${spinner_1.colors.muted('Use')} ${spinner_1.colors.cyan('relq resolve')} ${spinner_1.colors.muted('to see and resolve conflicts')}`);
|
|
336
|
+
console.log(`${spinner_1.colors.muted('Or use')} ${spinner_1.colors.cyan('relq pull --force')} ${spinner_1.colors.muted('to overwrite local')}`);
|
|
337
|
+
console.log('');
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
315
340
|
if (schemaExists && localSnapshot && !force) {
|
|
316
341
|
const localTables = new Set(localSnapshot.tables.map(t => t.name));
|
|
317
342
|
const remoteTables = new Set(currentSchema.tables.map(t => t.name));
|
|
318
343
|
const added = [...remoteTables].filter(t => !localTables.has(t));
|
|
319
344
|
const removed = [...localTables].filter(t => !remoteTables.has(t));
|
|
345
|
+
const conflicts = detectObjectConflicts(localSnapshot, currentSchema);
|
|
346
|
+
if (conflicts.length > 0 && !force) {
|
|
347
|
+
const mergeState = {
|
|
348
|
+
conflicts,
|
|
349
|
+
remoteSnapshot: currentSchema,
|
|
350
|
+
createdAt: new Date().toISOString(),
|
|
351
|
+
};
|
|
352
|
+
fs.writeFileSync(mergeStatePath, JSON.stringify(mergeState, null, 2));
|
|
353
|
+
console.log(`${spinner_1.colors.red('error:')} Merge conflict detected`);
|
|
354
|
+
console.log('');
|
|
355
|
+
console.log(`Both local and remote have modified the same objects:`);
|
|
356
|
+
console.log('');
|
|
357
|
+
for (const c of conflicts.slice(0, 10)) {
|
|
358
|
+
const name = c.parentName ? `${c.parentName}.${c.objectName}` : c.objectName;
|
|
359
|
+
console.log(` ${spinner_1.colors.red('conflict:')} ${c.objectType.toLowerCase()} ${spinner_1.colors.bold(name)}`);
|
|
360
|
+
console.log(` ${spinner_1.colors.muted(c.description)}`);
|
|
361
|
+
}
|
|
362
|
+
if (conflicts.length > 10) {
|
|
363
|
+
console.log(` ${spinner_1.colors.muted(`... and ${conflicts.length - 10} more`)}`);
|
|
364
|
+
}
|
|
365
|
+
console.log('');
|
|
366
|
+
console.log('To resolve:');
|
|
367
|
+
console.log(` ${spinner_1.colors.cyan('relq resolve --theirs <name>')} Take remote version`);
|
|
368
|
+
console.log(` ${spinner_1.colors.cyan('relq resolve --ours <name>')} Keep local version`);
|
|
369
|
+
console.log(` ${spinner_1.colors.cyan('relq resolve --all-theirs')} Take all remote`);
|
|
370
|
+
console.log(` ${spinner_1.colors.cyan('relq pull --force')} Force overwrite local`);
|
|
371
|
+
console.log('');
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
320
374
|
if (added.length === 0 && removed.length === 0) {
|
|
321
375
|
console.log(`${spinner_1.colors.green('✓')} Already up to date with remote`);
|
|
322
376
|
console.log('');
|
|
@@ -408,3 +462,65 @@ async function pullCommand(context) {
|
|
|
408
462
|
process.exit(1);
|
|
409
463
|
}
|
|
410
464
|
}
|
|
465
|
+
function detectObjectConflicts(local, remote) {
|
|
466
|
+
const conflicts = [];
|
|
467
|
+
for (const localTable of local.tables) {
|
|
468
|
+
const remoteTable = remote.tables.find(t => t.name === localTable.name);
|
|
469
|
+
if (!remoteTable)
|
|
470
|
+
continue;
|
|
471
|
+
for (const localCol of localTable.columns) {
|
|
472
|
+
const remoteCol = remoteTable.columns.find(c => c.name === localCol.name);
|
|
473
|
+
if (!remoteCol)
|
|
474
|
+
continue;
|
|
475
|
+
if (localCol.type !== remoteCol.type) {
|
|
476
|
+
conflicts.push({
|
|
477
|
+
objectType: 'COLUMN',
|
|
478
|
+
objectName: localCol.name,
|
|
479
|
+
parentName: localTable.name,
|
|
480
|
+
localValue: localCol.type,
|
|
481
|
+
remoteValue: remoteCol.type,
|
|
482
|
+
description: `Type changed: ${localCol.type} → ${remoteCol.type}`,
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
else if (localCol.nullable !== remoteCol.nullable) {
|
|
486
|
+
conflicts.push({
|
|
487
|
+
objectType: 'COLUMN',
|
|
488
|
+
objectName: localCol.name,
|
|
489
|
+
parentName: localTable.name,
|
|
490
|
+
localValue: localCol.nullable,
|
|
491
|
+
remoteValue: remoteCol.nullable,
|
|
492
|
+
description: `Nullable changed: ${localCol.nullable} → ${remoteCol.nullable}`,
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
else if (String(localCol.default || '') !== String(remoteCol.default || '')) {
|
|
496
|
+
if (localCol.default && remoteCol.default && localCol.default !== remoteCol.default) {
|
|
497
|
+
conflicts.push({
|
|
498
|
+
objectType: 'COLUMN',
|
|
499
|
+
objectName: localCol.name,
|
|
500
|
+
parentName: localTable.name,
|
|
501
|
+
localValue: localCol.default,
|
|
502
|
+
remoteValue: remoteCol.default,
|
|
503
|
+
description: `Default changed`,
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
for (const localEnum of local.enums) {
|
|
510
|
+
const remoteEnum = remote.enums.find(e => e.name === localEnum.name);
|
|
511
|
+
if (!remoteEnum)
|
|
512
|
+
continue;
|
|
513
|
+
const localVals = JSON.stringify(localEnum.values.sort());
|
|
514
|
+
const remoteVals = JSON.stringify(remoteEnum.values.sort());
|
|
515
|
+
if (localVals !== remoteVals) {
|
|
516
|
+
conflicts.push({
|
|
517
|
+
objectType: 'ENUM',
|
|
518
|
+
objectName: localEnum.name,
|
|
519
|
+
localValue: localEnum.values,
|
|
520
|
+
remoteValue: remoteEnum.values,
|
|
521
|
+
description: `Values differ`,
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
return conflicts;
|
|
526
|
+
}
|
|
@@ -36,9 +36,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.pushCommand = pushCommand;
|
|
37
37
|
const fs = __importStar(require("fs"));
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
|
+
const readline = __importStar(require("readline"));
|
|
39
40
|
const config_loader_1 = require("../utils/config-loader.cjs");
|
|
40
41
|
const env_loader_1 = require("../utils/env-loader.cjs");
|
|
41
42
|
const spinner_1 = require("../utils/spinner.cjs");
|
|
43
|
+
const fast_introspect_1 = require("../utils/fast-introspect.cjs");
|
|
44
|
+
const relqignore_1 = require("../utils/relqignore.cjs");
|
|
42
45
|
const repo_manager_1 = require("../utils/repo-manager.cjs");
|
|
43
46
|
async function pushCommand(context) {
|
|
44
47
|
const { config, flags } = context;
|
|
@@ -52,6 +55,11 @@ async function pushCommand(context) {
|
|
|
52
55
|
const force = flags['force'] === true;
|
|
53
56
|
const dryRun = flags['dry-run'] === true;
|
|
54
57
|
const applySQL = flags['apply'] === true;
|
|
58
|
+
const noVerify = flags['no-verify'] === true;
|
|
59
|
+
const skipPrompt = flags['yes'] === true || flags['y'] === true;
|
|
60
|
+
const includeFunctions = config.includeFunctions ?? false;
|
|
61
|
+
const includeTriggers = config.includeTriggers ?? false;
|
|
62
|
+
const includeViews = config.includeViews ?? false;
|
|
55
63
|
console.log('');
|
|
56
64
|
if (!(0, repo_manager_1.isInitialized)(projectRoot)) {
|
|
57
65
|
console.log(`${spinner_1.colors.red('fatal:')} not a relq repository`);
|
|
@@ -71,32 +79,101 @@ async function pushCommand(context) {
|
|
|
71
79
|
spinner.start('Connecting to remote...');
|
|
72
80
|
await (0, repo_manager_1.ensureRemoteTable)(connection);
|
|
73
81
|
spinner.succeed(`Connected to ${spinner_1.colors.cyan((0, env_loader_1.getConnectionDescription)(connection))}`);
|
|
74
|
-
spinner.start('Checking remote...');
|
|
82
|
+
spinner.start('Checking remote commits...');
|
|
75
83
|
const remoteCommits = await (0, repo_manager_1.fetchRemoteCommits)(connection, 100);
|
|
76
84
|
const remoteHead = remoteCommits.length > 0 ? remoteCommits[0].hash : null;
|
|
77
|
-
spinner.stop();
|
|
78
85
|
const localCommits = (0, repo_manager_1.getAllCommits)(projectRoot);
|
|
79
86
|
const remoteHashes = new Set(remoteCommits.map(c => c.hash));
|
|
80
87
|
const localHashes = new Set(localCommits.map(c => c.hash));
|
|
81
88
|
const toPush = localCommits.filter(c => !remoteHashes.has(c.hash));
|
|
82
|
-
|
|
83
|
-
|
|
89
|
+
const remoteMissing = remoteCommits.filter(c => !localHashes.has(c.hash));
|
|
90
|
+
spinner.succeed('Checked remote commits');
|
|
91
|
+
spinner.start('Introspecting remote database...');
|
|
92
|
+
const remoteDb = await (0, fast_introspect_1.fastIntrospectDatabase)(connection, undefined, {
|
|
93
|
+
includeFunctions,
|
|
94
|
+
includeTriggers,
|
|
95
|
+
});
|
|
96
|
+
spinner.succeed(`Found ${remoteDb.tables.length} tables in remote`);
|
|
97
|
+
const localSnapshot = (0, repo_manager_1.loadSnapshot)(projectRoot);
|
|
98
|
+
if (!localSnapshot) {
|
|
99
|
+
console.log(`${spinner_1.colors.red('error:')} No local snapshot found`);
|
|
100
|
+
console.log(`${spinner_1.colors.muted('Run')} ${spinner_1.colors.cyan('relq pull')} ${spinner_1.colors.muted('or')} ${spinner_1.colors.cyan('relq import')} ${spinner_1.colors.muted('first.')}`);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const ignorePatterns = (0, relqignore_1.loadRelqignore)(projectRoot);
|
|
104
|
+
const analysis = analyzeSync(localSnapshot, remoteDb, ignorePatterns, { includeFunctions, includeTriggers, includeViews });
|
|
105
|
+
const hasRemoteAhead = remoteMissing.length > 0;
|
|
106
|
+
const hasObjectsToDrop = analysis.objectsToDrop.length > 0;
|
|
107
|
+
const hasSchemaDrift = analysis.schemaDrift.length > 0;
|
|
108
|
+
if (hasRemoteAhead && !force) {
|
|
109
|
+
console.log('');
|
|
110
|
+
console.log(`${spinner_1.colors.red('error:')} Remote has ${remoteMissing.length} commit(s) you don't have locally`);
|
|
111
|
+
console.log('');
|
|
112
|
+
for (const commit of remoteMissing.slice(0, 3)) {
|
|
113
|
+
console.log(` ${spinner_1.colors.yellow((0, repo_manager_1.shortHash)(commit.hash))} ${commit.message}`);
|
|
114
|
+
}
|
|
115
|
+
if (remoteMissing.length > 3) {
|
|
116
|
+
console.log(` ${spinner_1.colors.muted(`... and ${remoteMissing.length - 3} more`)}`);
|
|
117
|
+
}
|
|
118
|
+
console.log('');
|
|
119
|
+
console.log(`${spinner_1.colors.yellow('hint:')} Run ${spinner_1.colors.cyan('relq pull')} to integrate remote changes.`);
|
|
120
|
+
console.log(`${spinner_1.colors.yellow('hint:')} Use ${spinner_1.colors.cyan('relq push --force')} to override (may cause data loss).`);
|
|
84
121
|
console.log('');
|
|
85
122
|
return;
|
|
86
123
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
console.log(`${spinner_1.colors.
|
|
124
|
+
if (hasObjectsToDrop) {
|
|
125
|
+
console.log('');
|
|
126
|
+
console.log(`${spinner_1.colors.yellow('⚠')} Remote has ${analysis.objectsToDrop.length} object(s) not in your local schema:`);
|
|
127
|
+
console.log('');
|
|
128
|
+
for (const obj of analysis.objectsToDrop.slice(0, 10)) {
|
|
129
|
+
console.log(` ${spinner_1.colors.red('DROP')} ${obj.type.toLowerCase()}: ${obj.name}`);
|
|
130
|
+
}
|
|
131
|
+
if (analysis.objectsToDrop.length > 10) {
|
|
132
|
+
console.log(` ${spinner_1.colors.muted(`... and ${analysis.objectsToDrop.length - 10} more`)}`);
|
|
133
|
+
}
|
|
90
134
|
console.log('');
|
|
91
|
-
|
|
92
|
-
|
|
135
|
+
if (!force) {
|
|
136
|
+
console.log(`${spinner_1.colors.red('error:')} Cannot push - remote has objects not in your history`);
|
|
137
|
+
console.log('');
|
|
138
|
+
console.log(`${spinner_1.colors.yellow('hint:')} Run ${spinner_1.colors.cyan('relq pull')} first to sync your local schema.`);
|
|
139
|
+
console.log(`${spinner_1.colors.yellow('hint:')} Use ${spinner_1.colors.cyan('relq push --force')} to DROP these objects from remote.`);
|
|
140
|
+
console.log('');
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const dependencyErrors = checkDropDependencies(analysis.objectsToDrop, remoteDb, ignorePatterns);
|
|
144
|
+
if (dependencyErrors.length > 0) {
|
|
145
|
+
console.log(`${spinner_1.colors.red('error:')} Cannot drop objects due to dependencies:`);
|
|
146
|
+
console.log('');
|
|
147
|
+
for (const err of dependencyErrors) {
|
|
148
|
+
console.log(` ${spinner_1.colors.red('✗')} ${err}`);
|
|
149
|
+
}
|
|
150
|
+
console.log('');
|
|
151
|
+
console.log(`${spinner_1.colors.muted('These objects are used by non-ignored objects in your schema.')}`);
|
|
152
|
+
console.log(`${spinner_1.colors.muted('Either add them to .relqignore or import them with')} ${spinner_1.colors.cyan('relq pull')}`);
|
|
153
|
+
console.log('');
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (!skipPrompt && !dryRun) {
|
|
157
|
+
console.log(`${spinner_1.colors.red('⚠ WARNING: This will DROP data from your database!')}`);
|
|
158
|
+
console.log('');
|
|
159
|
+
const confirmed = await askConfirmation(`Drop ${analysis.objectsToDrop.length} object(s) from remote? [y/N] `);
|
|
160
|
+
if (!confirmed) {
|
|
161
|
+
console.log(`${spinner_1.colors.muted('Cancelled.')}`);
|
|
162
|
+
console.log('');
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (toPush.length === 0 && !hasObjectsToDrop) {
|
|
168
|
+
console.log(`${spinner_1.colors.green('✓')} Everything up-to-date`);
|
|
93
169
|
console.log('');
|
|
94
170
|
return;
|
|
95
171
|
}
|
|
172
|
+
console.log('');
|
|
96
173
|
console.log(`Pushing to ${spinner_1.colors.cyan((0, env_loader_1.getConnectionDescription)(connection))}`);
|
|
97
174
|
console.log('');
|
|
98
|
-
if (
|
|
99
|
-
console.log(`${spinner_1.colors.
|
|
175
|
+
if (toPush.length > 0) {
|
|
176
|
+
console.log(`${spinner_1.colors.cyan('Commits:')} ${toPush.length}`);
|
|
100
177
|
for (const commit of toPush.slice(0, 5)) {
|
|
101
178
|
console.log(` ${spinner_1.colors.yellow((0, repo_manager_1.shortHash)(commit.hash))} ${commit.message}`);
|
|
102
179
|
}
|
|
@@ -104,17 +181,25 @@ async function pushCommand(context) {
|
|
|
104
181
|
console.log(` ${spinner_1.colors.muted(`... and ${toPush.length - 5} more`)}`);
|
|
105
182
|
}
|
|
106
183
|
console.log('');
|
|
184
|
+
}
|
|
185
|
+
if (dryRun) {
|
|
186
|
+
console.log(`${spinner_1.colors.yellow('Dry run')} - no changes applied`);
|
|
187
|
+
console.log('');
|
|
188
|
+
console.log(`${spinner_1.colors.muted('Use')} ${spinner_1.colors.cyan('relq push --apply')} ${spinner_1.colors.muted('to execute.')}`);
|
|
189
|
+
console.log('');
|
|
107
190
|
return;
|
|
108
191
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
192
|
+
if (toPush.length > 0) {
|
|
193
|
+
const commitsToProcess = [...toPush].reverse();
|
|
194
|
+
spinner.start(`Pushing ${toPush.length} commit(s)...`);
|
|
195
|
+
for (const commit of commitsToProcess) {
|
|
196
|
+
await (0, repo_manager_1.pushCommit)(connection, commit);
|
|
197
|
+
}
|
|
198
|
+
spinner.succeed(`Pushed ${toPush.length} commit(s)`);
|
|
113
199
|
}
|
|
114
|
-
spinner.succeed(`Pushed ${toPush.length} commit(s)`);
|
|
115
200
|
if (applySQL) {
|
|
116
201
|
spinner.start('Applying SQL changes...');
|
|
117
|
-
const pg = await Promise.resolve().then(() => __importStar(require("
|
|
202
|
+
const pg = await Promise.resolve().then(() => __importStar(require("../../addon/pg/index.cjs")));
|
|
118
203
|
const client = new pg.Client({
|
|
119
204
|
host: connection.host,
|
|
120
205
|
port: connection.port,
|
|
@@ -124,18 +209,42 @@ async function pushCommand(context) {
|
|
|
124
209
|
});
|
|
125
210
|
try {
|
|
126
211
|
await client.connect();
|
|
212
|
+
await client.query('BEGIN');
|
|
127
213
|
let sqlExecuted = 0;
|
|
214
|
+
let statementsRun = 0;
|
|
215
|
+
if (hasObjectsToDrop && force) {
|
|
216
|
+
for (const obj of analysis.objectsToDrop) {
|
|
217
|
+
const dropSQL = generateDropSQL(obj);
|
|
218
|
+
if (dropSQL) {
|
|
219
|
+
await client.query(dropSQL);
|
|
220
|
+
statementsRun++;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
const commitsToProcess = [...toPush].reverse();
|
|
128
225
|
for (const commit of commitsToProcess) {
|
|
129
226
|
const commitPath = path.join(projectRoot, '.relq', 'commits', `${commit.hash}.json`);
|
|
130
227
|
if (fs.existsSync(commitPath)) {
|
|
131
228
|
const enhancedCommit = JSON.parse(fs.readFileSync(commitPath, 'utf-8'));
|
|
132
|
-
if (enhancedCommit.sql) {
|
|
229
|
+
if (enhancedCommit.sql && enhancedCommit.sql.trim()) {
|
|
133
230
|
await client.query(enhancedCommit.sql);
|
|
134
231
|
sqlExecuted++;
|
|
232
|
+
statementsRun += enhancedCommit.sql.split(';').filter(s => s.trim()).length;
|
|
135
233
|
}
|
|
136
234
|
}
|
|
137
235
|
}
|
|
138
|
-
|
|
236
|
+
await client.query('COMMIT');
|
|
237
|
+
spinner.succeed(`Applied ${statementsRun} statement(s) from ${sqlExecuted} commit(s)`);
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
try {
|
|
241
|
+
await client.query('ROLLBACK');
|
|
242
|
+
spinner.fail('SQL execution failed - rolled back');
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
spinner.fail('SQL execution failed');
|
|
246
|
+
}
|
|
247
|
+
throw error;
|
|
139
248
|
}
|
|
140
249
|
finally {
|
|
141
250
|
await client.end();
|
|
@@ -144,22 +253,129 @@ async function pushCommand(context) {
|
|
|
144
253
|
const oldHash = remoteHead ? (0, repo_manager_1.shortHash)(remoteHead) : '(none)';
|
|
145
254
|
const newHash = (0, repo_manager_1.shortHash)(localHead);
|
|
146
255
|
console.log(` ${oldHash}..${newHash} ${spinner_1.colors.muted('main -> main')}`);
|
|
147
|
-
if (
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
return false;
|
|
154
|
-
})) {
|
|
256
|
+
if (hasObjectsToDrop && force && applySQL) {
|
|
257
|
+
console.log('');
|
|
258
|
+
console.log(`${spinner_1.colors.yellow('⚠')} Dropped ${analysis.objectsToDrop.length} object(s) from remote`);
|
|
259
|
+
}
|
|
260
|
+
if (!applySQL) {
|
|
155
261
|
console.log('');
|
|
156
|
-
console.log(`${spinner_1.colors.muted('Use')} ${spinner_1.colors.cyan('relq push --apply')} ${spinner_1.colors.muted('to
|
|
262
|
+
console.log(`${spinner_1.colors.muted('Use')} ${spinner_1.colors.cyan('relq push --apply')} ${spinner_1.colors.muted('to execute SQL.')}`);
|
|
157
263
|
}
|
|
264
|
+
console.log('');
|
|
158
265
|
}
|
|
159
266
|
catch (error) {
|
|
160
267
|
spinner.fail('Push failed');
|
|
161
268
|
console.error(spinner_1.colors.red(`Error: ${error instanceof Error ? error.message : error}`));
|
|
162
269
|
process.exit(1);
|
|
163
270
|
}
|
|
164
|
-
console.log('');
|
|
165
271
|
}
|
|
272
|
+
function analyzeSync(local, remote, patterns, options) {
|
|
273
|
+
const objectsToDrop = [];
|
|
274
|
+
const schemaDrift = [];
|
|
275
|
+
const localTables = new Set(local.tables.map(t => t.name));
|
|
276
|
+
const localEnums = new Set(local.enums.map(e => e.name));
|
|
277
|
+
const localDomains = new Set(local.domains.map(d => d.name));
|
|
278
|
+
const localSequences = new Set(local.sequences.map(s => s.name));
|
|
279
|
+
const localFunctions = new Set((local.functions || []).map(f => f.name));
|
|
280
|
+
const localViews = new Set((local.views || []).map(v => v.name));
|
|
281
|
+
const localMViews = new Set((local.materializedViews || []).map(v => v.name));
|
|
282
|
+
for (const table of remote.tables) {
|
|
283
|
+
if ((0, relqignore_1.isTableIgnored)(table.name, patterns).ignored)
|
|
284
|
+
continue;
|
|
285
|
+
if (!localTables.has(table.name)) {
|
|
286
|
+
objectsToDrop.push({ type: 'TABLE', name: table.name });
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
for (const enumType of remote.enums) {
|
|
290
|
+
if ((0, relqignore_1.isEnumIgnored)(enumType.name, patterns).ignored)
|
|
291
|
+
continue;
|
|
292
|
+
if (!localEnums.has(enumType.name)) {
|
|
293
|
+
objectsToDrop.push({ type: 'ENUM', name: enumType.name });
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
for (const domain of remote.domains) {
|
|
297
|
+
if ((0, relqignore_1.isDomainIgnored)(domain.name, patterns).ignored)
|
|
298
|
+
continue;
|
|
299
|
+
if (!localDomains.has(domain.name)) {
|
|
300
|
+
objectsToDrop.push({ type: 'DOMAIN', name: domain.name });
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
if (options.includeFunctions) {
|
|
304
|
+
for (const func of remote.functions || []) {
|
|
305
|
+
if ((0, relqignore_1.isFunctionIgnored)(func.name, patterns).ignored)
|
|
306
|
+
continue;
|
|
307
|
+
if (!localFunctions.has(func.name)) {
|
|
308
|
+
objectsToDrop.push({ type: 'FUNCTION', name: func.name });
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return {
|
|
313
|
+
toPush: [],
|
|
314
|
+
objectsToDrop,
|
|
315
|
+
schemaDrift,
|
|
316
|
+
remoteAhead: false,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
function checkDropDependencies(objectsToDrop, remoteDb, patterns) {
|
|
320
|
+
const errors = [];
|
|
321
|
+
const droppingTables = new Set(objectsToDrop.filter(o => o.type === 'TABLE').map(o => o.name));
|
|
322
|
+
const droppingEnums = new Set(objectsToDrop.filter(o => o.type === 'ENUM').map(o => o.name));
|
|
323
|
+
const droppingDomains = new Set(objectsToDrop.filter(o => o.type === 'DOMAIN').map(o => o.name));
|
|
324
|
+
const droppingSequences = new Set(objectsToDrop.filter(o => o.type === 'SEQUENCE').map(o => o.name));
|
|
325
|
+
for (const table of remoteDb.tables) {
|
|
326
|
+
if (droppingTables.has(table.name))
|
|
327
|
+
continue;
|
|
328
|
+
if ((0, relqignore_1.isTableIgnored)(table.name, patterns).ignored)
|
|
329
|
+
continue;
|
|
330
|
+
for (const col of table.columns) {
|
|
331
|
+
if ((0, relqignore_1.isColumnIgnored)(table.name, col.name, patterns).ignored)
|
|
332
|
+
continue;
|
|
333
|
+
const colType = (col.dataType || '').toLowerCase();
|
|
334
|
+
for (const enumName of droppingEnums) {
|
|
335
|
+
if (colType.includes(enumName.toLowerCase())) {
|
|
336
|
+
errors.push(`Cannot drop ENUM "${enumName}" - used by column "${table.name}.${col.name}"`);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
for (const domainName of droppingDomains) {
|
|
340
|
+
if (colType.includes(domainName.toLowerCase())) {
|
|
341
|
+
errors.push(`Cannot drop DOMAIN "${domainName}" - used by column "${table.name}.${col.name}"`);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return errors;
|
|
347
|
+
}
|
|
348
|
+
function generateDropSQL(obj) {
|
|
349
|
+
switch (obj.type) {
|
|
350
|
+
case 'TABLE':
|
|
351
|
+
return `DROP TABLE IF EXISTS "${obj.name}" CASCADE;`;
|
|
352
|
+
case 'ENUM':
|
|
353
|
+
return `DROP TYPE IF EXISTS "${obj.name}" CASCADE;`;
|
|
354
|
+
case 'DOMAIN':
|
|
355
|
+
return `DROP DOMAIN IF EXISTS "${obj.name}" CASCADE;`;
|
|
356
|
+
case 'FUNCTION':
|
|
357
|
+
return `DROP FUNCTION IF EXISTS "${obj.name}" CASCADE;`;
|
|
358
|
+
case 'VIEW':
|
|
359
|
+
return `DROP VIEW IF EXISTS "${obj.name}" CASCADE;`;
|
|
360
|
+
case 'MATERIALIZED_VIEW':
|
|
361
|
+
return `DROP MATERIALIZED VIEW IF EXISTS "${obj.name}" CASCADE;`;
|
|
362
|
+
case 'SEQUENCE':
|
|
363
|
+
return `DROP SEQUENCE IF EXISTS "${obj.name}" CASCADE;`;
|
|
364
|
+
default:
|
|
365
|
+
return '';
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
function askConfirmation(question) {
|
|
369
|
+
const rl = readline.createInterface({
|
|
370
|
+
input: process.stdin,
|
|
371
|
+
output: process.stdout,
|
|
372
|
+
});
|
|
373
|
+
return new Promise((resolve) => {
|
|
374
|
+
rl.question(question, (answer) => {
|
|
375
|
+
rl.close();
|
|
376
|
+
const a = answer.trim().toLowerCase();
|
|
377
|
+
resolve(a === 'y' || a === 'yes');
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
exports.default = pushCommand;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.remoteCommand = remoteCommand;
|
|
4
|
+
async function remoteCommand(context) {
|
|
5
|
+
console.log('');
|
|
6
|
+
console.log('⚠️ Remote tracking is coming soon.');
|
|
7
|
+
console.log('');
|
|
8
|
+
console.log('This will allow you to:');
|
|
9
|
+
console.log(' • Track multiple remote databases');
|
|
10
|
+
console.log(' • Push/pull to different environments');
|
|
11
|
+
console.log(' • Switch between staging/production');
|
|
12
|
+
console.log('');
|
|
13
|
+
console.log('For now, configure your remote in relq.config.ts');
|
|
14
|
+
console.log('');
|
|
15
|
+
}
|
|
16
|
+
exports.default = remoteCommand;
|