relq 1.0.0 → 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.
Files changed (159) hide show
  1. package/dist/cjs/addon/buffer/index.cjs +1881 -0
  2. package/dist/cjs/addon/pg/index.cjs +4812 -0
  3. package/dist/cjs/addon/pg-cursor/index.cjs +1451 -0
  4. package/dist/cjs/addon/pg-format/index.cjs +2270 -0
  5. package/dist/cjs/cli/commands/add.cjs +30 -1
  6. package/dist/cjs/cli/commands/branch.cjs +141 -0
  7. package/dist/cjs/cli/commands/checkout.cjs +134 -0
  8. package/dist/cjs/cli/commands/cherry-pick.cjs +283 -0
  9. package/dist/cjs/cli/commands/diff.cjs +148 -69
  10. package/dist/cjs/cli/commands/export.cjs +64 -5
  11. package/dist/cjs/cli/commands/fetch.cjs +34 -4
  12. package/dist/cjs/cli/commands/history.cjs +1 -1
  13. package/dist/cjs/cli/commands/import.cjs +283 -12
  14. package/dist/cjs/cli/commands/log.cjs +75 -0
  15. package/dist/cjs/cli/commands/merge.cjs +224 -0
  16. package/dist/cjs/cli/commands/migrate.cjs +1 -1
  17. package/dist/cjs/cli/commands/pull.cjs +123 -7
  18. package/dist/cjs/cli/commands/push.cjs +245 -29
  19. package/dist/cjs/cli/commands/remote.cjs +16 -0
  20. package/dist/cjs/cli/commands/reset.cjs +169 -0
  21. package/dist/cjs/cli/commands/resolve.cjs +193 -0
  22. package/dist/cjs/cli/commands/rollback.cjs +1 -1
  23. package/dist/cjs/cli/commands/stash.cjs +154 -0
  24. package/dist/cjs/cli/commands/status.cjs +48 -0
  25. package/dist/cjs/cli/commands/tag.cjs +147 -0
  26. package/dist/cjs/cli/index.cjs +46 -2
  27. package/dist/cjs/cli/utils/commit-manager.cjs +3 -3
  28. package/dist/cjs/cli/utils/env-loader.cjs +3 -2
  29. package/dist/cjs/cli/utils/fast-introspect.cjs +1 -1
  30. package/dist/cjs/cli/utils/project-root.cjs +56 -0
  31. package/dist/cjs/cli/utils/relqignore.cjs +296 -38
  32. package/dist/cjs/cli/utils/repo-manager.cjs +45 -3
  33. package/dist/cjs/cli/utils/schema-introspect.cjs +2 -2
  34. package/dist/cjs/cli/utils/sql-generator.cjs +1 -1
  35. package/dist/cjs/cli/utils/sql-parser.cjs +102 -13
  36. package/dist/cjs/condition/array-condition-builder.cjs +1 -1
  37. package/dist/cjs/condition/condition-collector.cjs +1 -1
  38. package/dist/cjs/condition/fulltext-condition-builder.cjs +1 -1
  39. package/dist/cjs/condition/geometric-condition-builder.cjs +1 -1
  40. package/dist/cjs/condition/jsonb-condition-builder.cjs +1 -1
  41. package/dist/cjs/condition/network-condition-builder.cjs +1 -1
  42. package/dist/cjs/condition/range-condition-builder.cjs +1 -1
  43. package/dist/cjs/copy/copy-builder.cjs +1 -1
  44. package/dist/cjs/core/query-builder.cjs +1 -1
  45. package/dist/cjs/core/relq-client.cjs +2 -2
  46. package/dist/cjs/count/count-builder.cjs +1 -1
  47. package/dist/cjs/cte/cte-builder.cjs +1 -1
  48. package/dist/cjs/delete/delete-builder.cjs +1 -1
  49. package/dist/cjs/function/create-function-builder.cjs +1 -1
  50. package/dist/cjs/functions/advanced-functions.cjs +1 -1
  51. package/dist/cjs/functions/case-builder.cjs +1 -1
  52. package/dist/cjs/functions/geometric-functions.cjs +1 -1
  53. package/dist/cjs/functions/network-functions.cjs +1 -1
  54. package/dist/cjs/functions/sql-functions.cjs +1 -1
  55. package/dist/cjs/indexing/create-index-builder.cjs +1 -1
  56. package/dist/cjs/indexing/drop-index-builder.cjs +1 -1
  57. package/dist/cjs/insert/conflict-builder.cjs +1 -1
  58. package/dist/cjs/insert/insert-builder.cjs +1 -1
  59. package/dist/cjs/maintenance/vacuum-builder.cjs +1 -1
  60. package/dist/cjs/pubsub/listen-notify-builder.cjs +1 -1
  61. package/dist/cjs/pubsub/listener-connection.cjs +2 -2
  62. package/dist/cjs/raw/raw-query-builder.cjs +1 -1
  63. package/dist/cjs/schema/schema-builder.cjs +1 -1
  64. package/dist/cjs/schema-definition/table-definition.cjs +1 -1
  65. package/dist/cjs/select/aggregate-builder.cjs +1 -1
  66. package/dist/cjs/select/select-builder.cjs +1 -1
  67. package/dist/cjs/sequence/sequence-builder.cjs +1 -1
  68. package/dist/cjs/table/alter-table-builder.cjs +1 -1
  69. package/dist/cjs/table/constraint-builder.cjs +1 -1
  70. package/dist/cjs/table/create-table-builder.cjs +1 -1
  71. package/dist/cjs/table/partition-builder.cjs +1 -1
  72. package/dist/cjs/table/truncate-builder.cjs +1 -1
  73. package/dist/cjs/transaction/transaction-builder.cjs +1 -1
  74. package/dist/cjs/trigger/create-trigger-builder.cjs +1 -1
  75. package/dist/cjs/update/array-update-builder.cjs +1 -1
  76. package/dist/cjs/update/update-builder.cjs +1 -1
  77. package/dist/cjs/utils/index.cjs +1 -1
  78. package/dist/cjs/view/create-view-builder.cjs +1 -1
  79. package/dist/cjs/window/window-builder.cjs +1 -1
  80. package/dist/esm/cli/commands/add.js +30 -1
  81. package/dist/esm/cli/commands/branch.js +105 -0
  82. package/dist/esm/cli/commands/checkout.js +98 -0
  83. package/dist/esm/cli/commands/cherry-pick.js +247 -0
  84. package/dist/esm/cli/commands/diff.js +148 -69
  85. package/dist/esm/cli/commands/export.js +64 -5
  86. package/dist/esm/cli/commands/fetch.js +35 -5
  87. package/dist/esm/cli/commands/history.js +1 -1
  88. package/dist/esm/cli/commands/import.js +283 -12
  89. package/dist/esm/cli/commands/log.js +74 -0
  90. package/dist/esm/cli/commands/merge.js +188 -0
  91. package/dist/esm/cli/commands/migrate.js +1 -1
  92. package/dist/esm/cli/commands/pull.js +124 -8
  93. package/dist/esm/cli/commands/push.js +246 -30
  94. package/dist/esm/cli/commands/remote.js +13 -0
  95. package/dist/esm/cli/commands/reset.js +133 -0
  96. package/dist/esm/cli/commands/resolve.js +157 -0
  97. package/dist/esm/cli/commands/rollback.js +1 -1
  98. package/dist/esm/cli/commands/stash.js +118 -0
  99. package/dist/esm/cli/commands/status.js +15 -0
  100. package/dist/esm/cli/commands/tag.js +111 -0
  101. package/dist/esm/cli/index.js +47 -3
  102. package/dist/esm/cli/utils/commit-manager.js +3 -3
  103. package/dist/esm/cli/utils/env-loader.js +3 -2
  104. package/dist/esm/cli/utils/fast-introspect.js +1 -1
  105. package/dist/esm/cli/utils/project-root.js +19 -0
  106. package/dist/esm/cli/utils/relqignore.js +277 -37
  107. package/dist/esm/cli/utils/repo-manager.js +41 -3
  108. package/dist/esm/cli/utils/schema-introspect.js +2 -2
  109. package/dist/esm/cli/utils/sql-generator.js +1 -1
  110. package/dist/esm/cli/utils/sql-parser.js +102 -13
  111. package/dist/esm/condition/array-condition-builder.js +1 -1
  112. package/dist/esm/condition/condition-collector.js +1 -1
  113. package/dist/esm/condition/fulltext-condition-builder.js +1 -1
  114. package/dist/esm/condition/geometric-condition-builder.js +1 -1
  115. package/dist/esm/condition/jsonb-condition-builder.js +1 -1
  116. package/dist/esm/condition/network-condition-builder.js +1 -1
  117. package/dist/esm/condition/range-condition-builder.js +1 -1
  118. package/dist/esm/copy/copy-builder.js +1 -1
  119. package/dist/esm/core/query-builder.js +1 -1
  120. package/dist/esm/core/relq-client.js +2 -2
  121. package/dist/esm/count/count-builder.js +1 -1
  122. package/dist/esm/cte/cte-builder.js +1 -1
  123. package/dist/esm/delete/delete-builder.js +1 -1
  124. package/dist/esm/function/create-function-builder.js +1 -1
  125. package/dist/esm/functions/advanced-functions.js +1 -1
  126. package/dist/esm/functions/case-builder.js +1 -1
  127. package/dist/esm/functions/geometric-functions.js +1 -1
  128. package/dist/esm/functions/network-functions.js +1 -1
  129. package/dist/esm/functions/sql-functions.js +1 -1
  130. package/dist/esm/indexing/create-index-builder.js +1 -1
  131. package/dist/esm/indexing/drop-index-builder.js +1 -1
  132. package/dist/esm/insert/conflict-builder.js +1 -1
  133. package/dist/esm/insert/insert-builder.js +1 -1
  134. package/dist/esm/maintenance/vacuum-builder.js +1 -1
  135. package/dist/esm/pubsub/listen-notify-builder.js +1 -1
  136. package/dist/esm/pubsub/listener-connection.js +2 -2
  137. package/dist/esm/raw/raw-query-builder.js +1 -1
  138. package/dist/esm/schema/schema-builder.js +1 -1
  139. package/dist/esm/schema-definition/table-definition.js +1 -1
  140. package/dist/esm/select/aggregate-builder.js +1 -1
  141. package/dist/esm/select/select-builder.js +1 -1
  142. package/dist/esm/sequence/sequence-builder.js +1 -1
  143. package/dist/esm/table/alter-table-builder.js +1 -1
  144. package/dist/esm/table/constraint-builder.js +1 -1
  145. package/dist/esm/table/create-table-builder.js +1 -1
  146. package/dist/esm/table/partition-builder.js +1 -1
  147. package/dist/esm/table/truncate-builder.js +1 -1
  148. package/dist/esm/transaction/transaction-builder.js +1 -1
  149. package/dist/esm/trigger/create-trigger-builder.js +1 -1
  150. package/dist/esm/update/array-update-builder.js +1 -1
  151. package/dist/esm/update/update-builder.js +1 -1
  152. package/dist/esm/utils/index.js +1 -1
  153. package/dist/esm/view/create-view-builder.js +1 -1
  154. package/dist/esm/window/window-builder.js +1 -1
  155. package/package.json +1 -1
  156. /package/dist/{addons/buffer.js → esm/addon/buffer/index.js} +0 -0
  157. /package/dist/{addons/pg.js → esm/addon/pg/index.js} +0 -0
  158. /package/dist/{addons/pg-cursor.js → esm/addon/pg-cursor/index.js} +0 -0
  159. /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.getIgnorePatterns)(projectRoot);
249
- const filteredTables = (0, relqignore_1.filterIgnored)(dbSchema.tables, ignorePatterns);
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: dbSchema.enums.map(e => ({
294
+ enums: filteredEnums.map(e => ({
279
295
  name: e.name,
280
296
  schema: 'public',
281
297
  values: e.values,
282
298
  })),
283
- domains: dbSchema.domains.map(d => ({
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: dbSchema.compositeTypes.map(c => ({
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: dbSchema.functions.map(f => ({
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: dbSchema.triggers.map(t => ({
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
- if (toPush.length === 0) {
83
- console.log(`${spinner_1.colors.green('✓')} Everything up-to-date`);
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
- const remoteMissing = remoteCommits.filter(c => !localHashes.has(c.hash));
88
- if (remoteMissing.length > 0 && !force) {
89
- console.log(`${spinner_1.colors.red('error:')} failed to push`);
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
- console.log(`${spinner_1.colors.yellow('hint:')} Updates were rejected because the remote contains work that you do not have locally.`);
92
- console.log(`${spinner_1.colors.yellow('hint:')} Run ${spinner_1.colors.cyan('relq pull')} to integrate the remote changes before pushing.`);
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 (dryRun) {
99
- console.log(`${spinner_1.colors.yellow('Would push')} ${toPush.length} commit(s):`);
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
- const commitsToProcess = [...toPush].reverse();
110
- spinner.start(`Pushing ${toPush.length} commit(s)...`);
111
- for (const commit of commitsToProcess) {
112
- await (0, repo_manager_1.pushCommit)(connection, commit);
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("../../../addons/pg.js")));
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
- spinner.succeed(`Applied SQL from ${sqlExecuted} commit(s)`);
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 (!applySQL && toPush.some(c => {
148
- const commitPath = path.join(projectRoot, '.relq', 'commits', `${c.hash}.json`);
149
- if (fs.existsSync(commitPath)) {
150
- const ec = JSON.parse(fs.readFileSync(commitPath, 'utf-8'));
151
- return ec.sql && ec.sql.trim().length > 0;
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 also execute SQL against database.')}`);
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;