relq 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/addon/buffer/index.cjs +1881 -0
- package/dist/cjs/addon/pg/index.cjs +4812 -0
- package/dist/cjs/addon/pg-cursor/index.cjs +1451 -0
- package/dist/cjs/addon/pg-format/index.cjs +2270 -0
- package/dist/cjs/cli/commands/add.cjs +30 -1
- package/dist/cjs/cli/commands/branch.cjs +141 -0
- package/dist/cjs/cli/commands/checkout.cjs +134 -0
- package/dist/cjs/cli/commands/cherry-pick.cjs +283 -0
- package/dist/cjs/cli/commands/diff.cjs +148 -69
- package/dist/cjs/cli/commands/export.cjs +64 -5
- package/dist/cjs/cli/commands/fetch.cjs +34 -4
- package/dist/cjs/cli/commands/history.cjs +1 -1
- package/dist/cjs/cli/commands/import.cjs +283 -12
- package/dist/cjs/cli/commands/log.cjs +75 -0
- package/dist/cjs/cli/commands/merge.cjs +224 -0
- package/dist/cjs/cli/commands/migrate.cjs +1 -1
- package/dist/cjs/cli/commands/pull.cjs +123 -7
- package/dist/cjs/cli/commands/push.cjs +245 -29
- package/dist/cjs/cli/commands/remote.cjs +16 -0
- package/dist/cjs/cli/commands/reset.cjs +169 -0
- package/dist/cjs/cli/commands/resolve.cjs +193 -0
- package/dist/cjs/cli/commands/rollback.cjs +1 -1
- package/dist/cjs/cli/commands/stash.cjs +154 -0
- package/dist/cjs/cli/commands/status.cjs +48 -0
- package/dist/cjs/cli/commands/tag.cjs +147 -0
- package/dist/cjs/cli/index.cjs +46 -2
- package/dist/cjs/cli/utils/commit-manager.cjs +3 -3
- package/dist/cjs/cli/utils/env-loader.cjs +3 -2
- package/dist/cjs/cli/utils/fast-introspect.cjs +1 -1
- package/dist/cjs/cli/utils/project-root.cjs +56 -0
- package/dist/cjs/cli/utils/relqignore.cjs +296 -38
- package/dist/cjs/cli/utils/repo-manager.cjs +45 -3
- package/dist/cjs/cli/utils/schema-introspect.cjs +2 -2
- package/dist/cjs/cli/utils/sql-generator.cjs +1 -1
- package/dist/cjs/cli/utils/sql-parser.cjs +94 -7
- package/dist/cjs/condition/array-condition-builder.cjs +1 -1
- package/dist/cjs/condition/condition-collector.cjs +1 -1
- package/dist/cjs/condition/fulltext-condition-builder.cjs +1 -1
- package/dist/cjs/condition/geometric-condition-builder.cjs +1 -1
- package/dist/cjs/condition/jsonb-condition-builder.cjs +1 -1
- package/dist/cjs/condition/network-condition-builder.cjs +1 -1
- package/dist/cjs/condition/range-condition-builder.cjs +1 -1
- package/dist/cjs/copy/copy-builder.cjs +1 -1
- package/dist/cjs/core/query-builder.cjs +1 -1
- package/dist/cjs/core/relq-client.cjs +2 -2
- package/dist/cjs/count/count-builder.cjs +1 -1
- package/dist/cjs/cte/cte-builder.cjs +1 -1
- package/dist/cjs/delete/delete-builder.cjs +1 -1
- package/dist/cjs/function/create-function-builder.cjs +1 -1
- package/dist/cjs/functions/advanced-functions.cjs +1 -1
- package/dist/cjs/functions/case-builder.cjs +1 -1
- package/dist/cjs/functions/geometric-functions.cjs +1 -1
- package/dist/cjs/functions/network-functions.cjs +1 -1
- package/dist/cjs/functions/sql-functions.cjs +1 -1
- package/dist/cjs/indexing/create-index-builder.cjs +1 -1
- package/dist/cjs/indexing/drop-index-builder.cjs +1 -1
- package/dist/cjs/insert/conflict-builder.cjs +1 -1
- package/dist/cjs/insert/insert-builder.cjs +1 -1
- package/dist/cjs/maintenance/vacuum-builder.cjs +1 -1
- package/dist/cjs/pubsub/listen-notify-builder.cjs +1 -1
- package/dist/cjs/pubsub/listener-connection.cjs +2 -2
- package/dist/cjs/raw/raw-query-builder.cjs +1 -1
- package/dist/cjs/schema/schema-builder.cjs +1 -1
- package/dist/cjs/schema-definition/table-definition.cjs +1 -1
- package/dist/cjs/select/aggregate-builder.cjs +1 -1
- package/dist/cjs/select/select-builder.cjs +1 -1
- package/dist/cjs/sequence/sequence-builder.cjs +1 -1
- package/dist/cjs/table/alter-table-builder.cjs +1 -1
- package/dist/cjs/table/constraint-builder.cjs +1 -1
- package/dist/cjs/table/create-table-builder.cjs +1 -1
- package/dist/cjs/table/partition-builder.cjs +1 -1
- package/dist/cjs/table/truncate-builder.cjs +1 -1
- package/dist/cjs/transaction/transaction-builder.cjs +1 -1
- package/dist/cjs/trigger/create-trigger-builder.cjs +1 -1
- package/dist/cjs/update/array-update-builder.cjs +1 -1
- package/dist/cjs/update/update-builder.cjs +1 -1
- package/dist/cjs/utils/index.cjs +1 -1
- package/dist/cjs/view/create-view-builder.cjs +1 -1
- package/dist/cjs/window/window-builder.cjs +1 -1
- package/dist/esm/cli/commands/add.js +30 -1
- package/dist/esm/cli/commands/branch.js +105 -0
- package/dist/esm/cli/commands/checkout.js +98 -0
- package/dist/esm/cli/commands/cherry-pick.js +247 -0
- package/dist/esm/cli/commands/diff.js +148 -69
- package/dist/esm/cli/commands/export.js +64 -5
- package/dist/esm/cli/commands/fetch.js +35 -5
- package/dist/esm/cli/commands/history.js +1 -1
- package/dist/esm/cli/commands/import.js +283 -12
- package/dist/esm/cli/commands/log.js +74 -0
- package/dist/esm/cli/commands/merge.js +188 -0
- package/dist/esm/cli/commands/migrate.js +1 -1
- package/dist/esm/cli/commands/pull.js +124 -8
- package/dist/esm/cli/commands/push.js +246 -30
- package/dist/esm/cli/commands/remote.js +13 -0
- package/dist/esm/cli/commands/reset.js +133 -0
- package/dist/esm/cli/commands/resolve.js +157 -0
- package/dist/esm/cli/commands/rollback.js +1 -1
- package/dist/esm/cli/commands/stash.js +118 -0
- package/dist/esm/cli/commands/status.js +15 -0
- package/dist/esm/cli/commands/tag.js +111 -0
- package/dist/esm/cli/index.js +47 -3
- package/dist/esm/cli/utils/commit-manager.js +3 -3
- package/dist/esm/cli/utils/env-loader.js +3 -2
- package/dist/esm/cli/utils/fast-introspect.js +1 -1
- package/dist/esm/cli/utils/project-root.js +19 -0
- package/dist/esm/cli/utils/relqignore.js +277 -37
- package/dist/esm/cli/utils/repo-manager.js +41 -3
- package/dist/esm/cli/utils/schema-introspect.js +2 -2
- package/dist/esm/cli/utils/sql-generator.js +1 -1
- package/dist/esm/cli/utils/sql-parser.js +94 -7
- package/dist/esm/condition/array-condition-builder.js +1 -1
- package/dist/esm/condition/condition-collector.js +1 -1
- package/dist/esm/condition/fulltext-condition-builder.js +1 -1
- package/dist/esm/condition/geometric-condition-builder.js +1 -1
- package/dist/esm/condition/jsonb-condition-builder.js +1 -1
- package/dist/esm/condition/network-condition-builder.js +1 -1
- package/dist/esm/condition/range-condition-builder.js +1 -1
- package/dist/esm/copy/copy-builder.js +1 -1
- package/dist/esm/core/query-builder.js +1 -1
- package/dist/esm/core/relq-client.js +2 -2
- package/dist/esm/count/count-builder.js +1 -1
- package/dist/esm/cte/cte-builder.js +1 -1
- package/dist/esm/delete/delete-builder.js +1 -1
- package/dist/esm/function/create-function-builder.js +1 -1
- package/dist/esm/functions/advanced-functions.js +1 -1
- package/dist/esm/functions/case-builder.js +1 -1
- package/dist/esm/functions/geometric-functions.js +1 -1
- package/dist/esm/functions/network-functions.js +1 -1
- package/dist/esm/functions/sql-functions.js +1 -1
- package/dist/esm/indexing/create-index-builder.js +1 -1
- package/dist/esm/indexing/drop-index-builder.js +1 -1
- package/dist/esm/insert/conflict-builder.js +1 -1
- package/dist/esm/insert/insert-builder.js +1 -1
- package/dist/esm/maintenance/vacuum-builder.js +1 -1
- package/dist/esm/pubsub/listen-notify-builder.js +1 -1
- package/dist/esm/pubsub/listener-connection.js +2 -2
- package/dist/esm/raw/raw-query-builder.js +1 -1
- package/dist/esm/schema/schema-builder.js +1 -1
- package/dist/esm/schema-definition/table-definition.js +1 -1
- package/dist/esm/select/aggregate-builder.js +1 -1
- package/dist/esm/select/select-builder.js +1 -1
- package/dist/esm/sequence/sequence-builder.js +1 -1
- package/dist/esm/table/alter-table-builder.js +1 -1
- package/dist/esm/table/constraint-builder.js +1 -1
- package/dist/esm/table/create-table-builder.js +1 -1
- package/dist/esm/table/partition-builder.js +1 -1
- package/dist/esm/table/truncate-builder.js +1 -1
- package/dist/esm/transaction/transaction-builder.js +1 -1
- package/dist/esm/trigger/create-trigger-builder.js +1 -1
- package/dist/esm/update/array-update-builder.js +1 -1
- package/dist/esm/update/update-builder.js +1 -1
- package/dist/esm/utils/index.js +1 -1
- package/dist/esm/view/create-view-builder.js +1 -1
- package/dist/esm/window/window-builder.js +1 -1
- package/package.json +1 -1
- /package/dist/{addons/buffer.js → esm/addon/buffer/index.js} +0 -0
- /package/dist/{addons/pg.js → esm/addon/pg/index.js} +0 -0
- /package/dist/{addons/pg-cursor.js → esm/addon/pg-cursor/index.js} +0 -0
- /package/dist/{addons/pg-format.js → esm/addon/pg-format/index.js} +0 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { colors, createSpinner } from "../utils/spinner.js";
|
|
4
|
+
import { isInitialized, getHead, loadCommit, loadSnapshot, saveSnapshot, createCommit, shortHash, } 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 = require('../utils/repo-manager').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 mergeCommand(context) {
|
|
18
|
+
const { config, 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 branchName = args[0];
|
|
26
|
+
const abort = flags['abort'] === true;
|
|
27
|
+
const mergeStatePath = path.join(projectRoot, '.relq', 'MERGE_STATE');
|
|
28
|
+
if (abort) {
|
|
29
|
+
if (fs.existsSync(mergeStatePath)) {
|
|
30
|
+
fs.unlinkSync(mergeStatePath);
|
|
31
|
+
console.log(`${colors.green('✓')} Merge aborted`);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
console.log(`${colors.muted('No merge in progress.')}`);
|
|
35
|
+
}
|
|
36
|
+
console.log('');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (fs.existsSync(mergeStatePath)) {
|
|
40
|
+
const mergeState = JSON.parse(fs.readFileSync(mergeStatePath, 'utf-8'));
|
|
41
|
+
console.log(`${colors.red('error:')} Merge in progress from '${mergeState.fromBranch}'`);
|
|
42
|
+
console.log('');
|
|
43
|
+
console.log(`${colors.muted('Use')} ${colors.cyan('relq resolve')} ${colors.muted('to resolve conflicts')}`);
|
|
44
|
+
console.log(`${colors.muted('Or')} ${colors.cyan('relq merge --abort')} ${colors.muted('to cancel')}`);
|
|
45
|
+
console.log('');
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (!branchName) {
|
|
49
|
+
console.log(`${colors.red('error:')} Please specify a branch to merge`);
|
|
50
|
+
console.log('');
|
|
51
|
+
console.log(`Usage: ${colors.cyan('relq merge <branch>')}`);
|
|
52
|
+
console.log('');
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const state = loadBranchState(projectRoot);
|
|
56
|
+
if (!state.branches[branchName]) {
|
|
57
|
+
console.log(`${colors.red('error:')} Branch not found: ${branchName}`);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (branchName === state.current) {
|
|
61
|
+
console.log(`${colors.red('error:')} Cannot merge branch into itself`);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const spinner = createSpinner();
|
|
65
|
+
spinner.start(`Merging '${branchName}' into '${state.current}'...`);
|
|
66
|
+
try {
|
|
67
|
+
const currentHash = getHead(projectRoot);
|
|
68
|
+
const incomingHash = state.branches[branchName];
|
|
69
|
+
if (!currentHash || !incomingHash) {
|
|
70
|
+
spinner.fail('No commits to merge');
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (currentHash === incomingHash) {
|
|
74
|
+
spinner.succeed('Already up to date');
|
|
75
|
+
console.log('');
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const currentCommit = loadCommit(currentHash, projectRoot);
|
|
79
|
+
const incomingCommit = loadCommit(incomingHash, projectRoot);
|
|
80
|
+
if (!currentCommit || !incomingCommit) {
|
|
81
|
+
spinner.fail('Cannot load commit data');
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const currentSnapshot = loadSnapshot(projectRoot) || currentCommit.schema;
|
|
85
|
+
const incomingSnapshot = incomingCommit.schema;
|
|
86
|
+
if (!currentSnapshot || !incomingSnapshot) {
|
|
87
|
+
spinner.fail('No snapshot data');
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const conflicts = detectMergeConflicts(currentSnapshot, incomingSnapshot);
|
|
91
|
+
if (conflicts.length > 0) {
|
|
92
|
+
const mergeState = {
|
|
93
|
+
fromBranch: branchName,
|
|
94
|
+
toBranch: state.current,
|
|
95
|
+
conflicts,
|
|
96
|
+
incomingSnapshot,
|
|
97
|
+
createdAt: new Date().toISOString(),
|
|
98
|
+
};
|
|
99
|
+
fs.writeFileSync(mergeStatePath, JSON.stringify(mergeState, null, 2));
|
|
100
|
+
spinner.fail(`${conflicts.length} conflict(s) detected`);
|
|
101
|
+
console.log('');
|
|
102
|
+
for (const c of conflicts.slice(0, 5)) {
|
|
103
|
+
const name = c.parentName ? `${c.parentName}.${c.objectName}` : c.objectName;
|
|
104
|
+
console.log(` ${colors.red('conflict:')} ${c.objectType.toLowerCase()} ${name}`);
|
|
105
|
+
}
|
|
106
|
+
if (conflicts.length > 5) {
|
|
107
|
+
console.log(` ${colors.muted(`... and ${conflicts.length - 5} more`)}`);
|
|
108
|
+
}
|
|
109
|
+
console.log('');
|
|
110
|
+
console.log(`${colors.muted('Use')} ${colors.cyan('relq resolve --all-theirs')} ${colors.muted('to accept incoming')}`);
|
|
111
|
+
console.log(`${colors.muted('Or')} ${colors.cyan('relq merge --abort')} ${colors.muted('to cancel')}`);
|
|
112
|
+
console.log('');
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const mergedSnapshot = mergeSnapshots(currentSnapshot, incomingSnapshot);
|
|
116
|
+
saveSnapshot(mergedSnapshot, projectRoot);
|
|
117
|
+
const author = config?.author || 'Relq CLI';
|
|
118
|
+
const message = `Merge branch '${branchName}' into ${state.current}`;
|
|
119
|
+
const commit = createCommit(mergedSnapshot, author, message, projectRoot);
|
|
120
|
+
spinner.succeed(`Merged '${branchName}' into '${state.current}'`);
|
|
121
|
+
console.log(` ${colors.yellow(shortHash(commit.hash))} ${message}`);
|
|
122
|
+
console.log('');
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
spinner.fail('Merge failed');
|
|
126
|
+
console.error(colors.red(`Error: ${error instanceof Error ? error.message : error}`));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function detectMergeConflicts(current, incoming) {
|
|
130
|
+
const conflicts = [];
|
|
131
|
+
for (const currentTable of current.tables) {
|
|
132
|
+
const incomingTable = incoming.tables.find(t => t.name === currentTable.name);
|
|
133
|
+
if (!incomingTable)
|
|
134
|
+
continue;
|
|
135
|
+
for (const currentCol of currentTable.columns) {
|
|
136
|
+
const incomingCol = incomingTable.columns.find(c => c.name === currentCol.name);
|
|
137
|
+
if (!incomingCol)
|
|
138
|
+
continue;
|
|
139
|
+
if (currentCol.type !== incomingCol.type) {
|
|
140
|
+
conflicts.push({
|
|
141
|
+
objectType: 'COLUMN',
|
|
142
|
+
objectName: currentCol.name,
|
|
143
|
+
parentName: currentTable.name,
|
|
144
|
+
currentValue: currentCol.type,
|
|
145
|
+
incomingValue: incomingCol.type,
|
|
146
|
+
description: `Type differs: ${currentCol.type} vs ${incomingCol.type}`,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
for (const currentEnum of current.enums) {
|
|
152
|
+
const incomingEnum = incoming.enums.find(e => e.name === currentEnum.name);
|
|
153
|
+
if (!incomingEnum)
|
|
154
|
+
continue;
|
|
155
|
+
const currentVals = JSON.stringify(currentEnum.values.sort());
|
|
156
|
+
const incomingVals = JSON.stringify(incomingEnum.values.sort());
|
|
157
|
+
if (currentVals !== incomingVals) {
|
|
158
|
+
conflicts.push({
|
|
159
|
+
objectType: 'ENUM',
|
|
160
|
+
objectName: currentEnum.name,
|
|
161
|
+
currentValue: currentEnum.values,
|
|
162
|
+
incomingValue: incomingEnum.values,
|
|
163
|
+
description: 'Values differ',
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return conflicts;
|
|
168
|
+
}
|
|
169
|
+
function mergeSnapshots(current, incoming) {
|
|
170
|
+
const result = JSON.parse(JSON.stringify(current));
|
|
171
|
+
for (const table of incoming.tables) {
|
|
172
|
+
if (!result.tables.find(t => t.name === table.name)) {
|
|
173
|
+
result.tables.push(table);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
for (const e of incoming.enums) {
|
|
177
|
+
if (!result.enums.find(x => x.name === e.name)) {
|
|
178
|
+
result.enums.push(e);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
for (const d of incoming.domains) {
|
|
182
|
+
if (!result.domains.find(x => x.name === d.name)) {
|
|
183
|
+
result.domains.push(d);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return result;
|
|
187
|
+
}
|
|
188
|
+
export default mergeCommand;
|
|
@@ -39,7 +39,7 @@ export async function migrateCommand(context) {
|
|
|
39
39
|
console.log(` Connection: ${getConnectionDescription(connection)}`);
|
|
40
40
|
console.log(` Migrations: ${migrationsDir}\n`);
|
|
41
41
|
try {
|
|
42
|
-
const { Pool } = await import("../../addon/pg.js");
|
|
42
|
+
const { Pool } = await import("../../addon/pg/index.js");
|
|
43
43
|
const pool = new Pool({
|
|
44
44
|
host: connection.host,
|
|
45
45
|
port: connection.port || 5432,
|
|
@@ -6,7 +6,7 @@ import { fastIntrospectDatabase } from "../utils/fast-introspect.js";
|
|
|
6
6
|
import { generateTypeScript } from "../utils/type-generator.js";
|
|
7
7
|
import { getConnectionDescription } from "../utils/env-loader.js";
|
|
8
8
|
import { createSpinner, colors, formatBytes } from "../utils/spinner.js";
|
|
9
|
-
import {
|
|
9
|
+
import { loadRelqignore, isTableIgnored, isColumnIgnored, isIndexIgnored, isConstraintIgnored, isEnumIgnored, isDomainIgnored, isCompositeTypeIgnored, isFunctionIgnored, } from "../utils/relqignore.js";
|
|
10
10
|
import { isInitialized, initRepository, getHead, saveSnapshot, loadSnapshot, createCommit, shortHash, fetchRemoteCommits, ensureRemoteTable, setFetchHead, getStagedChanges, getUnstagedChanges, } from "../utils/repo-manager.js";
|
|
11
11
|
function toCamelCase(str) {
|
|
12
12
|
return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
@@ -209,8 +209,24 @@ export async function pullCommand(context) {
|
|
|
209
209
|
includeTriggers,
|
|
210
210
|
});
|
|
211
211
|
spinner.succeed(`Found ${dbSchema.tables.length} tables`);
|
|
212
|
-
const ignorePatterns =
|
|
213
|
-
const filteredTables =
|
|
212
|
+
const ignorePatterns = loadRelqignore(projectRoot);
|
|
213
|
+
const filteredTables = dbSchema.tables
|
|
214
|
+
.filter(t => !isTableIgnored(t.name, ignorePatterns).ignored)
|
|
215
|
+
.map(t => ({
|
|
216
|
+
...t,
|
|
217
|
+
columns: t.columns.filter(c => !isColumnIgnored(t.name, c.name, ignorePatterns).ignored),
|
|
218
|
+
indexes: t.indexes.filter(i => !isIndexIgnored(t.name, i.name, ignorePatterns).ignored),
|
|
219
|
+
constraints: t.constraints.filter(c => !isConstraintIgnored(t.name, c.name, ignorePatterns).ignored),
|
|
220
|
+
}));
|
|
221
|
+
const filteredEnums = dbSchema.enums.filter(e => !isEnumIgnored(e.name, ignorePatterns).ignored);
|
|
222
|
+
const filteredDomains = dbSchema.domains.filter(d => !isDomainIgnored(d.name, ignorePatterns).ignored);
|
|
223
|
+
const filteredCompositeTypes = dbSchema.compositeTypes.filter(c => !isCompositeTypeIgnored(c.name, ignorePatterns).ignored);
|
|
224
|
+
const filteredFunctions = includeFunctions
|
|
225
|
+
? dbSchema.functions.filter(f => !isFunctionIgnored(f.name, ignorePatterns).ignored)
|
|
226
|
+
: [];
|
|
227
|
+
const filteredTriggers = includeTriggers
|
|
228
|
+
? dbSchema.triggers
|
|
229
|
+
: [];
|
|
214
230
|
const localHead = getHead(projectRoot);
|
|
215
231
|
const localSnapshot = loadSnapshot(projectRoot);
|
|
216
232
|
const schemaExists = fs.existsSync(schemaPath);
|
|
@@ -239,12 +255,12 @@ export async function pullCommand(context) {
|
|
|
239
255
|
definition: c.definition,
|
|
240
256
|
})),
|
|
241
257
|
})),
|
|
242
|
-
enums:
|
|
258
|
+
enums: filteredEnums.map(e => ({
|
|
243
259
|
name: e.name,
|
|
244
260
|
schema: 'public',
|
|
245
261
|
values: e.values,
|
|
246
262
|
})),
|
|
247
|
-
domains:
|
|
263
|
+
domains: filteredDomains.map(d => ({
|
|
248
264
|
name: d.name,
|
|
249
265
|
schema: 'public',
|
|
250
266
|
baseType: d.baseType,
|
|
@@ -252,20 +268,20 @@ export async function pullCommand(context) {
|
|
|
252
268
|
default: d.defaultValue || null,
|
|
253
269
|
check: d.checkExpression || null,
|
|
254
270
|
})),
|
|
255
|
-
compositeTypes:
|
|
271
|
+
compositeTypes: filteredCompositeTypes.map(c => ({
|
|
256
272
|
name: c.name,
|
|
257
273
|
schema: 'public',
|
|
258
274
|
attributes: c.attributes,
|
|
259
275
|
})),
|
|
260
276
|
sequences: [],
|
|
261
277
|
collations: [],
|
|
262
|
-
functions:
|
|
278
|
+
functions: filteredFunctions.map(f => ({
|
|
263
279
|
name: f.name,
|
|
264
280
|
returnType: f.returnType,
|
|
265
281
|
argTypes: f.argTypes,
|
|
266
282
|
language: f.language,
|
|
267
283
|
})),
|
|
268
|
-
triggers:
|
|
284
|
+
triggers: filteredTriggers.map(t => ({
|
|
269
285
|
name: t.name,
|
|
270
286
|
table: t.tableName,
|
|
271
287
|
events: [t.event],
|
|
@@ -276,11 +292,49 @@ export async function pullCommand(context) {
|
|
|
276
292
|
extensions: dbSchema.extensions.map(ext => ({ name: ext })),
|
|
277
293
|
};
|
|
278
294
|
console.log('');
|
|
295
|
+
const mergeStatePath = path.join(projectRoot, '.relq', 'MERGE_STATE');
|
|
296
|
+
if (fs.existsSync(mergeStatePath) && !force) {
|
|
297
|
+
console.log(`${colors.red('error:')} You have unresolved merge conflicts`);
|
|
298
|
+
console.log('');
|
|
299
|
+
console.log(`${colors.muted('Use')} ${colors.cyan('relq resolve')} ${colors.muted('to see and resolve conflicts')}`);
|
|
300
|
+
console.log(`${colors.muted('Or use')} ${colors.cyan('relq pull --force')} ${colors.muted('to overwrite local')}`);
|
|
301
|
+
console.log('');
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
279
304
|
if (schemaExists && localSnapshot && !force) {
|
|
280
305
|
const localTables = new Set(localSnapshot.tables.map(t => t.name));
|
|
281
306
|
const remoteTables = new Set(currentSchema.tables.map(t => t.name));
|
|
282
307
|
const added = [...remoteTables].filter(t => !localTables.has(t));
|
|
283
308
|
const removed = [...localTables].filter(t => !remoteTables.has(t));
|
|
309
|
+
const conflicts = detectObjectConflicts(localSnapshot, currentSchema);
|
|
310
|
+
if (conflicts.length > 0 && !force) {
|
|
311
|
+
const mergeState = {
|
|
312
|
+
conflicts,
|
|
313
|
+
remoteSnapshot: currentSchema,
|
|
314
|
+
createdAt: new Date().toISOString(),
|
|
315
|
+
};
|
|
316
|
+
fs.writeFileSync(mergeStatePath, JSON.stringify(mergeState, null, 2));
|
|
317
|
+
console.log(`${colors.red('error:')} Merge conflict detected`);
|
|
318
|
+
console.log('');
|
|
319
|
+
console.log(`Both local and remote have modified the same objects:`);
|
|
320
|
+
console.log('');
|
|
321
|
+
for (const c of conflicts.slice(0, 10)) {
|
|
322
|
+
const name = c.parentName ? `${c.parentName}.${c.objectName}` : c.objectName;
|
|
323
|
+
console.log(` ${colors.red('conflict:')} ${c.objectType.toLowerCase()} ${colors.bold(name)}`);
|
|
324
|
+
console.log(` ${colors.muted(c.description)}`);
|
|
325
|
+
}
|
|
326
|
+
if (conflicts.length > 10) {
|
|
327
|
+
console.log(` ${colors.muted(`... and ${conflicts.length - 10} more`)}`);
|
|
328
|
+
}
|
|
329
|
+
console.log('');
|
|
330
|
+
console.log('To resolve:');
|
|
331
|
+
console.log(` ${colors.cyan('relq resolve --theirs <name>')} Take remote version`);
|
|
332
|
+
console.log(` ${colors.cyan('relq resolve --ours <name>')} Keep local version`);
|
|
333
|
+
console.log(` ${colors.cyan('relq resolve --all-theirs')} Take all remote`);
|
|
334
|
+
console.log(` ${colors.cyan('relq pull --force')} Force overwrite local`);
|
|
335
|
+
console.log('');
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
284
338
|
if (added.length === 0 && removed.length === 0) {
|
|
285
339
|
console.log(`${colors.green('✓')} Already up to date with remote`);
|
|
286
340
|
console.log('');
|
|
@@ -372,3 +426,65 @@ export async function pullCommand(context) {
|
|
|
372
426
|
process.exit(1);
|
|
373
427
|
}
|
|
374
428
|
}
|
|
429
|
+
function detectObjectConflicts(local, remote) {
|
|
430
|
+
const conflicts = [];
|
|
431
|
+
for (const localTable of local.tables) {
|
|
432
|
+
const remoteTable = remote.tables.find(t => t.name === localTable.name);
|
|
433
|
+
if (!remoteTable)
|
|
434
|
+
continue;
|
|
435
|
+
for (const localCol of localTable.columns) {
|
|
436
|
+
const remoteCol = remoteTable.columns.find(c => c.name === localCol.name);
|
|
437
|
+
if (!remoteCol)
|
|
438
|
+
continue;
|
|
439
|
+
if (localCol.type !== remoteCol.type) {
|
|
440
|
+
conflicts.push({
|
|
441
|
+
objectType: 'COLUMN',
|
|
442
|
+
objectName: localCol.name,
|
|
443
|
+
parentName: localTable.name,
|
|
444
|
+
localValue: localCol.type,
|
|
445
|
+
remoteValue: remoteCol.type,
|
|
446
|
+
description: `Type changed: ${localCol.type} → ${remoteCol.type}`,
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
else if (localCol.nullable !== remoteCol.nullable) {
|
|
450
|
+
conflicts.push({
|
|
451
|
+
objectType: 'COLUMN',
|
|
452
|
+
objectName: localCol.name,
|
|
453
|
+
parentName: localTable.name,
|
|
454
|
+
localValue: localCol.nullable,
|
|
455
|
+
remoteValue: remoteCol.nullable,
|
|
456
|
+
description: `Nullable changed: ${localCol.nullable} → ${remoteCol.nullable}`,
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
else if (String(localCol.default || '') !== String(remoteCol.default || '')) {
|
|
460
|
+
if (localCol.default && remoteCol.default && localCol.default !== remoteCol.default) {
|
|
461
|
+
conflicts.push({
|
|
462
|
+
objectType: 'COLUMN',
|
|
463
|
+
objectName: localCol.name,
|
|
464
|
+
parentName: localTable.name,
|
|
465
|
+
localValue: localCol.default,
|
|
466
|
+
remoteValue: remoteCol.default,
|
|
467
|
+
description: `Default changed`,
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
for (const localEnum of local.enums) {
|
|
474
|
+
const remoteEnum = remote.enums.find(e => e.name === localEnum.name);
|
|
475
|
+
if (!remoteEnum)
|
|
476
|
+
continue;
|
|
477
|
+
const localVals = JSON.stringify(localEnum.values.sort());
|
|
478
|
+
const remoteVals = JSON.stringify(remoteEnum.values.sort());
|
|
479
|
+
if (localVals !== remoteVals) {
|
|
480
|
+
conflicts.push({
|
|
481
|
+
objectType: 'ENUM',
|
|
482
|
+
objectName: localEnum.name,
|
|
483
|
+
localValue: localEnum.values,
|
|
484
|
+
remoteValue: remoteEnum.values,
|
|
485
|
+
description: `Values differ`,
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
return conflicts;
|
|
490
|
+
}
|