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
@@ -1,9 +1,12 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
+ import * as readline from 'readline';
3
4
  import { requireValidConfig } from "../utils/config-loader.js";
4
5
  import { getConnectionDescription } from "../utils/env-loader.js";
5
6
  import { colors, createSpinner } from "../utils/spinner.js";
6
- import { isInitialized, getHead, shortHash, fetchRemoteCommits, pushCommit, ensureRemoteTable, getAllCommits, } from "../utils/repo-manager.js";
7
+ import { fastIntrospectDatabase } from "../utils/fast-introspect.js";
8
+ import { loadRelqignore, isTableIgnored, isColumnIgnored, isEnumIgnored, isDomainIgnored, isFunctionIgnored, } from "../utils/relqignore.js";
9
+ import { isInitialized, getHead, shortHash, fetchRemoteCommits, pushCommit, ensureRemoteTable, getAllCommits, loadSnapshot, } from "../utils/repo-manager.js";
7
10
  export async function pushCommand(context) {
8
11
  const { config, flags } = context;
9
12
  if (!config) {
@@ -16,6 +19,11 @@ export async function pushCommand(context) {
16
19
  const force = flags['force'] === true;
17
20
  const dryRun = flags['dry-run'] === true;
18
21
  const applySQL = flags['apply'] === true;
22
+ const noVerify = flags['no-verify'] === true;
23
+ const skipPrompt = flags['yes'] === true || flags['y'] === true;
24
+ const includeFunctions = config.includeFunctions ?? false;
25
+ const includeTriggers = config.includeTriggers ?? false;
26
+ const includeViews = config.includeViews ?? false;
19
27
  console.log('');
20
28
  if (!isInitialized(projectRoot)) {
21
29
  console.log(`${colors.red('fatal:')} not a relq repository`);
@@ -35,32 +43,101 @@ export async function pushCommand(context) {
35
43
  spinner.start('Connecting to remote...');
36
44
  await ensureRemoteTable(connection);
37
45
  spinner.succeed(`Connected to ${colors.cyan(getConnectionDescription(connection))}`);
38
- spinner.start('Checking remote...');
46
+ spinner.start('Checking remote commits...');
39
47
  const remoteCommits = await fetchRemoteCommits(connection, 100);
40
48
  const remoteHead = remoteCommits.length > 0 ? remoteCommits[0].hash : null;
41
- spinner.stop();
42
49
  const localCommits = getAllCommits(projectRoot);
43
50
  const remoteHashes = new Set(remoteCommits.map(c => c.hash));
44
51
  const localHashes = new Set(localCommits.map(c => c.hash));
45
52
  const toPush = localCommits.filter(c => !remoteHashes.has(c.hash));
46
- if (toPush.length === 0) {
47
- console.log(`${colors.green('✓')} Everything up-to-date`);
53
+ const remoteMissing = remoteCommits.filter(c => !localHashes.has(c.hash));
54
+ spinner.succeed('Checked remote commits');
55
+ spinner.start('Introspecting remote database...');
56
+ const remoteDb = await fastIntrospectDatabase(connection, undefined, {
57
+ includeFunctions,
58
+ includeTriggers,
59
+ });
60
+ spinner.succeed(`Found ${remoteDb.tables.length} tables in remote`);
61
+ const localSnapshot = loadSnapshot(projectRoot);
62
+ if (!localSnapshot) {
63
+ console.log(`${colors.red('error:')} No local snapshot found`);
64
+ console.log(`${colors.muted('Run')} ${colors.cyan('relq pull')} ${colors.muted('or')} ${colors.cyan('relq import')} ${colors.muted('first.')}`);
65
+ return;
66
+ }
67
+ const ignorePatterns = loadRelqignore(projectRoot);
68
+ const analysis = analyzeSync(localSnapshot, remoteDb, ignorePatterns, { includeFunctions, includeTriggers, includeViews });
69
+ const hasRemoteAhead = remoteMissing.length > 0;
70
+ const hasObjectsToDrop = analysis.objectsToDrop.length > 0;
71
+ const hasSchemaDrift = analysis.schemaDrift.length > 0;
72
+ if (hasRemoteAhead && !force) {
73
+ console.log('');
74
+ console.log(`${colors.red('error:')} Remote has ${remoteMissing.length} commit(s) you don't have locally`);
75
+ console.log('');
76
+ for (const commit of remoteMissing.slice(0, 3)) {
77
+ console.log(` ${colors.yellow(shortHash(commit.hash))} ${commit.message}`);
78
+ }
79
+ if (remoteMissing.length > 3) {
80
+ console.log(` ${colors.muted(`... and ${remoteMissing.length - 3} more`)}`);
81
+ }
82
+ console.log('');
83
+ console.log(`${colors.yellow('hint:')} Run ${colors.cyan('relq pull')} to integrate remote changes.`);
84
+ console.log(`${colors.yellow('hint:')} Use ${colors.cyan('relq push --force')} to override (may cause data loss).`);
48
85
  console.log('');
49
86
  return;
50
87
  }
51
- const remoteMissing = remoteCommits.filter(c => !localHashes.has(c.hash));
52
- if (remoteMissing.length > 0 && !force) {
53
- console.log(`${colors.red('error:')} failed to push`);
88
+ if (hasObjectsToDrop) {
89
+ console.log('');
90
+ console.log(`${colors.yellow('')} Remote has ${analysis.objectsToDrop.length} object(s) not in your local schema:`);
91
+ console.log('');
92
+ for (const obj of analysis.objectsToDrop.slice(0, 10)) {
93
+ console.log(` ${colors.red('DROP')} ${obj.type.toLowerCase()}: ${obj.name}`);
94
+ }
95
+ if (analysis.objectsToDrop.length > 10) {
96
+ console.log(` ${colors.muted(`... and ${analysis.objectsToDrop.length - 10} more`)}`);
97
+ }
54
98
  console.log('');
55
- console.log(`${colors.yellow('hint:')} Updates were rejected because the remote contains work that you do not have locally.`);
56
- console.log(`${colors.yellow('hint:')} Run ${colors.cyan('relq pull')} to integrate the remote changes before pushing.`);
99
+ if (!force) {
100
+ console.log(`${colors.red('error:')} Cannot push - remote has objects not in your history`);
101
+ console.log('');
102
+ console.log(`${colors.yellow('hint:')} Run ${colors.cyan('relq pull')} first to sync your local schema.`);
103
+ console.log(`${colors.yellow('hint:')} Use ${colors.cyan('relq push --force')} to DROP these objects from remote.`);
104
+ console.log('');
105
+ return;
106
+ }
107
+ const dependencyErrors = checkDropDependencies(analysis.objectsToDrop, remoteDb, ignorePatterns);
108
+ if (dependencyErrors.length > 0) {
109
+ console.log(`${colors.red('error:')} Cannot drop objects due to dependencies:`);
110
+ console.log('');
111
+ for (const err of dependencyErrors) {
112
+ console.log(` ${colors.red('✗')} ${err}`);
113
+ }
114
+ console.log('');
115
+ console.log(`${colors.muted('These objects are used by non-ignored objects in your schema.')}`);
116
+ console.log(`${colors.muted('Either add them to .relqignore or import them with')} ${colors.cyan('relq pull')}`);
117
+ console.log('');
118
+ return;
119
+ }
120
+ if (!skipPrompt && !dryRun) {
121
+ console.log(`${colors.red('⚠ WARNING: This will DROP data from your database!')}`);
122
+ console.log('');
123
+ const confirmed = await askConfirmation(`Drop ${analysis.objectsToDrop.length} object(s) from remote? [y/N] `);
124
+ if (!confirmed) {
125
+ console.log(`${colors.muted('Cancelled.')}`);
126
+ console.log('');
127
+ return;
128
+ }
129
+ }
130
+ }
131
+ if (toPush.length === 0 && !hasObjectsToDrop) {
132
+ console.log(`${colors.green('✓')} Everything up-to-date`);
57
133
  console.log('');
58
134
  return;
59
135
  }
136
+ console.log('');
60
137
  console.log(`Pushing to ${colors.cyan(getConnectionDescription(connection))}`);
61
138
  console.log('');
62
- if (dryRun) {
63
- console.log(`${colors.yellow('Would push')} ${toPush.length} commit(s):`);
139
+ if (toPush.length > 0) {
140
+ console.log(`${colors.cyan('Commits:')} ${toPush.length}`);
64
141
  for (const commit of toPush.slice(0, 5)) {
65
142
  console.log(` ${colors.yellow(shortHash(commit.hash))} ${commit.message}`);
66
143
  }
@@ -68,17 +145,25 @@ export async function pushCommand(context) {
68
145
  console.log(` ${colors.muted(`... and ${toPush.length - 5} more`)}`);
69
146
  }
70
147
  console.log('');
148
+ }
149
+ if (dryRun) {
150
+ console.log(`${colors.yellow('Dry run')} - no changes applied`);
151
+ console.log('');
152
+ console.log(`${colors.muted('Use')} ${colors.cyan('relq push --apply')} ${colors.muted('to execute.')}`);
153
+ console.log('');
71
154
  return;
72
155
  }
73
- const commitsToProcess = [...toPush].reverse();
74
- spinner.start(`Pushing ${toPush.length} commit(s)...`);
75
- for (const commit of commitsToProcess) {
76
- await pushCommit(connection, commit);
156
+ if (toPush.length > 0) {
157
+ const commitsToProcess = [...toPush].reverse();
158
+ spinner.start(`Pushing ${toPush.length} commit(s)...`);
159
+ for (const commit of commitsToProcess) {
160
+ await pushCommit(connection, commit);
161
+ }
162
+ spinner.succeed(`Pushed ${toPush.length} commit(s)`);
77
163
  }
78
- spinner.succeed(`Pushed ${toPush.length} commit(s)`);
79
164
  if (applySQL) {
80
165
  spinner.start('Applying SQL changes...');
81
- const pg = await import("../../addon/pg.js");
166
+ const pg = await import("../../addon/pg/index.js");
82
167
  const client = new pg.Client({
83
168
  host: connection.host,
84
169
  port: connection.port,
@@ -88,18 +173,42 @@ export async function pushCommand(context) {
88
173
  });
89
174
  try {
90
175
  await client.connect();
176
+ await client.query('BEGIN');
91
177
  let sqlExecuted = 0;
178
+ let statementsRun = 0;
179
+ if (hasObjectsToDrop && force) {
180
+ for (const obj of analysis.objectsToDrop) {
181
+ const dropSQL = generateDropSQL(obj);
182
+ if (dropSQL) {
183
+ await client.query(dropSQL);
184
+ statementsRun++;
185
+ }
186
+ }
187
+ }
188
+ const commitsToProcess = [...toPush].reverse();
92
189
  for (const commit of commitsToProcess) {
93
190
  const commitPath = path.join(projectRoot, '.relq', 'commits', `${commit.hash}.json`);
94
191
  if (fs.existsSync(commitPath)) {
95
192
  const enhancedCommit = JSON.parse(fs.readFileSync(commitPath, 'utf-8'));
96
- if (enhancedCommit.sql) {
193
+ if (enhancedCommit.sql && enhancedCommit.sql.trim()) {
97
194
  await client.query(enhancedCommit.sql);
98
195
  sqlExecuted++;
196
+ statementsRun += enhancedCommit.sql.split(';').filter(s => s.trim()).length;
99
197
  }
100
198
  }
101
199
  }
102
- spinner.succeed(`Applied SQL from ${sqlExecuted} commit(s)`);
200
+ await client.query('COMMIT');
201
+ spinner.succeed(`Applied ${statementsRun} statement(s) from ${sqlExecuted} commit(s)`);
202
+ }
203
+ catch (error) {
204
+ try {
205
+ await client.query('ROLLBACK');
206
+ spinner.fail('SQL execution failed - rolled back');
207
+ }
208
+ catch {
209
+ spinner.fail('SQL execution failed');
210
+ }
211
+ throw error;
103
212
  }
104
213
  finally {
105
214
  await client.end();
@@ -108,22 +217,129 @@ export async function pushCommand(context) {
108
217
  const oldHash = remoteHead ? shortHash(remoteHead) : '(none)';
109
218
  const newHash = shortHash(localHead);
110
219
  console.log(` ${oldHash}..${newHash} ${colors.muted('main -> main')}`);
111
- if (!applySQL && toPush.some(c => {
112
- const commitPath = path.join(projectRoot, '.relq', 'commits', `${c.hash}.json`);
113
- if (fs.existsSync(commitPath)) {
114
- const ec = JSON.parse(fs.readFileSync(commitPath, 'utf-8'));
115
- return ec.sql && ec.sql.trim().length > 0;
116
- }
117
- return false;
118
- })) {
220
+ if (hasObjectsToDrop && force && applySQL) {
221
+ console.log('');
222
+ console.log(`${colors.yellow('⚠')} Dropped ${analysis.objectsToDrop.length} object(s) from remote`);
223
+ }
224
+ if (!applySQL) {
119
225
  console.log('');
120
- console.log(`${colors.muted('Use')} ${colors.cyan('relq push --apply')} ${colors.muted('to also execute SQL against database.')}`);
226
+ console.log(`${colors.muted('Use')} ${colors.cyan('relq push --apply')} ${colors.muted('to execute SQL.')}`);
121
227
  }
228
+ console.log('');
122
229
  }
123
230
  catch (error) {
124
231
  spinner.fail('Push failed');
125
232
  console.error(colors.red(`Error: ${error instanceof Error ? error.message : error}`));
126
233
  process.exit(1);
127
234
  }
128
- console.log('');
129
235
  }
236
+ function analyzeSync(local, remote, patterns, options) {
237
+ const objectsToDrop = [];
238
+ const schemaDrift = [];
239
+ const localTables = new Set(local.tables.map(t => t.name));
240
+ const localEnums = new Set(local.enums.map(e => e.name));
241
+ const localDomains = new Set(local.domains.map(d => d.name));
242
+ const localSequences = new Set(local.sequences.map(s => s.name));
243
+ const localFunctions = new Set((local.functions || []).map(f => f.name));
244
+ const localViews = new Set((local.views || []).map(v => v.name));
245
+ const localMViews = new Set((local.materializedViews || []).map(v => v.name));
246
+ for (const table of remote.tables) {
247
+ if (isTableIgnored(table.name, patterns).ignored)
248
+ continue;
249
+ if (!localTables.has(table.name)) {
250
+ objectsToDrop.push({ type: 'TABLE', name: table.name });
251
+ }
252
+ }
253
+ for (const enumType of remote.enums) {
254
+ if (isEnumIgnored(enumType.name, patterns).ignored)
255
+ continue;
256
+ if (!localEnums.has(enumType.name)) {
257
+ objectsToDrop.push({ type: 'ENUM', name: enumType.name });
258
+ }
259
+ }
260
+ for (const domain of remote.domains) {
261
+ if (isDomainIgnored(domain.name, patterns).ignored)
262
+ continue;
263
+ if (!localDomains.has(domain.name)) {
264
+ objectsToDrop.push({ type: 'DOMAIN', name: domain.name });
265
+ }
266
+ }
267
+ if (options.includeFunctions) {
268
+ for (const func of remote.functions || []) {
269
+ if (isFunctionIgnored(func.name, patterns).ignored)
270
+ continue;
271
+ if (!localFunctions.has(func.name)) {
272
+ objectsToDrop.push({ type: 'FUNCTION', name: func.name });
273
+ }
274
+ }
275
+ }
276
+ return {
277
+ toPush: [],
278
+ objectsToDrop,
279
+ schemaDrift,
280
+ remoteAhead: false,
281
+ };
282
+ }
283
+ function checkDropDependencies(objectsToDrop, remoteDb, patterns) {
284
+ const errors = [];
285
+ const droppingTables = new Set(objectsToDrop.filter(o => o.type === 'TABLE').map(o => o.name));
286
+ const droppingEnums = new Set(objectsToDrop.filter(o => o.type === 'ENUM').map(o => o.name));
287
+ const droppingDomains = new Set(objectsToDrop.filter(o => o.type === 'DOMAIN').map(o => o.name));
288
+ const droppingSequences = new Set(objectsToDrop.filter(o => o.type === 'SEQUENCE').map(o => o.name));
289
+ for (const table of remoteDb.tables) {
290
+ if (droppingTables.has(table.name))
291
+ continue;
292
+ if (isTableIgnored(table.name, patterns).ignored)
293
+ continue;
294
+ for (const col of table.columns) {
295
+ if (isColumnIgnored(table.name, col.name, patterns).ignored)
296
+ continue;
297
+ const colType = (col.dataType || '').toLowerCase();
298
+ for (const enumName of droppingEnums) {
299
+ if (colType.includes(enumName.toLowerCase())) {
300
+ errors.push(`Cannot drop ENUM "${enumName}" - used by column "${table.name}.${col.name}"`);
301
+ }
302
+ }
303
+ for (const domainName of droppingDomains) {
304
+ if (colType.includes(domainName.toLowerCase())) {
305
+ errors.push(`Cannot drop DOMAIN "${domainName}" - used by column "${table.name}.${col.name}"`);
306
+ }
307
+ }
308
+ }
309
+ }
310
+ return errors;
311
+ }
312
+ function generateDropSQL(obj) {
313
+ switch (obj.type) {
314
+ case 'TABLE':
315
+ return `DROP TABLE IF EXISTS "${obj.name}" CASCADE;`;
316
+ case 'ENUM':
317
+ return `DROP TYPE IF EXISTS "${obj.name}" CASCADE;`;
318
+ case 'DOMAIN':
319
+ return `DROP DOMAIN IF EXISTS "${obj.name}" CASCADE;`;
320
+ case 'FUNCTION':
321
+ return `DROP FUNCTION IF EXISTS "${obj.name}" CASCADE;`;
322
+ case 'VIEW':
323
+ return `DROP VIEW IF EXISTS "${obj.name}" CASCADE;`;
324
+ case 'MATERIALIZED_VIEW':
325
+ return `DROP MATERIALIZED VIEW IF EXISTS "${obj.name}" CASCADE;`;
326
+ case 'SEQUENCE':
327
+ return `DROP SEQUENCE IF EXISTS "${obj.name}" CASCADE;`;
328
+ default:
329
+ return '';
330
+ }
331
+ }
332
+ function askConfirmation(question) {
333
+ const rl = readline.createInterface({
334
+ input: process.stdin,
335
+ output: process.stdout,
336
+ });
337
+ return new Promise((resolve) => {
338
+ rl.question(question, (answer) => {
339
+ rl.close();
340
+ const a = answer.trim().toLowerCase();
341
+ resolve(a === 'y' || a === 'yes');
342
+ });
343
+ });
344
+ }
345
+ export default pushCommand;
@@ -0,0 +1,13 @@
1
+ export async function remoteCommand(context) {
2
+ console.log('');
3
+ console.log('⚠️ Remote tracking is coming soon.');
4
+ console.log('');
5
+ console.log('This will allow you to:');
6
+ console.log(' • Track multiple remote databases');
7
+ console.log(' • Push/pull to different environments');
8
+ console.log(' • Switch between staging/production');
9
+ console.log('');
10
+ console.log('For now, configure your remote in relq.config.ts');
11
+ console.log('');
12
+ }
13
+ export default remoteCommand;
@@ -0,0 +1,133 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import * as readline from 'readline';
4
+ import { colors, createSpinner } from "../utils/spinner.js";
5
+ import { isInitialized, getHead, setHead, loadCommit, saveSnapshot, shortHash, getAllCommits, } from "../utils/repo-manager.js";
6
+ export async function resetCommand(context) {
7
+ const { flags, args } = context;
8
+ const projectRoot = process.cwd();
9
+ console.log('');
10
+ if (!isInitialized(projectRoot)) {
11
+ console.log(`${colors.red('fatal:')} not a relq repository`);
12
+ return;
13
+ }
14
+ const hard = flags['hard'] === true;
15
+ const soft = flags['soft'] === true;
16
+ const target = args[0];
17
+ if (!target) {
18
+ console.log(`${colors.red('error:')} Please specify a target`);
19
+ console.log('');
20
+ console.log('Usage:');
21
+ console.log(` ${colors.cyan('relq reset --hard HEAD~1')} Reset to previous commit`);
22
+ console.log(` ${colors.cyan('relq reset --hard <hash>')} Reset to specific commit`);
23
+ console.log('');
24
+ return;
25
+ }
26
+ if (!hard && !soft) {
27
+ console.log(`${colors.red('error:')} Please specify --hard or --soft`);
28
+ console.log('');
29
+ console.log(` ${colors.cyan('--hard')} Discard all changes (DANGEROUS)`);
30
+ console.log(` ${colors.cyan('--soft')} Keep changes unstaged`);
31
+ console.log('');
32
+ return;
33
+ }
34
+ const currentHead = getHead(projectRoot);
35
+ if (!currentHead) {
36
+ console.log(`${colors.red('error:')} No commits yet`);
37
+ return;
38
+ }
39
+ const allCommits = getAllCommits(projectRoot);
40
+ let targetHash = null;
41
+ if (target.startsWith('HEAD~')) {
42
+ const n = parseInt(target.slice(5)) || 1;
43
+ const headIndex = allCommits.findIndex(c => c.hash === currentHead);
44
+ if (headIndex >= 0 && headIndex + n < allCommits.length) {
45
+ targetHash = allCommits[headIndex + n].hash;
46
+ }
47
+ }
48
+ else if (target === 'HEAD') {
49
+ targetHash = currentHead;
50
+ }
51
+ else {
52
+ const match = allCommits.find(c => c.hash.startsWith(target));
53
+ if (match) {
54
+ targetHash = match.hash;
55
+ }
56
+ }
57
+ if (!targetHash) {
58
+ console.log(`${colors.red('error:')} Cannot find commit: ${target}`);
59
+ console.log('');
60
+ console.log('Available commits:');
61
+ for (const c of allCommits.slice(0, 5)) {
62
+ console.log(` ${colors.yellow(shortHash(c.hash))} ${c.message}`);
63
+ }
64
+ return;
65
+ }
66
+ const targetCommit = loadCommit(targetHash, projectRoot);
67
+ if (!targetCommit) {
68
+ console.log(`${colors.red('error:')} Cannot load commit: ${targetHash}`);
69
+ return;
70
+ }
71
+ if (hard) {
72
+ console.log(`${colors.red('⚠ WARNING: This will discard all local changes!')}`);
73
+ console.log('');
74
+ console.log(`Reset to: ${colors.yellow(shortHash(targetHash))} ${targetCommit.message}`);
75
+ console.log('');
76
+ const rl = readline.createInterface({
77
+ input: process.stdin,
78
+ output: process.stdout
79
+ });
80
+ const confirmed = await new Promise((resolve) => {
81
+ rl.question(`Continue? [y/N] `, (answer) => {
82
+ rl.close();
83
+ resolve(answer.trim().toLowerCase() === 'y');
84
+ });
85
+ });
86
+ if (!confirmed) {
87
+ console.log(`${colors.muted('Cancelled.')}`);
88
+ console.log('');
89
+ return;
90
+ }
91
+ }
92
+ const spinner = createSpinner();
93
+ try {
94
+ spinner.start(`Resetting to ${shortHash(targetHash)}...`);
95
+ const commitPath = path.join(projectRoot, '.relq', 'commits', `${targetHash}.json`);
96
+ if (!fs.existsSync(commitPath)) {
97
+ spinner.fail('Cannot find commit data');
98
+ return;
99
+ }
100
+ const commitData = JSON.parse(fs.readFileSync(commitPath, 'utf-8'));
101
+ const targetSnapshot = commitData.snapshot;
102
+ if (!targetSnapshot) {
103
+ spinner.fail('Commit has no snapshot data');
104
+ return;
105
+ }
106
+ if (hard) {
107
+ saveSnapshot(targetSnapshot, projectRoot);
108
+ setHead(targetHash, projectRoot);
109
+ const stagedPath = path.join(projectRoot, '.relq', 'staged.json');
110
+ const unstagedPath = path.join(projectRoot, '.relq', 'unstaged.json');
111
+ if (fs.existsSync(stagedPath))
112
+ fs.unlinkSync(stagedPath);
113
+ if (fs.existsSync(unstagedPath))
114
+ fs.unlinkSync(unstagedPath);
115
+ const mergeStatePath = path.join(projectRoot, '.relq', 'MERGE_STATE');
116
+ if (fs.existsSync(mergeStatePath))
117
+ fs.unlinkSync(mergeStatePath);
118
+ }
119
+ spinner.succeed(`Reset to ${colors.yellow(shortHash(targetHash))}`);
120
+ console.log('');
121
+ console.log(`HEAD is now at ${colors.yellow(shortHash(targetHash))} ${targetCommit.message}`);
122
+ console.log('');
123
+ if (soft) {
124
+ console.log(`${colors.muted('Changes are unstaged. Use')} ${colors.cyan('relq status')} ${colors.muted('to see.')}`);
125
+ }
126
+ }
127
+ catch (error) {
128
+ spinner.fail('Reset failed');
129
+ console.error(colors.red(`Error: ${error instanceof Error ? error.message : error}`));
130
+ process.exit(1);
131
+ }
132
+ }
133
+ export default resetCommand;
@@ -0,0 +1,157 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { colors } from "../utils/spinner.js";
4
+ import { isInitialized, loadSnapshot, saveSnapshot, } from "../utils/repo-manager.js";
5
+ export async function resolveCommand(context) {
6
+ const { flags, args } = context;
7
+ const projectRoot = process.cwd();
8
+ console.log('');
9
+ if (!isInitialized(projectRoot)) {
10
+ console.log(`${colors.red('fatal:')} not a relq repository`);
11
+ return;
12
+ }
13
+ const mergeStatePath = path.join(projectRoot, '.relq', 'MERGE_STATE');
14
+ if (!fs.existsSync(mergeStatePath)) {
15
+ console.log(`${colors.green('✓')} No conflicts to resolve`);
16
+ console.log('');
17
+ return;
18
+ }
19
+ const mergeState = JSON.parse(fs.readFileSync(mergeStatePath, 'utf-8'));
20
+ if (mergeState.conflicts.length === 0) {
21
+ fs.unlinkSync(mergeStatePath);
22
+ console.log(`${colors.green('✓')} All conflicts resolved`);
23
+ console.log('');
24
+ return;
25
+ }
26
+ const allTheirs = flags['all-theirs'] === true;
27
+ const allOurs = flags['all-ours'] === true;
28
+ const theirs = flags['theirs'] === true;
29
+ const ours = flags['ours'] === true;
30
+ const objectName = args[0];
31
+ if (allTheirs) {
32
+ console.log(`Resolving ${mergeState.conflicts.length} conflict(s) with --all-theirs`);
33
+ console.log('');
34
+ saveSnapshot(mergeState.remoteSnapshot, projectRoot);
35
+ fs.unlinkSync(mergeStatePath);
36
+ console.log(`${colors.green('✓')} Applied remote versions for all conflicts`);
37
+ console.log('');
38
+ console.log(`${colors.muted('Run')} ${colors.cyan('relq commit -m "Merge remote changes"')} ${colors.muted('to complete.')}`);
39
+ console.log('');
40
+ return;
41
+ }
42
+ if (allOurs) {
43
+ console.log(`Resolving ${mergeState.conflicts.length} conflict(s) with --all-ours`);
44
+ console.log('');
45
+ fs.unlinkSync(mergeStatePath);
46
+ console.log(`${colors.green('✓')} Kept local versions for all conflicts`);
47
+ console.log('');
48
+ console.log(`${colors.muted('Run')} ${colors.cyan('relq commit -m "Keep local changes"')} ${colors.muted('to complete.')}`);
49
+ console.log('');
50
+ return;
51
+ }
52
+ if ((theirs || ours) && objectName) {
53
+ const conflict = mergeState.conflicts.find(c => c.objectName === objectName ||
54
+ `${c.parentName}.${c.objectName}` === objectName);
55
+ if (!conflict) {
56
+ console.log(`${colors.red('error:')} No conflict found for '${objectName}'`);
57
+ console.log('');
58
+ console.log('Conflicts:');
59
+ for (const c of mergeState.conflicts) {
60
+ const name = c.parentName ? `${c.parentName}.${c.objectName}` : c.objectName;
61
+ console.log(` ${c.objectType}: ${name}`);
62
+ }
63
+ console.log('');
64
+ return;
65
+ }
66
+ const localSnapshot = loadSnapshot(projectRoot);
67
+ if (!localSnapshot) {
68
+ console.log(`${colors.red('error:')} No local snapshot found`);
69
+ return;
70
+ }
71
+ if (theirs) {
72
+ applyResolution(localSnapshot, conflict, mergeState.remoteSnapshot, 'theirs');
73
+ saveSnapshot(localSnapshot, projectRoot);
74
+ }
75
+ mergeState.conflicts = mergeState.conflicts.filter(c => c !== conflict);
76
+ if (mergeState.conflicts.length === 0) {
77
+ fs.unlinkSync(mergeStatePath);
78
+ console.log(`${colors.green('✓')} All conflicts resolved`);
79
+ }
80
+ else {
81
+ fs.writeFileSync(mergeStatePath, JSON.stringify(mergeState, null, 2));
82
+ console.log(`${colors.green('✓')} Resolved: ${conflict.objectType} ${objectName}`);
83
+ console.log(`${colors.yellow(String(mergeState.conflicts.length))} conflict(s) remaining`);
84
+ }
85
+ console.log('');
86
+ return;
87
+ }
88
+ console.log(`${colors.yellow('⚠')} You have ${mergeState.conflicts.length} unresolved conflict(s):`);
89
+ console.log('');
90
+ for (const conflict of mergeState.conflicts) {
91
+ const name = conflict.parentName
92
+ ? `${conflict.parentName}.${conflict.objectName}`
93
+ : conflict.objectName;
94
+ console.log(` ${colors.red('conflict:')} ${conflict.objectType.toLowerCase()} ${colors.bold(name)}`);
95
+ console.log(` ${colors.muted(conflict.description)}`);
96
+ }
97
+ console.log('');
98
+ console.log('To resolve:');
99
+ console.log(` ${colors.cyan('relq resolve --theirs <name>')} Take remote version`);
100
+ console.log(` ${colors.cyan('relq resolve --ours <name>')} Keep local version`);
101
+ console.log(` ${colors.cyan('relq resolve --all-theirs')} Take all remote`);
102
+ console.log(` ${colors.cyan('relq resolve --all-ours')} Keep all local`);
103
+ console.log('');
104
+ }
105
+ function applyResolution(local, conflict, remote, resolution) {
106
+ if (resolution !== 'theirs')
107
+ return;
108
+ switch (conflict.objectType) {
109
+ case 'TABLE': {
110
+ const remoteTable = remote.tables.find(t => t.name === conflict.objectName);
111
+ const localIdx = local.tables.findIndex(t => t.name === conflict.objectName);
112
+ if (remoteTable) {
113
+ if (localIdx >= 0) {
114
+ local.tables[localIdx] = remoteTable;
115
+ }
116
+ else {
117
+ local.tables.push(remoteTable);
118
+ }
119
+ }
120
+ break;
121
+ }
122
+ case 'COLUMN': {
123
+ const [tableName, colName] = conflict.objectName.includes('.')
124
+ ? conflict.objectName.split('.')
125
+ : [conflict.parentName, conflict.objectName];
126
+ const remoteTable = remote.tables.find(t => t.name === tableName);
127
+ const localTable = local.tables.find(t => t.name === tableName);
128
+ if (remoteTable && localTable) {
129
+ const remoteCol = remoteTable.columns.find(c => c.name === colName);
130
+ const localIdx = localTable.columns.findIndex(c => c.name === colName);
131
+ if (remoteCol) {
132
+ if (localIdx >= 0) {
133
+ localTable.columns[localIdx] = remoteCol;
134
+ }
135
+ else {
136
+ localTable.columns.push(remoteCol);
137
+ }
138
+ }
139
+ }
140
+ break;
141
+ }
142
+ case 'ENUM': {
143
+ const remoteEnum = remote.enums.find(e => e.name === conflict.objectName);
144
+ const localIdx = local.enums.findIndex(e => e.name === conflict.objectName);
145
+ if (remoteEnum) {
146
+ if (localIdx >= 0) {
147
+ local.enums[localIdx] = remoteEnum;
148
+ }
149
+ else {
150
+ local.enums.push(remoteEnum);
151
+ }
152
+ }
153
+ break;
154
+ }
155
+ }
156
+ }
157
+ export default resolveCommand;
@@ -49,7 +49,7 @@ export async function rollbackCommand(context) {
49
49
  console.log(` Connection: ${getConnectionDescription(connection)}`);
50
50
  console.log('');
51
51
  try {
52
- const { Pool } = await import("../../addon/pg.js");
52
+ const { Pool } = await import("../../addon/pg/index.js");
53
53
  const pool = new Pool({
54
54
  host: connection.host,
55
55
  port: connection.port || 5432,