mysqlsyncr 1.1.10 → 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 +246 -186
- 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 +4070 -1730
- package/dist/index.js +4106 -1768
- package/package.json +66 -59
- package/rollup.config.js +25 -24
- package/src/DBStructureChecker.js +47 -0
- package/src/acceptCurrent.js +225 -0
- package/src/apply.js +481 -348
- 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 -170
- package/src/loggers.js +156 -139
- package/src/normalizers.js +28 -6
- package/src/options.js +133 -0
- package/src/parsers.js +197 -145
- 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 +311 -229
- 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/cli.js
CHANGED
|
@@ -1,35 +1,62 @@
|
|
|
1
|
-
import yargs from 'yargs';
|
|
2
|
-
import { hideBin } from 'yargs/helpers';
|
|
3
|
-
import { setVerbose } from './loggers.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
.
|
|
15
|
-
.
|
|
16
|
-
.
|
|
17
|
-
.
|
|
18
|
-
.
|
|
19
|
-
.option('
|
|
20
|
-
|
|
21
|
-
.
|
|
22
|
-
.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
1
|
+
import yargs from 'yargs';
|
|
2
|
+
import { hideBin } from 'yargs/helpers';
|
|
3
|
+
import { setVerbose } from './loggers.js';
|
|
4
|
+
import { promptForOptions, promptForPassword, DATABASE_COMMANDS } from './prompt.js';
|
|
5
|
+
|
|
6
|
+
const COMMANDS = ['dump', 'compare', 'update', 'ui', 'rebuild'];
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Parse the command line. With no command, asks for the command and its options
|
|
10
|
+
* in a terminal, or prints the help otherwise. command is null when there's nothing to run.
|
|
11
|
+
*/
|
|
12
|
+
export const cli = async () => {
|
|
13
|
+
const parser = yargs(hideBin(process.argv))
|
|
14
|
+
.command('dump', 'Dump the database structure to files')
|
|
15
|
+
.command('compare', 'Compare the database structure with JSON dumps (dry run)')
|
|
16
|
+
.command('update', 'Update the database structure based on JSON dumps')
|
|
17
|
+
.command('ui', 'Open the UI')
|
|
18
|
+
.command('rebuild', "Rebuild the dump's JSON from each table's CREATE TABLE and list what changes (dry run unless --write)")
|
|
19
|
+
.option('uiPort', { alias: 'U', describe: 'UI port', default: 3600 })
|
|
20
|
+
.option('openUI', { alias: 'O', describe: 'Open UI', default: true })
|
|
21
|
+
.option('host', { alias: 'h', describe: 'Database host', default: 'localhost' })
|
|
22
|
+
.option('port', { alias: 'P', describe: 'Database port', default: 3306 })
|
|
23
|
+
.option('user', { alias: 'u', describe: 'Database user', default: 'root' })
|
|
24
|
+
// No default, so a missing password can be asked for; -p with no value means none
|
|
25
|
+
.option('password', { alias: 'p', describe: 'Database password (asked for in a terminal when not given)', type: 'string' })
|
|
26
|
+
.option('database', { alias: 'd', describe: 'Target database', default: null })
|
|
27
|
+
.option('output', { alias: 'o', describe: 'Output directory for dumping. default is one level up from the current directory in a new folder called db-dump', default: '../db-dump' })
|
|
28
|
+
.option('write', { describe: 'With rebuild, write the rebuilt files', type: 'boolean', default: false })
|
|
29
|
+
.option('verbose', { alias: 'v', describe: 'Enable verbose logging', type: 'boolean', default: false })
|
|
30
|
+
// .demandCommand(1)
|
|
31
|
+
.help();
|
|
32
|
+
const argv = parser.argv;
|
|
33
|
+
|
|
34
|
+
let command = argv._[0];
|
|
35
|
+
if (command === undefined) {
|
|
36
|
+
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
37
|
+
command = await promptForOptions(argv);
|
|
38
|
+
} else {
|
|
39
|
+
parser.showHelp();
|
|
40
|
+
command = null;
|
|
41
|
+
}
|
|
42
|
+
} else if (!COMMANDS.includes(command)) {
|
|
43
|
+
console.error(`Unknown command: ${command}. Use one of: ${COMMANDS.join(', ')}`);
|
|
44
|
+
command = null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const interactive = process.stdin.isTTY && process.stdout.isTTY;
|
|
48
|
+
if (DATABASE_COMMANDS.includes(command) && argv.password === undefined) {
|
|
49
|
+
argv.password = interactive ? await promptForPassword(argv) : '';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const config = {
|
|
53
|
+
host: argv.host,
|
|
54
|
+
port: argv.port,
|
|
55
|
+
user: argv.user,
|
|
56
|
+
password: argv.password,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
setVerbose(argv.verbose);
|
|
60
|
+
|
|
61
|
+
return { command, config, argv };
|
|
35
62
|
};
|
package/src/compare.js
CHANGED
|
@@ -1,93 +1,107 @@
|
|
|
1
|
-
import fs from 'fs/promises';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { getDatabases, getDatabaseStructure } from './getters.js';
|
|
4
|
-
import { findDifferences } from './differences.js';
|
|
5
|
-
import { logDifferences, logger } from './loggers.js';
|
|
6
|
-
import { applyDifferences } from './apply.js';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
expectedDatabases =
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const
|
|
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
|
-
logger('
|
|
54
|
-
logger('
|
|
55
|
-
logger(
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { getDatabases, getDatabaseStructure } from './getters.js';
|
|
4
|
+
import { findDifferences } from './differences.js';
|
|
5
|
+
import { logDifferences, logger } from './loggers.js';
|
|
6
|
+
import { applyDifferences, describeFailures } from './apply.js';
|
|
7
|
+
import { loadSyncOptions, getTableOptions, applyDefinerOption } from './options.js';
|
|
8
|
+
import { findMissingRows } from './rows.js';
|
|
9
|
+
|
|
10
|
+
export const compareAllDatabases = async (connection, inputDir, update = false, command, options) => {
|
|
11
|
+
if (connection.state === 'disconnected') {
|
|
12
|
+
await connection.reconnect(); // Ensure the connection is open
|
|
13
|
+
}
|
|
14
|
+
// Each expected database is a folder in the input directory; files like diffs.json,
|
|
15
|
+
// dumpInfo.json and syncOptions.json sit alongside them
|
|
16
|
+
const entries = await fs.readdir(inputDir, { withFileTypes: true });
|
|
17
|
+
const expectedDatabases = entries.filter(entry => entry.isDirectory()).map(entry => entry.name);
|
|
18
|
+
const syncOptions = await loadSyncOptions(inputDir);
|
|
19
|
+
logger('Expected databases from input directory:', expectedDatabases);
|
|
20
|
+
|
|
21
|
+
// Check existing databases
|
|
22
|
+
const existingDatabases = await getDatabases(connection);
|
|
23
|
+
// console.log('Existing databases:', existingDatabases);
|
|
24
|
+
const allDifferences = {};
|
|
25
|
+
const missingDatabases = [];
|
|
26
|
+
logger('All differences:', allDifferences);
|
|
27
|
+
|
|
28
|
+
// Note missing databases and prepare to create them
|
|
29
|
+
for (const database of expectedDatabases) {
|
|
30
|
+
if(options && options.database && options.database !== database){
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!existingDatabases.includes(database)) {
|
|
35
|
+
allDifferences[database] = [{
|
|
36
|
+
database: database,
|
|
37
|
+
type: 'missing_database',
|
|
38
|
+
message: 'Database not found'
|
|
39
|
+
}];
|
|
40
|
+
missingDatabases.push(database);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Create missing databases at the beginning
|
|
45
|
+
if (missingDatabases.length > 0 && update) {
|
|
46
|
+
logger('Creating missing databases:');
|
|
47
|
+
for (const database of missingDatabases) {
|
|
48
|
+
logger(`Creating database ${database}...`);
|
|
49
|
+
await connection.query(`CREATE DATABASE \`${database}\`;`);
|
|
50
|
+
logger(`Database ${database} created.`);
|
|
51
|
+
}
|
|
52
|
+
}else if (missingDatabases.length > 0 && !update){
|
|
53
|
+
logger('Missing databases found but update is not enabled. Please enable update to create missing databases.');
|
|
54
|
+
logger('Please run the command again with the --update flag to create missing databases.');
|
|
55
|
+
logger('Missing databases:');
|
|
56
|
+
logger(missingDatabases);
|
|
57
|
+
} else {
|
|
58
|
+
logger('All expected databases exist.');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
for (const database of expectedDatabases) {
|
|
62
|
+
if(options && options.database && options.database !== database){
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if(missingDatabases.includes(database)){
|
|
66
|
+
logger('Skipping database', database, 'because it is missing');
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const filePath = path.join(inputDir, database, `${database}.json`);
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const fileContent = await fs.readFile(filePath, 'utf-8');
|
|
73
|
+
const expectedStructure = applyDefinerOption(JSON.parse(fileContent), syncOptions.definer);
|
|
74
|
+
logger('Getting database structure for', database);
|
|
75
|
+
const currentStructure = await getDatabaseStructure(connection, database, command, options);
|
|
76
|
+
logger('Database structure for', database, 'obtained');
|
|
77
|
+
const differences = findDifferences(expectedStructure, currentStructure, (tableName) => getTableOptions(syncOptions, database, tableName));
|
|
78
|
+
// A seed-row problem shouldn't hide this database's structure differences
|
|
79
|
+
try {
|
|
80
|
+
differences.push(...await findMissingRows(connection, database, expectedStructure, currentStructure, syncOptions));
|
|
81
|
+
} catch (err) {
|
|
82
|
+
console.error(`Could not check seed rows for ${database}: ${err.message}`);
|
|
83
|
+
}
|
|
84
|
+
logger('Differences for', database, 'obtained');
|
|
85
|
+
if (!update) {
|
|
86
|
+
logger('Logging differences for', database);
|
|
87
|
+
logDifferences(differences, database, true);
|
|
88
|
+
} else {
|
|
89
|
+
logger('Logging differences for', database);
|
|
90
|
+
logDifferences(differences, database, false);
|
|
91
|
+
logger('Applying differences for', database);
|
|
92
|
+
try {
|
|
93
|
+
const result = await applyDifferences(connection, database, differences);
|
|
94
|
+
if (result.failures.length > 0) {
|
|
95
|
+
console.error(`Error applying differences for ${database}: ${describeFailures(result)}`);
|
|
96
|
+
}
|
|
97
|
+
} catch (err) {
|
|
98
|
+
console.error(`Error applying differences for ${database}: ${err.message}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
allDifferences[database] = differences;
|
|
102
|
+
} catch (err) {
|
|
103
|
+
console.error(`Error processing database ${database}: ${err.message}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return allDifferences;
|
|
93
107
|
}
|