diff --git a/packages/api/package.json b/packages/api/package.json index 52b435f7..156c04ce 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -29,6 +29,8 @@ "dbgate-query-splitter": "^4.1.1", "dbgate-sqltree": "^4.1.1", "dbgate-tools": "^4.1.1", + "diff": "^5.0.0", + "diff2html": "^3.4.13", "eslint": "^6.8.0", "express": "^4.17.1", "express-basic-auth": "^1.2.0", diff --git a/packages/api/src/controllers/databaseConnections.js b/packages/api/src/controllers/databaseConnections.js index c82738b8..011a1ee3 100644 --- a/packages/api/src/controllers/databaseConnections.js +++ b/packages/api/src/controllers/databaseConnections.js @@ -3,16 +3,27 @@ const connections = require('./connections'); const archive = require('./archive'); const socket = require('../utility/socket'); const { fork } = require('child_process'); -const { DatabaseAnalyser, getAlterDatabaseScript, generateDbPairingId, matchPairedObjects } = require('dbgate-tools'); +const { + DatabaseAnalyser, + computeDbDiffRows, + getCreateObjectScript, + getAlterDatabaseScript, + generateDbPairingId, + matchPairedObjects, + extendDatabaseInfo, +} = require('dbgate-tools'); +const { html, parse } = require('diff2html'); const { handleProcessCommunication } = require('../utility/processComm'); const config = require('./config'); const fs = require('fs-extra'); const exportDbModel = require('../utility/exportDbModel'); -const { archivedir, resolveArchiveFolder } = require('../utility/directories'); +const { archivedir, resolveArchiveFolder, uploadsdir } = require('../utility/directories'); const path = require('path'); const importDbModel = require('../utility/importDbModel'); const requireEngineDriver = require('../utility/requireEngineDriver'); const generateDeploySql = require('../shell/generateDeploySql'); +const { createTwoFilesPatch } = require('diff'); +const diff2htmlPage = require('../utility/diff2htmlPage'); module.exports = { /** @type {import('dbgate-types').OpenedDatabaseConnection[]} */ @@ -285,4 +296,55 @@ module.exports = { // const res = await this.sendRequest(opened, { msgtype: 'queryData', sql }); // return res; // }, + + async getUnifiedDiff({ sourceConid, sourceDatabase, targetConid, targetDatabase }) { + const dbDiffOptions = { + // schemaMode: 'ignore', + }; + + const sourceDb = generateDbPairingId( + extendDatabaseInfo(await this.structure({ conid: sourceConid, database: sourceDatabase })) + ); + const targetDb = generateDbPairingId( + extendDatabaseInfo(await this.structure({ conid: targetConid, database: targetDatabase })) + ); + // const sourceConnection = await connections.get({conid:sourceConid}) + const connection = await connections.get({ conid: targetConid }); + const driver = requireEngineDriver(connection); + const targetDbPaired = matchPairedObjects(sourceDb, targetDb, dbDiffOptions); + const diffRows = computeDbDiffRows(sourceDb, targetDbPaired, dbDiffOptions, driver); + + // console.log('sourceDb', sourceDb); + // console.log('targetDb', targetDb); + // console.log('sourceConid, sourceDatabase', sourceConid, sourceDatabase); + + let res = ''; + for (const row of diffRows) { + // console.log('PAIR', row.source && row.source.pureName, row.target && row.target.pureName); + const unifiedDiff = createTwoFilesPatch( + (row.source && row.source.pureName) || '', + (row.target && row.target.pureName) || '', + getCreateObjectScript(row.source, driver), + getCreateObjectScript(row.target, driver), + '', + '' + ); + res += unifiedDiff; + } + return res; + }, + + generateDbDiffReport_meta: 'post', + async generateDbDiffReport({ sourceConid, sourceDatabase, targetConid, targetDatabase }) { + const unifiedDiff = await this.getUnifiedDiff({ sourceConid, sourceDatabase, targetConid, targetDatabase }); + + const diffJson = parse(unifiedDiff); + // $: diffHtml = html(diffJson, { outputFormat: 'side-by-side', drawFileList: false }); + const diffHtml = html(diffJson, {}); + + const fileName = `${uuidv1()}.html`; + await fs.writeFile(path.join(uploadsdir(), fileName), diff2htmlPage(diffHtml)); + + return fileName; + }, }; diff --git a/packages/api/src/controllers/uploads.js b/packages/api/src/controllers/uploads.js index 342a7be8..1d2cafe6 100644 --- a/packages/api/src/controllers/uploads.js +++ b/packages/api/src/controllers/uploads.js @@ -25,4 +25,12 @@ module.exports = { }); }); }, + + get_meta: { + method: 'get', + raw: true, + }, + get(req, res) { + res.sendFile(path.join(uploadsdir(), req.query.file)); + }, }; diff --git a/packages/api/src/utility/diff2htmlPage.js b/packages/api/src/utility/diff2htmlPage.js new file mode 100644 index 00000000..dd5735f2 --- /dev/null +++ b/packages/api/src/utility/diff2htmlPage.js @@ -0,0 +1,8 @@ +const diff2htmlCss = + '.d2h-d-none{display:none}.d2h-wrapper{text-align:left}.d2h-file-header{background-color:#f7f7f7;border-bottom:1px solid #d8d8d8;font-family:Source Sans Pro,Helvetica Neue,Helvetica,Arial,sans-serif;height:35px;padding:5px 10px}.d2h-file-header,.d2h-file-stats{display:-webkit-box;display:-ms-flexbox;display:flex}.d2h-file-stats{font-size:14px;margin-left:auto}.d2h-lines-added{border:1px solid #b4e2b4;border-radius:5px 0 0 5px;color:#399839;padding:2px;text-align:right;vertical-align:middle}.d2h-lines-deleted{border:1px solid #e9aeae;border-radius:0 5px 5px 0;color:#c33;margin-left:1px;padding:2px;text-align:left;vertical-align:middle}.d2h-file-name-wrapper{-webkit-box-align:center;-ms-flex-align:center;align-items:center;display:-webkit-box;display:-ms-flexbox;display:flex;font-size:15px;width:100%}.d2h-file-name{overflow-x:hidden;text-overflow:ellipsis;white-space:nowrap}.d2h-file-wrapper{border:1px solid #ddd;border-radius:3px;margin-bottom:1em}.d2h-file-collapse{-webkit-box-pack:end;-ms-flex-pack:end;-webkit-box-align:center;-ms-flex-align:center;align-items:center;border:1px solid #ddd;border-radius:3px;cursor:pointer;display:none;font-size:12px;justify-content:flex-end;padding:4px 8px}.d2h-file-collapse.d2h-selected{background-color:#c8e1ff}.d2h-file-collapse-input{margin:0 4px 0 0}.d2h-diff-table{border-collapse:collapse;font-family:Menlo,Consolas,monospace;font-size:13px;width:100%}.d2h-files-diff{width:100%}.d2h-file-diff{overflow-y:hidden}.d2h-file-side-diff{display:inline-block;margin-bottom:-8px;margin-right:-4px;overflow-x:scroll;overflow-y:hidden;width:50%}.d2h-code-line{padding:0 8em}.d2h-code-line,.d2h-code-side-line{display:inline-block;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;white-space:nowrap;width:100%}.d2h-code-side-line{padding:0 4.5em}.d2h-code-line-ctn{word-wrap:normal;background:none;display:inline-block;padding:0;-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;user-select:text;vertical-align:middle;white-space:pre;width:100%}.d2h-code-line del,.d2h-code-side-line del{background-color:#ffb6ba}.d2h-code-line del,.d2h-code-line ins,.d2h-code-side-line del,.d2h-code-side-line ins{border-radius:.2em;display:inline-block;margin-top:-1px;text-decoration:none;vertical-align:middle}.d2h-code-line ins,.d2h-code-side-line ins{background-color:#97f295;text-align:left}.d2h-code-line-prefix{word-wrap:normal;background:none;display:inline;padding:0;white-space:pre}.line-num1{float:left}.line-num1,.line-num2{-webkit-box-sizing:border-box;box-sizing:border-box;overflow:hidden;padding:0 .5em;text-overflow:ellipsis;width:3.5em}.line-num2{float:right}.d2h-code-linenumber{background-color:#fff;border:solid #eee;border-width:0 1px;-webkit-box-sizing:border-box;box-sizing:border-box;color:rgba(0,0,0,.3);cursor:pointer;display:inline-block;position:absolute;text-align:right;width:7.5em}.d2h-code-linenumber:after{content:"\200b"}.d2h-code-side-linenumber{background-color:#fff;border:solid #eee;border-width:0 1px;-webkit-box-sizing:border-box;box-sizing:border-box;color:rgba(0,0,0,.3);cursor:pointer;display:inline-block;overflow:hidden;padding:0 .5em;position:absolute;text-align:right;text-overflow:ellipsis;width:4em}.d2h-code-side-linenumber:after{content:"\200b"}.d2h-code-side-emptyplaceholder,.d2h-emptyplaceholder{background-color:#f1f1f1;border-color:#e1e1e1}.d2h-code-line-prefix,.d2h-code-linenumber,.d2h-code-side-linenumber,.d2h-emptyplaceholder{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.d2h-code-linenumber,.d2h-code-side-linenumber{direction:rtl}.d2h-del{background-color:#fee8e9;border-color:#e9aeae}.d2h-ins{background-color:#dfd;border-color:#b4e2b4}.d2h-info{background-color:#f8fafd;border-color:#d5e4f2;color:rgba(0,0,0,.3)}.d2h-file-diff .d2h-del.d2h-change{background-color:#fdf2d0}.d2h-file-diff .d2h-ins.d2h-change{background-color:#ded}.d2h-file-list-wrapper{margin-bottom:10px}.d2h-file-list-wrapper a{color:#3572b0;text-decoration:none}.d2h-file-list-wrapper a:visited{color:#3572b0}.d2h-file-list-header{text-align:left}.d2h-file-list-title{font-weight:700}.d2h-file-list-line{display:-webkit-box;display:-ms-flexbox;display:flex;text-align:left}.d2h-file-list{display:block;list-style:none;margin:0;padding:0}.d2h-file-list>li{border-bottom:1px solid #ddd;margin:0;padding:5px 10px}.d2h-file-list>li:last-child{border-bottom:none}.d2h-file-switch{cursor:pointer;display:none;font-size:10px}.d2h-icon{fill:currentColor;margin-right:10px;vertical-align:middle}.d2h-deleted{color:#c33}.d2h-added{color:#399839}.d2h-changed{color:#d0b44c}.d2h-moved{color:#3572b0}.d2h-tag{background-color:#fff;display:-webkit-box;display:-ms-flexbox;display:flex;font-size:10px;margin-left:5px;padding:0 2px}.d2h-deleted-tag{border:1px solid #c33}.d2h-added-tag{border:1px solid #399839}.d2h-changed-tag{border:1px solid #d0b44c}.d2h-moved-tag{border:1px solid #3572b0}'; + +function diff2htmlPage(content) { + return `
${content}`; +} + +module.exports = diff2htmlPage; diff --git a/packages/web/src/utility/computeDiffRows.ts b/packages/tools/src/computeDiffRows.ts similarity index 95% rename from packages/web/src/utility/computeDiffRows.ts rename to packages/tools/src/computeDiffRows.ts index 0903242a..48121185 100644 --- a/packages/web/src/utility/computeDiffRows.ts +++ b/packages/tools/src/computeDiffRows.ts @@ -1,4 +1,4 @@ -import { DbDiffOptions, testEqualColumns, testEqualTables } from 'dbgate-tools'; +import { DbDiffOptions, testEqualColumns, testEqualTables } from './diffTools'; import { DatabaseInfo, EngineDriver, TableInfo } from 'dbgate-types'; export function computeDiffRowsCore(sourceList, targetList, testEqual) { @@ -69,7 +69,7 @@ export function computeTableDiffColumns( })); } -export function getCreateTableScript(table: TableInfo, driver: EngineDriver) { +export function getCreateObjectScript(table: TableInfo, driver: EngineDriver) { if (!table || !driver) return ''; const dmp = driver.createDumper(); dmp.createTable(table); diff --git a/packages/tools/src/index.ts b/packages/tools/src/index.ts index b9fac388..c0e5ee9e 100644 --- a/packages/tools/src/index.ts +++ b/packages/tools/src/index.ts @@ -15,3 +15,4 @@ export * from './diffTools'; export * from './schemaEditorTools'; export * from './yamlModelConv'; export * from './stringTools'; +export * from './computeDiffRows'; diff --git a/packages/web/src/forms/SelectField.svelte b/packages/web/src/forms/SelectField.svelte index 9c72e09a..ab751835 100644 --- a/packages/web/src/forms/SelectField.svelte +++ b/packages/web/src/forms/SelectField.svelte @@ -22,7 +22,7 @@ {#if isNative}