relq 1.0.1 → 1.0.3

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 (194) 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 +430 -25
  6. package/dist/cjs/cli/commands/branch.cjs +131 -0
  7. package/dist/cjs/cli/commands/checkout.cjs +121 -0
  8. package/dist/cjs/cli/commands/cherry-pick.cjs +282 -0
  9. package/dist/cjs/cli/commands/commit.cjs +21 -29
  10. package/dist/cjs/cli/commands/diff.cjs +144 -69
  11. package/dist/cjs/cli/commands/export.cjs +70 -11
  12. package/dist/cjs/cli/commands/fetch.cjs +42 -18
  13. package/dist/cjs/cli/commands/generate.cjs +28 -54
  14. package/dist/cjs/cli/commands/history.cjs +19 -40
  15. package/dist/cjs/cli/commands/import.cjs +305 -41
  16. package/dist/cjs/cli/commands/init.cjs +69 -59
  17. package/dist/cjs/cli/commands/introspect.cjs +4 -8
  18. package/dist/cjs/cli/commands/log.cjs +84 -15
  19. package/dist/cjs/cli/commands/merge.cjs +207 -0
  20. package/dist/cjs/cli/commands/migrate.cjs +13 -26
  21. package/dist/cjs/cli/commands/pull.cjs +321 -95
  22. package/dist/cjs/cli/commands/push.cjs +228 -52
  23. package/dist/cjs/cli/commands/remote.cjs +17 -0
  24. package/dist/cjs/cli/commands/reset.cjs +148 -0
  25. package/dist/cjs/cli/commands/resolve.cjs +191 -0
  26. package/dist/cjs/cli/commands/rollback.cjs +17 -39
  27. package/dist/cjs/cli/commands/stash.cjs +152 -0
  28. package/dist/cjs/cli/commands/status.cjs +52 -9
  29. package/dist/cjs/cli/commands/sync.cjs +30 -50
  30. package/dist/cjs/cli/commands/tag.cjs +146 -0
  31. package/dist/cjs/cli/index.cjs +117 -10
  32. package/dist/cjs/cli/utils/change-tracker.cjs +107 -3
  33. package/dist/cjs/cli/utils/cli-utils.cjs +217 -0
  34. package/dist/cjs/cli/utils/commit-manager.cjs +3 -3
  35. package/dist/cjs/cli/utils/config-loader.cjs +34 -8
  36. package/dist/cjs/cli/utils/env-loader.cjs +3 -2
  37. package/dist/cjs/cli/utils/fast-introspect.cjs +110 -4
  38. package/dist/cjs/cli/utils/git-utils.cjs +42 -161
  39. package/dist/cjs/cli/utils/pool-manager.cjs +156 -0
  40. package/dist/cjs/cli/utils/project-root.cjs +107 -0
  41. package/dist/cjs/cli/utils/relqignore.cjs +297 -38
  42. package/dist/cjs/cli/utils/repo-manager.cjs +92 -3
  43. package/dist/cjs/cli/utils/schema-comparator.cjs +301 -11
  44. package/dist/cjs/cli/utils/schema-diff.cjs +202 -1
  45. package/dist/cjs/cli/utils/schema-hash.cjs +2 -1
  46. package/dist/cjs/cli/utils/schema-introspect.cjs +9 -5
  47. package/dist/cjs/cli/utils/snapshot-manager.cjs +1 -0
  48. package/dist/cjs/cli/utils/spinner.cjs +14 -106
  49. package/dist/cjs/cli/utils/sql-generator.cjs +2 -2
  50. package/dist/cjs/cli/utils/sql-parser.cjs +94 -7
  51. package/dist/cjs/cli/utils/type-generator.cjs +28 -16
  52. package/dist/cjs/condition/array-condition-builder.cjs +1 -1
  53. package/dist/cjs/condition/condition-collector.cjs +1 -1
  54. package/dist/cjs/condition/fulltext-condition-builder.cjs +1 -1
  55. package/dist/cjs/condition/geometric-condition-builder.cjs +1 -1
  56. package/dist/cjs/condition/jsonb-condition-builder.cjs +1 -1
  57. package/dist/cjs/condition/network-condition-builder.cjs +1 -1
  58. package/dist/cjs/condition/range-condition-builder.cjs +1 -1
  59. package/dist/cjs/copy/copy-builder.cjs +1 -1
  60. package/dist/cjs/core/query-builder.cjs +1 -1
  61. package/dist/cjs/core/relq-client.cjs +2 -2
  62. package/dist/cjs/count/count-builder.cjs +1 -1
  63. package/dist/cjs/cte/cte-builder.cjs +1 -1
  64. package/dist/cjs/delete/delete-builder.cjs +1 -1
  65. package/dist/cjs/function/create-function-builder.cjs +1 -1
  66. package/dist/cjs/functions/advanced-functions.cjs +1 -1
  67. package/dist/cjs/functions/case-builder.cjs +1 -1
  68. package/dist/cjs/functions/geometric-functions.cjs +1 -1
  69. package/dist/cjs/functions/network-functions.cjs +1 -1
  70. package/dist/cjs/functions/sql-functions.cjs +1 -1
  71. package/dist/cjs/indexing/create-index-builder.cjs +1 -1
  72. package/dist/cjs/indexing/drop-index-builder.cjs +1 -1
  73. package/dist/cjs/insert/conflict-builder.cjs +1 -1
  74. package/dist/cjs/insert/insert-builder.cjs +1 -1
  75. package/dist/cjs/maintenance/vacuum-builder.cjs +1 -1
  76. package/dist/cjs/pubsub/listen-notify-builder.cjs +1 -1
  77. package/dist/cjs/pubsub/listener-connection.cjs +2 -2
  78. package/dist/cjs/raw/raw-query-builder.cjs +1 -1
  79. package/dist/cjs/schema/schema-builder.cjs +1 -1
  80. package/dist/cjs/schema-definition/table-definition.cjs +1 -1
  81. package/dist/cjs/select/aggregate-builder.cjs +1 -1
  82. package/dist/cjs/select/select-builder.cjs +1 -1
  83. package/dist/cjs/sequence/sequence-builder.cjs +1 -1
  84. package/dist/cjs/table/alter-table-builder.cjs +1 -1
  85. package/dist/cjs/table/constraint-builder.cjs +1 -1
  86. package/dist/cjs/table/create-table-builder.cjs +1 -1
  87. package/dist/cjs/table/partition-builder.cjs +1 -1
  88. package/dist/cjs/table/truncate-builder.cjs +1 -1
  89. package/dist/cjs/transaction/transaction-builder.cjs +1 -1
  90. package/dist/cjs/trigger/create-trigger-builder.cjs +1 -1
  91. package/dist/cjs/update/array-update-builder.cjs +1 -1
  92. package/dist/cjs/update/update-builder.cjs +1 -1
  93. package/dist/cjs/utils/index.cjs +1 -1
  94. package/dist/cjs/view/create-view-builder.cjs +1 -1
  95. package/dist/cjs/window/window-builder.cjs +1 -1
  96. package/dist/config.d.ts +16 -25
  97. package/dist/esm/cli/commands/add.js +399 -27
  98. package/dist/esm/cli/commands/branch.js +95 -0
  99. package/dist/esm/cli/commands/checkout.js +85 -0
  100. package/dist/esm/cli/commands/cherry-pick.js +246 -0
  101. package/dist/esm/cli/commands/commit.js +22 -30
  102. package/dist/esm/cli/commands/diff.js +144 -69
  103. package/dist/esm/cli/commands/export.js +71 -12
  104. package/dist/esm/cli/commands/fetch.js +42 -18
  105. package/dist/esm/cli/commands/generate.js +28 -54
  106. package/dist/esm/cli/commands/history.js +11 -32
  107. package/dist/esm/cli/commands/import.js +306 -42
  108. package/dist/esm/cli/commands/init.js +65 -55
  109. package/dist/esm/cli/commands/introspect.js +4 -8
  110. package/dist/esm/cli/commands/log.js +78 -10
  111. package/dist/esm/cli/commands/merge.js +171 -0
  112. package/dist/esm/cli/commands/migrate.js +13 -26
  113. package/dist/esm/cli/commands/pull.js +313 -87
  114. package/dist/esm/cli/commands/push.js +223 -47
  115. package/dist/esm/cli/commands/remote.js +14 -0
  116. package/dist/esm/cli/commands/reset.js +112 -0
  117. package/dist/esm/cli/commands/resolve.js +155 -0
  118. package/dist/esm/cli/commands/rollback.js +17 -39
  119. package/dist/esm/cli/commands/stash.js +116 -0
  120. package/dist/esm/cli/commands/status.js +20 -10
  121. package/dist/esm/cli/commands/sync.js +30 -50
  122. package/dist/esm/cli/commands/tag.js +110 -0
  123. package/dist/esm/cli/index.js +118 -11
  124. package/dist/esm/cli/utils/change-tracker.js +107 -3
  125. package/dist/esm/cli/utils/cli-utils.js +169 -0
  126. package/dist/esm/cli/utils/commit-manager.js +3 -3
  127. package/dist/esm/cli/utils/config-loader.js +34 -8
  128. package/dist/esm/cli/utils/env-loader.js +3 -2
  129. package/dist/esm/cli/utils/fast-introspect.js +110 -4
  130. package/dist/esm/cli/utils/git-utils.js +2 -124
  131. package/dist/esm/cli/utils/pool-manager.js +114 -0
  132. package/dist/esm/cli/utils/project-root.js +69 -0
  133. package/dist/esm/cli/utils/relqignore.js +278 -37
  134. package/dist/esm/cli/utils/repo-manager.js +83 -3
  135. package/dist/esm/cli/utils/schema-comparator.js +301 -11
  136. package/dist/esm/cli/utils/schema-diff.js +202 -1
  137. package/dist/esm/cli/utils/schema-hash.js +2 -1
  138. package/dist/esm/cli/utils/schema-introspect.js +9 -5
  139. package/dist/esm/cli/utils/snapshot-manager.js +1 -0
  140. package/dist/esm/cli/utils/spinner.js +1 -101
  141. package/dist/esm/cli/utils/sql-generator.js +2 -2
  142. package/dist/esm/cli/utils/sql-parser.js +94 -7
  143. package/dist/esm/cli/utils/type-generator.js +28 -16
  144. package/dist/esm/condition/array-condition-builder.js +1 -1
  145. package/dist/esm/condition/condition-collector.js +1 -1
  146. package/dist/esm/condition/fulltext-condition-builder.js +1 -1
  147. package/dist/esm/condition/geometric-condition-builder.js +1 -1
  148. package/dist/esm/condition/jsonb-condition-builder.js +1 -1
  149. package/dist/esm/condition/network-condition-builder.js +1 -1
  150. package/dist/esm/condition/range-condition-builder.js +1 -1
  151. package/dist/esm/copy/copy-builder.js +1 -1
  152. package/dist/esm/core/query-builder.js +1 -1
  153. package/dist/esm/core/relq-client.js +2 -2
  154. package/dist/esm/count/count-builder.js +1 -1
  155. package/dist/esm/cte/cte-builder.js +1 -1
  156. package/dist/esm/delete/delete-builder.js +1 -1
  157. package/dist/esm/function/create-function-builder.js +1 -1
  158. package/dist/esm/functions/advanced-functions.js +1 -1
  159. package/dist/esm/functions/case-builder.js +1 -1
  160. package/dist/esm/functions/geometric-functions.js +1 -1
  161. package/dist/esm/functions/network-functions.js +1 -1
  162. package/dist/esm/functions/sql-functions.js +1 -1
  163. package/dist/esm/indexing/create-index-builder.js +1 -1
  164. package/dist/esm/indexing/drop-index-builder.js +1 -1
  165. package/dist/esm/insert/conflict-builder.js +1 -1
  166. package/dist/esm/insert/insert-builder.js +1 -1
  167. package/dist/esm/maintenance/vacuum-builder.js +1 -1
  168. package/dist/esm/pubsub/listen-notify-builder.js +1 -1
  169. package/dist/esm/pubsub/listener-connection.js +2 -2
  170. package/dist/esm/raw/raw-query-builder.js +1 -1
  171. package/dist/esm/schema/schema-builder.js +1 -1
  172. package/dist/esm/schema-definition/table-definition.js +1 -1
  173. package/dist/esm/select/aggregate-builder.js +1 -1
  174. package/dist/esm/select/select-builder.js +1 -1
  175. package/dist/esm/sequence/sequence-builder.js +1 -1
  176. package/dist/esm/table/alter-table-builder.js +1 -1
  177. package/dist/esm/table/constraint-builder.js +1 -1
  178. package/dist/esm/table/create-table-builder.js +1 -1
  179. package/dist/esm/table/partition-builder.js +1 -1
  180. package/dist/esm/table/truncate-builder.js +1 -1
  181. package/dist/esm/transaction/transaction-builder.js +1 -1
  182. package/dist/esm/trigger/create-trigger-builder.js +1 -1
  183. package/dist/esm/update/array-update-builder.js +1 -1
  184. package/dist/esm/update/update-builder.js +1 -1
  185. package/dist/esm/utils/index.js +1 -1
  186. package/dist/esm/view/create-view-builder.js +1 -1
  187. package/dist/esm/window/window-builder.js +1 -1
  188. package/dist/index.d.ts +25 -8
  189. package/dist/schema-builder.d.ts +16 -6
  190. package/package.json +1 -1
  191. /package/dist/{addons/buffer.js → esm/addon/buffer/index.js} +0 -0
  192. /package/dist/{addons/pg.js → esm/addon/pg/index.js} +0 -0
  193. /package/dist/{addons/pg-cursor.js → esm/addon/pg-cursor/index.js} +0 -0
  194. /package/dist/{addons/pg-format.js → esm/addon/pg-format/index.js} +0 -0
@@ -0,0 +1,246 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { colors, createSpinner } from "../utils/spinner.js";
4
+ import { isInitialized, loadCommit, loadParentCommit, loadSnapshot, saveSnapshot, createCommit, shortHash, resolveRef, } from "../utils/repo-manager.js";
5
+ export async function cherryPickCommand(context) {
6
+ const { config, args, flags, projectRoot } = context;
7
+ console.log('');
8
+ if (!isInitialized(projectRoot)) {
9
+ console.log(`${colors.red('fatal:')} not a relq repository`);
10
+ return;
11
+ }
12
+ const abort = flags['abort'] === true;
13
+ const cherryPickStatePath = path.join(projectRoot, '.relq', 'CHERRY_PICK_STATE');
14
+ if (abort) {
15
+ if (fs.existsSync(cherryPickStatePath)) {
16
+ fs.unlinkSync(cherryPickStatePath);
17
+ console.log('Cherry-pick aborted');
18
+ }
19
+ else {
20
+ console.log('No cherry-pick in progress.');
21
+ }
22
+ console.log('');
23
+ return;
24
+ }
25
+ if (fs.existsSync(cherryPickStatePath)) {
26
+ const state = JSON.parse(fs.readFileSync(cherryPickStatePath, 'utf-8'));
27
+ console.log(`${colors.red('error:')} Cherry-pick in progress from ${shortHash(state.fromCommit)}`);
28
+ console.log('');
29
+ console.log(`${colors.muted('Use')} ${colors.cyan('relq resolve')} ${colors.muted('to resolve conflicts')}`);
30
+ console.log(`${colors.muted('Or')} ${colors.cyan('relq cherry-pick --abort')} ${colors.muted('to cancel')}`);
31
+ console.log('');
32
+ return;
33
+ }
34
+ const ref = args[0];
35
+ if (!ref) {
36
+ console.log(`${colors.red('error:')} Please specify a commit`);
37
+ console.log('');
38
+ console.log(`Usage: ${colors.cyan('relq cherry-pick <commit>')}`);
39
+ console.log('');
40
+ return;
41
+ }
42
+ const spinner = createSpinner();
43
+ spinner.start(`Cherry-picking ${shortHash(ref)}...`);
44
+ try {
45
+ const hash = resolveRef(ref, projectRoot);
46
+ if (!hash) {
47
+ spinner.fail(`Commit not found: ${ref}`);
48
+ return;
49
+ }
50
+ const targetCommit = loadCommit(hash, projectRoot);
51
+ const parentCommit = loadParentCommit(hash, projectRoot);
52
+ if (!targetCommit) {
53
+ spinner.fail('Cannot load commit');
54
+ return;
55
+ }
56
+ if (!parentCommit) {
57
+ spinner.fail('Cannot cherry-pick first commit (no parent)');
58
+ return;
59
+ }
60
+ const diff = calculateCommitDiff(parentCommit.schema, targetCommit.schema);
61
+ const currentSnapshot = loadSnapshot(projectRoot);
62
+ if (!currentSnapshot) {
63
+ spinner.fail('No snapshot found');
64
+ return;
65
+ }
66
+ const conflicts = detectCherryPickConflicts(currentSnapshot, diff);
67
+ if (conflicts.length > 0) {
68
+ const state = {
69
+ fromCommit: hash,
70
+ originalMessage: targetCommit.message,
71
+ conflicts,
72
+ diff,
73
+ createdAt: new Date().toISOString(),
74
+ };
75
+ fs.writeFileSync(cherryPickStatePath, JSON.stringify(state, null, 2));
76
+ spinner.fail(`${conflicts.length} conflict(s) detected`);
77
+ console.log('');
78
+ for (const c of conflicts.slice(0, 5)) {
79
+ const name = c.parentName ? `${c.parentName}.${c.objectName}` : c.objectName;
80
+ console.log(` ${colors.red('conflict:')} ${c.objectType.toLowerCase()} ${name}`);
81
+ console.log(` ${colors.muted(c.description)}`);
82
+ }
83
+ if (conflicts.length > 5) {
84
+ console.log(` ${colors.muted(`... and ${conflicts.length - 5} more`)}`);
85
+ }
86
+ console.log('');
87
+ console.log(`${colors.muted('Use')} ${colors.cyan('relq resolve --theirs')} ${colors.muted('to resolve')}`);
88
+ console.log(`${colors.muted('Or')} ${colors.cyan('relq cherry-pick --abort')} ${colors.muted('to cancel')}`);
89
+ console.log('');
90
+ return;
91
+ }
92
+ const newSnapshot = applyCommitDiff(currentSnapshot, diff);
93
+ saveSnapshot(newSnapshot, projectRoot);
94
+ const author = config?.author || 'Relq CLI';
95
+ const message = `Cherry-picked ${shortHash(hash)}: ${targetCommit.message}`;
96
+ const commit = createCommit(newSnapshot, author, message, projectRoot);
97
+ spinner.succeed(`Cherry-picked ${colors.yellow(shortHash(hash))}`);
98
+ console.log(` ${colors.cyan(commit.message)}`);
99
+ console.log('');
100
+ }
101
+ catch (error) {
102
+ spinner.fail('Cherry-pick failed');
103
+ console.error(colors.red(`Error: ${error instanceof Error ? error.message : error}`));
104
+ }
105
+ }
106
+ function calculateCommitDiff(parent, target) {
107
+ const diff = {
108
+ tablesAdded: [],
109
+ tablesRemoved: [],
110
+ tablesModified: [],
111
+ enumsAdded: [],
112
+ enumsRemoved: [],
113
+ columnsAdded: [],
114
+ columnsRemoved: [],
115
+ };
116
+ const parentTables = new Map(parent.tables.map(t => [t.name, t]));
117
+ const targetTables = new Map(target.tables.map(t => [t.name, t]));
118
+ for (const [name, table] of targetTables) {
119
+ if (!parentTables.has(name)) {
120
+ diff.tablesAdded.push(table);
121
+ }
122
+ }
123
+ for (const [name] of parentTables) {
124
+ if (!targetTables.has(name)) {
125
+ diff.tablesRemoved.push(name);
126
+ }
127
+ }
128
+ for (const [name, targetTable] of targetTables) {
129
+ const parentTable = parentTables.get(name);
130
+ if (!parentTable)
131
+ continue;
132
+ const parentCols = new Map(parentTable.columns.map(c => [c.name, c]));
133
+ const targetCols = new Map(targetTable.columns.map(c => [c.name, c]));
134
+ const changes = { columnsAdded: [], columnsRemoved: [] };
135
+ for (const [colName, col] of targetCols) {
136
+ if (!parentCols.has(colName)) {
137
+ changes.columnsAdded.push(col);
138
+ }
139
+ }
140
+ for (const [colName] of parentCols) {
141
+ if (!targetCols.has(colName)) {
142
+ changes.columnsRemoved.push(colName);
143
+ }
144
+ }
145
+ if (changes.columnsAdded.length > 0 || changes.columnsRemoved.length > 0) {
146
+ diff.tablesModified.push({ name, changes });
147
+ }
148
+ }
149
+ const parentEnums = new Set(parent.enums.map(e => e.name));
150
+ const targetEnums = new Map(target.enums.map(e => [e.name, e]));
151
+ for (const [name, e] of targetEnums) {
152
+ if (!parentEnums.has(name)) {
153
+ diff.enumsAdded.push(e);
154
+ }
155
+ }
156
+ for (const name of parentEnums) {
157
+ if (!targetEnums.has(name)) {
158
+ diff.enumsRemoved.push(name);
159
+ }
160
+ }
161
+ return diff;
162
+ }
163
+ function detectCherryPickConflicts(current, diff) {
164
+ const conflicts = [];
165
+ for (const table of diff.tablesAdded) {
166
+ if (current.tables.find(t => t.name === table.name)) {
167
+ conflicts.push({
168
+ objectType: 'TABLE',
169
+ objectName: table.name,
170
+ currentValue: 'exists',
171
+ incomingValue: 'add',
172
+ description: 'Table already exists in current snapshot',
173
+ });
174
+ }
175
+ }
176
+ for (const tableName of diff.tablesRemoved) {
177
+ if (!current.tables.find(t => t.name === tableName)) {
178
+ conflicts.push({
179
+ objectType: 'TABLE',
180
+ objectName: tableName,
181
+ currentValue: 'missing',
182
+ incomingValue: 'remove',
183
+ description: 'Table does not exist in current snapshot',
184
+ });
185
+ }
186
+ }
187
+ for (const mod of diff.tablesModified) {
188
+ const currentTable = current.tables.find(t => t.name === mod.name);
189
+ if (!currentTable) {
190
+ conflicts.push({
191
+ objectType: 'TABLE',
192
+ objectName: mod.name,
193
+ currentValue: 'missing',
194
+ incomingValue: 'modify',
195
+ description: 'Table does not exist in current snapshot',
196
+ });
197
+ continue;
198
+ }
199
+ for (const col of mod.changes.columnsAdded) {
200
+ if (currentTable.columns.find(c => c.name === col.name)) {
201
+ conflicts.push({
202
+ objectType: 'COLUMN',
203
+ objectName: col.name,
204
+ parentName: mod.name,
205
+ currentValue: 'exists',
206
+ incomingValue: 'add',
207
+ description: 'Column already exists',
208
+ });
209
+ }
210
+ }
211
+ }
212
+ for (const e of diff.enumsAdded) {
213
+ if (current.enums.find(en => en.name === e.name)) {
214
+ conflicts.push({
215
+ objectType: 'ENUM',
216
+ objectName: e.name,
217
+ currentValue: 'exists',
218
+ incomingValue: 'add',
219
+ description: 'Enum already exists',
220
+ });
221
+ }
222
+ }
223
+ return conflicts;
224
+ }
225
+ function applyCommitDiff(snapshot, diff) {
226
+ const result = JSON.parse(JSON.stringify(snapshot));
227
+ for (const table of diff.tablesAdded) {
228
+ result.tables.push(table);
229
+ }
230
+ result.tables = result.tables.filter(t => !diff.tablesRemoved.includes(t.name));
231
+ for (const mod of diff.tablesModified) {
232
+ const table = result.tables.find(t => t.name === mod.name);
233
+ if (!table)
234
+ continue;
235
+ for (const col of mod.changes.columnsAdded) {
236
+ table.columns.push(col);
237
+ }
238
+ table.columns = table.columns.filter(c => !mod.changes.columnsRemoved.includes(c.name));
239
+ }
240
+ for (const e of diff.enumsAdded) {
241
+ result.enums.push(e);
242
+ }
243
+ result.enums = result.enums.filter(e => !diff.enumsRemoved.includes(e.name));
244
+ return result;
245
+ }
246
+ export default cherryPickCommand;
@@ -1,31 +1,27 @@
1
1
  import * as crypto from 'crypto';
2
- import { colors } from "../utils/spinner.js";
3
- import { isInitialized, getHead, getStagedChanges, getUnstagedChanges, shortHash, } from "../utils/repo-manager.js";
2
+ import { fatal, hint } from "../utils/cli-utils.js";
3
+ import { isInitialized, getHead, getStagedChanges, getUnstagedChanges, shortHash, hashFileContent, saveFileHash, } from "../utils/repo-manager.js";
4
+ import { loadConfig } from "../../config/config.js";
4
5
  import { sortChangesByDependency, generateCombinedSQL, } from "../utils/change-tracker.js";
5
6
  import * as fs from 'fs';
6
7
  import * as path from 'path';
7
8
  export async function commitCommand(context) {
8
- const { config, flags, args } = context;
9
- const projectRoot = process.cwd();
9
+ const { config, flags, args, projectRoot } = context;
10
10
  const author = config?.author || 'Developer <dev@example.com>';
11
11
  console.log('');
12
12
  if (!isInitialized(projectRoot)) {
13
- console.log(`${colors.red('error:')} relq not initialized`);
14
- console.log('');
15
- console.log(`${colors.muted('Run')} ${colors.cyan('relq init')} ${colors.muted('first.')}`);
16
- return;
13
+ fatal('not a relq repository (or any parent directories): .relq', "run 'relq init' to initialize");
17
14
  }
18
15
  const staged = getStagedChanges(projectRoot);
19
16
  if (staged.length === 0) {
20
- console.log(`${colors.yellow('Nothing to commit')}`);
21
- console.log('');
17
+ console.log('nothing to commit, working tree clean');
22
18
  const unstaged = getUnstagedChanges(projectRoot);
23
19
  if (unstaged.length > 0) {
24
- console.log(`${colors.muted(`${unstaged.length} unstaged change(s).`)}`);
25
- console.log(`${colors.muted('Use')} ${colors.cyan('relq add .')} ${colors.muted('to stage all changes.')}`);
20
+ console.log(`${unstaged.length} unstaged change(s).`);
21
+ hint("run 'relq add .' to stage all changes");
26
22
  }
27
23
  else {
28
- console.log(`${colors.muted('Use')} ${colors.cyan('relq add <table>')} ${colors.muted('to stage changes.')}`);
24
+ hint("run 'relq add <table>' to stage changes");
29
25
  }
30
26
  return;
31
27
  }
@@ -35,10 +31,7 @@ export async function commitCommand(context) {
35
31
  message = args.join(' ');
36
32
  }
37
33
  else {
38
- console.log(`${colors.red('error:')} commit message required`);
39
- console.log('');
40
- console.log(`${colors.muted('Usage:')} relq commit -m "message"`);
41
- return;
34
+ fatal('commit message required', "usage: relq commit -m '<message>'");
42
35
  }
43
36
  }
44
37
  const sortedChanges = sortChangesByDependency(staged);
@@ -77,7 +70,7 @@ export async function commitCommand(context) {
77
70
  fs.writeFileSync(path.join(commitsDir, `${hash}.json`), JSON.stringify(commit, null, 2), 'utf-8');
78
71
  fs.writeFileSync(path.join(projectRoot, '.relq', 'HEAD'), hash, 'utf-8');
79
72
  const workingPath = path.join(projectRoot, '.relq', 'working.json');
80
- const unstaged = getUnstagedChanges(projectRoot);
73
+ const unstaged = getUnstagedChanges(projectRoot).filter(c => c.objectType !== 'SCHEMA_FILE');
81
74
  if (unstaged.length > 0) {
82
75
  fs.writeFileSync(workingPath, JSON.stringify({
83
76
  timestamp: new Date().toISOString(),
@@ -90,20 +83,19 @@ export async function commitCommand(context) {
90
83
  fs.unlinkSync(workingPath);
91
84
  }
92
85
  }
93
- console.log(`${colors.yellow(`[${shortHash(hash)}]`)} ${message}`);
94
- console.log('');
95
- if (creates > 0) {
96
- console.log(` ${colors.green(`${creates} created`)}`);
97
- }
98
- if (alters > 0) {
99
- console.log(` ${colors.yellow(`${alters} altered`)}`);
100
- }
101
- if (drops > 0) {
102
- console.log(` ${colors.red(`${drops} dropped`)}`);
86
+ const commitConfig = await loadConfig();
87
+ const schemaPathRaw = typeof commitConfig.schema === 'string' ? commitConfig.schema : './db/schema.ts';
88
+ const schemaFilePath = path.resolve(projectRoot, schemaPathRaw);
89
+ if (fs.existsSync(schemaFilePath)) {
90
+ const currentContent = fs.readFileSync(schemaFilePath, 'utf-8');
91
+ const currentHash = hashFileContent(currentContent);
92
+ saveFileHash(currentHash, projectRoot);
103
93
  }
94
+ console.log(`[${shortHash(hash)}] ${message}`);
95
+ console.log(` ${creates} create(s), ${alters} alter(s), ${drops} drop(s)`);
104
96
  console.log('');
105
- console.log(`${colors.muted('Run')} ${colors.cyan('relq push')} ${colors.muted('to apply changes to database.')}`);
106
- console.log(`${colors.muted('Run')} ${colors.cyan('relq export')} ${colors.muted('to export as SQL file.')}`);
97
+ hint("run 'relq push' to apply changes to database");
98
+ hint("run 'relq export' to export as SQL file");
107
99
  console.log('');
108
100
  }
109
101
  export default commitCommand;
@@ -1,81 +1,156 @@
1
1
  import { requireValidConfig } from "../utils/config-loader.js";
2
- import { introspectDatabase } from "../utils/schema-introspect.js";
3
- import { loadSnapshot, snapshotToDatabaseSchema } from "../utils/snapshot-manager.js";
4
- import { formatDiff } from "../utils/schema-diff.js";
5
- import { normalizeSchema } from "../utils/schema-hash.js";
6
- import { diffSchemas } from "../utils/schema-diff.js";
7
- import { generateMigration } from "../utils/migration-generator.js";
2
+ import { fastIntrospectDatabase } from "../utils/fast-introspect.js";
8
3
  import { getConnectionDescription } from "../utils/env-loader.js";
9
- const colors = {
10
- reset: '\x1b[0m',
11
- bold: '\x1b[1m',
12
- dim: '\x1b[2m',
13
- red: '\x1b[31m',
14
- green: '\x1b[32m',
15
- yellow: '\x1b[33m',
16
- blue: '\x1b[34m',
17
- cyan: '\x1b[36m',
18
- magenta: '\x1b[35m',
19
- };
20
- function filterDiff(diff, ignorePatterns) {
21
- const patterns = ignorePatterns.map(p => {
22
- const regexStr = p.replace(/\*/g, '.*').replace(/\?/g, '.');
23
- return new RegExp(`^${regexStr}$`, 'i');
24
- });
25
- const matchesPattern = (name) => patterns.some(p => p.test(name));
26
- const tables = diff.tables.filter(t => !matchesPattern(t.name));
27
- const hasChanges = tables.length > 0 || diff.extensions.length > 0;
28
- return {
29
- ...diff,
30
- tables,
31
- hasChanges,
32
- };
33
- }
4
+ import { colors, createSpinner, fatal } from "../utils/cli-utils.js";
5
+ import { isInitialized, loadSnapshot, getStagedChanges, getUnstagedChanges, } from "../utils/repo-manager.js";
6
+ import { getChangeDisplayName, generateChangeSQL, sortChangesByDependency } from "../utils/change-tracker.js";
34
7
  export async function diffCommand(context) {
35
- const { config, flags } = context;
36
- if (!config) {
37
- console.error('Error: No configuration found.');
38
- process.exit(1);
8
+ const { config, args, flags, projectRoot } = context;
9
+ console.log('');
10
+ if (!isInitialized(projectRoot)) {
11
+ fatal('not a relq repository (or any parent directories): .relq', `Run ${colors.cyan('relq init')} to initialize.`);
39
12
  }
40
- requireValidConfig(config);
41
- const connection = config.connection;
42
- const snapshotPath = config.sync?.snapshot || '.relq/snapshot.json';
43
- const ignorePatterns = config.sync?.ignore || ['_relq_*'];
44
13
  const showSQL = flags['sql'] === true;
45
- console.log(`${colors.bold}Comparing:${colors.reset} local snapshot ${getConnectionDescription(connection)}`);
46
- console.log('');
47
- try {
48
- const dbSchema = await introspectDatabase(connection);
49
- const snapshot = loadSnapshot(snapshotPath);
50
- if (!snapshot) {
51
- console.log(`${colors.yellow}No snapshot found.${colors.reset}`);
52
- console.log(`Run "${colors.cyan}relq pull${colors.reset}" to create initial snapshot.`);
53
- return;
14
+ const staged = flags['staged'] === true;
15
+ const target = args[0];
16
+ if (staged) {
17
+ await showStagedDiff(projectRoot, showSQL);
18
+ return;
19
+ }
20
+ if (target === 'remote/live' || target === 'remote' || target === 'live' || target === 'origin') {
21
+ if (!config) {
22
+ fatal('No configuration found', `Run ${colors.cyan('relq init')} to create one.`);
54
23
  }
55
- const localSchema = snapshotToDatabaseSchema(snapshot);
56
- const normalizedLocal = normalizeSchema(localSchema);
57
- const normalizedDb = normalizeSchema(dbSchema);
58
- const diff = diffSchemas(normalizedLocal, normalizedDb);
59
- const filteredDiff = filterDiff(diff, ignorePatterns);
60
- if (!filteredDiff.hasChanges) {
61
- console.log(`${colors.green}No differences.${colors.reset}`);
62
- return;
24
+ await requireValidConfig(config, { calledFrom: 'diff' });
25
+ await showOriginDiff(config, projectRoot, showSQL);
26
+ return;
27
+ }
28
+ await showUnstagedDiff(projectRoot, showSQL);
29
+ }
30
+ function getSymbol(change) {
31
+ if (change.type === 'CREATE')
32
+ return colors.green('+');
33
+ if (change.type === 'DROP')
34
+ return colors.red('-');
35
+ return colors.yellow('~');
36
+ }
37
+ async function showUnstagedDiff(projectRoot, showSQL) {
38
+ const unstaged = getUnstagedChanges(projectRoot);
39
+ if (unstaged.length === 0) {
40
+ console.log(`${colors.green('No unstaged changes.')}`);
41
+ console.log(`${colors.muted('Use')} ${colors.cyan('relq diff remote/live')} ${colors.muted('to compare with remote.')}`);
42
+ console.log('');
43
+ return;
44
+ }
45
+ console.log(`${colors.bold('Unstaged changes:')}`);
46
+ console.log('');
47
+ const sorted = sortChangesByDependency(unstaged);
48
+ for (const change of sorted) {
49
+ console.log(` ${getSymbol(change)} ${change.objectType.toLowerCase()}: ${getChangeDisplayName(change)}`);
50
+ }
51
+ if (showSQL) {
52
+ console.log('');
53
+ console.log(`${colors.bold('SQL:')}`);
54
+ for (const change of sorted) {
55
+ const sql = generateChangeSQL(change);
56
+ if (sql)
57
+ console.log(`${colors.cyan(sql)}`);
63
58
  }
64
- console.log(formatDiff(filteredDiff));
65
- if (showSQL) {
66
- console.log('');
67
- console.log(`${colors.bold}Generated SQL:${colors.reset}`);
68
- console.log('');
69
- const { up } = generateMigration(filteredDiff, { includeDown: false });
70
- for (const sql of up) {
71
- console.log(`${colors.cyan}${sql}${colors.reset}`);
72
- }
59
+ }
60
+ console.log('');
61
+ console.log(`${colors.muted('Use')} ${colors.cyan('relq add .')} ${colors.muted('to stage all.')}`);
62
+ console.log('');
63
+ }
64
+ async function showStagedDiff(projectRoot, showSQL) {
65
+ const staged = getStagedChanges(projectRoot);
66
+ if (staged.length === 0) {
67
+ console.log(`${colors.green('No staged changes.')}`);
68
+ console.log(`${colors.muted('Use')} ${colors.cyan('relq add .')} ${colors.muted('to stage changes.')}`);
69
+ console.log('');
70
+ return;
71
+ }
72
+ console.log(`${colors.bold('Staged changes:')}`);
73
+ console.log('');
74
+ const sorted = sortChangesByDependency(staged);
75
+ for (const change of sorted) {
76
+ console.log(` ${getSymbol(change)} ${change.objectType.toLowerCase()}: ${getChangeDisplayName(change)}`);
77
+ }
78
+ if (showSQL) {
79
+ console.log('');
80
+ console.log(`${colors.bold('SQL:')}`);
81
+ for (const change of sorted) {
82
+ const sql = generateChangeSQL(change);
83
+ if (sql)
84
+ console.log(`${colors.cyan(sql)}`);
73
85
  }
86
+ }
87
+ console.log('');
88
+ console.log(`${colors.muted('Use')} ${colors.cyan('relq commit -m "message"')} ${colors.muted('to commit.')}`);
89
+ console.log('');
90
+ }
91
+ async function showOriginDiff(config, projectRoot, showSQL) {
92
+ const connection = config.connection;
93
+ const spinner = createSpinner();
94
+ const snapshot = loadSnapshot(projectRoot);
95
+ if (!snapshot) {
96
+ console.log(`${colors.yellow('No local snapshot.')}`);
97
+ console.log(`${colors.muted('Run')} ${colors.cyan('relq pull')} ${colors.muted('first.')}`);
98
+ console.log('');
99
+ return;
100
+ }
101
+ spinner.start(`Connecting to ${getConnectionDescription(connection)}...`);
102
+ const remoteDb = await fastIntrospectDatabase(connection, undefined, {
103
+ includeFunctions: config.includeFunctions ?? false,
104
+ includeTriggers: config.includeTriggers ?? false,
105
+ });
106
+ spinner.succeed(`Connected`);
107
+ const diffs = compareSchemas(snapshot, remoteDb);
108
+ if (diffs.length === 0) {
109
+ console.log('');
110
+ console.log('Local and remote are in sync.');
74
111
  console.log('');
75
- console.log(`${colors.dim}Use "relq diff --sql" to see generated SQL statements.${colors.reset}`);
112
+ return;
113
+ }
114
+ console.log('');
115
+ console.log(`${colors.bold('Differences:')}`);
116
+ console.log('');
117
+ for (const d of diffs.slice(0, 20)) {
118
+ const sym = d.type === 'added' ? colors.green('+') : d.type === 'removed' ? colors.red('-') : colors.yellow('~');
119
+ console.log(` ${sym} ${d.description}`);
76
120
  }
77
- catch (error) {
78
- console.error('Error:', error instanceof Error ? error.message : error);
79
- process.exit(1);
121
+ if (diffs.length > 20) {
122
+ console.log(` ${colors.muted(`... and ${diffs.length - 20} more`)}`);
123
+ }
124
+ console.log('');
125
+ console.log(`${colors.muted('Use')} ${colors.cyan('relq pull')} ${colors.muted('to sync local with remote.')}`);
126
+ console.log('');
127
+ }
128
+ function compareSchemas(local, remote) {
129
+ const diffs = [];
130
+ const localTableNames = local.tables.map(t => t.name);
131
+ const remoteTableNames = (remote.tables || []).map((t) => t.name);
132
+ for (const name of remoteTableNames) {
133
+ if (!localTableNames.includes(name)) {
134
+ diffs.push({ type: 'added', description: `table: ${name}` });
135
+ }
136
+ }
137
+ for (const name of localTableNames) {
138
+ if (!remoteTableNames.includes(name)) {
139
+ diffs.push({ type: 'removed', description: `table: ${name}` });
140
+ }
141
+ }
142
+ const localEnumNames = local.enums.map(e => e.name);
143
+ const remoteEnumNames = (remote.enums || []).map((e) => e.name);
144
+ for (const name of remoteEnumNames) {
145
+ if (!localEnumNames.includes(name)) {
146
+ diffs.push({ type: 'added', description: `enum: ${name}` });
147
+ }
148
+ }
149
+ for (const name of localEnumNames) {
150
+ if (!remoteEnumNames.includes(name)) {
151
+ diffs.push({ type: 'removed', description: `enum: ${name}` });
152
+ }
80
153
  }
154
+ return diffs;
81
155
  }
156
+ export default diffCommand;