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.
Files changed (62) hide show
  1. package/README.md +246 -186
  2. package/__tests__/acceptCurrent.test.js +117 -0
  3. package/__tests__/columnAttributes.test.js +101 -0
  4. package/__tests__/options.test.js +259 -0
  5. package/__tests__/parser.test.js +167 -166
  6. package/__tests__/parser2.test.js +155 -152
  7. package/__tests__/paths.test.js +21 -0
  8. package/__tests__/prompt.test.js +106 -0
  9. package/__tests__/sync.test.js +348 -0
  10. package/__tests__/templateDrift.test.js +72 -0
  11. package/__tests__/versions.test.js +138 -0
  12. package/dist/index.cjs +4070 -1730
  13. package/dist/index.js +4106 -1768
  14. package/package.json +66 -59
  15. package/rollup.config.js +25 -24
  16. package/src/DBStructureChecker.js +47 -0
  17. package/src/acceptCurrent.js +225 -0
  18. package/src/apply.js +481 -348
  19. package/src/cli.js +61 -34
  20. package/src/compare.js +106 -92
  21. package/src/differences.js +258 -212
  22. package/src/dump.js +63 -54
  23. package/src/getters.js +151 -139
  24. package/src/index.js +223 -170
  25. package/src/loggers.js +156 -139
  26. package/src/normalizers.js +28 -6
  27. package/src/options.js +133 -0
  28. package/src/parsers.js +197 -145
  29. package/src/paths.js +41 -0
  30. package/src/prompt.js +109 -0
  31. package/src/rebuild.js +106 -0
  32. package/src/rows.js +46 -0
  33. package/src/socketServer.js +311 -229
  34. package/src/templateDrift.js +93 -0
  35. package/src/versions.js +112 -0
  36. package/ui/README.md +55 -40
  37. package/ui/dist/spa/assets/KFOMCnqEu92Fr1ME7kSn66aGLdTylUAMQXC89YmC2DPNWuYjalmUiAw-k1h7X_-h.woff +0 -0
  38. package/ui/dist/spa/assets/KFOMCnqEu92Fr1ME7kSn66aGLdTylUAMQXC89YmC2DPNWuZtalmUiAw-B7du-70m.woff +0 -0
  39. package/ui/dist/spa/assets/KFOMCnqEu92Fr1ME7kSn66aGLdTylUAMQXC89YmC2DPNWuaabVmUiAw-CoAZ_DKt.woff +0 -0
  40. package/ui/dist/spa/assets/KFOMCnqEu92Fr1ME7kSn66aGLdTylUAMQXC89YmC2DPNWub2bVmUiAw-D0406B4n.woff +0 -0
  41. package/ui/dist/spa/assets/KFOMCnqEu92Fr1ME7kSn66aGLdTylUAMQXC89YmC2DPNWubEbFmUiAw-CnAg2DeQ.woff +0 -0
  42. package/ui/dist/spa/assets/KFOMCnqEu92Fr1ME7kSn66aGLdTylUAMQXC89YmC2DPNWubEbVmUiAw-BG9VWE5v.woff +0 -0
  43. package/ui/dist/spa/assets/MainLayout-DLH1Kfg-.js +1 -0
  44. package/ui/dist/spa/assets/axios-2hQ_j_Tz.js +1 -0
  45. package/ui/dist/spa/assets/flUhRq6tzZclQEJ-Vdg-IuiaDsNa-OUIwM9U8.woff +0 -0
  46. package/ui/dist/spa/assets/index-Cqrg985P.js +23 -0
  47. package/ui/dist/spa/assets/index-pOWtXlXQ.css +1 -0
  48. package/ui/dist/spa/index.html +2 -2
  49. package/ui/package.json +43 -42
  50. package/ui/src/components/DefinitionDiff.vue +169 -0
  51. package/ui/src/pages/MainComponent.vue +704 -78
  52. package/ui/dist/spa/assets/KFOkCnqEu92Fr1MmgVxIIzQ-Y2mpUUkw.woff +0 -0
  53. package/ui/dist/spa/assets/KFOlCnqEu92Fr1MmEU9fBBc--ViM7Ag4Q.woff +0 -0
  54. package/ui/dist/spa/assets/KFOlCnqEu92Fr1MmSU5fBBc--CrFzpgl6.woff +0 -0
  55. package/ui/dist/spa/assets/KFOlCnqEu92Fr1MmWUlfBBc--CW3_XjG_.woff +0 -0
  56. package/ui/dist/spa/assets/KFOlCnqEu92Fr1MmYUtfBBc--Dr8fnyGc.woff +0 -0
  57. package/ui/dist/spa/assets/KFOmCnqEu92Fr1Mu4mxM-DAYh6T4l.woff +0 -0
  58. package/ui/dist/spa/assets/MainLayout-CvQD3xKr.js +0 -1
  59. package/ui/dist/spa/assets/axios-C0XcclIb.js +0 -1
  60. package/ui/dist/spa/assets/flUhRq6tzZclQEJ-Vdg-IuiaDsNa-Dr0goTwe.woff +0 -0
  61. package/ui/dist/spa/assets/index-CwWo8xmP.js +0 -40
  62. 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
- export const cli = () => {
6
- const argv = yargs(hideBin(process.argv))
7
- .command('dump', 'Dump the database structure to files')
8
- .command('compare', 'Compare the database structure with JSON dumps (dry run)')
9
- .command('update', 'Update the database structure based on JSON dumps')
10
- .command('ui', 'Open the UI')
11
- .option('uiPort', { alias: 'U', describe: 'UI port', default: 3600 })
12
- .option('openUI', { alias: 'O', describe: 'Open UI', default: true })
13
- .option('host', { alias: 'h', describe: 'Database host', default: 'localhost' })
14
- .option('port', { alias: 'P', describe: 'Database port', default: 3306 })
15
- .option('user', { alias: 'u', describe: 'Database user', default: 'root' })
16
- .option('password', { alias: 'p', describe: 'Database password', default: '' })
17
- .option('database', { alias: 'd', describe: 'Target database', default: null })
18
- .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' })
19
- .option('verbose', { alias: 'v', describe: 'Enable verbose logging', type: 'boolean', default: false })
20
- // .demandCommand(1)
21
- .help()
22
- .argv;
23
-
24
- const command = argv._[0];
25
- const config = {
26
- host: argv.host,
27
- port: argv.port,
28
- user: argv.user,
29
- password: argv.password,
30
- };
31
-
32
- setVerbose(argv.verbose);
33
-
34
- return { command, config, argv };
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
- export const compareAllDatabases = async (connection, inputDir, update = false, command, options) => {
9
- if (connection.state === 'disconnected') {
10
- await connection.reconnect(); // Ensure the connection is open
11
- }
12
- // Read the expected databases from the input directory
13
- let expectedDatabases = await fs.readdir(inputDir);
14
- //filter out diffs.json
15
- expectedDatabases = expectedDatabases.filter(file => file !== 'diffs.json');
16
- //filter out dumpInfo.json
17
- expectedDatabases = expectedDatabases.filter(file => file !== 'dumpInfo.json');
18
- logger('Expected databases from input directory:', expectedDatabases);
19
-
20
- // Check existing databases
21
- const existingDatabases = await getDatabases(connection);
22
- // console.log('Existing databases:', existingDatabases);
23
- const allDifferences = {};
24
- const missingDatabases = [];
25
- logger('All differences:', allDifferences);
26
-
27
- // Note missing databases and prepare to create them
28
- for (const database of expectedDatabases) {
29
- if(options && options.database && options.database !== database){
30
- continue;
31
- }
32
-
33
- if (!existingDatabases.includes(database)) {
34
- allDifferences[database] = [{
35
- database: database,
36
- type: 'missing_database',
37
- message: 'Database not found'
38
- }];
39
- missingDatabases.push(database);
40
- }
41
- }
42
-
43
- // Create missing databases at the beginning
44
- if (missingDatabases.length > 0 && update) {
45
- logger('Creating missing databases:');
46
- for (const database of missingDatabases) {
47
- logger(`Creating database ${database}...`);
48
- await connection.query(`CREATE DATABASE \`${database}\`;`);
49
- logger(`Database ${database} created.`);
50
- }
51
- }else if (missingDatabases.length > 0 && !update){
52
- logger('Missing databases found but update is not enabled. Please enable update to create missing databases.');
53
- logger('Please run the command again with the --update flag to create missing databases.');
54
- logger('Missing databases:');
55
- logger(missingDatabases);
56
- } else {
57
- logger('All expected databases exist.');
58
- }
59
-
60
- for (const database of expectedDatabases) {
61
- if(options && options.database && options.database !== database){
62
- continue;
63
- }
64
- if(missingDatabases.includes(database)){
65
- logger('Skipping database', database, 'because it is missing');
66
- continue;
67
- }
68
- const filePath = path.join(inputDir, database, `${database}.json`);
69
-
70
- try {
71
- const fileContent = await fs.readFile(filePath, 'utf-8');
72
- const expectedStructure = JSON.parse(fileContent);
73
- logger('Getting database structure for', database);
74
- const currentStructure = await getDatabaseStructure(connection, database, command, options);
75
- logger('Database structure for', database, 'obtained');
76
- const differences = findDifferences(expectedStructure, currentStructure);
77
- logger('Differences for', database, 'obtained');
78
- if (!update) {
79
- logger('Logging differences for', database);
80
- logDifferences(differences, database, true);
81
- } else {
82
- logger('Logging differences for', database);
83
- logDifferences(differences, database, false);
84
- logger('Applying differences for', database);
85
- await applyDifferences(connection, database, differences);
86
- }
87
- allDifferences[database] = differences;
88
- } catch (err) {
89
- logger(`Error processing database ${database}:`, err.message);
90
- }
91
- }
92
- return allDifferences;
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
  }