mysqlsyncr 1.1.12 → 1.2.1
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/README.md +59 -0
- package/__tests__/acceptCurrent.test.js +117 -0
- package/__tests__/columnAttributes.test.js +101 -0
- package/__tests__/options.test.js +259 -0
- package/__tests__/parser.test.js +167 -166
- package/__tests__/parser2.test.js +155 -152
- package/__tests__/paths.test.js +21 -0
- package/__tests__/prompt.test.js +106 -0
- package/__tests__/sync.test.js +348 -0
- package/__tests__/templateDrift.test.js +72 -0
- package/__tests__/versions.test.js +138 -0
- package/dist/index.cjs +4059 -1733
- package/dist/index.js +4101 -1777
- package/package.json +66 -59
- package/rollup.config.js +2 -1
- package/src/DBStructureChecker.js +47 -0
- package/src/acceptCurrent.js +225 -0
- package/src/apply.js +254 -136
- package/src/cli.js +61 -34
- package/src/compare.js +106 -92
- package/src/differences.js +258 -212
- package/src/dump.js +63 -54
- package/src/getters.js +151 -139
- package/src/index.js +223 -169
- package/src/loggers.js +156 -139
- package/src/normalizers.js +28 -6
- package/src/options.js +133 -0
- package/src/parsers.js +82 -36
- package/src/paths.js +41 -0
- package/src/prompt.js +109 -0
- package/src/rebuild.js +106 -0
- package/src/rows.js +46 -0
- package/src/socketServer.js +106 -24
- package/src/templateDrift.js +93 -0
- package/src/versions.js +112 -0
- package/ui/README.md +55 -40
- package/ui/dist/spa/assets/KFOMCnqEu92Fr1ME7kSn66aGLdTylUAMQXC89YmC2DPNWuYjalmUiAw-k1h7X_-h.woff +0 -0
- package/ui/dist/spa/assets/KFOMCnqEu92Fr1ME7kSn66aGLdTylUAMQXC89YmC2DPNWuZtalmUiAw-B7du-70m.woff +0 -0
- package/ui/dist/spa/assets/KFOMCnqEu92Fr1ME7kSn66aGLdTylUAMQXC89YmC2DPNWuaabVmUiAw-CoAZ_DKt.woff +0 -0
- package/ui/dist/spa/assets/KFOMCnqEu92Fr1ME7kSn66aGLdTylUAMQXC89YmC2DPNWub2bVmUiAw-D0406B4n.woff +0 -0
- package/ui/dist/spa/assets/KFOMCnqEu92Fr1ME7kSn66aGLdTylUAMQXC89YmC2DPNWubEbFmUiAw-CnAg2DeQ.woff +0 -0
- package/ui/dist/spa/assets/KFOMCnqEu92Fr1ME7kSn66aGLdTylUAMQXC89YmC2DPNWubEbVmUiAw-BG9VWE5v.woff +0 -0
- package/ui/dist/spa/assets/MainLayout-DLH1Kfg-.js +1 -0
- package/ui/dist/spa/assets/axios-2hQ_j_Tz.js +1 -0
- package/ui/dist/spa/assets/flUhRq6tzZclQEJ-Vdg-IuiaDsNa-OUIwM9U8.woff +0 -0
- package/ui/dist/spa/assets/index-Cqrg985P.js +23 -0
- package/ui/dist/spa/assets/index-pOWtXlXQ.css +1 -0
- package/ui/dist/spa/index.html +2 -2
- package/ui/package.json +43 -42
- package/ui/src/components/DefinitionDiff.vue +169 -0
- package/ui/src/pages/MainComponent.vue +704 -78
- package/ui/dist/spa/assets/KFOkCnqEu92Fr1MmgVxIIzQ-Y2mpUUkw.woff +0 -0
- package/ui/dist/spa/assets/KFOlCnqEu92Fr1MmEU9fBBc--ViM7Ag4Q.woff +0 -0
- package/ui/dist/spa/assets/KFOlCnqEu92Fr1MmSU5fBBc--CrFzpgl6.woff +0 -0
- package/ui/dist/spa/assets/KFOlCnqEu92Fr1MmWUlfBBc--CW3_XjG_.woff +0 -0
- package/ui/dist/spa/assets/KFOlCnqEu92Fr1MmYUtfBBc--Dr8fnyGc.woff +0 -0
- package/ui/dist/spa/assets/KFOmCnqEu92Fr1Mu4mxM-DAYh6T4l.woff +0 -0
- package/ui/dist/spa/assets/MainLayout-CvQD3xKr.js +0 -1
- package/ui/dist/spa/assets/axios-C0XcclIb.js +0 -1
- package/ui/dist/spa/assets/flUhRq6tzZclQEJ-Vdg-IuiaDsNa-Dr0goTwe.woff +0 -0
- package/ui/dist/spa/assets/index-CwWo8xmP.js +0 -40
- package/ui/dist/spa/assets/index-kXpA8n8r.css +0 -5
package/src/index.js
CHANGED
|
@@ -1,169 +1,223 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import
|
|
13
|
-
import
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
try {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { cli } from './cli.js';
|
|
4
|
+
import { DBStructureChecker } from './DBStructureChecker.js';
|
|
5
|
+
import { dumpAllDatabases, getDatabasesFromExistingDumps } from './dump.js';
|
|
6
|
+
import { compareAllDatabases } from './compare.js';
|
|
7
|
+
import { applyDifferences, describeFailures } from './apply.js';
|
|
8
|
+
import { startApiServer } from './socketServer.js'; // Import the new socket server
|
|
9
|
+
import { rebuildTemplate } from './rebuild.js';
|
|
10
|
+
import { assertOutsidePackage, templateFolderProblem } from './paths.js';
|
|
11
|
+
import { getVersionInfo } from './versions.js';
|
|
12
|
+
import { acceptCurrentIntoTemplate } from './acceptCurrent.js';
|
|
13
|
+
import open from 'open';
|
|
14
|
+
import fs from 'fs';
|
|
15
|
+
import path from 'path';
|
|
16
|
+
import { fileURLToPath } from 'node:url';
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
const compare = async (config, output) => {
|
|
20
|
+
return new Promise(async (resolve, reject) => {
|
|
21
|
+
let checker
|
|
22
|
+
try {
|
|
23
|
+
checker = new DBStructureChecker(config);
|
|
24
|
+
// Without this a refused connection is an unhandled 'error' event and the promise never settles
|
|
25
|
+
checker.connection.on('error', (err) => {
|
|
26
|
+
reject({ type: 'error', error: err.message });
|
|
27
|
+
});
|
|
28
|
+
checker.connection.on('connect', async () => {
|
|
29
|
+
try {
|
|
30
|
+
const diffs = await compareAllDatabases(checker.connection, output, false, 'compare', { output: output });
|
|
31
|
+
resolve({ type: 'success', diffs });
|
|
32
|
+
} catch (e) {
|
|
33
|
+
// e.g. an invalid syncOptions.json
|
|
34
|
+
reject({ type: 'error', error: e.message });
|
|
35
|
+
} finally {
|
|
36
|
+
await checker.closeConnection();
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
} catch (e) {
|
|
40
|
+
reject({ type: 'error', error: e.message });
|
|
41
|
+
if (checker) await checker.closeConnection();
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const apply = async (config, database, diffs) => {
|
|
47
|
+
return new Promise((resolve, reject) => {
|
|
48
|
+
let checker;
|
|
49
|
+
try {
|
|
50
|
+
checker = new DBStructureChecker(config, database);
|
|
51
|
+
|
|
52
|
+
checker.connection.on('error', async (err) => {
|
|
53
|
+
console.error('Database connection error in route apply:', err);
|
|
54
|
+
reject({ type: 'error', error: err.message });
|
|
55
|
+
await checker.closeConnection();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
checker.connection.on('connect', async () => {
|
|
59
|
+
console.log('Connected to the database');
|
|
60
|
+
checker.targetDatabase = database;
|
|
61
|
+
checker.command = 'apply';
|
|
62
|
+
checker.options = { config, database, diffs };
|
|
63
|
+
try {
|
|
64
|
+
console.log('Applying the database structure based on JSON dumps');
|
|
65
|
+
const result = await applyDifferences(checker.connection, database, diffs);
|
|
66
|
+
if (result.failures.length > 0) {
|
|
67
|
+
// Callers treat a resolved apply as done, so anything that failed rejects
|
|
68
|
+
reject({ type: 'error', error: describeFailures(result), failures: result.failures, warnings: result.warnings });
|
|
69
|
+
} else {
|
|
70
|
+
resolve({ type: 'success', message: `Applied ${result.attempted} of ${result.attempted}`, warnings: result.warnings });
|
|
71
|
+
}
|
|
72
|
+
await checker.closeConnection();
|
|
73
|
+
} catch (err) {
|
|
74
|
+
reject({ type: 'error', error: err.message });
|
|
75
|
+
await checker.closeConnection();
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
} catch (e) {
|
|
79
|
+
reject({ type: 'error', error: e.message });
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export { DBStructureChecker, dumpAllDatabases, compare, startApiServer, apply, getDatabasesFromExistingDumps, rebuildTemplate, acceptCurrentIntoTemplate };
|
|
85
|
+
|
|
86
|
+
// Show which MySQL versions are being compared, and what that means
|
|
87
|
+
const printVersions = async (connection, templateDir) => {
|
|
88
|
+
const { server, template, notes } = await getVersionInfo(connection, templateDir);
|
|
89
|
+
const templateText = template
|
|
90
|
+
? `${template.version}${template.inferred ? ` (inferred: ${template.reason})` : ''}`
|
|
91
|
+
: 'unknown';
|
|
92
|
+
console.log(`MySQL server: ${server ?? 'unknown'}; template: ${templateText}`);
|
|
93
|
+
for (const note of notes) console.log(` Note: ${note}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Only run this if this is the main module
|
|
97
|
+
const isMain = (() => {
|
|
98
|
+
// CommonJS build (dist/index.cjs, the mysqlsyncr bin)
|
|
99
|
+
if (typeof require !== 'undefined' && typeof module !== 'undefined') {
|
|
100
|
+
return require.main === module;
|
|
101
|
+
}
|
|
102
|
+
// ES module (node src/index.js): compare real paths, since the bin may be a symlink
|
|
103
|
+
try {
|
|
104
|
+
return !!process.argv[1] && fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url));
|
|
105
|
+
} catch (e) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
})();
|
|
109
|
+
|
|
110
|
+
if (isMain) {
|
|
111
|
+
(async () => {
|
|
112
|
+
const { command, config, argv } = await cli();
|
|
113
|
+
if (!command) {
|
|
114
|
+
process.exitCode = 1;
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
// dump writes the template, compare writes diffs.json and rebuild --write rewrites the template
|
|
118
|
+
if (command === 'dump' || command === 'compare' || (command === 'rebuild' && argv.write)) {
|
|
119
|
+
try {
|
|
120
|
+
assertOutsidePackage(argv.output);
|
|
121
|
+
} catch (err) {
|
|
122
|
+
console.error(err.message);
|
|
123
|
+
process.exitCode = 1;
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// compare, update and rebuild read an existing template; an empty folder would compare nothing
|
|
128
|
+
if (command === 'compare' || command === 'update' || command === 'rebuild') {
|
|
129
|
+
const problem = templateFolderProblem(argv.output);
|
|
130
|
+
if (problem) {
|
|
131
|
+
console.error(`Template folder: ${problem}`);
|
|
132
|
+
process.exitCode = 1;
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (command === 'dump' && !fs.existsSync(argv.output)) {
|
|
137
|
+
fs.mkdirSync(argv.output, { recursive: true });
|
|
138
|
+
console.log(`Created ${path.resolve(argv.output)}`);
|
|
139
|
+
}
|
|
140
|
+
console.log("MySQLsyncr started via command line")
|
|
141
|
+
if (command === 'ui') {
|
|
142
|
+
const PORT = argv.uiPort
|
|
143
|
+
startApiServer(PORT, argv); // Start the Socket.IO server
|
|
144
|
+
console.log('Server started on port', PORT);
|
|
145
|
+
console.log('Open http://localhost:' + PORT + ' in your browser to use the UI');
|
|
146
|
+
if (argv.openUI && argv.openUI === true || argv.openUI === 'true') {
|
|
147
|
+
await open(`http://localhost:${PORT}`);
|
|
148
|
+
}
|
|
149
|
+
} else if (command === 'rebuild') {
|
|
150
|
+
// Works on the files only; no database connection needed
|
|
151
|
+
try {
|
|
152
|
+
const { changes: report, notes } = await rebuildTemplate(argv.output, { write: argv.write });
|
|
153
|
+
for (const [table, changes] of Object.entries(report)) {
|
|
154
|
+
console.log(`\n${table}`);
|
|
155
|
+
for (const change of changes) console.log(` ${change}`);
|
|
156
|
+
}
|
|
157
|
+
const count = Object.keys(report).length;
|
|
158
|
+
console.log(`\n${count} table(s) ${argv.write ? 'rebuilt' : 'would change. Run again with --write to rebuild them'}`);
|
|
159
|
+
if (Object.keys(notes).length > 0) {
|
|
160
|
+
console.log('\nCheck by hand:');
|
|
161
|
+
for (const [table, note] of Object.entries(notes)) console.log(` ${table}: ${note}`);
|
|
162
|
+
}
|
|
163
|
+
} catch (err) {
|
|
164
|
+
console.error(err.message);
|
|
165
|
+
process.exitCode = 1;
|
|
166
|
+
}
|
|
167
|
+
} else {
|
|
168
|
+
// Handle other commands (dump, compare, update) as before
|
|
169
|
+
console.log(`Connecting to MySQL as ${config.user}@${config.host}:${config.port}${config.password ? '' : ' with no password'}`);
|
|
170
|
+
const checker = new DBStructureChecker(config, argv.database);
|
|
171
|
+
checker.connection.on('error', (err) => {
|
|
172
|
+
console.error(`MySQL connection error (${config.user}@${config.host}:${config.port}): ${err.message}`);
|
|
173
|
+
if (err.code === 'ER_ACCESS_DENIED_ERROR') {
|
|
174
|
+
// e.g. 'root'@'172.17.0.1' when MySQL runs in Docker
|
|
175
|
+
console.error(`The user and password were rejected. The address in MySQL's message is where the server saw the connection come from, not the host connected to.`);
|
|
176
|
+
}
|
|
177
|
+
process.exitCode = 1;
|
|
178
|
+
});
|
|
179
|
+
checker.connection.on('connect', async () => {
|
|
180
|
+
console.log('Connected to the database');
|
|
181
|
+
checker.targetDatabase = argv.database;
|
|
182
|
+
checker.command = command;
|
|
183
|
+
checker.options = argv;
|
|
184
|
+
try {
|
|
185
|
+
if (command === 'dump') {
|
|
186
|
+
console.log('Dumping the database structure to files');
|
|
187
|
+
await dumpAllDatabases(checker.connection, command, argv);
|
|
188
|
+
} else if (command === 'compare') {
|
|
189
|
+
await printVersions(checker.connection, argv.output);
|
|
190
|
+
console.log('Comparing the database structure with JSON dumps (dry run)');
|
|
191
|
+
let diffs = await compareAllDatabases(checker.connection, argv.output, false, command, argv); // Dry run
|
|
192
|
+
let totalDiffs = Object.values(diffs).reduce((acc, arr) => acc + arr.length, 0);
|
|
193
|
+
console.log('Total differences count:', totalDiffs);
|
|
194
|
+
fs.writeFileSync(path.join(argv.output, 'diffs.json'), JSON.stringify(diffs, null, 4));
|
|
195
|
+
} else if (command === 'update') {
|
|
196
|
+
await printVersions(checker.connection, argv.output);
|
|
197
|
+
console.log('Updating the database structure based on JSON dumps');
|
|
198
|
+
let totalDiffs = Infinity; // Initialize with a large number
|
|
199
|
+
let previousDiffs = totalDiffs; // Store previous differences
|
|
200
|
+
let runCount = 0; // Counter for the number of runs
|
|
201
|
+
let maxRuns = 6;
|
|
202
|
+
while (runCount < maxRuns && totalDiffs > 0 && (totalDiffs < previousDiffs || previousDiffs === Infinity)) {
|
|
203
|
+
console.log('Running update command. Run count:', runCount);
|
|
204
|
+
let diffs = await compareAllDatabases(checker.connection, argv.output, true, command, argv); // Apply updates
|
|
205
|
+
previousDiffs = totalDiffs; // Update previous differences
|
|
206
|
+
totalDiffs = Object.values(diffs).reduce((acc, arr) => acc + arr.length, 0);
|
|
207
|
+
console.log('Total differences count:', totalDiffs);
|
|
208
|
+
runCount++;
|
|
209
|
+
if (totalDiffs > 0 && runCount < maxRuns) {
|
|
210
|
+
console.log('Total differences count is still greater than 0. Waiting 1 second before next run. Run count:', runCount);
|
|
211
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
} catch (err) {
|
|
216
|
+
console.error(err.message);
|
|
217
|
+
} finally {
|
|
218
|
+
await checker.closeConnection();
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
})();
|
|
223
|
+
}
|