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
@@ -0,0 +1,98 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { colors } from "../utils/spinner.js";
4
+ import { isInitialized, getHead, setHead, loadCommit, saveSnapshot, getStagedChanges, getUnstagedChanges, } from "../utils/repo-manager.js";
5
+ function loadBranchState(projectRoot) {
6
+ const branchPath = path.join(projectRoot, '.relq', 'branches.json');
7
+ if (fs.existsSync(branchPath)) {
8
+ return JSON.parse(fs.readFileSync(branchPath, 'utf-8'));
9
+ }
10
+ const head = getHead(projectRoot);
11
+ return { current: 'main', branches: { main: head || '' } };
12
+ }
13
+ function saveBranchState(state, projectRoot) {
14
+ const branchPath = path.join(projectRoot, '.relq', 'branches.json');
15
+ fs.writeFileSync(branchPath, JSON.stringify(state, null, 2));
16
+ }
17
+ export async function checkoutCommand(context) {
18
+ const { args, flags } = context;
19
+ const projectRoot = process.cwd();
20
+ console.log('');
21
+ if (!isInitialized(projectRoot)) {
22
+ console.log(`${colors.red('fatal:')} not a relq repository`);
23
+ return;
24
+ }
25
+ const createBranch = flags['b'] === true;
26
+ const branchName = args[0];
27
+ if (!branchName) {
28
+ console.log(`${colors.red('error:')} Please specify a branch`);
29
+ console.log('');
30
+ console.log(`Usage: ${colors.cyan('relq checkout <branch>')}`);
31
+ console.log(` ${colors.cyan('relq checkout -b <new-branch>')}`);
32
+ console.log('');
33
+ return;
34
+ }
35
+ const staged = getStagedChanges(projectRoot);
36
+ const unstaged = getUnstagedChanges(projectRoot);
37
+ if (staged.length > 0 || unstaged.length > 0) {
38
+ console.log(`${colors.red('error:')} You have uncommitted changes`);
39
+ console.log('');
40
+ console.log('Commit or stash them before switching branches:');
41
+ console.log(` ${colors.cyan('relq commit -m "message"')}`);
42
+ console.log(` ${colors.cyan('relq stash')}`);
43
+ console.log('');
44
+ return;
45
+ }
46
+ const state = loadBranchState(projectRoot);
47
+ if (createBranch) {
48
+ if (state.branches[branchName]) {
49
+ console.log(`${colors.red('error:')} Branch already exists: ${branchName}`);
50
+ return;
51
+ }
52
+ const head = getHead(projectRoot);
53
+ if (!head) {
54
+ console.log(`${colors.red('error:')} No commits yet`);
55
+ return;
56
+ }
57
+ state.branches[branchName] = head;
58
+ state.current = branchName;
59
+ saveBranchState(state, projectRoot);
60
+ console.log(`${colors.green('✓')} Switched to new branch '${branchName}'`);
61
+ console.log('');
62
+ return;
63
+ }
64
+ if (!state.branches[branchName]) {
65
+ console.log(`${colors.red('error:')} Branch not found: ${branchName}`);
66
+ console.log('');
67
+ console.log('Available branches:');
68
+ for (const name of Object.keys(state.branches)) {
69
+ console.log(` ${name}`);
70
+ }
71
+ console.log('');
72
+ return;
73
+ }
74
+ if (state.current === branchName) {
75
+ console.log(`Already on '${branchName}'`);
76
+ console.log('');
77
+ return;
78
+ }
79
+ const currentHead = getHead(projectRoot);
80
+ if (currentHead) {
81
+ state.branches[state.current] = currentHead;
82
+ }
83
+ const targetHash = state.branches[branchName];
84
+ const targetCommit = loadCommit(targetHash, projectRoot);
85
+ if (!targetCommit) {
86
+ console.log(`${colors.red('error:')} Cannot find commit for branch`);
87
+ return;
88
+ }
89
+ if (targetCommit.schema) {
90
+ saveSnapshot(targetCommit.schema, projectRoot);
91
+ }
92
+ setHead(targetHash, projectRoot);
93
+ state.current = branchName;
94
+ saveBranchState(state, projectRoot);
95
+ console.log(`${colors.green('✓')} Switched to branch '${branchName}'`);
96
+ console.log('');
97
+ }
98
+ export default checkoutCommand;
@@ -0,0 +1,247 @@
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 } = 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 abort = flags['abort'] === true;
14
+ const cherryPickStatePath = path.join(projectRoot, '.relq', 'CHERRY_PICK_STATE');
15
+ if (abort) {
16
+ if (fs.existsSync(cherryPickStatePath)) {
17
+ fs.unlinkSync(cherryPickStatePath);
18
+ console.log(`${colors.green('✓')} Cherry-pick aborted`);
19
+ }
20
+ else {
21
+ console.log(`${colors.muted('No cherry-pick in progress.')}`);
22
+ }
23
+ console.log('');
24
+ return;
25
+ }
26
+ if (fs.existsSync(cherryPickStatePath)) {
27
+ const state = JSON.parse(fs.readFileSync(cherryPickStatePath, 'utf-8'));
28
+ console.log(`${colors.red('error:')} Cherry-pick in progress from ${shortHash(state.fromCommit)}`);
29
+ console.log('');
30
+ console.log(`${colors.muted('Use')} ${colors.cyan('relq resolve')} ${colors.muted('to resolve conflicts')}`);
31
+ console.log(`${colors.muted('Or')} ${colors.cyan('relq cherry-pick --abort')} ${colors.muted('to cancel')}`);
32
+ console.log('');
33
+ return;
34
+ }
35
+ const ref = args[0];
36
+ if (!ref) {
37
+ console.log(`${colors.red('error:')} Please specify a commit`);
38
+ console.log('');
39
+ console.log(`Usage: ${colors.cyan('relq cherry-pick <commit>')}`);
40
+ console.log('');
41
+ return;
42
+ }
43
+ const spinner = createSpinner();
44
+ spinner.start(`Cherry-picking ${shortHash(ref)}...`);
45
+ try {
46
+ const hash = resolveRef(ref, projectRoot);
47
+ if (!hash) {
48
+ spinner.fail(`Commit not found: ${ref}`);
49
+ return;
50
+ }
51
+ const targetCommit = loadCommit(hash, projectRoot);
52
+ const parentCommit = loadParentCommit(hash, projectRoot);
53
+ if (!targetCommit) {
54
+ spinner.fail('Cannot load commit');
55
+ return;
56
+ }
57
+ if (!parentCommit) {
58
+ spinner.fail('Cannot cherry-pick first commit (no parent)');
59
+ return;
60
+ }
61
+ const diff = calculateCommitDiff(parentCommit.schema, targetCommit.schema);
62
+ const currentSnapshot = loadSnapshot(projectRoot);
63
+ if (!currentSnapshot) {
64
+ spinner.fail('No snapshot found');
65
+ return;
66
+ }
67
+ const conflicts = detectCherryPickConflicts(currentSnapshot, diff);
68
+ if (conflicts.length > 0) {
69
+ const state = {
70
+ fromCommit: hash,
71
+ originalMessage: targetCommit.message,
72
+ conflicts,
73
+ diff,
74
+ createdAt: new Date().toISOString(),
75
+ };
76
+ fs.writeFileSync(cherryPickStatePath, JSON.stringify(state, null, 2));
77
+ spinner.fail(`${conflicts.length} conflict(s) detected`);
78
+ console.log('');
79
+ for (const c of conflicts.slice(0, 5)) {
80
+ const name = c.parentName ? `${c.parentName}.${c.objectName}` : c.objectName;
81
+ console.log(` ${colors.red('conflict:')} ${c.objectType.toLowerCase()} ${name}`);
82
+ console.log(` ${colors.muted(c.description)}`);
83
+ }
84
+ if (conflicts.length > 5) {
85
+ console.log(` ${colors.muted(`... and ${conflicts.length - 5} more`)}`);
86
+ }
87
+ console.log('');
88
+ console.log(`${colors.muted('Use')} ${colors.cyan('relq resolve --theirs')} ${colors.muted('to resolve')}`);
89
+ console.log(`${colors.muted('Or')} ${colors.cyan('relq cherry-pick --abort')} ${colors.muted('to cancel')}`);
90
+ console.log('');
91
+ return;
92
+ }
93
+ const newSnapshot = applyCommitDiff(currentSnapshot, diff);
94
+ saveSnapshot(newSnapshot, projectRoot);
95
+ const author = config?.author || 'Relq CLI';
96
+ const message = `Cherry-picked ${shortHash(hash)}: ${targetCommit.message}`;
97
+ const commit = createCommit(newSnapshot, author, message, projectRoot);
98
+ spinner.succeed(`Cherry-picked ${colors.yellow(shortHash(hash))}`);
99
+ console.log(` ${colors.cyan(commit.message)}`);
100
+ console.log('');
101
+ }
102
+ catch (error) {
103
+ spinner.fail('Cherry-pick failed');
104
+ console.error(colors.red(`Error: ${error instanceof Error ? error.message : error}`));
105
+ }
106
+ }
107
+ function calculateCommitDiff(parent, target) {
108
+ const diff = {
109
+ tablesAdded: [],
110
+ tablesRemoved: [],
111
+ tablesModified: [],
112
+ enumsAdded: [],
113
+ enumsRemoved: [],
114
+ columnsAdded: [],
115
+ columnsRemoved: [],
116
+ };
117
+ const parentTables = new Map(parent.tables.map(t => [t.name, t]));
118
+ const targetTables = new Map(target.tables.map(t => [t.name, t]));
119
+ for (const [name, table] of targetTables) {
120
+ if (!parentTables.has(name)) {
121
+ diff.tablesAdded.push(table);
122
+ }
123
+ }
124
+ for (const [name] of parentTables) {
125
+ if (!targetTables.has(name)) {
126
+ diff.tablesRemoved.push(name);
127
+ }
128
+ }
129
+ for (const [name, targetTable] of targetTables) {
130
+ const parentTable = parentTables.get(name);
131
+ if (!parentTable)
132
+ continue;
133
+ const parentCols = new Map(parentTable.columns.map(c => [c.name, c]));
134
+ const targetCols = new Map(targetTable.columns.map(c => [c.name, c]));
135
+ const changes = { columnsAdded: [], columnsRemoved: [] };
136
+ for (const [colName, col] of targetCols) {
137
+ if (!parentCols.has(colName)) {
138
+ changes.columnsAdded.push(col);
139
+ }
140
+ }
141
+ for (const [colName] of parentCols) {
142
+ if (!targetCols.has(colName)) {
143
+ changes.columnsRemoved.push(colName);
144
+ }
145
+ }
146
+ if (changes.columnsAdded.length > 0 || changes.columnsRemoved.length > 0) {
147
+ diff.tablesModified.push({ name, changes });
148
+ }
149
+ }
150
+ const parentEnums = new Set(parent.enums.map(e => e.name));
151
+ const targetEnums = new Map(target.enums.map(e => [e.name, e]));
152
+ for (const [name, e] of targetEnums) {
153
+ if (!parentEnums.has(name)) {
154
+ diff.enumsAdded.push(e);
155
+ }
156
+ }
157
+ for (const name of parentEnums) {
158
+ if (!targetEnums.has(name)) {
159
+ diff.enumsRemoved.push(name);
160
+ }
161
+ }
162
+ return diff;
163
+ }
164
+ function detectCherryPickConflicts(current, diff) {
165
+ const conflicts = [];
166
+ for (const table of diff.tablesAdded) {
167
+ if (current.tables.find(t => t.name === table.name)) {
168
+ conflicts.push({
169
+ objectType: 'TABLE',
170
+ objectName: table.name,
171
+ currentValue: 'exists',
172
+ incomingValue: 'add',
173
+ description: 'Table already exists in current snapshot',
174
+ });
175
+ }
176
+ }
177
+ for (const tableName of diff.tablesRemoved) {
178
+ if (!current.tables.find(t => t.name === tableName)) {
179
+ conflicts.push({
180
+ objectType: 'TABLE',
181
+ objectName: tableName,
182
+ currentValue: 'missing',
183
+ incomingValue: 'remove',
184
+ description: 'Table does not exist in current snapshot',
185
+ });
186
+ }
187
+ }
188
+ for (const mod of diff.tablesModified) {
189
+ const currentTable = current.tables.find(t => t.name === mod.name);
190
+ if (!currentTable) {
191
+ conflicts.push({
192
+ objectType: 'TABLE',
193
+ objectName: mod.name,
194
+ currentValue: 'missing',
195
+ incomingValue: 'modify',
196
+ description: 'Table does not exist in current snapshot',
197
+ });
198
+ continue;
199
+ }
200
+ for (const col of mod.changes.columnsAdded) {
201
+ if (currentTable.columns.find(c => c.name === col.name)) {
202
+ conflicts.push({
203
+ objectType: 'COLUMN',
204
+ objectName: col.name,
205
+ parentName: mod.name,
206
+ currentValue: 'exists',
207
+ incomingValue: 'add',
208
+ description: 'Column already exists',
209
+ });
210
+ }
211
+ }
212
+ }
213
+ for (const e of diff.enumsAdded) {
214
+ if (current.enums.find(en => en.name === e.name)) {
215
+ conflicts.push({
216
+ objectType: 'ENUM',
217
+ objectName: e.name,
218
+ currentValue: 'exists',
219
+ incomingValue: 'add',
220
+ description: 'Enum already exists',
221
+ });
222
+ }
223
+ }
224
+ return conflicts;
225
+ }
226
+ function applyCommitDiff(snapshot, diff) {
227
+ const result = JSON.parse(JSON.stringify(snapshot));
228
+ for (const table of diff.tablesAdded) {
229
+ result.tables.push(table);
230
+ }
231
+ result.tables = result.tables.filter(t => !diff.tablesRemoved.includes(t.name));
232
+ for (const mod of diff.tablesModified) {
233
+ const table = result.tables.find(t => t.name === mod.name);
234
+ if (!table)
235
+ continue;
236
+ for (const col of mod.changes.columnsAdded) {
237
+ table.columns.push(col);
238
+ }
239
+ table.columns = table.columns.filter(c => !mod.changes.columnsRemoved.includes(c.name));
240
+ }
241
+ for (const e of diff.enumsAdded) {
242
+ result.enums.push(e);
243
+ }
244
+ result.enums = result.enums.filter(e => !diff.enumsRemoved.includes(e.name));
245
+ return result;
246
+ }
247
+ export default cherryPickCommand;
@@ -1,81 +1,160 @@
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 } from "../utils/spinner.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 } = context;
9
+ const projectRoot = process.cwd();
10
+ console.log('');
11
+ if (!isInitialized(projectRoot)) {
12
+ console.log(`${colors.red('fatal:')} not a relq repository`);
13
+ console.log(`${colors.muted('Run')} ${colors.cyan('relq init')} ${colors.muted('to initialize.')}`);
14
+ return;
39
15
  }
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
16
  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;
17
+ const staged = flags['staged'] === true;
18
+ const target = args[0];
19
+ if (staged) {
20
+ await showStagedDiff(projectRoot, showSQL);
21
+ return;
22
+ }
23
+ if (target === 'remote/live' || target === 'remote' || target === 'live' || target === 'origin') {
24
+ if (!config) {
25
+ console.error('Error: No configuration found.');
26
+ process.exit(1);
54
27
  }
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;
28
+ requireValidConfig(config);
29
+ await showOriginDiff(config, projectRoot, showSQL);
30
+ return;
31
+ }
32
+ await showUnstagedDiff(projectRoot, showSQL);
33
+ }
34
+ function getSymbol(change) {
35
+ if (change.type === 'CREATE')
36
+ return colors.green('+');
37
+ if (change.type === 'DROP')
38
+ return colors.red('-');
39
+ return colors.yellow('~');
40
+ }
41
+ async function showUnstagedDiff(projectRoot, showSQL) {
42
+ const unstaged = getUnstagedChanges(projectRoot);
43
+ if (unstaged.length === 0) {
44
+ console.log(`${colors.green('No unstaged changes.')}`);
45
+ console.log(`${colors.muted('Use')} ${colors.cyan('relq diff remote/live')} ${colors.muted('to compare with remote.')}`);
46
+ console.log('');
47
+ return;
48
+ }
49
+ console.log(`${colors.bold('Unstaged changes:')}`);
50
+ console.log('');
51
+ const sorted = sortChangesByDependency(unstaged);
52
+ for (const change of sorted) {
53
+ console.log(` ${getSymbol(change)} ${change.objectType.toLowerCase()}: ${getChangeDisplayName(change)}`);
54
+ }
55
+ if (showSQL) {
56
+ console.log('');
57
+ console.log(`${colors.bold('SQL:')}`);
58
+ for (const change of sorted) {
59
+ const sql = generateChangeSQL(change);
60
+ if (sql)
61
+ console.log(`${colors.cyan(sql)}`);
63
62
  }
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
- }
63
+ }
64
+ console.log('');
65
+ console.log(`${colors.muted('Use')} ${colors.cyan('relq add .')} ${colors.muted('to stage all.')}`);
66
+ console.log('');
67
+ }
68
+ async function showStagedDiff(projectRoot, showSQL) {
69
+ const staged = getStagedChanges(projectRoot);
70
+ if (staged.length === 0) {
71
+ console.log(`${colors.green('No staged changes.')}`);
72
+ console.log(`${colors.muted('Use')} ${colors.cyan('relq add .')} ${colors.muted('to stage changes.')}`);
73
+ console.log('');
74
+ return;
75
+ }
76
+ console.log(`${colors.bold('Staged changes:')}`);
77
+ console.log('');
78
+ const sorted = sortChangesByDependency(staged);
79
+ for (const change of sorted) {
80
+ console.log(` ${getSymbol(change)} ${change.objectType.toLowerCase()}: ${getChangeDisplayName(change)}`);
81
+ }
82
+ if (showSQL) {
83
+ console.log('');
84
+ console.log(`${colors.bold('SQL:')}`);
85
+ for (const change of sorted) {
86
+ const sql = generateChangeSQL(change);
87
+ if (sql)
88
+ console.log(`${colors.cyan(sql)}`);
73
89
  }
90
+ }
91
+ console.log('');
92
+ console.log(`${colors.muted('Use')} ${colors.cyan('relq commit -m "message"')} ${colors.muted('to commit.')}`);
93
+ console.log('');
94
+ }
95
+ async function showOriginDiff(config, projectRoot, showSQL) {
96
+ const connection = config.connection;
97
+ const spinner = createSpinner();
98
+ const snapshot = loadSnapshot(projectRoot);
99
+ if (!snapshot) {
100
+ console.log(`${colors.yellow('No local snapshot.')}`);
101
+ console.log(`${colors.muted('Run')} ${colors.cyan('relq pull')} ${colors.muted('first.')}`);
102
+ console.log('');
103
+ return;
104
+ }
105
+ spinner.start(`Connecting to ${getConnectionDescription(connection)}...`);
106
+ const remoteDb = await fastIntrospectDatabase(connection, undefined, {
107
+ includeFunctions: config.includeFunctions ?? false,
108
+ includeTriggers: config.includeTriggers ?? false,
109
+ });
110
+ spinner.succeed(`Connected`);
111
+ const diffs = compareSchemas(snapshot, remoteDb);
112
+ if (diffs.length === 0) {
113
+ console.log('');
114
+ console.log(`${colors.green('✓ Local and remote are in sync.')}`);
74
115
  console.log('');
75
- console.log(`${colors.dim}Use "relq diff --sql" to see generated SQL statements.${colors.reset}`);
116
+ return;
117
+ }
118
+ console.log('');
119
+ console.log(`${colors.bold('Differences:')}`);
120
+ console.log('');
121
+ for (const d of diffs.slice(0, 20)) {
122
+ const sym = d.type === 'added' ? colors.green('+') : d.type === 'removed' ? colors.red('-') : colors.yellow('~');
123
+ console.log(` ${sym} ${d.description}`);
76
124
  }
77
- catch (error) {
78
- console.error('Error:', error instanceof Error ? error.message : error);
79
- process.exit(1);
125
+ if (diffs.length > 20) {
126
+ console.log(` ${colors.muted(`... and ${diffs.length - 20} more`)}`);
127
+ }
128
+ console.log('');
129
+ console.log(`${colors.muted('Use')} ${colors.cyan('relq pull')} ${colors.muted('to sync local with remote.')}`);
130
+ console.log('');
131
+ }
132
+ function compareSchemas(local, remote) {
133
+ const diffs = [];
134
+ const localTableNames = local.tables.map(t => t.name);
135
+ const remoteTableNames = (remote.tables || []).map((t) => t.name);
136
+ for (const name of remoteTableNames) {
137
+ if (!localTableNames.includes(name)) {
138
+ diffs.push({ type: 'added', description: `table: ${name}` });
139
+ }
140
+ }
141
+ for (const name of localTableNames) {
142
+ if (!remoteTableNames.includes(name)) {
143
+ diffs.push({ type: 'removed', description: `table: ${name}` });
144
+ }
145
+ }
146
+ const localEnumNames = local.enums.map(e => e.name);
147
+ const remoteEnumNames = (remote.enums || []).map((e) => e.name);
148
+ for (const name of remoteEnumNames) {
149
+ if (!localEnumNames.includes(name)) {
150
+ diffs.push({ type: 'added', description: `enum: ${name}` });
151
+ }
152
+ }
153
+ for (const name of localEnumNames) {
154
+ if (!remoteEnumNames.includes(name)) {
155
+ diffs.push({ type: 'removed', description: `enum: ${name}` });
156
+ }
80
157
  }
158
+ return diffs;
81
159
  }
160
+ export default diffCommand;
@@ -5,6 +5,8 @@ import { createSpinner, colors } from "../utils/spinner.js";
5
5
  import { isInitialized, getStagedChanges, getUnstagedChanges, loadSnapshot, } from "../utils/repo-manager.js";
6
6
  import { generateCombinedSQL, sortChangesByDependency, getChangeDisplayName, } from "../utils/change-tracker.js";
7
7
  import { generateFullSchemaSQL } from "../utils/sql-generator.js";
8
+ import { loadRelqignore, isTableIgnored, isColumnIgnored, isIndexIgnored, isConstraintIgnored, isEnumIgnored, isDomainIgnored, isSequenceIgnored, isCompositeTypeIgnored, isFunctionIgnored, } from "../utils/relqignore.js";
9
+ import { loadConfig } from "../../config/config.js";
8
10
  export async function exportCommand(context) {
9
11
  const spinner = createSpinner();
10
12
  const { args, flags } = context;
@@ -46,7 +48,18 @@ async function exportFromSnapshot(projectRoot, absoluteOutputPath, options) {
46
48
  return;
47
49
  }
48
50
  spinner.succeed('Loaded snapshot');
49
- const schema = normalizedToDbSchema(snapshot);
51
+ const config = await loadConfig();
52
+ const ignorePatterns = loadRelqignore(projectRoot);
53
+ const filteredSnapshot = filterNormalizedSchema(snapshot, ignorePatterns, {
54
+ includeFunctions: config.includeFunctions ?? options.includeFunctions ?? false,
55
+ includeTriggers: config.includeTriggers ?? options.includeTriggers ?? false,
56
+ includeViews: config.includeViews ?? false,
57
+ includeFDW: config.includeFDW ?? false,
58
+ });
59
+ const schema = normalizedToDbSchema(filteredSnapshot);
60
+ const ignoredCount = (snapshot.tables.length - filteredSnapshot.tables.length) +
61
+ (snapshot.enums.length - filteredSnapshot.enums.length) +
62
+ ((snapshot.functions?.length || 0) - (filteredSnapshot.functions?.length || 0));
50
63
  console.log('');
51
64
  console.log(colors.cyan('Schema Summary:'));
52
65
  console.log(` ${colors.green('•')} Tables: ${schema.tables.length}`);
@@ -55,11 +68,14 @@ async function exportFromSnapshot(projectRoot, absoluteOutputPath, options) {
55
68
  console.log(` ${colors.green('•')} Composite Types: ${schema.compositeTypes.length}`);
56
69
  console.log(` ${colors.green('•')} Sequences: ${schema.sequences?.length || 0}`);
57
70
  console.log(` ${colors.green('•')} Extensions: ${schema.extensions.length}`);
58
- if (snapshot.functions?.length) {
59
- console.log(` ${colors.green('•')} Functions: ${snapshot.functions.length}`);
71
+ if (filteredSnapshot.functions?.length) {
72
+ console.log(` ${colors.green('•')} Functions: ${filteredSnapshot.functions.length}`);
73
+ }
74
+ if (filteredSnapshot.triggers?.length) {
75
+ console.log(` ${colors.green('•')} Triggers: ${filteredSnapshot.triggers.length}`);
60
76
  }
61
- if (snapshot.triggers?.length) {
62
- console.log(` ${colors.green('•')} Triggers: ${snapshot.triggers.length}`);
77
+ if (ignoredCount > 0) {
78
+ console.log(` ${colors.muted(`ℹ ${ignoredCount} object(s) filtered by .relqignore`)}`);
63
79
  }
64
80
  spinner.start('Generating SQL statements');
65
81
  const sqlContent = generateFullSQL(schema, options);
@@ -294,4 +310,47 @@ function generateFullSQL(schema, options) {
294
310
  headerComment: header,
295
311
  });
296
312
  }
313
+ function filterNormalizedSchema(schema, patterns, options) {
314
+ const filteredTables = schema.tables
315
+ .filter(table => !isTableIgnored(table.name, patterns).ignored)
316
+ .map(table => ({
317
+ ...table,
318
+ columns: table.columns.filter(col => !isColumnIgnored(table.name, col.name, patterns).ignored),
319
+ indexes: table.indexes.filter(idx => !isIndexIgnored(table.name, idx.name, patterns).ignored),
320
+ constraints: table.constraints?.filter(con => !isConstraintIgnored(table.name, con.name, patterns).ignored) || [],
321
+ }));
322
+ const filteredEnums = schema.enums.filter(e => !isEnumIgnored(e.name, patterns).ignored);
323
+ const filteredDomains = schema.domains.filter(d => !isDomainIgnored(d.name, patterns).ignored);
324
+ const filteredCompositeTypes = schema.compositeTypes.filter(c => !isCompositeTypeIgnored(c.name, patterns).ignored);
325
+ const filteredSequences = schema.sequences.filter(s => !isSequenceIgnored(s.name, patterns).ignored);
326
+ const filteredFunctions = options.includeFunctions
327
+ ? (schema.functions || []).filter(f => !isFunctionIgnored(f.name, patterns).ignored)
328
+ : [];
329
+ const filteredTriggers = options.includeTriggers
330
+ ? schema.triggers || []
331
+ : [];
332
+ const filteredViews = options.includeViews
333
+ ? schema.views || []
334
+ : [];
335
+ const filteredMaterializedViews = options.includeViews
336
+ ? schema.materializedViews || []
337
+ : [];
338
+ const filteredForeignTables = options.includeFDW
339
+ ? schema.foreignTables || []
340
+ : [];
341
+ return {
342
+ extensions: schema.extensions,
343
+ enums: filteredEnums,
344
+ domains: filteredDomains,
345
+ compositeTypes: filteredCompositeTypes,
346
+ sequences: filteredSequences,
347
+ collations: schema.collations,
348
+ tables: filteredTables,
349
+ functions: filteredFunctions,
350
+ triggers: filteredTriggers,
351
+ views: filteredViews,
352
+ materializedViews: filteredMaterializedViews,
353
+ foreignTables: filteredForeignTables,
354
+ };
355
+ }
297
356
  export default exportCommand;