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
@@ -1,153 +1,156 @@
1
- import { parseCreateTableSQL, parseDatabaseStructure } from '../src/parsers';
2
-
3
-
4
-
5
- describe('Create Table Parser', () => {
6
-
7
- test('should correctly parse a valid create table statement. Check #1', () => {
8
- const input = `CREATE TABLE \`Alarms\` (
9
- \`alarmId\` int NOT NULL AUTO_INCREMENT,
10
- \`alarmRuleId\` int NOT NULL,
11
- \`alarmStatus\` int NOT NULL,
12
- \`alarmStatus_Text\` varchar(50) NOT NULL,
13
- \`alarmCreated_Date\` timestamp NULL DEFAULT NULL,
14
- \`alarmConfirmed_Date\` timestamp NULL DEFAULT NULL,
15
- \`alarmRecall_Date\` timestamp NULL DEFAULT NULL,
16
- \`activeAlarm\` tinyint(1) NOT NULL DEFAULT '0',
17
- \`emailSent\` tinyint(1) DEFAULT '0',
18
- \`emailSent_AnswerPlus\` tinyint(1) DEFAULT '0',
19
- \`ticketEmailSent\` tinyint(1) DEFAULT '0',
20
- \`type\` varchar(45) DEFAULT NULL,
21
- \`oorValue\` float(10,2) DEFAULT NULL,
22
- \`thresholdValue\` float(10,2) DEFAULT NULL,
23
- \`dateCreated\` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
24
- \`dateUpdated\` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
25
- PRIMARY KEY (\`alarmId\`),
26
- UNIQUE KEY \`UniqueAlarmRules\` (\`alarmRuleId\`),
27
- KEY \`idx_alarms_alarmruleid_alarmstatus\` (\`alarmRuleId\`, \`alarmStatus\`)
28
- ) ENGINE=InnoDB AUTO_INCREMENT=117 DEFAULT CHARSET=latin1 COMMENT='Alarm table for alarms '`;
29
-
30
-
31
- const expectedOutput = {
32
- "name": "Alarms",
33
- "columns": [
34
- {
35
- "Field": "alarmId",
36
- "Type": "int",
37
- "NotNull": true,
38
- "AutoIncrement": true
39
- },
40
- {
41
- "Field": "alarmRuleId",
42
- "Type": "int",
43
- "NotNull": true
44
- },
45
- {
46
- "Field": "alarmStatus",
47
- "Type": "int",
48
- "NotNull": true
49
- },
50
- {
51
- "Field": "alarmStatus_Text",
52
- "Type": "varchar(50)",
53
- "NotNull": true
54
- },
55
- {
56
- "Field": "alarmCreated_Date",
57
- "Type": "timestamp",
58
- "Default": "NULL"
59
- },
60
- {
61
- "Field": "alarmConfirmed_Date",
62
- "Type": "timestamp",
63
- "Default": "NULL"
64
- },
65
- {
66
- "Field": "alarmRecall_Date",
67
- "Type": "timestamp",
68
- "Default": "NULL"
69
- },
70
- {
71
- "Field": "activeAlarm",
72
- "Type": "tinyint(1)",
73
- "NotNull": true,
74
- "Default": "0"
75
- },
76
- {
77
- "Field": "emailSent",
78
- "Type": "tinyint(1)",
79
- "Default": "0"
80
- },
81
- {
82
- "Field": "emailSent_AnswerPlus",
83
- "Type": "tinyint(1)",
84
- "Default": "0"
85
- },
86
- {
87
- "Field": "ticketEmailSent",
88
- "Type": "tinyint(1)",
89
- "Default": "0"
90
- },
91
- {
92
- "Field": "type",
93
- "Type": "varchar(45)",
94
- "Default": "NULL"
95
- },
96
- {
97
- "Field": "oorValue",
98
- "Type": "float(10,2)"
99
- },
100
- {
101
- "Field": "thresholdValue",
102
- "Type": "float(10,2)"
103
- },
104
- {
105
- "Field": "dateCreated",
106
- "Type": "timestamp",
107
- "Default": "CURRENT_TIMESTAMP"
108
- },
109
- {
110
- "Field": "dateUpdated",
111
- "Type": "timestamp",
112
- "NotNull": true,
113
- "Default": "CURRENT_TIMESTAMP"
114
- }
115
- ],
116
- "indexes": [
117
- {
118
- "ColumnName": [
119
- "alarmId"
120
- ],
121
- "Primary": true,
122
- "Name": "PRIMARY"
123
- },
124
- {
125
- "ColumnName": [
126
- "alarmRuleId"
127
- ],
128
- "Unique": true,
129
- "Name": "UniqueAlarmRules"
130
- },
131
- {
132
- "ColumnName": [
133
- "alarmRuleId",
134
- "alarmStatus"
135
- ],
136
- "Name": "idx_alarms_alarmruleid_alarmstatus"
137
- }
138
- ],
139
- "constraints": [],
140
- "engine": null,
141
- "charset": null,
142
- "collate": null
143
- }
144
-
145
-
146
-
147
- const result = parseCreateTableSQL(input);
148
- console.log('Result:', JSON.stringify(result, null, 2))
149
- expect(result).toEqual(expectedOutput);
150
- });
151
-
152
-
1
+ import { parseCreateTableSQL, parseDatabaseStructure } from '../src/parsers';
2
+
3
+
4
+
5
+ describe('Create Table Parser', () => {
6
+
7
+ test('should correctly parse a valid create table statement. Check #1', () => {
8
+ const input = `CREATE TABLE \`Alarms\` (
9
+ \`alarmId\` int NOT NULL AUTO_INCREMENT,
10
+ \`alarmRuleId\` int NOT NULL,
11
+ \`alarmStatus\` int NOT NULL,
12
+ \`alarmStatus_Text\` varchar(50) NOT NULL,
13
+ \`alarmCreated_Date\` timestamp NULL DEFAULT NULL,
14
+ \`alarmConfirmed_Date\` timestamp NULL DEFAULT NULL,
15
+ \`alarmRecall_Date\` timestamp NULL DEFAULT NULL,
16
+ \`activeAlarm\` tinyint(1) NOT NULL DEFAULT '0',
17
+ \`emailSent\` tinyint(1) DEFAULT '0',
18
+ \`emailSent_AnswerPlus\` tinyint(1) DEFAULT '0',
19
+ \`ticketEmailSent\` tinyint(1) DEFAULT '0',
20
+ \`type\` varchar(45) DEFAULT NULL,
21
+ \`oorValue\` float(10,2) DEFAULT NULL,
22
+ \`thresholdValue\` float(10,2) DEFAULT NULL,
23
+ \`dateCreated\` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
24
+ \`dateUpdated\` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
25
+ PRIMARY KEY (\`alarmId\`),
26
+ UNIQUE KEY \`UniqueAlarmRules\` (\`alarmRuleId\`),
27
+ KEY \`idx_alarms_alarmruleid_alarmstatus\` (\`alarmRuleId\`, \`alarmStatus\`)
28
+ ) ENGINE=InnoDB AUTO_INCREMENT=117 DEFAULT CHARSET=latin1 COMMENT='Alarm table for alarms '`;
29
+
30
+
31
+ const expectedOutput = {
32
+ "name": "Alarms",
33
+ "columns": [
34
+ {
35
+ "Field": "alarmId",
36
+ "Type": "int",
37
+ "NotNull": true,
38
+ "AutoIncrement": true
39
+ },
40
+ {
41
+ "Field": "alarmRuleId",
42
+ "Type": "int",
43
+ "NotNull": true
44
+ },
45
+ {
46
+ "Field": "alarmStatus",
47
+ "Type": "int",
48
+ "NotNull": true
49
+ },
50
+ {
51
+ "Field": "alarmStatus_Text",
52
+ "Type": "varchar(50)",
53
+ "NotNull": true
54
+ },
55
+ {
56
+ "Field": "alarmCreated_Date",
57
+ "Type": "timestamp",
58
+ "Default": "NULL"
59
+ },
60
+ {
61
+ "Field": "alarmConfirmed_Date",
62
+ "Type": "timestamp",
63
+ "Default": "NULL"
64
+ },
65
+ {
66
+ "Field": "alarmRecall_Date",
67
+ "Type": "timestamp",
68
+ "Default": "NULL"
69
+ },
70
+ {
71
+ "Field": "activeAlarm",
72
+ "Type": "tinyint(1)",
73
+ "NotNull": true,
74
+ "Default": "0"
75
+ },
76
+ {
77
+ "Field": "emailSent",
78
+ "Type": "tinyint(1)",
79
+ "Default": "0"
80
+ },
81
+ {
82
+ "Field": "emailSent_AnswerPlus",
83
+ "Type": "tinyint(1)",
84
+ "Default": "0"
85
+ },
86
+ {
87
+ "Field": "ticketEmailSent",
88
+ "Type": "tinyint(1)",
89
+ "Default": "0"
90
+ },
91
+ {
92
+ "Field": "type",
93
+ "Type": "varchar(45)",
94
+ "Default": "NULL"
95
+ },
96
+ {
97
+ "Field": "oorValue",
98
+ "Type": "float(10,2)",
99
+ "Default": "NULL"
100
+ },
101
+ {
102
+ "Field": "thresholdValue",
103
+ "Type": "float(10,2)",
104
+ "Default": "NULL"
105
+ },
106
+ {
107
+ "Field": "dateCreated",
108
+ "Type": "timestamp",
109
+ "Default": "CURRENT_TIMESTAMP"
110
+ },
111
+ {
112
+ "Field": "dateUpdated",
113
+ "Type": "timestamp",
114
+ "NotNull": true,
115
+ "Default": "CURRENT_TIMESTAMP",
116
+ "OnUpdate": "CURRENT_TIMESTAMP"
117
+ }
118
+ ],
119
+ "indexes": [
120
+ {
121
+ "ColumnName": [
122
+ "alarmId"
123
+ ],
124
+ "Primary": true,
125
+ "Name": "PRIMARY"
126
+ },
127
+ {
128
+ "ColumnName": [
129
+ "alarmRuleId"
130
+ ],
131
+ "Unique": true,
132
+ "Name": "UniqueAlarmRules"
133
+ },
134
+ {
135
+ "ColumnName": [
136
+ "alarmRuleId",
137
+ "alarmStatus"
138
+ ],
139
+ "Name": "idx_alarms_alarmruleid_alarmstatus"
140
+ }
141
+ ],
142
+ "constraints": [],
143
+ "engine": null,
144
+ "charset": null,
145
+ "collate": null
146
+ }
147
+
148
+
149
+
150
+ const result = parseCreateTableSQL(input);
151
+ console.log('Result:', JSON.stringify(result, null, 2))
152
+ expect(result).toEqual(expectedOutput);
153
+ });
154
+
155
+
153
156
  });
@@ -0,0 +1,21 @@
1
+ import path from 'path';
2
+ import { packageRoot, isInsidePackage, assertOutsidePackage } from '../src/paths';
3
+
4
+ describe('package folder check', () => {
5
+ test('finds the package root', () => {
6
+ expect(path.basename(path.join(packageRoot, 'package.json'))).toBe('package.json');
7
+ expect(require(path.join(packageRoot, 'package.json')).name).toBe('mysqlsyncr');
8
+ });
9
+
10
+ test('the package folder and anything under it are inside', () => {
11
+ expect(isInsidePackage(packageRoot)).toBe(true);
12
+ expect(isInsidePackage(path.join(packageRoot, 'db-dump'))).toBe(true);
13
+ expect(() => assertOutsidePackage(path.join(packageRoot, 'src', 'x'))).toThrow('inside the mysqlsyncr package folder');
14
+ });
15
+
16
+ test('sibling and parent folders are outside', () => {
17
+ expect(isInsidePackage(path.join(packageRoot, '..', 'db-dump'))).toBe(false);
18
+ expect(isInsidePackage(packageRoot + '-dumps')).toBe(false);
19
+ expect(isInsidePackage(path.dirname(packageRoot))).toBe(false);
20
+ });
21
+ });
@@ -0,0 +1,106 @@
1
+ import fs from 'fs';
2
+ import os from 'os';
3
+ import path from 'path';
4
+ import { PassThrough } from 'stream';
5
+ import { promptForOptions, promptForPassword } from '../src/prompt';
6
+
7
+ const defaults = () => ({ host: 'localhost', port: 3306, user: 'root', password: undefined, database: null, output: '../db-dump', write: false });
8
+
9
+ // A folder holding a one-database template, as compare/update/rebuild need
10
+ const templateDir = () => {
11
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'mysqlsyncr-prompt-'));
12
+ fs.mkdirSync(path.join(dir, 'Alerts'));
13
+ fs.writeFileSync(path.join(dir, 'Alerts', 'Alerts.json'), '{"tables":{}}');
14
+ return dir;
15
+ };
16
+
17
+ // Answers the prompts one line at a time, each once its question has been written
18
+ const answer = async (answers, ask) => {
19
+ const input = new PassThrough();
20
+ const output = new PassThrough();
21
+ let written = '';
22
+ let next = 0;
23
+ output.on('data', (chunk) => {
24
+ written += chunk.toString();
25
+ if (/[:)] $/.test(written) && next < answers.length) {
26
+ written = '';
27
+ const line = answers[next++];
28
+ setImmediate(() => input.write(`${line}\r`));
29
+ }
30
+ });
31
+ let transcript = '';
32
+ output.on('data', (chunk) => { transcript += chunk.toString(); });
33
+ const result = await ask({ input, output });
34
+ return { result, transcript };
35
+ };
36
+
37
+ const run = async (answers, argv = defaults()) => {
38
+ const { result, transcript } = await answer(answers, (streams) => promptForOptions(argv, streams));
39
+ return { command: result, argv, transcript };
40
+ };
41
+
42
+ describe('promptForOptions', () => {
43
+ test('compare: asks for the connection, keeping defaults for blank answers', async () => {
44
+ const dir = templateDir();
45
+ const { command, argv, transcript } = await run(['1', 'db.example', '', 'admin', 'secret', '', dir]);
46
+ expect(command).toBe('compare');
47
+ expect(argv).toMatchObject({ host: 'db.example', port: 3306, user: 'admin', password: 'secret', database: null, output: dir });
48
+ expect(transcript).not.toContain('secret');
49
+ expect(transcript).toContain('typing is hidden');
50
+ });
51
+
52
+ test('a blank password is no password', async () => {
53
+ const { argv } = await run(['compare', '', '', '', '', '', templateDir()]);
54
+ expect(argv.password).toBe('');
55
+ });
56
+
57
+ test('accepts the command name and a single database; dump may use a new folder', async () => {
58
+ const { command, argv } = await run(['dump', '', '', '', '', 'Alerts', '/new/dump/folder']);
59
+ expect(command).toBe('dump');
60
+ expect(argv).toMatchObject({ database: 'Alerts', output: '/new/dump/folder' });
61
+ });
62
+
63
+ test('update needs confirming', async () => {
64
+ expect((await run(['update', '', '', '', '', '', templateDir(), 'n'])).command).toBeNull();
65
+ expect((await run(['update', '', '', '', '', '', templateDir(), 'y'])).command).toBe('update');
66
+ });
67
+
68
+ test('rebuild asks only for the folder and whether to write', async () => {
69
+ const dir = templateDir();
70
+ const { command, argv } = await run(['5', dir, 'y']);
71
+ expect(command).toBe('rebuild');
72
+ expect(argv).toMatchObject({ output: dir, write: true, host: 'localhost' });
73
+ });
74
+
75
+ test('ui asks nothing else', async () => {
76
+ expect((await run(['ui'])).command).toBe('ui');
77
+ });
78
+ });
79
+
80
+ describe('folder checks', () => {
81
+ test('asks again until the folder is outside the package', async () => {
82
+ const dir = templateDir();
83
+ const { command, argv, transcript } = await run(['rebuild', './templates', '', dir, 'n']);
84
+ expect(command).toBe('rebuild');
85
+ expect(argv.output).toBe(dir);
86
+ expect(transcript).toContain('inside the mysqlsyncr package folder');
87
+ });
88
+
89
+ test('compare asks again when the folder is missing or has no template', async () => {
90
+ const dir = templateDir();
91
+ const empty = fs.mkdtempSync(path.join(os.tmpdir(), 'mysqlsyncr-empty-'));
92
+ const { argv, transcript } = await run(['compare', '', '', '', '', '', path.join(dir, 'missing'), empty, dir]);
93
+ expect(argv.output).toBe(dir);
94
+ expect(transcript).toContain("doesn't exist");
95
+ expect(transcript).toContain('has no template in it');
96
+ });
97
+ });
98
+
99
+ describe('promptForPassword', () => {
100
+ test('asks without echoing, naming the user and host', async () => {
101
+ const { result, transcript } = await answer(['hunter2'], (streams) => promptForPassword({ user: 'root', host: 'localhost' }, streams));
102
+ expect(result).toBe('hunter2');
103
+ expect(transcript).toContain('for root@localhost');
104
+ expect(transcript).not.toContain('hunter2');
105
+ });
106
+ });