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/package.json
CHANGED
|
@@ -1,59 +1,66 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "mysqlsyncr",
|
|
3
|
-
"version": "1.1
|
|
4
|
-
"description": "",
|
|
5
|
-
"main": "dist/index.cjs.js",
|
|
6
|
-
"module": "dist/index.esm.js",
|
|
7
|
-
"exports": {
|
|
8
|
-
".": {
|
|
9
|
-
"import": "./dist/index.js",
|
|
10
|
-
"require": "./dist/index.cjs"
|
|
11
|
-
}
|
|
12
|
-
},
|
|
13
|
-
"type": "module",
|
|
14
|
-
"scripts": {
|
|
15
|
-
"test": "jest",
|
|
16
|
-
"test:watch": "jest --watch",
|
|
17
|
-
"start": "node src/index.js",
|
|
18
|
-
"build": "rollup -c"
|
|
19
|
-
},
|
|
20
|
-
"bin": {
|
|
21
|
-
"mysqlsyncr": "dist/index.cjs"
|
|
22
|
-
},
|
|
23
|
-
"author": "",
|
|
24
|
-
"license": "ISC",
|
|
25
|
-
"repository": {
|
|
26
|
-
"type": "git",
|
|
27
|
-
"url": "git+https://github.com/Craigzyc/mysqlsyncr.git"
|
|
28
|
-
},
|
|
29
|
-
"dependencies": {
|
|
30
|
-
"cors": "^2.8.5",
|
|
31
|
-
"express": "^4.
|
|
32
|
-
"mysql2": "^3.
|
|
33
|
-
"open": "^10.1.0",
|
|
34
|
-
"yargs": "^17.7.2"
|
|
35
|
-
},
|
|
36
|
-
"devDependencies": {
|
|
37
|
-
"@babel/
|
|
38
|
-
"babel-
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"@
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
},
|
|
48
|
-
"babel": {
|
|
49
|
-
"presets": [
|
|
50
|
-
"@babel/preset-env"
|
|
51
|
-
]
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "mysqlsyncr",
|
|
3
|
+
"version": "1.2.1",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "dist/index.cjs.js",
|
|
6
|
+
"module": "dist/index.esm.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./dist/index.js",
|
|
10
|
+
"require": "./dist/index.cjs"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"scripts": {
|
|
15
|
+
"test": "jest",
|
|
16
|
+
"test:watch": "jest --watch",
|
|
17
|
+
"start": "node src/index.js",
|
|
18
|
+
"build": "rollup -c"
|
|
19
|
+
},
|
|
20
|
+
"bin": {
|
|
21
|
+
"mysqlsyncr": "dist/index.cjs"
|
|
22
|
+
},
|
|
23
|
+
"author": "",
|
|
24
|
+
"license": "ISC",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/Craigzyc/mysqlsyncr.git"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"cors": "^2.8.5",
|
|
31
|
+
"express": "^4.22.3",
|
|
32
|
+
"mysql2": "^3.24.6",
|
|
33
|
+
"open": "^10.1.0",
|
|
34
|
+
"yargs": "^17.7.2"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@babel/core": "^7.18.10",
|
|
38
|
+
"@babel/preset-env": "^7.26.0",
|
|
39
|
+
"@rollup/plugin-babel": "^6.0.4",
|
|
40
|
+
"@rollup/plugin-commonjs": "^25.0.0",
|
|
41
|
+
"@rollup/plugin-node-resolve": "^15.0.0",
|
|
42
|
+
"babel-jest": "^29.7.0",
|
|
43
|
+
"babel-plugin-transform-import-meta": "^2.3.2",
|
|
44
|
+
"dotenv": "^16.4.5",
|
|
45
|
+
"jest": "^29.0.0",
|
|
46
|
+
"rollup": "^4.12.0"
|
|
47
|
+
},
|
|
48
|
+
"babel": {
|
|
49
|
+
"presets": [
|
|
50
|
+
"@babel/preset-env"
|
|
51
|
+
],
|
|
52
|
+
"env": {
|
|
53
|
+
"test": {
|
|
54
|
+
"plugins": [
|
|
55
|
+
"babel-plugin-transform-import-meta"
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"jest": {
|
|
61
|
+
"transform": {
|
|
62
|
+
"^.+\\.js$": "babel-jest"
|
|
63
|
+
},
|
|
64
|
+
"testEnvironment": "node"
|
|
65
|
+
}
|
|
66
|
+
}
|
package/rollup.config.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import babel from 'rollup
|
|
1
|
+
import { babel } from '@rollup/plugin-babel';
|
|
2
2
|
import resolve from '@rollup/plugin-node-resolve';
|
|
3
3
|
import commonjs from '@rollup/plugin-commonjs';
|
|
4
4
|
|
|
@@ -16,6 +16,7 @@ export default {
|
|
|
16
16
|
],
|
|
17
17
|
plugins: [
|
|
18
18
|
babel({
|
|
19
|
+
babelHelpers: 'bundled',
|
|
19
20
|
exclude: 'node_modules/**'
|
|
20
21
|
}),
|
|
21
22
|
resolve(),
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import mysql from 'mysql2';
|
|
2
|
+
import util from 'util';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { dumpAllDatabases } from './dump.js';
|
|
6
|
+
import { compareAllDatabases } from './compare.js';
|
|
7
|
+
|
|
8
|
+
// The config also carries mysqlsyncr's own settings (the template folder); mysql2
|
|
9
|
+
// warns about options it doesn't know, so pass it only the connection's
|
|
10
|
+
const connectionOptions = (config) => {
|
|
11
|
+
const { output, ...options } = config || {};
|
|
12
|
+
return options;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class DBStructureChecker {
|
|
16
|
+
constructor(config, targetDatabase = null) {
|
|
17
|
+
this.config = config;
|
|
18
|
+
this.targetDatabase = targetDatabase;
|
|
19
|
+
this.connection = mysql.createConnection(connectionOptions(this.config))
|
|
20
|
+
this.dumpAllDatabases = dumpAllDatabases;
|
|
21
|
+
this.compareAllDatabases = compareAllDatabases;
|
|
22
|
+
this.getDatabases = () => {
|
|
23
|
+
|
|
24
|
+
fs.readFileSync(path.join(__dirname, 'databases.json'), 'utf8');
|
|
25
|
+
}
|
|
26
|
+
// this.connection.on('error', (err) => {
|
|
27
|
+
// this.emit('error', err);
|
|
28
|
+
// console.error('Database connection error:', err);
|
|
29
|
+
// });
|
|
30
|
+
this.connection.query = util.promisify(this.connection.query); // Promisify for async/await
|
|
31
|
+
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async reconnect() {
|
|
35
|
+
this.connection = mysql.createConnection(connectionOptions(this.config));
|
|
36
|
+
this.connection.query = util.promisify(this.connection.query); // Promisify for async/await
|
|
37
|
+
await this.connection.connect(); // Connect to the database
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async closeConnection() {
|
|
41
|
+
if (this.connection.state !== 'disconnected') {
|
|
42
|
+
await this.connection.end();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { getDatabaseStructure } from './getters.js';
|
|
4
|
+
import { parseCreateTableSQL } from './parsers.js';
|
|
5
|
+
import { loadSyncOptions, getTableOptions, applyAutoIncrementOption, applyDefinerOption } from './options.js';
|
|
6
|
+
import { assertOutsidePackage } from './paths.js';
|
|
7
|
+
|
|
8
|
+
// --- Editing one column or index line of a CREATE TABLE statement ---
|
|
9
|
+
|
|
10
|
+
// 'col:name' for a column line, 'idx:name' for an index line ('idx:PRIMARY' for the primary key), else null
|
|
11
|
+
const elementKey = (line) => {
|
|
12
|
+
const column = line.match(/^\s*`([^`]+)`\s/);
|
|
13
|
+
if (column) return `col:${column[1]}`;
|
|
14
|
+
if (/^\s*PRIMARY KEY\b/i.test(line)) return 'idx:PRIMARY';
|
|
15
|
+
const index = line.match(/^\s*(?:UNIQUE |FULLTEXT |SPATIAL )?KEY\s+`([^`]+)`/i);
|
|
16
|
+
if (index) return `idx:${index[1]}`;
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Split into the CREATE TABLE line, the definition lines (without their separating commas) and the closing line onwards
|
|
21
|
+
const splitCreateSQL = (sql) => {
|
|
22
|
+
const lines = sql.replace(/\r\n/g, '\n').split('\n');
|
|
23
|
+
let end = lines.length - 1;
|
|
24
|
+
while (end > 0 && !lines[end].trim().startsWith(')')) end--;
|
|
25
|
+
if (end <= 0) throw new Error('Could not find the column definitions in the CREATE TABLE statement');
|
|
26
|
+
return {
|
|
27
|
+
head: lines[0],
|
|
28
|
+
body: lines.slice(1, end).map(line => line.replace(/,\s*$/, '')),
|
|
29
|
+
tail: lines.slice(end).join('\n')
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const joinCreateSQL = ({ head, body, tail }) => [head, ...body.map((line, i) => i < body.length - 1 ? `${line},` : line), tail].join('\n');
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Make the template's line for one column or index match the live table: replace
|
|
37
|
+
* it, remove it when the live table doesn't have it, or add it after the element
|
|
38
|
+
* that precedes it in the live table.
|
|
39
|
+
*/
|
|
40
|
+
export const replaceCreateSQLElement = (templateSQL, liveSQL, key) => {
|
|
41
|
+
const template = splitCreateSQL(templateSQL);
|
|
42
|
+
const live = splitCreateSQL(liveSQL);
|
|
43
|
+
const templateIndex = template.body.findIndex(line => elementKey(line) === key);
|
|
44
|
+
const liveIndex = live.body.findIndex(line => elementKey(line) === key);
|
|
45
|
+
|
|
46
|
+
if (liveIndex === -1) {
|
|
47
|
+
if (templateIndex !== -1) template.body.splice(templateIndex, 1);
|
|
48
|
+
} else if (templateIndex !== -1) {
|
|
49
|
+
template.body[templateIndex] = live.body[liveIndex];
|
|
50
|
+
} else {
|
|
51
|
+
const kind = key.slice(0, 4);
|
|
52
|
+
let insertAt = -1;
|
|
53
|
+
for (let i = liveIndex - 1; i >= 0 && insertAt === -1; i--) {
|
|
54
|
+
const previousKey = elementKey(live.body[i]);
|
|
55
|
+
if (previousKey?.startsWith(kind)) {
|
|
56
|
+
const position = template.body.findIndex(line => elementKey(line) === previousKey);
|
|
57
|
+
if (position !== -1) insertAt = position + 1;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (insertAt === -1) {
|
|
61
|
+
// First of its kind: columns go first, indexes go after the last column
|
|
62
|
+
const lastColumn = template.body.reduce((last, line, i) => elementKey(line)?.startsWith('col:') ? i : last, -1);
|
|
63
|
+
insertAt = kind === 'col:' ? 0 : lastColumn + 1;
|
|
64
|
+
}
|
|
65
|
+
template.body.splice(insertAt, 0, live.body[liveIndex]);
|
|
66
|
+
}
|
|
67
|
+
return joinCreateSQL(template);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// --- Applying differences to the template ---
|
|
71
|
+
|
|
72
|
+
const tableFile = (tableName) => path.join('tables', `${tableName}.sql`);
|
|
73
|
+
|
|
74
|
+
const requireTable = (structure, tableName, which) => {
|
|
75
|
+
const table = structure.tables?.[tableName];
|
|
76
|
+
if (!table) throw new Error(`Table ${tableName} is not in the ${which}`);
|
|
77
|
+
return table;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Rebuild the template's list of columns or indexes in createSQL order, using the
|
|
81
|
+
// live entry for the changed element and keeping the template's other entries
|
|
82
|
+
const syncEntries = (templateEntries = [], parsedEntries, liveEntries, nameKey, changedName) =>
|
|
83
|
+
parsedEntries.map(entry => {
|
|
84
|
+
const name = entry[nameKey];
|
|
85
|
+
if (name === changedName) return liveEntries.find(e => e[nameKey] === name) ?? entry;
|
|
86
|
+
return templateEntries.find(e => e[nameKey] === name) ?? entry;
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const acceptTableElement = (template, current, diff, files) => {
|
|
90
|
+
const templateTable = requireTable(template, diff.tableName, 'template');
|
|
91
|
+
const currentTable = requireTable(current, diff.tableName, 'database');
|
|
92
|
+
if (!templateTable.createSQL) throw new Error(`Table ${diff.tableName} has no createSQL in the template`);
|
|
93
|
+
|
|
94
|
+
const isField = diff.type.endsWith('_field');
|
|
95
|
+
const name = isField ? diff.field.Field : (diff.indexName ?? diff.index?.Name);
|
|
96
|
+
const createSQL = replaceCreateSQLElement(templateTable.createSQL, currentTable.createSQL, `${isField ? 'col' : 'idx'}:${name}`);
|
|
97
|
+
const parsed = parseCreateTableSQL(createSQL);
|
|
98
|
+
|
|
99
|
+
templateTable.createSQL = createSQL;
|
|
100
|
+
if (isField) {
|
|
101
|
+
templateTable.columns = syncEntries(templateTable.columns, parsed.columns, currentTable.columns, 'Field', name);
|
|
102
|
+
} else {
|
|
103
|
+
templateTable.indexes = syncEntries(templateTable.indexes, parsed.indexes, currentTable.indexes, 'Name', name);
|
|
104
|
+
}
|
|
105
|
+
files.set(tableFile(diff.tableName), createSQL);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const acceptDifference = (template, current, diff, { database, syncOptions, files }) => {
|
|
109
|
+
switch (diff.type) {
|
|
110
|
+
case 'extra_table': {
|
|
111
|
+
const table = requireTable(current, diff.tableName, 'database');
|
|
112
|
+
const createSQL = applyAutoIncrementOption(table.createSQL, getTableOptions(syncOptions, database, diff.tableName).autoIncrement);
|
|
113
|
+
template.tables[diff.tableName] = { ...table, createSQL };
|
|
114
|
+
files.set(tableFile(diff.tableName), createSQL);
|
|
115
|
+
for (const trigger of table.triggers || []) {
|
|
116
|
+
files.set(path.join('tables', 'triggers', `${trigger.Name}.sql`), trigger.Definition);
|
|
117
|
+
}
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
case 'missing_table':
|
|
121
|
+
delete template.tables[diff.tableName];
|
|
122
|
+
files.set(tableFile(diff.tableName), null);
|
|
123
|
+
break;
|
|
124
|
+
|
|
125
|
+
case 'missing_field':
|
|
126
|
+
case 'extra_field':
|
|
127
|
+
case 'mismatched_field':
|
|
128
|
+
case 'missing_index':
|
|
129
|
+
case 'extra_index':
|
|
130
|
+
case 'mismatched_index':
|
|
131
|
+
acceptTableElement(template, current, diff, files);
|
|
132
|
+
break;
|
|
133
|
+
|
|
134
|
+
case 'missing_trigger':
|
|
135
|
+
case 'extra_trigger':
|
|
136
|
+
case 'mismatched_trigger': {
|
|
137
|
+
const templateTable = requireTable(template, diff.tableName, 'template');
|
|
138
|
+
const liveTrigger = current.tables?.[diff.tableName]?.triggers?.find(t => t.Name === diff.trigger.Name);
|
|
139
|
+
templateTable.triggers = (templateTable.triggers || []).filter(t => t.Name !== diff.trigger.Name);
|
|
140
|
+
if (liveTrigger) templateTable.triggers.push(liveTrigger);
|
|
141
|
+
files.set(path.join('tables', 'triggers', `${diff.trigger.Name}.sql`), liveTrigger?.Definition ?? null);
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
case 'missing_procedure':
|
|
146
|
+
case 'mismatched_procedure': {
|
|
147
|
+
const liveProcedure = current.procedures?.find(p => p.Name === diff.Name);
|
|
148
|
+
const procedures = (template.procedures || []).filter(p => p.Name !== diff.Name);
|
|
149
|
+
if (liveProcedure) procedures.push(liveProcedure);
|
|
150
|
+
template.procedures = procedures;
|
|
151
|
+
files.set(path.join('procedures', `${diff.Name}.sql`), liveProcedure?.Definition ?? null);
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
case 'missing_view':
|
|
156
|
+
case 'extra_view':
|
|
157
|
+
case 'mismatched_view': {
|
|
158
|
+
const liveView = current.views?.[diff.viewName];
|
|
159
|
+
template.views = { ...template.views };
|
|
160
|
+
if (liveView) template.views[diff.viewName] = liveView;
|
|
161
|
+
else delete template.views[diff.viewName];
|
|
162
|
+
files.set(path.join('views', `${diff.viewName}.sql`), liveView ?? null);
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
default:
|
|
167
|
+
throw new Error(`${diff.type} can't be kept as the current value; edit the template or sync options instead`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const describeDifference = (diff) => {
|
|
172
|
+
const name = diff.tableName || diff.viewName || diff.Name || diff.database || '';
|
|
173
|
+
const detail = diff.field?.Field || diff.indexName || diff.trigger?.Name || '';
|
|
174
|
+
return `${diff.type} ${name}${detail ? '.' + detail : ''}`;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* The opposite of applyDifferences: change the template so it matches the live
|
|
179
|
+
* database for each difference, instead of changing the database. Updates
|
|
180
|
+
* <templateDir>/<database>/<database>.json and the matching .sql files.
|
|
181
|
+
* Differences that succeed are saved even if others fail; then throws an Error
|
|
182
|
+
* whose `errors` lists each failure as { difference, error }.
|
|
183
|
+
*/
|
|
184
|
+
export const acceptCurrentIntoTemplate = async (connection, templateDir, database, differences) => {
|
|
185
|
+
assertOutsidePackage(templateDir);
|
|
186
|
+
const dbDir = path.join(templateDir, database);
|
|
187
|
+
const jsonPath = path.join(dbDir, `${database}.json`);
|
|
188
|
+
const template = JSON.parse(await fs.readFile(jsonPath, 'utf-8'));
|
|
189
|
+
template.tables = template.tables || {};
|
|
190
|
+
const syncOptions = await loadSyncOptions(templateDir);
|
|
191
|
+
// Kept procedures, views and triggers get the definer option, like a dump
|
|
192
|
+
const current = applyDefinerOption(await getDatabaseStructure(connection, database, 'compare', { output: templateDir }), syncOptions.definer);
|
|
193
|
+
|
|
194
|
+
// relative path -> new file content, or null to delete the file
|
|
195
|
+
const files = new Map();
|
|
196
|
+
const errors = [];
|
|
197
|
+
for (const diff of differences) {
|
|
198
|
+
try {
|
|
199
|
+
acceptDifference(template, current, diff, { database, syncOptions, files });
|
|
200
|
+
} catch (e) {
|
|
201
|
+
console.error(`Error keeping current value for ${describeDifference(diff)}: ${e.message}`);
|
|
202
|
+
errors.push({ difference: describeDifference(diff), error: e.message });
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (errors.length < differences.length) {
|
|
207
|
+
if (template.views && Object.keys(template.views).length === 0) delete template.views;
|
|
208
|
+
await fs.writeFile(jsonPath, JSON.stringify(template, null, 4), 'utf-8');
|
|
209
|
+
for (const [relativePath, content] of files) {
|
|
210
|
+
const filePath = path.join(dbDir, relativePath);
|
|
211
|
+
if (content === null) {
|
|
212
|
+
await fs.rm(filePath, { force: true });
|
|
213
|
+
} else {
|
|
214
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
215
|
+
await fs.writeFile(filePath, `${content}\n`, 'utf-8');
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (errors.length > 0) {
|
|
221
|
+
const error = new Error(`${errors.length} of ${differences.length} difference(s) could not be kept: ${errors.map(e => `${e.difference} (${e.error})`).join('; ')}`);
|
|
222
|
+
error.errors = errors;
|
|
223
|
+
throw error;
|
|
224
|
+
}
|
|
225
|
+
}
|