diff --git a/app/package.json b/app/package.json index e806eab49..2631e84a6 100644 --- a/app/package.json +++ b/app/package.json @@ -1,6 +1,6 @@ { "name": "dbgate", - "version": "3.7.33", + "version": "3.8.0", "private": true, "author": "Jan Prochazka ", "dependencies": { diff --git a/packages/api/.eslintrc.js b/packages/api/.eslintrc.js deleted file mode 100644 index a0427ca1b..000000000 --- a/packages/api/.eslintrc.js +++ /dev/null @@ -1,21 +0,0 @@ -module.exports = { - env: { - node: true, - commonjs: true, - es6: true, - jquery: false, - jest: true, - jasmine: true, - }, - extends: 'eslint:recommended', - globals: { - Atomics: 'readonly', - SharedArrayBuffer: 'readonly', - }, - parserOptions: { - ecmaVersion: 2018, - }, - rules: { - 'no-unused-vars': 'warn', - }, -}; diff --git a/packages/api/package.json b/packages/api/package.json index 88e76bc04..352a0bd80 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -12,11 +12,6 @@ "license": "GPL", "keywords": [ "sql", - "mssql", - "mssql", - "postgresql", - "csv", - "excel", "json", "import", "export", @@ -30,8 +25,6 @@ "byline": "^5.0.0", "cors": "^2.8.5", "cross-env": "^6.0.3", - "csv": "^5.3.2", - "dbgate-engines": "^1.0.0", "dbgate-sqltree": "^1.0.0", "dbgate-tools": "^1.0.0", "eslint": "^6.8.0", @@ -43,12 +36,9 @@ "http": "^0.0.0", "line-reader": "^0.4.0", "lodash": "^4.17.15", - "mssql": "^6.0.1", - "mysql": "^2.17.1", + "ncp": "^2.0.0", "nedb-promises": "^4.0.1", - "pg": "^7.17.0", - "pg-query-stream": "^3.1.1", - "xlsx": "^0.16.8" + "tar": "^6.0.5" }, "scripts": { "start": "nodemon src/index.js", diff --git a/packages/api/src/controllers/databaseConnections.js b/packages/api/src/controllers/databaseConnections.js index be16f7e95..37a5a0a9b 100644 --- a/packages/api/src/controllers/databaseConnections.js +++ b/packages/api/src/controllers/databaseConnections.js @@ -2,7 +2,7 @@ const uuidv1 = require('uuid/v1'); const connections = require('./connections'); const socket = require('../utility/socket'); const { fork } = require('child_process'); -const DatabaseAnalyser = require('dbgate-engines/default/DatabaseAnalyser'); +const { DatabaseAnalyser } = require('dbgate-tools'); module.exports = { /** @type {import('dbgate-types').OpenedDatabaseConnection[]} */ diff --git a/packages/api/src/controllers/files.js b/packages/api/src/controllers/files.js deleted file mode 100644 index 6be3173df..000000000 --- a/packages/api/src/controllers/files.js +++ /dev/null @@ -1,12 +0,0 @@ -const xlsx = require('xlsx'); -const _ = require('lodash'); - -module.exports = { - openedReaders: {}, - - analyseExcel_meta: 'get', - async analyseExcel({ filePath }) { - const workbook = xlsx.readFile(filePath, { bookSheets: true }); - return workbook.SheetNames; - }, -}; diff --git a/packages/api/src/controllers/plugins.js b/packages/api/src/controllers/plugins.js new file mode 100644 index 000000000..2d671ebaa --- /dev/null +++ b/packages/api/src/controllers/plugins.js @@ -0,0 +1,150 @@ +const fs = require('fs-extra'); +const axios = require('axios'); +const path = require('path'); +const { pluginsdir, datadir } = require('../utility/directories'); +const socket = require('../utility/socket'); +const requirePlugin = require('../shell/requirePlugin'); +const downloadPackage = require('../utility/downloadPackage'); + +// async function loadPackageInfo(dir) { +// const readmeFile = path.join(dir, 'README.md'); +// const packageFile = path.join(dir, 'package.json'); + +// if (!(await fs.exists(packageFile))) { +// return null; +// } + +// let readme = null; +// let manifest = null; +// if (await fs.exists(readmeFile)) readme = await fs.readFile(readmeFile, { encoding: 'utf-8' }); +// if (await fs.exists(packageFile)) manifest = JSON.parse(await fs.readFile(packageFile, { encoding: 'utf-8' })); +// return { +// readme, +// manifest, +// }; +// } + +const preinstallPlugins = [ + 'dbgate-plugin-mssql', + 'dbgate-plugin-mysql', + 'dbgate-plugin-postgres', + 'dbgate-plugin-csv', + 'dbgate-plugin-excel', +]; + +module.exports = { + script_meta: 'get', + async script({ packageName }) { + const file = path.join(pluginsdir(), packageName, 'dist', 'frontend.js'); + const data = await fs.readFile(file, { + encoding: 'utf-8', + }); + return data; + }, + + search_meta: 'get', + async search({ filter }) { + // DOCS: https://github.com/npm/registry/blob/master/docs/REGISTRY-API.md#get-v1search + const resp = await axios.default.get( + `http://registry.npmjs.com/-/v1/search?text=${encodeURIComponent(filter)}+keywords:dbgateplugin&size=25&from=0` + ); + const { objects } = resp.data || {}; + return (objects || []).map((x) => x.package); + }, + + info_meta: 'get', + async info({ packageName }) { + try { + const infoResp = await axios.default.get(`https://registry.npmjs.org/${packageName}`); + const { latest } = infoResp.data['dist-tags']; + const manifest = infoResp.data.versions[latest]; + const { readme } = infoResp.data; + + return { + readme, + manifest, + }; + } catch (err) { + return { + state: 'error', + error: err.message, + }; + } + + // const dir = path.join(pluginstmpdir(), packageName); + // if (!(await fs.exists(dir))) { + // await downloadPackage(packageName, dir); + // } + // return await loadPackageInfo(dir); + // return await { + // ...loadPackageInfo(dir), + // installed: loadPackageInfo(path.join(pluginsdir(), packageName)), + // }; + }, + + installed_meta: 'get', + async installed() { + const files = await fs.readdir(pluginsdir()); + const res = []; + for (const packageName of files) { + const manifest = await fs + .readFile(path.join(pluginsdir(), packageName, 'package.json')) + .then((x) => JSON.parse(x)); + const readmeFile = path.join(pluginsdir(), packageName, 'README.md'); + if (await fs.exists(readmeFile)) { + manifest.readme = await fs.readFile(readmeFile, { encoding: 'utf-8' }); + } + res.push(manifest); + } + return res; + // const res = await Promise.all( + // files.map((packageName) => + // fs.readFile(path.join(pluginsdir(), packageName, 'package.json')).then((x) => JSON.parse(x)) + // ) + // ); + }, + + install_meta: 'post', + async install({ packageName }) { + const dir = path.join(pluginsdir(), packageName); + if (!(await fs.exists(dir))) { + await downloadPackage(packageName, dir); + } + socket.emitChanged(`installed-plugins-changed`); + }, + + uninstall_meta: 'post', + async uninstall({ packageName }) { + const dir = path.join(pluginsdir(), packageName); + await fs.rmdir(dir, { recursive: true }); + socket.emitChanged(`installed-plugins-changed`); + this.removedPlugins.push(packageName); + await fs.writeFile(path.join(datadir(), 'removed-plugins'), this.removedPlugins.join('\n')); + }, + + command_meta: 'post', + async command({ packageName, command, args }) { + const content = requirePlugin(packageName); + return content.commands[command](args); + }, + + async _init() { + const installed = await this.installed(); + try { + this.removedPlugins = (await fs.readFile(path.join(datadir(), 'removed-plugins'), { encoding: 'utf-8' })).split( + '\n' + ); + } catch (err) { + this.removedPlugins = []; + } + for (const packageName of preinstallPlugins) { + if (this.removedPlugins.includes(packageName)) continue; + try { + console.log('Preinstalling plugin', packageName); + await this.install({ packageName }); + } catch (err) { + console.error('Error preinstalling plugin', packageName, err); + } + } + }, +}; diff --git a/packages/api/src/controllers/runners.js b/packages/api/src/controllers/runners.js index 98dbf7185..b87ae7776 100644 --- a/packages/api/src/controllers/runners.js +++ b/packages/api/src/controllers/runners.js @@ -5,10 +5,25 @@ const uuidv1 = require('uuid/v1'); const byline = require('byline'); const socket = require('../utility/socket'); const { fork } = require('child_process'); -const { rundir, uploadsdir } = require('../utility/directories'); +const { rundir, uploadsdir, pluginsdir } = require('../utility/directories'); +const { extractShellApiPlugins, extractShellApiFunctionName } = require('dbgate-tools'); + +function extractPlugins(script) { + const requireRegex = /\s*\/\/\s*@require\s+([^\s]+)\s*\n/g; + const matches = [...script.matchAll(requireRegex)]; + return matches.map((x) => x[1]); +} + +const requirePluginsTemplate = (plugins) => + plugins + .map( + (packageName) => `const ${_.camelCase(packageName)} = require(process.env.PLUGIN_${_.camelCase(packageName)});\n` + ) + .join('') + `dbgateApi.registerPlugins(${plugins.map((x) => _.camelCase(x)).join(',')});\n`; const scriptTemplate = (script) => ` -const dbgateApi = require(process.env.DBGATE_API || "dbgate-api"); +const dbgateApi = require(process.env.DBGATE_API); +${requirePluginsTemplate(extractPlugins(script))} require=null; async function run() { ${script} @@ -19,10 +34,11 @@ dbgateApi.runScript(run); `; const loaderScriptTemplate = (functionName, props, runid) => ` -const dbgateApi = require(process.env.DBGATE_API || "dbgate-api"); +const dbgateApi = require(process.env.DBGATE_API); +${requirePluginsTemplate(extractShellApiPlugins(functionName, props))} require=null; async function run() { -const reader=await dbgateApi.${functionName}(${JSON.stringify(props)}); +const reader=await ${extractShellApiFunctionName(functionName)}(${JSON.stringify(props)}); const writer=await dbgateApi.collectorWriter({runid: '${runid}'}); await dbgateApi.copyStream(reader, writer); } @@ -73,12 +89,14 @@ module.exports = { const scriptFile = path.join(uploadsdir(), runid + '.js'); fs.writeFileSync(`${scriptFile}`, scriptText); fs.mkdirSync(directory); + const pluginNames = fs.readdirSync(pluginsdir()); console.log(`RUNNING SCRIPT ${scriptFile}`); const subprocess = fork(scriptFile, ['--checkParent'], { cwd: directory, stdio: ['ignore', 'pipe', 'pipe', 'ipc'], env: { DBGATE_API: process.argv[1], + ..._.fromPairs(pluginNames.map((name) => [`PLUGIN_${_.camelCase(name)}`, path.join(pluginsdir(), name)])), }, }); const pipeDispatcher = (severity) => (data) => @@ -153,4 +171,3 @@ module.exports = { return promise; }, }; - diff --git a/packages/api/src/controllers/uploads.js b/packages/api/src/controllers/uploads.js index 54407d4da..342a7be8c 100644 --- a/packages/api/src/controllers/uploads.js +++ b/packages/api/src/controllers/uploads.js @@ -2,21 +2,6 @@ const path = require('path'); const { uploadsdir } = require('../utility/directories'); const uuidv1 = require('uuid/v1'); -const extensions = [ - { - ext: '.xlsx', - type: 'excel', - }, - { - ext: '.jsonl', - type: 'jsonl', - }, - { - ext: '.csv', - type: 'csv', - }, -]; - module.exports = { upload_meta: { method: 'post', @@ -31,19 +16,10 @@ module.exports = { const uploadName = uuidv1(); const filePath = path.join(uploadsdir(), uploadName); console.log(`Uploading file ${data.name}, size=${data.size}`); - let storageType = null; - let shortName = data.name; - for (const { ext, type } of extensions) { - if (data.name.endsWith(ext)) { - storageType = type; - shortName = data.name.slice(0, -ext.length); - } - } + data.mv(filePath, () => { res.json({ originalName: data.name, - shortName, - storageType, uploadName, filePath, }); diff --git a/packages/api/src/main.js b/packages/api/src/main.js index bafaf0c59..37841c0cf 100644 --- a/packages/api/src/main.js +++ b/packages/api/src/main.js @@ -20,9 +20,9 @@ const sessions = require('./controllers/sessions'); const runners = require('./controllers/runners'); const jsldata = require('./controllers/jsldata'); const config = require('./controllers/config'); -const files = require('./controllers/files'); const archive = require('./controllers/archive'); const uploads = require('./controllers/uploads'); +const plugins = require('./controllers/plugins'); const { rundir } = require('./utility/directories'); @@ -64,9 +64,9 @@ function start(argument = null) { useController(app, '/runners', runners); useController(app, '/jsldata', jsldata); useController(app, '/config', config); - useController(app, '/files', files); useController(app, '/archive', archive); useController(app, '/uploads', uploads); + useController(app, '/plugins', plugins); if (process.env.PAGES_DIRECTORY) { app.use('/pages', express.static(process.env.PAGES_DIRECTORY)); diff --git a/packages/api/src/proc/connectProcess.js b/packages/api/src/proc/connectProcess.js index 9ba051492..a4c916b2a 100644 --- a/packages/api/src/proc/connectProcess.js +++ b/packages/api/src/proc/connectProcess.js @@ -1,13 +1,12 @@ -const engines = require('dbgate-engines'); -const driverConnect = require('../utility/driverConnect'); const childProcessChecker = require('../utility/childProcessChecker'); +const requireEngineDriver = require('../utility/requireEngineDriver'); function start() { childProcessChecker(); process.on('message', async (connection) => { try { - const driver = engines(connection); - const conn = await driverConnect(driver, connection); + const driver = requireEngineDriver(connection); + const conn = await driver.connect(connection); const res = await driver.getVersion(conn); process.send({ msgtype: 'connected', ...res }); } catch (e) { diff --git a/packages/api/src/proc/databaseConnectionProcess.js b/packages/api/src/proc/databaseConnectionProcess.js index 08665fcf9..6611fcdcf 100644 --- a/packages/api/src/proc/databaseConnectionProcess.js +++ b/packages/api/src/proc/databaseConnectionProcess.js @@ -1,7 +1,6 @@ -const engines = require('dbgate-engines'); const stableStringify = require('json-stable-stringify'); -const driverConnect = require('../utility/driverConnect'); const childProcessChecker = require('../utility/childProcessChecker'); +const requireEngineDriver = require('../utility/requireEngineDriver'); let systemConnection; let storedConnection; @@ -26,14 +25,14 @@ async function checkedAsyncCall(promise) { } async function handleFullRefresh() { - const driver = engines(storedConnection); + const driver = requireEngineDriver(storedConnection); analysedStructure = await checkedAsyncCall(driver.analyseFull(systemConnection)); process.send({ msgtype: 'structure', structure: analysedStructure }); setStatusName('ok'); } async function handleIncrementalRefresh() { - const driver = engines(storedConnection); + const driver = requireEngineDriver(storedConnection); const newStructure = await checkedAsyncCall(driver.analyseIncremental(systemConnection, analysedStructure)); if (newStructure != null) { analysedStructure = newStructure; @@ -58,8 +57,8 @@ async function handleConnect({ connection, structure }) { lastPing = new Date().getTime(); if (!structure) setStatusName('pending'); - const driver = engines(storedConnection); - systemConnection = await checkedAsyncCall(driverConnect(driver, storedConnection)); + const driver = requireEngineDriver(storedConnection); + systemConnection = await checkedAsyncCall(driver.connect(storedConnection)); if (structure) { analysedStructure = structure; handleIncrementalRefresh(); @@ -82,7 +81,7 @@ function waitConnected() { async function handleQueryData({ msgid, sql }) { await waitConnected(); - const driver = engines(storedConnection); + const driver = requireEngineDriver(storedConnection); try { const res = await driver.query(systemConnection, sql); process.send({ msgtype: 'response', msgid, ...res }); diff --git a/packages/api/src/proc/serverConnectionProcess.js b/packages/api/src/proc/serverConnectionProcess.js index 0d08b95ca..225f8cdd1 100644 --- a/packages/api/src/proc/serverConnectionProcess.js +++ b/packages/api/src/proc/serverConnectionProcess.js @@ -1,7 +1,6 @@ -const engines = require('dbgate-engines'); const stableStringify = require('json-stable-stringify'); -const driverConnect = require('../utility/driverConnect'); const childProcessChecker = require('../utility/childProcessChecker'); +const requireEngineDriver = require('../utility/requireEngineDriver'); let systemConnection; let storedConnection; @@ -10,7 +9,7 @@ let lastStatus = null; let lastPing = null; async function handleRefresh() { - const driver = engines(storedConnection); + const driver = requireEngineDriver(storedConnection); try { const databases = await driver.listDatabases(systemConnection); setStatusName('ok'); @@ -46,9 +45,9 @@ async function handleConnect(connection) { setStatusName('pending'); lastPing = new Date().getTime(); - const driver = engines(storedConnection); + const driver = requireEngineDriver(storedConnection); try { - systemConnection = await driverConnect(driver, storedConnection); + systemConnection = await driver.connect(storedConnection); handleRefresh(); setInterval(handleRefresh, 30 * 1000); } catch (err) { @@ -66,8 +65,8 @@ function handlePing() { } async function handleCreateDatabase({ name }) { - const driver = engines(storedConnection); - systemConnection = await driverConnect(driver, storedConnection); + const driver = requireEngineDriver(storedConnection); + systemConnection = await driver.connect(storedConnection); console.log(`RUNNING SCRIPT: CREATE DATABASE ${driver.dialect.quoteIdentifier(name)}`); await driver.query(systemConnection, `CREATE DATABASE ${driver.dialect.quoteIdentifier(name)}`); await handleRefresh(); diff --git a/packages/api/src/proc/sessionProcess.js b/packages/api/src/proc/sessionProcess.js index a24bf42c7..eae01137e 100644 --- a/packages/api/src/proc/sessionProcess.js +++ b/packages/api/src/proc/sessionProcess.js @@ -1,4 +1,3 @@ -const engines = require('dbgate-engines'); const uuidv1 = require('uuid/v1'); const path = require('path'); const fs = require('fs'); @@ -6,8 +5,8 @@ const _ = require('lodash'); const childProcessChecker = require('../utility/childProcessChecker'); const goSplit = require('../utility/goSplit'); -const driverConnect = require('../utility/driverConnect'); const { jsldir } = require('../utility/directories'); +const requireEngineDriver = require('../utility/requireEngineDriver'); let systemConnection; let storedConnection; @@ -119,8 +118,8 @@ class StreamHandler { async function handleConnect(connection) { storedConnection = connection; - const driver = engines(storedConnection); - systemConnection = await driverConnect(driver, storedConnection); + const driver = requireEngineDriver(storedConnection); + systemConnection = await driver.connect(storedConnection); for (const [resolve] of afterConnectCallbacks) { resolve(); } @@ -142,7 +141,7 @@ function waitConnected() { async function handleExecuteQuery({ sql }) { await waitConnected(); - const driver = engines(storedConnection); + const driver = requireEngineDriver(storedConnection); let resultIndex = 0; for (const sqlItem of goSplit(sql)) { diff --git a/packages/api/src/shell/csvReader.js b/packages/api/src/shell/csvReader.js deleted file mode 100644 index 25261c8d0..000000000 --- a/packages/api/src/shell/csvReader.js +++ /dev/null @@ -1,55 +0,0 @@ -const _ = require('lodash'); -const csv = require('csv'); -const fs = require('fs'); -const stream = require('stream'); - -class CsvPrepareStream extends stream.Transform { - constructor({ header }) { - super({ objectMode: true }); - this.structure = null; - this.header = header; - } - _transform(chunk, encoding, done) { - if (this.structure) { - this.push( - _.zipObject( - this.structure.columns.map((x) => x.columnName), - chunk - ) - ); - done(); - } else { - if (this.header) { - this.structure = { columns: chunk.map((columnName) => ({ columnName })) }; - this.push(this.structure); - } else { - this.structure = { columns: chunk.map((value, index) => ({ columnName: `col${index + 1}` })) }; - this.push(this.structure); - this.push( - _.zipObject( - this.structure.columns.map((x) => x.columnName), - chunk - ) - ); - } - done(); - } - } -} - -async function csvReader({ fileName, encoding = 'utf-8', header = true, delimiter, limitRows = undefined }) { - console.log(`Reading file ${fileName}`); - const csvStream = csv.parse({ - // @ts-ignore - delimiter, - skip_lines_with_error: true, - to_line: limitRows ? limitRows + 1 : -1, - }); - const fileStream = fs.createReadStream(fileName, encoding); - const csvPrepare = new CsvPrepareStream({ header }); - fileStream.pipe(csvStream); - csvStream.pipe(csvPrepare); - return csvPrepare; -} - -module.exports = csvReader; diff --git a/packages/api/src/shell/csvWriter.js b/packages/api/src/shell/csvWriter.js deleted file mode 100644 index a5297c651..000000000 --- a/packages/api/src/shell/csvWriter.js +++ /dev/null @@ -1,36 +0,0 @@ -const csv = require('csv'); -const fs = require('fs'); -const stream = require('stream'); - -class CsvPrepareStream extends stream.Transform { - constructor({ header }) { - super({ objectMode: true }); - this.structure = null; - this.header = header; - } - _transform(chunk, encoding, done) { - if (this.structure) { - this.push(this.structure.columns.map((col) => chunk[col.columnName])); - done(); - } else { - this.structure = chunk; - if (this.header) { - this.push(chunk.columns.map((x) => x.columnName)); - } - done(); - } - } -} - -async function csvWriter({ fileName, encoding = 'utf-8', header = true, delimiter, quoted }) { - console.log(`Writing file ${fileName}`); - const csvPrepare = new CsvPrepareStream({ header }); - const csvStream = csv.stringify({ delimiter, quoted }); - const fileStream = fs.createWriteStream(fileName, encoding); - csvPrepare.pipe(csvStream); - csvStream.pipe(fileStream); - csvPrepare['finisher'] = fileStream; - return csvPrepare; -} - -module.exports = csvWriter; diff --git a/packages/api/src/shell/excelSheetReader.js b/packages/api/src/shell/excelSheetReader.js deleted file mode 100644 index 2ad325d1d..000000000 --- a/packages/api/src/shell/excelSheetReader.js +++ /dev/null @@ -1,41 +0,0 @@ -const xlsx = require('xlsx'); -const stream = require('stream'); -const _ = require('lodash'); - -const loadedWorkbooks = {}; - -async function loadWorkbook(fileName) { - let workbook = loadedWorkbooks[fileName]; - if (workbook) return workbook; - console.log(`Loading excel ${fileName}`); - workbook = xlsx.readFile(fileName); - loadedWorkbooks[fileName] = workbook; - return workbook; -} - -async function excelSheetReader({ fileName, sheetName, limitRows = undefined }) { - const workbook = await loadWorkbook(fileName); - const sheet = workbook.Sheets[sheetName]; - - const pass = new stream.PassThrough({ - objectMode: true, - }); - const rows = xlsx.utils.sheet_to_json(sheet, { header: 1 }); - const header = rows[0]; - const structure = { - columns: _.range(header.length).map((index) => ({ columnName: header[index] })), - }; - pass.write(structure); - for (let rowIndex = 1; rowIndex < rows.length; rowIndex++) { - if (limitRows && rowIndex > limitRows) break; - const row = rows[rowIndex]; - const rowData = _.fromPairs(structure.columns.map((col, index) => [col.columnName, row[index]])); - if (_.isEmpty(_.omitBy(rowData, (v) => v == null || v.toString().trim().length == 0))) continue; - pass.write(rowData); - } - pass.end(); - - return pass; -} - -module.exports = excelSheetReader; diff --git a/packages/api/src/shell/excelSheetWriter.js b/packages/api/src/shell/excelSheetWriter.js deleted file mode 100644 index 30ce9f983..000000000 --- a/packages/api/src/shell/excelSheetWriter.js +++ /dev/null @@ -1,56 +0,0 @@ -const xlsx = require('xlsx'); -const stream = require('stream'); -const finalizer = require('./finalizer'); - -const writingWorkbooks = {}; - -async function saveExcelFiles() { - for (const file in writingWorkbooks) { - xlsx.writeFile(writingWorkbooks[file], file); - } -} - -finalizer.register(saveExcelFiles); - -function createWorkbook(fileName) { - let workbook = writingWorkbooks[fileName]; - if (workbook) return workbook; - workbook = xlsx.utils.book_new(); - writingWorkbooks[fileName] = workbook; - return workbook; -} - -class ExcelSheetWriterStream extends stream.Writable { - constructor({ fileName, sheetName }) { - super({ objectMode: true }); - this.rows = []; - this.structure = null; - this.fileName = fileName; - this.sheetName = sheetName; - } - _write(chunk, enc, next) { - if (this.structure) { - this.rows.push(this.structure.columns.map((col) => chunk[col.columnName])); - } else { - this.structure = chunk; - this.rows.push(chunk.columns.map((x) => x.columnName)); - } - - next(); - } - - _final(callback) { - const workbook = createWorkbook(this.fileName); - xlsx.utils.book_append_sheet(workbook, xlsx.utils.aoa_to_sheet(this.rows), this.sheetName || 'Sheet 1'); - callback(); - } -} - -async function excelSheetWriter({ fileName, sheetName }) { - return new ExcelSheetWriterStream({ - fileName, - sheetName, - }); -} - -module.exports = excelSheetWriter; diff --git a/packages/api/src/shell/index.js b/packages/api/src/shell/index.js index a7143d375..521ca41c2 100644 --- a/packages/api/src/shell/index.js +++ b/packages/api/src/shell/index.js @@ -1,14 +1,10 @@ const queryReader = require('./queryReader'); -const csvWriter = require('./csvWriter'); -const csvReader = require('./csvReader'); const runScript = require('./runScript'); const tableWriter = require('./tableWriter'); const tableReader = require('./tableReader'); const copyStream = require('./copyStream'); const fakeObjectReader = require('./fakeObjectReader'); const consoleObjectWriter = require('./consoleObjectWriter'); -const excelSheetReader = require('./excelSheetReader'); -const excelSheetWriter = require('./excelSheetWriter'); const jsonLinesWriter = require('./jsonLinesWriter'); const jsonLinesReader = require('./jsonLinesReader'); const jslDataReader = require('./jslDataReader'); @@ -16,16 +12,15 @@ const archiveWriter = require('./archiveWriter'); const archiveReader = require('./archiveReader'); const collectorWriter = require('./collectorWriter'); const finalizer = require('./finalizer'); +const registerPlugins = require('./registerPlugins'); +const requirePlugin = require('./requirePlugin'); -module.exports = { +const dbgateApi = { queryReader, - csvWriter, - csvReader, runScript, tableWriter, tableReader, copyStream, - excelSheetReader, jsonLinesWriter, jsonLinesReader, fakeObjectReader, @@ -34,6 +29,10 @@ module.exports = { archiveWriter, archiveReader, collectorWriter, - excelSheetWriter, finalizer, + registerPlugins, }; + +requirePlugin.initialize(dbgateApi); + +module.exports = dbgateApi; diff --git a/packages/api/src/shell/jsonLinesReader.js b/packages/api/src/shell/jsonLinesReader.js index e36db95fc..0a7018bd1 100644 --- a/packages/api/src/shell/jsonLinesReader.js +++ b/packages/api/src/shell/jsonLinesReader.js @@ -18,6 +18,7 @@ class ParseStream extends stream.Transform { } if (!this.limitRows || this.rowsWritten < this.limitRows) { this.push(obj); + this.rowsWritten += 1; } done(); } diff --git a/packages/api/src/shell/queryReader.js b/packages/api/src/shell/queryReader.js index 12ae13bec..13b2e4159 100644 --- a/packages/api/src/shell/queryReader.js +++ b/packages/api/src/shell/queryReader.js @@ -1,12 +1,10 @@ -const driverConnect = require('../utility/driverConnect'); - -const engines = require('dbgate-engines'); +const requireEngineDriver = require("../utility/requireEngineDriver"); async function queryReader({ connection, sql }) { console.log(`Reading query ${sql}`); - const driver = engines(connection); - const pool = await driverConnect(driver, connection); + const driver = requireEngineDriver(connection); + const pool = await driver.connect(connection); console.log(`Connected.`); return await driver.readQuery(pool, sql); } diff --git a/packages/api/src/shell/registerPlugins.js b/packages/api/src/shell/registerPlugins.js new file mode 100644 index 000000000..634463a8a --- /dev/null +++ b/packages/api/src/shell/registerPlugins.js @@ -0,0 +1,9 @@ +const requirePlugin = require('./requirePlugin'); + +function registerPlugins(...plugins) { + for (const plugin of plugins) { + requirePlugin(plugin.packageName, plugin); + } +} + +module.exports = registerPlugins; diff --git a/packages/api/src/shell/requirePlugin.js b/packages/api/src/shell/requirePlugin.js new file mode 100644 index 000000000..eb8b3f537 --- /dev/null +++ b/packages/api/src/shell/requirePlugin.js @@ -0,0 +1,37 @@ +const path = require('path'); +const { pluginsdir } = require('../utility/directories'); + +const loadedPlugins = {}; + +const dbgateEnv = { + dbgateApi: null, +}; + +function requirePlugin(packageName, requiredPlugin = null) { + if (!packageName) throw new Error('Missing packageName in plugin'); + if (loadedPlugins[packageName]) return loadedPlugins[packageName]; + + if (requiredPlugin == null) { + let module; + const modulePath = path.join(pluginsdir(), packageName, 'dist', 'backend.js'); + console.log(`Loading module ${packageName} from ${modulePath}`); + try { + // @ts-ignore + module = __non_webpack_require__(modulePath); + } catch (err) { + console.error('Failed load webpacked module', err); + module = require(modulePath); + } + requiredPlugin = module.__esModule ? module.default : module; + } + loadedPlugins[packageName] = requiredPlugin; + if (requiredPlugin.initialize) requiredPlugin.initialize(dbgateEnv); + + return requiredPlugin; +} + +requirePlugin.initialize = (value) => { + dbgateEnv.dbgateApi = value; +}; + +module.exports = requirePlugin; diff --git a/packages/api/src/shell/tableReader.js b/packages/api/src/shell/tableReader.js index 172fabb0f..54a09b512 100644 --- a/packages/api/src/shell/tableReader.js +++ b/packages/api/src/shell/tableReader.js @@ -1,11 +1,9 @@ const { quoteFullName } = require('dbgate-tools'); -const driverConnect = require('../utility/driverConnect'); - -const engines = require('dbgate-engines'); +const requireEngineDriver = require('../utility/requireEngineDriver'); async function tableReader({ connection, pureName, schemaName }) { - const driver = engines(connection); - const pool = await driverConnect(driver, connection); + const driver = requireEngineDriver(connection); + const pool = await driver.connect(connection); console.log(`Connected.`); const fullName = { pureName, schemaName }; @@ -14,11 +12,13 @@ async function tableReader({ connection, pureName, schemaName }) { const query = `select * from ${quoteFullName(driver.dialect, fullName)}`; if (table) { console.log(`Reading table ${table.pureName}`); + // @ts-ignore return await driver.readQuery(pool, query, table); } const view = await driver.analyseSingleObject(pool, fullName, 'views'); if (view) { console.log(`Reading view ${view.pureName}`); + // @ts-ignore return await driver.readQuery(pool, query, view); } diff --git a/packages/api/src/shell/tableWriter.js b/packages/api/src/shell/tableWriter.js index 0d7b16533..f59e3e4c7 100644 --- a/packages/api/src/shell/tableWriter.js +++ b/packages/api/src/shell/tableWriter.js @@ -1,12 +1,10 @@ -const driverConnect = require('../utility/driverConnect'); - -const engines = require('dbgate-engines'); +const requireEngineDriver = require("../utility/requireEngineDriver"); async function tableWriter({ connection, schemaName, pureName, ...options }) { console.log(`Write table ${schemaName}.${pureName}`); - const driver = engines(connection); - const pool = await driverConnect(driver, connection); + const driver = requireEngineDriver(connection); + const pool = await driver.connect(connection); console.log(`Connected.`); return await driver.writeTable(pool, { schemaName, pureName }, options); } diff --git a/packages/api/src/utility/directories.js b/packages/api/src/utility/directories.js index d001c0920..4f0ace4b9 100644 --- a/packages/api/src/utility/directories.js +++ b/packages/api/src/utility/directories.js @@ -35,6 +35,7 @@ const dirFunc = (dirname, clean = false) => () => { const jsldir = dirFunc('jsl', true); const rundir = dirFunc('run', true); const uploadsdir = dirFunc('uploads', true); +const pluginsdir = dirFunc('plugins'); const archivedir = dirFunc('archive'); module.exports = { @@ -44,4 +45,5 @@ module.exports = { uploadsdir, archivedir, ensureDirectory, + pluginsdir, }; diff --git a/packages/api/src/utility/downloadPackage.js b/packages/api/src/utility/downloadPackage.js new file mode 100644 index 000000000..37a5ea661 --- /dev/null +++ b/packages/api/src/utility/downloadPackage.js @@ -0,0 +1,64 @@ +// const pacote = require('pacote'); +const axios = require('axios'); +// const tarballExtract = require('tarball-extract'); +const uuidv1 = require('uuid/v1'); +const path = require('path'); +const fs = require('fs'); +const zlib = require('zlib'); +const tar = require('tar'); +const ncp = require('ncp').ncp; +const { uploadsdir } = require('./directories'); + +function extractTarball(tmpFile, destination) { + return new Promise((resolve, reject) => { + fs.createReadStream(tmpFile) + .pipe(zlib.createGunzip()) + .pipe(tar.extract({ cwd: destination })) + .on('error', (err) => reject(err)) + .on('end', () => resolve()); + }); +} + +function saveStreamToFile(pipedStream, fileName) { + return new Promise((resolve, reject) => { + const fileStream = fs.createWriteStream(fileName); + fileStream.on('close', () => resolve()); + pipedStream.pipe(fileStream); + }); +} + +function copyDirectory(source, target) { + return new Promise((resolve, reject) => { + ncp(source, target, (err) => { + if (err) reject(err); + resolve(); + }); + }); +} + +async function downloadPackage(packageName, directory) { + // await pacote.extract(packageName, directory); + const infoResp = await axios.default.get(`https://registry.npmjs.org/${packageName}`); + + const { latest } = infoResp.data['dist-tags'] || {}; + if (!latest) return false; + + const tarball = infoResp.data.versions[latest].dist.tarball; + + const tmpFile = path.join(uploadsdir(), uuidv1() + '.tgz'); + console.log(`Downloading tarball ${tarball} into ${tmpFile}`); + const tarballResp = await axios.default({ + method: 'get', + url: tarball, + responseType: 'stream', + }); + await saveStreamToFile(tarballResp.data, tmpFile); + const tmpDir = path.join(uploadsdir(), uuidv1()); + fs.mkdirSync(tmpDir); + await extractTarball(tmpFile, tmpDir); + await copyDirectory(path.join(tmpDir, 'package'), directory); + + return true; +} + +module.exports = downloadPackage; diff --git a/packages/api/src/utility/driverConnect.js b/packages/api/src/utility/driverConnect.js deleted file mode 100644 index 2f4f4c9e9..000000000 --- a/packages/api/src/utility/driverConnect.js +++ /dev/null @@ -1,21 +0,0 @@ -const mssql = require('mssql'); -const mysql = require('mysql'); -const pg = require('pg'); -const pgQueryStream = require('pg-query-stream'); -const fs = require('fs'); -const stream = require('stream'); - -const nativeModules = { - mssql, - mysql, - pg, - pgQueryStream, - fs, - stream, -}; - -function driverConnect(driver, connection) { - return driver.connect(nativeModules, connection); -} - -module.exports = driverConnect; diff --git a/packages/api/src/utility/requireEngineDriver.js b/packages/api/src/utility/requireEngineDriver.js new file mode 100644 index 000000000..d263111a9 --- /dev/null +++ b/packages/api/src/utility/requireEngineDriver.js @@ -0,0 +1,24 @@ +const _ = require('lodash'); +const requirePlugin = require('../shell/requirePlugin'); + + +/** @returns {import('dbgate-types').EngineDriver} */ +function requireEngineDriver(connection) { + let engine = null; + if (_.isString(connection)) { + engine = connection; + } else if (_.isPlainObject(connection)) { + engine = connection.engine; + } + if (!engine) { + throw new Error('Could not get driver from connection'); + } + if (engine.includes('@')) { + const [shortName, packageName] = engine.split('@'); + const plugin = requirePlugin(packageName); + return plugin.driver; + } + throw new Error(`Could not found engine driver ${engine}`); +} + +module.exports = requireEngineDriver; diff --git a/packages/api/webpack.config.js b/packages/api/webpack.config.js index ccb4ff33e..d2f383753 100644 --- a/packages/api/webpack.config.js +++ b/packages/api/webpack.config.js @@ -14,9 +14,9 @@ var config = { libraryTarget: 'commonjs2', }, - optimization: { - minimize: false, - }, + // optimization: { + // minimize: false, + // }, // module: { // rules: [ @@ -29,7 +29,7 @@ var config = { plugins: [ new webpack.IgnorePlugin({ checkResource(resource) { - const lazyImports = ['pg-native', 'uws']; + const lazyImports = ['uws']; if (!lazyImports.includes(resource)) { return false; } diff --git a/packages/datalib/src/GridDisplay.ts b/packages/datalib/src/GridDisplay.ts index d3ffc940d..177a0b715 100644 --- a/packages/datalib/src/GridDisplay.ts +++ b/packages/datalib/src/GridDisplay.ts @@ -424,6 +424,7 @@ export abstract class GridDisplay { } getPageQuery(offset: number, count: number) { + if (!this.driver) return null; const select = this.createSelect(); if (!select) return null; if (this.driver.dialect.rangeSelect) select.range = { offset: offset, limit: count }; diff --git a/packages/engines/README.md b/packages/engines/README.md deleted file mode 100644 index 00fc33089..000000000 --- a/packages/engines/README.md +++ /dev/null @@ -1,52 +0,0 @@ -# dbgate-engines - -JavaScript library implementing MySQL, MS SQL and PostgreSQL operations. Server as abstraction layer for other DbGate packages, which could be database-engine independend. It can be used both on frontent (in browser) and on backend (in nodejs), but connection to real database is allowed only on backend. - -## Installation - - yarn add dbgate-engines - -## Usage -```javascript -const engines = require('dbgate-engines'); -// driver supports operations of EngineDriver listed belowe -const driver = engine('mysql'); -``` - -In most cases, you don't use driver methods directly, but you pass driver instance into other dbgate packages. - -## Driver definition - - -```typescript -export interface EngineDriver { - // works on both frontend and backend - engine: string; - dialect: SqlDialect; - createDumper(): SqlDumper; - - // works only on backend - connect(nativeModules, { server, port, user, password, database }): any; - query(pool: any, sql: string): Promise; - stream(pool: any, sql: string, options: StreamOptions); - readQuery(pool: any, sql: string, structure?: TableInfo): Promise; - writeTable(pool: any, name: NamedObjectInfo, options: WriteTableOptions): Promise; - analyseSingleObject( - pool: any, - name: NamedObjectInfo, - objectTypeField: keyof DatabaseInfo - ): Promise; - analyseSingleTable(pool: any, name: NamedObjectInfo): Promise; - getVersion(pool: any): Promise<{ version: string }>; - listDatabases( - pool: any - ): Promise< - { - name: string; - }[] - >; - analyseFull(pool: any): Promise; - analyseIncremental(pool: any, structure: DatabaseInfo): Promise; -} - -``` \ No newline at end of file diff --git a/packages/engines/index.d.ts b/packages/engines/index.d.ts deleted file mode 100644 index 1ce5b0395..000000000 --- a/packages/engines/index.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -import types from "dbgate-types"; - -declare function getDriver( - connection: string | { engine: string } -): types.EngineDriver; - -export = getDriver; diff --git a/packages/engines/index.js b/packages/engines/index.js deleted file mode 100644 index eb2dd4adc..000000000 --- a/packages/engines/index.js +++ /dev/null @@ -1,24 +0,0 @@ -const _ = require("lodash"); -const mssql = require("./mssql"); -const mysql = require("./mysql"); -const postgres = require("./postgres"); - -const drivers = { - mssql, - mysql, - postgres -}; - -function getDriver(connection) { - if (_.isString(connection)) { - return drivers[connection]; - } - if (_.isPlainObject(connection)) { - const { engine } = connection; - if (engine) { - return drivers[engine]; - } - } - throw new Error(`Cannot extract engine from ${connection}`); -} -module.exports = getDriver; diff --git a/packages/engines/mssql/MsSqlAnalyser.js b/packages/engines/mssql/MsSqlAnalyser.js deleted file mode 100644 index 8a272a3c8..000000000 --- a/packages/engines/mssql/MsSqlAnalyser.js +++ /dev/null @@ -1,212 +0,0 @@ -const fp = require('lodash/fp'); -const _ = require('lodash'); -const sql = require('./sql'); - -const DatabaseAnalyser = require('../default/DatabaseAnalyser'); -const { filter } = require('lodash'); -const { isTypeString, isTypeNumeric } = require('dbgate-tools'); - -function objectTypeToField(type) { - switch (type.trim()) { - case 'U': - return 'tables'; - case 'V': - return 'views'; - case 'P': - return 'procedures'; - case 'IF': - case 'FN': - case 'TF': - return 'functions'; - case 'TR': - return 'triggers'; - default: - return null; - } -} - -function getColumnInfo({ - isNullable, - isIdentity, - columnName, - dataType, - charMaxLength, - numericPrecision, - numericScale, -}) { - let fullDataType = dataType; - if (charMaxLength && isTypeString(dataType)) fullDataType = `${dataType}(${charMaxLength})`; - if (numericPrecision && numericScale && isTypeNumeric(dataType)) - fullDataType = `${dataType}(${numericPrecision},${numericScale})`; - return { - columnName, - dataType: fullDataType, - notNull: !isNullable, - autoIncrement: !!isIdentity, - }; -} - -class MsSqlAnalyser extends DatabaseAnalyser { - constructor(pool, driver) { - super(pool, driver); - this.singleObjectId = null; - } - - createQuery(resFileName, typeFields) { - let res = sql[resFileName]; - if (this.singleObjectFilter) { - const { typeField } = this.singleObjectFilter; - if (!this.singleObjectId) return null; - if (!typeFields || !typeFields.includes(typeField)) return null; - return res.replace('=[OBJECT_ID_CONDITION]', ` = ${this.singleObjectId}`); - } - if (!this.modifications || !typeFields || this.modifications.length == 0) { - res = res.replace('=[OBJECT_ID_CONDITION]', ' is not null'); - } else { - const filterIds = this.modifications - .filter((x) => typeFields.includes(x.objectTypeField) && (x.action == 'add' || x.action == 'change')) - .map((x) => x.objectId); - if (filterIds.length == 0) { - res = res.replace('=[OBJECT_ID_CONDITION]', ' = 0'); - } else { - res = res.replace('=[OBJECT_ID_CONDITION]', ` in (${filterIds.join(',')})`); - } - } - return res; - } - - async getSingleObjectId() { - if (this.singleObjectFilter) { - const { schemaName, pureName, typeField } = this.singleObjectFilter; - const fullName = schemaName ? `[${schemaName}].[${pureName}]` : pureName; - const resId = await this.driver.query(this.pool, `SELECT OBJECT_ID('${fullName}') AS id`); - this.singleObjectId = resId.rows[0].id; - } - } - - async _runAnalysis() { - await this.getSingleObjectId(); - const tablesRows = await this.driver.query(this.pool, this.createQuery('tables', ['tables'])); - const columnsRows = await this.driver.query(this.pool, this.createQuery('columns', ['tables'])); - const pkColumnsRows = await this.driver.query(this.pool, this.createQuery('primaryKeys', ['tables'])); - const fkColumnsRows = await this.driver.query(this.pool, this.createQuery('foreignKeys', ['tables'])); - const schemaRows = await this.driver.query(this.pool, this.createQuery('getSchemas')); - - const schemas = schemaRows.rows; - - const sqlCodeRows = await this.driver.query( - this.pool, - this.createQuery('loadSqlCode', ['views', 'procedures', 'functions', 'triggers']) - ); - const getCreateSql = (row) => - sqlCodeRows.rows - .filter((x) => x.pureName == row.pureName && x.schemaName == row.schemaName) - .map((x) => x.codeText) - .join(''); - const viewsRows = await this.driver.query(this.pool, this.createQuery('views', ['views'])); - const programmableRows = await this.driver.query( - this.pool, - this.createQuery('programmables', ['procedures', 'functions']) - ); - const viewColumnRows = await this.driver.query(this.pool, this.createQuery('viewColumns', ['views'])); - - const tables = tablesRows.rows.map((row) => ({ - ...row, - columns: columnsRows.rows.filter((col) => col.objectId == row.objectId).map(getColumnInfo), - primaryKey: DatabaseAnalyser.extractPrimaryKeys(row, pkColumnsRows.rows), - foreignKeys: DatabaseAnalyser.extractForeignKeys(row, fkColumnsRows.rows), - })); - - const views = viewsRows.rows.map((row) => ({ - ...row, - createSql: getCreateSql(row), - columns: viewColumnRows.rows.filter((col) => col.objectId == row.objectId).map(getColumnInfo), - })); - - const procedures = programmableRows.rows - .filter((x) => x.sqlObjectType.trim() == 'P') - .map((row) => ({ - ...row, - createSql: getCreateSql(row), - })); - - const functions = programmableRows.rows - .filter((x) => ['FN', 'IF', 'TF'].includes(x.sqlObjectType.trim())) - .map((row) => ({ - ...row, - createSql: getCreateSql(row), - })); - - return this.mergeAnalyseResult( - { - tables, - views, - procedures, - functions, - schemas, - }, - (x) => x.objectId - ); - } - - getDeletedObjectsForField(idArray, objectTypeField) { - return this.structure[objectTypeField] - .filter((x) => !idArray.includes(x.objectId)) - .map((x) => ({ - oldName: _.pick(x, ['schemaName', 'pureName']), - objectId: x.objectId, - action: 'remove', - objectTypeField, - })); - } - - getDeletedObjects(idArray) { - return [ - ...this.getDeletedObjectsForField(idArray, 'tables'), - ...this.getDeletedObjectsForField(idArray, 'views'), - ...this.getDeletedObjectsForField(idArray, 'procedures'), - ...this.getDeletedObjectsForField(idArray, 'functions'), - ...this.getDeletedObjectsForField(idArray, 'triggers'), - ]; - } - - async getModifications() { - const modificationsQueryData = await this.driver.query(this.pool, this.createQuery('modifications')); - // console.log('MOD - SRC', modifications); - // console.log( - // 'MODs', - // this.structure.tables.map((x) => x.modifyDate) - // ); - const modifications = modificationsQueryData.rows.map((x) => { - const { type, objectId, modifyDate, schemaName, pureName } = x; - const field = objectTypeToField(type); - if (!this.structure[field]) return null; - // @ts-ignore - const obj = this.structure[field].find((x) => x.objectId == objectId); - - // object not modified - if (obj && Math.abs(new Date(modifyDate).getTime() - new Date(obj.modifyDate).getTime()) < 1000) return null; - - /** @type {import('dbgate-types').DatabaseModification} */ - const action = obj - ? { - newName: { schemaName, pureName }, - oldName: _.pick(obj, ['schemaName', 'pureName']), - action: 'change', - objectTypeField: field, - objectId, - } - : { - newName: { schemaName, pureName }, - action: 'add', - objectTypeField: field, - objectId, - }; - return action; - }); - - return [..._.compact(modifications), ...this.getDeletedObjects(modificationsQueryData.rows.map((x) => x.objectId))]; - } -} - -module.exports = MsSqlAnalyser; diff --git a/packages/engines/mssql/MsSqlDumper.js b/packages/engines/mssql/MsSqlDumper.js deleted file mode 100644 index ba5bf0b8a..000000000 --- a/packages/engines/mssql/MsSqlDumper.js +++ /dev/null @@ -1,54 +0,0 @@ -const SqlDumper = require('../default/SqlDumper'); - -class MsSqlDumper extends SqlDumper { - autoIncrement() { - this.put(' ^identity'); - } - - putStringValue(value) { - if (/[^\u0000-\u00ff]/.test(value)) { - this.putRaw('N'); - } - super.putStringValue(value); - } - - allowIdentityInsert(table, allow) { - this.putCmd('^set ^identity_insert %f %k;&n', table, allow ? 'on' : 'off'); - } - - /** @param type {import('dbgate-types').TransformType} */ - transform(type, dumpExpr) { - switch (type) { - case 'GROUP:YEAR': - case 'YEAR': - this.put('^datepart(^year, %c)', dumpExpr); - break; - case 'MONTH': - this.put('^datepart(^month, %c)', dumpExpr); - break; - case 'DAY': - this.put('^datepart(^day, %c)', dumpExpr); - break; - case 'GROUP:MONTH': - this.put( - "^convert(^varchar(100), ^datepart(^year, %c)) + '-' + ^convert(^varchar(100), ^datepart(^month, %c))", - dumpExpr, - dumpExpr - ); - break; - case 'GROUP:DAY': - this.put( - "^convert(^varchar(100), ^datepart(^year, %c)) + '-' + ^convert(^varchar(100), ^datepart(^month, %c))+'-' + ^convert(^varchar(100), ^datepart(^day, %c))", - dumpExpr, - dumpExpr, - dumpExpr - ); - break; - default: - dumpExpr(); - break; - } - } -} - -module.exports = MsSqlDumper; diff --git a/packages/engines/mssql/createBulkInsertStream.js b/packages/engines/mssql/createBulkInsertStream.js deleted file mode 100644 index b3f345102..000000000 --- a/packages/engines/mssql/createBulkInsertStream.js +++ /dev/null @@ -1,48 +0,0 @@ -const createBulkInsertStreamBase = require('../default/createBulkInsertStreamBase'); - -/** - * - * @param {import('dbgate-types').EngineDriver} driver - */ -function createBulkInsertStream(driver, mssql, stream, pool, name, options) { - const writable = createBulkInsertStreamBase(driver, stream, pool, name, options); - - const fullName = name.schemaName ? `[${name.schemaName}].[${name.pureName}]` : name.pureName; - - writable.send = async () => { - if (!writable.templateColumns) { - const fullNameQuoted = name.schemaName - ? `${driver.dialect.quoteIdentifier(name.schemaName)}.${driver.dialect.quoteIdentifier(name.pureName)}` - : driver.dialect.quoteIdentifier(name.pureName); - - const respTemplate = await pool.request().query(`SELECT * FROM ${fullNameQuoted} WHERE 1=0`); - writable.templateColumns = respTemplate.recordset.toTable().columns; - } - - const rows = writable.buffer; - writable.buffer = []; - const table = new mssql.Table(fullName); - // table.create = options.createIfNotExists; - for (const column of this.columnNames) { - const tcol = writable.templateColumns.find((x) => x.name == column); - // console.log('TCOL', tcol); - // console.log('TYPE', tcol.type, mssql.Int); - // table.columns.add(column, tcol ? tcol.type : mssql.NVarChar(mssql.MAX)); - table.columns.add(column, tcol ? tcol.type : mssql.NVarChar(mssql.MAX), { - nullable: tcol.nullable, - length: tcol.length, - precision: tcol.precision, - scale: tcol.scale, - }); - } - for (const row of rows) { - table.rows.add(...this.columnNames.map((col) => row[col])); - } - const request = pool.request(); - await request.bulk(table); - }; - - return writable; -} - -module.exports = createBulkInsertStream; diff --git a/packages/engines/mssql/index.js b/packages/engines/mssql/index.js deleted file mode 100644 index 2eda2cde9..000000000 --- a/packages/engines/mssql/index.js +++ /dev/null @@ -1,210 +0,0 @@ -const _ = require('lodash'); -const MsSqlAnalyser = require('./MsSqlAnalyser'); -const MsSqlDumper = require('./MsSqlDumper'); -const createBulkInsertStream = require('./createBulkInsertStream'); -const driverBase = require('../default/driverBase'); - -/** @type {import('dbgate-types').SqlDialect} */ -const dialect = { - limitSelect: true, - rangeSelect: true, - offsetFetchRangeSyntax: true, - stringEscapeChar: "'", - fallbackDataType: 'nvarchar(max)', - quoteIdentifier(s) { - return `[${s}]`; - }, -}; - -function extractColumns(columns) { - const mapper = {}; - const res = _.sortBy(_.values(columns), 'index').map((col) => ({ - ...col, - columnName: col.name, - notNull: !col.nullable, - })); - - const generateName = () => { - let index = 1; - while (res.find((x) => x.columnName == `col${index}`)) index += 1; - return `col${index}`; - }; - - // const groups = _.groupBy(res, 'columnName'); - // for (const colname of _.keys(groups)) { - // if (groups[colname].length == 1) continue; - // mapper[colname] = []; - // for (const col of groups[colname]) { - // col.columnName = generateName(); - // mapper[colname].push(colname); - // } - // } - - for (const col of res) { - if (!col.columnName) { - const newName = generateName(); - mapper[col.columnName] = newName; - col.columnName = newName; - } - } - - return [res, mapper]; -} - -/** @type {import('dbgate-types').EngineDriver} */ -const driver = { - ...driverBase, - analyserClass: MsSqlAnalyser, - dumperClass: MsSqlDumper, - async connect(nativeModules, { server, port, user, password, database }) { - const pool = new nativeModules.mssql.ConnectionPool({ - server, - port, - user, - password, - database, - requestTimeout: 1000 * 3600, - options: { - enableArithAbort: true, - }, - }); - await pool.connect(); - pool._nativeModules = nativeModules; - return pool; - }, - // @ts-ignore - async query(pool, sql) { - if (sql == null) { - return { - rows: [], - columns: [], - }; - } - const resp = await pool.request().query(sql); - // console.log(Object.keys(resp.recordset)); - // console.log(resp); - const res = {}; - - if (resp.recordset) { - const [columns] = extractColumns(resp.recordset.columns); - res.columns = columns; - res.rows = resp.recordset; - } - if (resp.rowsAffected) { - res.rowsAffected = _.sum(resp.rowsAffected); - } - return res; - }, - async stream(pool, sql, options) { - const request = await pool.request(); - let currentMapper = null; - - const handleInfo = (info) => { - const { message, lineNumber, procName } = info; - options.info({ - message, - line: lineNumber, - procedure: procName, - time: new Date(), - severity: 'info', - }); - }; - - const handleDone = (result) => { - // console.log('RESULT', result); - options.done(result); - }; - - const handleRow = (row) => { - // if (currentMapper) { - // for (const colname of _.keys(currentMapper)) { - // let index = 0; - // for (const newcolname of currentMapper[colname]) { - // row[newcolname] = row[colname][index]; - // index += 1; - // } - // delete row[colname]; - // } - // } - if (currentMapper) { - row = { ...row }; - for (const colname of _.keys(currentMapper)) { - const newcolname = currentMapper[colname]; - row[newcolname] = row[colname]; - if (_.isArray(row[newcolname])) row[newcolname] = row[newcolname].join(','); - delete row[colname]; - } - } - - options.row(row); - }; - - const handleRecordset = (columns) => { - const [extractedColumns, mapper] = extractColumns(columns); - currentMapper = mapper; - options.recordset(extractedColumns); - }; - - const handleError = (error) => { - const { message, lineNumber, procName } = error; - options.info({ - message, - line: lineNumber, - procedure: procName, - time: new Date(), - severity: 'error', - }); - }; - - request.stream = true; - request.on('recordset', handleRecordset); - request.on('row', handleRow); - request.on('error', handleError); - request.on('done', handleDone); - request.on('info', handleInfo); - request.query(sql); - - return request; - }, - async readQuery(pool, sql, structure) { - const request = await pool.request(); - const { stream } = pool._nativeModules; - - const pass = new stream.PassThrough({ - objectMode: true, - highWaterMark: 100, - }); - - request.stream = true; - request.on('recordset', (driverColumns) => { - const [columns, mapper] = extractColumns(driverColumns); - pass.write(structure || { columns }); - }); - request.on('row', (row) => pass.write(row)); - request.on('error', (err) => { - console.error(err); - pass.end(); - }); - request.on('done', () => pass.end()); - - request.query(sql); - - return pass; - }, - async writeTable(pool, name, options) { - const { stream, mssql } = pool._nativeModules; - return createBulkInsertStream(this, mssql, stream, pool, name, options); - }, - async getVersion(pool) { - const { version } = (await this.query(pool, 'SELECT @@VERSION AS version')).rows[0]; - return { version }; - }, - async listDatabases(pool) { - const { rows } = await this.query(pool, 'SELECT name FROM sys.databases order by name'); - return rows; - }, - dialect, - engine: 'mssql', -}; - -module.exports = driver; diff --git a/packages/engines/mssql/sql/columns.js b/packages/engines/mssql/sql/columns.js deleted file mode 100644 index 3de7ceffa..000000000 --- a/packages/engines/mssql/sql/columns.js +++ /dev/null @@ -1,20 +0,0 @@ -module.exports = ` -select c.name as columnName, t.name as dataType, c.object_id as objectId, c.is_identity as isIdentity, - c.max_length as maxLength, c.precision, c.scale, c.is_nullable as isNullable, - col.CHARACTER_MAXIMUM_LENGTH as charMaxLength, - d.definition as defaultValue, d.name as defaultConstraint, - m.definition as computedExpression, m.is_persisted as isPersisted, c.column_id as columnId, - col.NUMERIC_PRECISION as numericPrecision, - col.NUMERIC_SCALE as numericScale, - -- TODO only if version >= 2008 - c.is_sparse as isSparse -from sys.columns c -inner join sys.types t on c.system_type_id = t.system_type_id and c.user_type_id = t.user_type_id -inner join sys.objects o on c.object_id = o.object_id -INNER JOIN sys.schemas u ON u.schema_id=o.schema_id -INNER JOIN INFORMATION_SCHEMA.COLUMNS col ON col.TABLE_NAME = o.name AND col.TABLE_SCHEMA = u.name and col.COLUMN_NAME = c.name -left join sys.default_constraints d on c.default_object_id = d.object_id -left join sys.computed_columns m on m.object_id = c.object_id and m.column_id = c.column_id -where o.type = 'U' and o.object_id =[OBJECT_ID_CONDITION] -order by c.column_id -`; diff --git a/packages/engines/mssql/sql/foreignKeys.js b/packages/engines/mssql/sql/foreignKeys.js deleted file mode 100644 index 568117b77..000000000 --- a/packages/engines/mssql/sql/foreignKeys.js +++ /dev/null @@ -1,40 +0,0 @@ -module.exports = ` -SELECT - schemaName = FK.TABLE_SCHEMA, - pureName = FK.TABLE_NAME, - columnName = CU.COLUMN_NAME, - - refSchemaName = ISNULL(IXS.name, PK.TABLE_SCHEMA), - refTableName = ISNULL(IXT.name, PK.TABLE_NAME), - refColumnName = IXCC.name, - - constraintName = C.CONSTRAINT_NAME, - updateAction = rc.UPDATE_RULE, - deleteAction = rc.DELETE_RULE, - - objectId = o.object_id - -FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS C -INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS FK ON C.CONSTRAINT_NAME = FK.CONSTRAINT_NAME - -LEFT JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS PK ON C.UNIQUE_CONSTRAINT_NAME = PK.CONSTRAINT_NAME -LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE CU ON C.CONSTRAINT_NAME = CU.CONSTRAINT_NAME ---LEFT JOIN ( ---SELECT i1.TABLE_NAME, i2.COLUMN_NAME ---FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS i1 ---INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE i2 ON i1.CONSTRAINT_NAME = i2.CONSTRAINT_NAME ---WHERE i1.CONSTRAINT_TYPE = 'PRIMARY KEY' ---) PT ON PT.TABLE_NAME = PK.TABLE_NAME -INNER JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc ON FK.CONSTRAINT_NAME = rc.CONSTRAINT_NAME - -LEFT JOIN sys.indexes IX ON IX.name = C.UNIQUE_CONSTRAINT_NAME -LEFT JOIN sys.objects IXT ON IXT.object_id = IX.object_id -LEFT JOIN sys.index_columns IXC ON IX.index_id = IXC.index_id and IX.object_id = IXC.object_id -LEFT JOIN sys.columns IXCC ON IXCC.column_id = IXC.column_id AND IXCC.object_id = IXC.object_id -LEFT JOIN sys.schemas IXS ON IXT.schema_id = IXS.schema_id - -inner join sys.objects o on FK.TABLE_NAME = o.name -inner join sys.schemas s on o.schema_id = s.schema_id and FK.TABLE_SCHEMA = s.name - -where o.object_id =[OBJECT_ID_CONDITION] -`; diff --git a/packages/engines/mssql/sql/getSchemas.js b/packages/engines/mssql/sql/getSchemas.js deleted file mode 100644 index a7cb0a4de..000000000 --- a/packages/engines/mssql/sql/getSchemas.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = `select schema_id as objectId, name as schemaName from sys.schemas`; diff --git a/packages/engines/mssql/sql/index.js b/packages/engines/mssql/sql/index.js deleted file mode 100644 index cb40e88a5..000000000 --- a/packages/engines/mssql/sql/index.js +++ /dev/null @@ -1,23 +0,0 @@ -const columns = require('./columns'); -const foreignKeys = require('./foreignKeys'); -const primaryKeys = require('./primaryKeys'); -const tables = require('./tables'); -const modifications = require('./modifications'); -const loadSqlCode = require('./loadSqlCode'); -const views = require('./views'); -const programmables = require('./programmables'); -const viewColumns = require('./viewColumns'); -const getSchemas = require('./getSchemas'); - -module.exports = { - columns, - tables, - foreignKeys, - primaryKeys, - modifications, - loadSqlCode, - views, - programmables, - viewColumns, - getSchemas, -}; diff --git a/packages/engines/mssql/sql/loadSqlCode.js b/packages/engines/mssql/sql/loadSqlCode.js deleted file mode 100644 index c5f8ee87d..000000000 --- a/packages/engines/mssql/sql/loadSqlCode.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = ` -select s.name as pureName, u.name as schemaName, c.text AS codeText - from sys.objects s - inner join sys.syscomments c on s.object_id = c.id - inner join sys.schemas u on u.schema_id = s.schema_id -where (s.object_id =[OBJECT_ID_CONDITION]) -order by u.name, s.name, c.colid -`; diff --git a/packages/engines/mssql/sql/modifications.js b/packages/engines/mssql/sql/modifications.js deleted file mode 100644 index 4389fd6ca..000000000 --- a/packages/engines/mssql/sql/modifications.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = ` -select o.object_id as objectId, o.modify_date as modifyDate, o.type, o.name as pureName, s.name as schemaName -from sys.objects o -inner join sys.schemas s on o.schema_id = s.schema_id -where o.type in ('U', 'V', 'P', 'IF', 'FN', 'TF') -- , 'TR' - triggers disabled -`; diff --git a/packages/engines/mssql/sql/primaryKeys.js b/packages/engines/mssql/sql/primaryKeys.js deleted file mode 100644 index f67beb7f2..000000000 --- a/packages/engines/mssql/sql/primaryKeys.js +++ /dev/null @@ -1,14 +0,0 @@ -module.exports = ` -select o.object_id, pureName = t.Table_Name, schemaName = t.Table_Schema, columnName = c.Column_Name, constraintName=t.constraint_name from - INFORMATION_SCHEMA.TABLE_CONSTRAINTS t, - sys.objects o, - sys.schemas s, - INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE c -where - c.Constraint_Name = t.Constraint_Name - and t.table_name = o.name - and o.schema_id = s.schema_id and t.Table_Schema = s.name - and c.Table_Name = t.Table_Name - and Constraint_Type = 'PRIMARY KEY' - and o.object_id =[OBJECT_ID_CONDITION] -`; diff --git a/packages/engines/mssql/sql/programmables.js b/packages/engines/mssql/sql/programmables.js deleted file mode 100644 index 8e651d3ec..000000000 --- a/packages/engines/mssql/sql/programmables.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = ` -select o.name as pureName, s.name as schemaName, o.object_id as objectId, o.create_date as createDate, o.modify_date as modifyDate, o.type as sqlObjectType -from sys.objects o -inner join sys.schemas s on o.schema_id = s.schema_id -where o.type in ('P', 'IF', 'FN', 'TF') and o.object_id =[OBJECT_ID_CONDITION] -`; diff --git a/packages/engines/mssql/sql/tables.js b/packages/engines/mssql/sql/tables.js deleted file mode 100644 index 46ab78440..000000000 --- a/packages/engines/mssql/sql/tables.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = ` -select - o.name as pureName, s.name as schemaName, o.object_id as objectId, - o.create_date as createDate, o.modify_date as modifyDate -from sys.tables o -inner join sys.schemas s on o.schema_id = s.schema_id -where o.object_id =[OBJECT_ID_CONDITION] -`; diff --git a/packages/engines/mssql/sql/viewColumns.js b/packages/engines/mssql/sql/viewColumns.js deleted file mode 100644 index 0868c43ae..000000000 --- a/packages/engines/mssql/sql/viewColumns.js +++ /dev/null @@ -1,18 +0,0 @@ -module.exports = ` -select - o.object_id AS objectId, - col.TABLE_SCHEMA as schemaName, - col.TABLE_NAME as pureName, - col.COLUMN_NAME as columnName, - col.IS_NULLABLE as isNullable, - col.DATA_TYPE as dataType, - col.CHARACTER_MAXIMUM_LENGTH as charMaxLength, - col.NUMERIC_PRECISION as precision, - col.NUMERIC_SCALE as scale, - col.COLUMN_DEFAULT -FROM sys.objects o -INNER JOIN sys.schemas u ON u.schema_id=o.schema_id -INNER JOIN INFORMATION_SCHEMA.COLUMNS col ON col.TABLE_NAME = o.name AND col.TABLE_SCHEMA = u.name -WHERE o.type in ('V') and o.object_id =[OBJECT_ID_CONDITION] -order by col.ORDINAL_POSITION -`; diff --git a/packages/engines/mssql/sql/views.js b/packages/engines/mssql/sql/views.js deleted file mode 100644 index f0edb498f..000000000 --- a/packages/engines/mssql/sql/views.js +++ /dev/null @@ -1,10 +0,0 @@ -module.exports = ` -SELECT - o.name as pureName, - u.name as schemaName, - o.object_id as objectId, - o.create_date as createDate, - o.modify_date as modifyDate -FROM sys.objects o INNER JOIN sys.schemas u ON u.schema_id=o.schema_id -WHERE type in ('V') and o.object_id =[OBJECT_ID_CONDITION] -`; diff --git a/packages/engines/mysql/MySqlAnalyser.js b/packages/engines/mysql/MySqlAnalyser.js deleted file mode 100644 index 64de86467..000000000 --- a/packages/engines/mysql/MySqlAnalyser.js +++ /dev/null @@ -1,214 +0,0 @@ -const fp = require('lodash/fp'); -const _ = require('lodash'); -const sql = require('./sql'); - -const DatabaseAnalayser = require('../default/DatabaseAnalyser'); -const { isTypeString, isTypeNumeric } = require('dbgate-tools'); -const { rangeStep } = require('lodash/fp'); - -function getColumnInfo({ - isNullable, - extra, - columnName, - dataType, - charMaxLength, - numericPrecision, - numericScale, - defaultValue, -}) { - let fullDataType = dataType; - if (charMaxLength && isTypeString(dataType)) fullDataType = `${dataType}(${charMaxLength})`; - if (numericPrecision && numericScale && isTypeNumeric(dataType)) - fullDataType = `${dataType}(${numericPrecision},${numericScale})`; - return { - notNull: !isNullable || isNullable == 'NO' || isNullable == 'no', - autoIncrement: extra && extra.toLowerCase().includes('auto_increment'), - columnName, - dataType: fullDataType, - defaultValue, - }; -} - -function objectTypeToField(type) { - if (type == 'VIEW') return 'views'; - if (type == 'BASE TABLE') return 'tables'; - return null; -} - -class MySqlAnalyser extends DatabaseAnalayser { - constructor(pool, driver) { - super(pool, driver); - } - - createQuery(resFileName, typeFields) { - let res = sql[resFileName]; - if (this.singleObjectFilter) { - const { typeField, pureName } = this.singleObjectFilter; - if (!typeFields || !typeFields.includes(typeField)) return null; - res = res.replace('=[OBJECT_NAME_CONDITION]', ` = '${pureName}'`).replace('#DATABASE#', this.pool._database_name); - return res; - } - if (!this.modifications || !typeFields || this.modifications.length == 0) { - res = res.replace('=[OBJECT_NAME_CONDITION]', ' is not null'); - } else { - const filterNames = this.modifications - .filter((x) => typeFields.includes(x.objectTypeField) && (x.action == 'add' || x.action == 'change')) - .map((x) => x.newName && x.newName.pureName) - .filter(Boolean); - if (filterNames.length == 0) { - res = res.replace('=[OBJECT_NAME_CONDITION]', ' IS NULL'); - } else { - res = res.replace('=[OBJECT_NAME_CONDITION]', ` in (${filterNames.map((x) => `'${x}'`).join(',')})`); - } - } - res = res.replace('#DATABASE#', this.pool._database_name); - return res; - } - - getRequestedViewNames(allViewNames) { - if (this.singleObjectFilter) { - const { typeField, pureName } = this.singleObjectFilter; - if (typeField == 'views') return [pureName]; - } - if (this.modifications) { - return this.modifications.filter((x) => x.objectTypeField == 'views').map((x) => x.newName.pureName); - } - return allViewNames; - } - - async getViewTexts(allViewNames) { - const res = {}; - for (const viewName of this.getRequestedViewNames(allViewNames)) { - const resp = await this.driver.query(this.pool, `SHOW CREATE VIEW \`${viewName}\``); - res[viewName] = resp.rows[0]['Create View']; - } - return res; - } - - async _runAnalysis() { - const tables = await this.driver.query(this.pool, this.createQuery('tables', ['tables'])); - const columns = await this.driver.query(this.pool, this.createQuery('columns', ['tables', 'views'])); - const pkColumns = await this.driver.query(this.pool, this.createQuery('primaryKeys', ['tables'])); - const fkColumns = await this.driver.query(this.pool, this.createQuery('foreignKeys', ['tables'])); - const views = await this.driver.query(this.pool, this.createQuery('views', ['views'])); - const programmables = await this.driver.query( - this.pool, - this.createQuery('programmables', ['procedures', 'functions']) - ); - - const viewTexts = await this.getViewTexts(views.rows.map((x) => x.pureName)); - - return this.mergeAnalyseResult( - { - tables: tables.rows.map((table) => ({ - ...table, - columns: columns.rows.filter((col) => col.pureName == table.pureName).map(getColumnInfo), - primaryKey: DatabaseAnalayser.extractPrimaryKeys(table, pkColumns.rows), - foreignKeys: DatabaseAnalayser.extractForeignKeys(table, fkColumns.rows), - })), - views: views.rows.map((view) => ({ - ...view, - columns: columns.rows.filter((col) => col.pureName == view.pureName).map(getColumnInfo), - createSql: viewTexts[view.pureName], - requiresFormat: true, - })), - procedures: programmables.rows.filter((x) => x.objectType == 'PROCEDURE').map(fp.omit(['objectType'])), - functions: programmables.rows.filter((x) => x.objectType == 'FUNCTION').map(fp.omit(['objectType'])), - }, - (x) => x.pureName - ); - } - - getDeletedObjectsForField(nameArray, objectTypeField) { - return this.structure[objectTypeField] - .filter((x) => !nameArray.includes(x.pureName)) - .map((x) => ({ - oldName: _.pick(x, ['pureName']), - action: 'remove', - objectTypeField, - })); - } - - getDeletedObjects(nameArray) { - return [ - ...this.getDeletedObjectsForField(nameArray, 'tables'), - ...this.getDeletedObjectsForField(nameArray, 'views'), - ...this.getDeletedObjectsForField(nameArray, 'procedures'), - ...this.getDeletedObjectsForField(nameArray, 'functions'), - ...this.getDeletedObjectsForField(nameArray, 'triggers'), - ]; - } - - async getModifications() { - const tableModificationsQueryData = await this.driver.query(this.pool, this.createQuery('tableModifications')); - const procedureModificationsQueryData = await this.driver.query( - this.pool, - this.createQuery('procedureModifications') - ); - const functionModificationsQueryData = await this.driver.query( - this.pool, - this.createQuery('functionModifications') - ); - - const allModifications = _.compact([ - ...tableModificationsQueryData.rows.map((x) => { - if (x.objectType == 'BASE TABLE') return { ...x, objectTypeField: 'tables' }; - if (x.objectType == 'VIEW') return { ...x, objectTypeField: 'views' }; - return null; - }), - ...procedureModificationsQueryData.rows.map((x) => ({ - objectTypeField: 'procedures', - modifyDate: x.Modified, - pureName: x.Name, - })), - ...functionModificationsQueryData.rows.map((x) => ({ - objectTypeField: 'functions', - modifyDate: x.Modified, - pureName: x.Name, - })), - ]); - - // console.log('allModifications', allModifications); - // console.log( - // 'DATES', - // this.structure.procedures.map((x) => x.modifyDate) - // ); - // console.log('MOD - SRC', modifications); - // console.log( - // 'MODs', - // this.structure.tables.map((x) => x.modifyDate) - // ); - const modifications = allModifications.map((x) => { - const { objectType, modifyDate, pureName } = x; - const field = objectTypeToField(objectType); - - if (!field || !this.structure[field]) return null; - // @ts-ignore - const obj = this.structure[field].find((x) => x.pureName == pureName); - - // object not modified - if (obj && Math.abs(new Date(modifyDate).getTime() - new Date(obj.modifyDate).getTime()) < 1000) return null; - - // console.log('MODIFICATION OF ', field, pureName, modifyDate, obj.modifyDate); - - /** @type {import('dbgate-types').DatabaseModification} */ - const action = obj - ? { - newName: { pureName }, - oldName: _.pick(obj, ['pureName']), - action: 'change', - objectTypeField: field, - } - : { - newName: { pureName }, - action: 'add', - objectTypeField: field, - }; - return action; - }); - - return [..._.compact(modifications), ...this.getDeletedObjects([...allModifications.map((x) => x.pureName)])]; - } -} - -module.exports = MySqlAnalyser; diff --git a/packages/engines/mysql/MySqlDumper.js b/packages/engines/mysql/MySqlDumper.js deleted file mode 100644 index 3b778de0c..000000000 --- a/packages/engines/mysql/MySqlDumper.js +++ /dev/null @@ -1,30 +0,0 @@ -const SqlDumper = require('../default/SqlDumper'); - -class MySqlDumper extends SqlDumper { - /** @param type {import('dbgate-types').TransformType} */ - transform(type, dumpExpr) { - switch (type) { - case 'GROUP:YEAR': - case 'YEAR': - this.put('^year(%c)', dumpExpr); - break; - case 'MONTH': - this.put('^month(%c)', dumpExpr); - break; - case 'DAY': - this.put('^day(%c)', dumpExpr); - break; - case 'GROUP:MONTH': - this.put("^date_format(%c, '%s')", dumpExpr, '%Y-%m'); - break; - case 'GROUP:DAY': - this.put("^date_format(%c, '%s')", dumpExpr, '%Y-%m-%d'); - break; - default: - dumpExpr(); - break; - } - } -} - -module.exports = MySqlDumper; diff --git a/packages/engines/mysql/index.js b/packages/engines/mysql/index.js deleted file mode 100644 index 5aee7c46d..000000000 --- a/packages/engines/mysql/index.js +++ /dev/null @@ -1,153 +0,0 @@ -const driverBase = require('../default/driverBase'); -const MySqlAnalyser = require('./MySqlAnalyser'); -const MySqlDumper = require('./MySqlDumper'); - -/** @type {import('dbgate-types').SqlDialect} */ -const dialect = { - rangeSelect: true, - stringEscapeChar: '\\', - fallbackDataType: 'longtext', - quoteIdentifier(s) { - return '`' + s + '`'; - }, -}; - -function extractColumns(fields) { - if (fields) - return fields.map((col) => ({ - columnName: col.name, - })); - return null; -} - -/** @type {import('dbgate-types').EngineDriver} */ -const driver = { - ...driverBase, - analyserClass: MySqlAnalyser, - dumperClass: MySqlDumper, - - async connect(nativeModules, { server, port, user, password, database }) { - const connection = nativeModules.mysql.createConnection({ - host: server, - port, - user, - password, - database, - }); - connection._database_name = database; - connection._nativeModules = nativeModules; - return connection; - }, - async query(connection, sql) { - if (sql == null) { - return { - rows: [], - columns: [], - }; - } - return new Promise((resolve, reject) => { - connection.query(sql, function (error, results, fields) { - if (error) reject(error); - resolve({ rows: results, columns: extractColumns(fields) }); - }); - }); - }, - async stream(connection, sql, options) { - const query = connection.query(sql); - - // const handleInfo = (info) => { - // const { message, lineNumber, procName } = info; - // options.info({ - // message, - // line: lineNumber, - // procedure: procName, - // time: new Date(), - // severity: 'info', - // }); - // }; - - const handleEnd = (result) => { - // console.log('RESULT', result); - options.done(result); - }; - - const handleRow = (row) => { - options.row(row); - }; - - const handleFields = (columns) => { - console.log('FIELDS', columns[0].name); - options.recordset(extractColumns(columns)); - }; - - const handleError = (error) => { - console.log('ERROR', error); - const { message, lineNumber, procName } = error; - options.info({ - message, - line: lineNumber, - procedure: procName, - time: new Date(), - severity: 'error', - }); - }; - - query.on('error', handleError).on('fields', handleFields).on('result', handleRow).on('end', handleEnd); - - return query; - }, - async readQuery(connection, sql, structure) { - const query = connection.query(sql); - const { stream } = connection._nativeModules; - - const pass = new stream.PassThrough({ - objectMode: true, - highWaterMark: 100, - }); - - query - .on('error', (err) => { - console.error(err); - pass.end(); - }) - .on('fields', (fields) => pass.write(structure || { columns: extractColumns(fields) })) - .on('result', (row) => pass.write(row)) - .on('end', () => pass.end()); - - return pass; - }, - async getVersion(connection) { - const { rows } = await this.query(connection, "show variables like 'version'"); - const version = rows[0].Value; - return { version }; - }, - // async analyseFull(pool) { - // const analyser = new MySqlAnalyser(pool, this); - // return analyser.fullAnalysis(); - // }, - // async analyseIncremental(pool, structure) { - // const analyser = new MySqlAnalyser(pool, this); - // return analyser.incrementalAnalysis(structure); - // }, - // async analyseSingleObject(pool, name, typeField = 'tables') { - // const analyser = new MySqlAnalyser(pool, this); - // analyser.singleObjectFilter = { ...name, typeField }; - // const res = await analyser.fullAnalysis(); - // return res.tables[0]; - // }, - // // @ts-ignore - // analyseSingleTable(pool, name) { - // return this.analyseSingleObject(pool, name, 'tables'); - // }, - async listDatabases(connection) { - const { rows } = await this.query(connection, 'show databases'); - return rows.map((x) => ({ name: x.Database })); - }, - // createDumper() { - // return new MySqlDumper(this); - // }, - dialect, - engine: 'mysql', -}; - -module.exports = driver; diff --git a/packages/engines/mysql/sql/columns.js b/packages/engines/mysql/sql/columns.js deleted file mode 100644 index 0adb27e2b..000000000 --- a/packages/engines/mysql/sql/columns.js +++ /dev/null @@ -1,15 +0,0 @@ -module.exports = ` -select - TABLE_NAME as pureName, - COLUMN_NAME as columnName, - IS_NULLABLE as isNullable, - DATA_TYPE as dataType, - CHARACTER_MAXIMUM_LENGTH as charMaxLength, - NUMERIC_PRECISION as numericPrecision, - NUMERIC_SCALE as numericScale, - COLUMN_DEFAULT as defaultValue, - EXTRA as extra -from INFORMATION_SCHEMA.COLUMNS -where TABLE_SCHEMA = '#DATABASE#' and TABLE_NAME =[OBJECT_NAME_CONDITION] -order by ORDINAL_POSITION -`; diff --git a/packages/engines/mysql/sql/foreignKeys.js b/packages/engines/mysql/sql/foreignKeys.js deleted file mode 100644 index 5b43b4031..000000000 --- a/packages/engines/mysql/sql/foreignKeys.js +++ /dev/null @@ -1,17 +0,0 @@ -module.exports = ` -select - REFERENTIAL_CONSTRAINTS.CONSTRAINT_NAME as constraintName, - REFERENTIAL_CONSTRAINTS.TABLE_NAME as pureName, - REFERENTIAL_CONSTRAINTS.UPDATE_RULE as updateAction, - REFERENTIAL_CONSTRAINTS.DELETE_RULE as deleteAction, - REFERENTIAL_CONSTRAINTS.REFERENCED_TABLE_NAME as refTableName, - KEY_COLUMN_USAGE.COLUMN_NAME as columnName, - KEY_COLUMN_USAGE.REFERENCED_COLUMN_NAME as refColumnName -from INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS -inner join INFORMATION_SCHEMA.KEY_COLUMN_USAGE - on REFERENTIAL_CONSTRAINTS.TABLE_NAME = KEY_COLUMN_USAGE.TABLE_NAME - and REFERENTIAL_CONSTRAINTS.CONSTRAINT_NAME = KEY_COLUMN_USAGE.CONSTRAINT_NAME - and REFERENTIAL_CONSTRAINTS.CONSTRAINT_SCHEMA = KEY_COLUMN_USAGE.CONSTRAINT_SCHEMA -where REFERENTIAL_CONSTRAINTS.CONSTRAINT_SCHEMA = '#DATABASE#' and REFERENTIAL_CONSTRAINTS.TABLE_NAME =[OBJECT_NAME_CONDITION] -order by KEY_COLUMN_USAGE.ORDINAL_POSITION -`; diff --git a/packages/engines/mysql/sql/functionModifications.js b/packages/engines/mysql/sql/functionModifications.js deleted file mode 100644 index f422a219a..000000000 --- a/packages/engines/mysql/sql/functionModifications.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = ` -SHOW FUNCTION STATUS WHERE Db = '#DATABASE#' -`; diff --git a/packages/engines/mysql/sql/index.js b/packages/engines/mysql/sql/index.js deleted file mode 100644 index f5a4992ec..000000000 --- a/packages/engines/mysql/sql/index.js +++ /dev/null @@ -1,21 +0,0 @@ -const columns = require('./columns'); -const tables = require('./tables'); -const primaryKeys = require('./primaryKeys'); -const foreignKeys = require('./foreignKeys'); -const tableModifications = require('./tableModifications'); -const views = require('./views'); -const programmables = require('./programmables'); -const procedureModifications = require('./procedureModifications'); -const functionModifications = require('./functionModifications'); - -module.exports = { - columns, - tables, - primaryKeys, - foreignKeys, - tableModifications, - views, - programmables, - procedureModifications, - functionModifications, -}; diff --git a/packages/engines/mysql/sql/primaryKeys.js b/packages/engines/mysql/sql/primaryKeys.js deleted file mode 100644 index 2b1b346e8..000000000 --- a/packages/engines/mysql/sql/primaryKeys.js +++ /dev/null @@ -1,12 +0,0 @@ -module.exports = `select - TABLE_CONSTRAINTS.CONSTRAINT_NAME as constraintName, - TABLE_CONSTRAINTS.TABLE_NAME as pureName, - KEY_COLUMN_USAGE.COLUMN_NAME as columnName -from INFORMATION_SCHEMA.TABLE_CONSTRAINTS -inner join INFORMATION_SCHEMA.KEY_COLUMN_USAGE - on TABLE_CONSTRAINTS.TABLE_NAME = KEY_COLUMN_USAGE.TABLE_NAME - and TABLE_CONSTRAINTS.CONSTRAINT_NAME = KEY_COLUMN_USAGE.CONSTRAINT_NAME - and TABLE_CONSTRAINTS.CONSTRAINT_SCHEMA = KEY_COLUMN_USAGE.CONSTRAINT_SCHEMA -where TABLE_CONSTRAINTS.CONSTRAINT_SCHEMA = '#DATABASE#' and TABLE_CONSTRAINTS.TABLE_NAME =[OBJECT_NAME_CONDITION] AND TABLE_CONSTRAINTS.CONSTRAINT_TYPE = 'PRIMARY KEY' -order by KEY_COLUMN_USAGE.ORDINAL_POSITION -`; diff --git a/packages/engines/mysql/sql/procedureModifications.js b/packages/engines/mysql/sql/procedureModifications.js deleted file mode 100644 index 0ccbd49f8..000000000 --- a/packages/engines/mysql/sql/procedureModifications.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = ` -SHOW PROCEDURE STATUS WHERE Db = '#DATABASE#' -`; diff --git a/packages/engines/mysql/sql/programmables.js b/packages/engines/mysql/sql/programmables.js deleted file mode 100644 index dd1b36021..000000000 --- a/packages/engines/mysql/sql/programmables.js +++ /dev/null @@ -1,9 +0,0 @@ -module.exports = ` -select - ROUTINE_NAME as pureName, - ROUTINE_TYPE as objectType, - COALESCE(LAST_ALTERED, CREATED) as modifyDate, - ROUTINE_DEFINITION as createSql -from information_schema.routines -where ROUTINE_SCHEMA = '#DATABASE#' and ROUTINE_NAME =[OBJECT_NAME_CONDITION] -`; diff --git a/packages/engines/mysql/sql/tableModifications.js b/packages/engines/mysql/sql/tableModifications.js deleted file mode 100644 index 6382ea5ae..000000000 --- a/packages/engines/mysql/sql/tableModifications.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = ` -select - TABLE_NAME as pureName, - TABLE_TYPE as objectType, - case when ENGINE='InnoDB' then CREATE_TIME else coalesce(UPDATE_TIME, CREATE_TIME) end as modifyDate -from information_schema.tables -where TABLE_SCHEMA = '#DATABASE#' -`; diff --git a/packages/engines/mysql/sql/tables.js b/packages/engines/mysql/sql/tables.js deleted file mode 100644 index eed928431..000000000 --- a/packages/engines/mysql/sql/tables.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = ` -select - TABLE_NAME as pureName, - case when ENGINE='InnoDB' then CREATE_TIME else coalesce(UPDATE_TIME, CREATE_TIME) end as modifyDate -from information_schema.tables -where TABLE_SCHEMA = '#DATABASE#' and TABLE_TYPE='BASE TABLE' and TABLE_NAME =[OBJECT_NAME_CONDITION]; -`; diff --git a/packages/engines/mysql/sql/views.js b/packages/engines/mysql/sql/views.js deleted file mode 100644 index ea4d8ef35..000000000 --- a/packages/engines/mysql/sql/views.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = ` -select - TABLE_NAME as pureName, - coalesce(UPDATE_TIME, CREATE_TIME) as modifyDate -from information_schema.tables -where TABLE_SCHEMA = '#DATABASE#' and TABLE_NAME =[OBJECT_NAME_CONDITION] and TABLE_TYPE = 'VIEW'; -`; diff --git a/packages/engines/package.json b/packages/engines/package.json deleted file mode 100644 index a0b011d0f..000000000 --- a/packages/engines/package.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "dbgate-engines", - "version": "1.0.3", - "main": "index.js", - "typings": "./index.d.ts", - "homepage": "https://dbgate.org/", - "repository": { - "type": "git", - "url": "https://github.com/dbshell/dbgate.git" - }, - "funding": "https://www.paypal.com/paypalme/JanProchazkaCz/30eur", - "author": "Jan Prochazka", - "license": "GPL", - "keywords": [ - "sql", - "mssql", - "mssql", - "postgresql", - "dbgate" - ], - "devDependencies": { - "@types/lodash": "^4.14.149", - "dbgate-types": "^1.0.0", - "nodemon": "^2.0.2", - "typescript": "^3.7.5" - }, - "dependencies": { - "dbgate-tools": "^1.0.0", - "lodash": "^4.17.15", - "moment": "^2.24.0" - } -} diff --git a/packages/engines/postgres/PostgreAnalyser.js b/packages/engines/postgres/PostgreAnalyser.js deleted file mode 100644 index 16ed42b5d..000000000 --- a/packages/engines/postgres/PostgreAnalyser.js +++ /dev/null @@ -1,173 +0,0 @@ -const fp = require('lodash/fp'); -const _ = require('lodash'); -const sql = require('./sql'); - -const DatabaseAnalayser = require('../default/DatabaseAnalyser'); -const { isTypeString, isTypeNumeric } = require('dbgate-tools'); - -function normalizeTypeName(dataType) { - if (dataType == 'character varying') return 'varchar'; - if (dataType == 'timestamp without time zone') return 'timestamp'; - return dataType; -} - -function getColumnInfo({ - isNullable, - isIdentity, - columnName, - dataType, - charMaxLength, - numericPrecision, - numericScale, - defaultValue, -}) { - const normDataType = normalizeTypeName(dataType); - let fullDataType = normDataType; - if (charMaxLength && isTypeString(normDataType)) fullDataType = `${normDataType}(${charMaxLength})`; - if (numericPrecision && numericScale && isTypeNumeric(normDataType)) - fullDataType = `${normDataType}(${numericPrecision},${numericScale})`; - return { - columnName, - dataType: fullDataType, - notNull: !isNullable || isNullable == 'NO' || isNullable == 'no', - autoIncrement: !!isIdentity, - defaultValue, - }; -} - -class PostgreAnalyser extends DatabaseAnalayser { - constructor(pool, driver) { - super(pool, driver); - } - - createQuery(resFileName, typeFields) { - let res = sql[resFileName]; - - if (this.singleObjectFilter) { - const { typeField, schemaName, pureName } = this.singleObjectFilter; - if (!typeFields || !typeFields.includes(typeField)) return null; - res = res.replace(/=OBJECT_ID_CONDITION/g, ` = '${typeField}:${schemaName || 'public'}.${pureName}'`); - return res; - } - if (!this.modifications || !typeFields || this.modifications.length == 0) { - res = res.replace(/=OBJECT_ID_CONDITION/g, ' is not null'); - } else { - const filterNames = this.modifications - .filter((x) => typeFields.includes(x.objectTypeField) && (x.action == 'add' || x.action == 'change')) - .filter((x) => x.newName) - .map((x) => `${x.objectTypeField}:${x.newName.schemaName}.${x.newName.pureName}`); - if (filterNames.length == 0) { - res = res.replace(/=OBJECT_ID_CONDITION/g, ' IS NULL'); - } else { - res = res.replace(/=OBJECT_ID_CONDITION/g, ` in (${filterNames.map((x) => `'${x}'`).join(',')})`); - } - } - return res; - - // let res = sql[resFileName]; - // res = res.replace('=[OBJECT_ID_CONDITION]', ' is not null'); - // return res; - } - async _runAnalysis() { - const tables = await this.driver.query(this.pool, this.createQuery('tableModifications', ['tables'])); - const columns = await this.driver.query(this.pool, this.createQuery('columns', ['tables'])); - const pkColumns = await this.driver.query(this.pool, this.createQuery('primaryKeys', ['tables'])); - const fkColumns = await this.driver.query(this.pool, this.createQuery('foreignKeys', ['tables'])); - const views = await this.driver.query(this.pool, this.createQuery('views', ['views'])); - const routines = await this.driver.query(this.pool, this.createQuery('routines', ['procedures', 'functions'])); - // console.log('PG fkColumns', fkColumns.rows); - - return this.mergeAnalyseResult( - { - tables: tables.rows.map((table) => ({ - ...table, - columns: columns.rows - .filter((col) => col.pureName == table.pureName && col.schemaName == table.schemaName) - .map(getColumnInfo), - primaryKey: DatabaseAnalayser.extractPrimaryKeys(table, pkColumns.rows), - foreignKeys: DatabaseAnalayser.extractForeignKeys(table, fkColumns.rows), - })), - views: views.rows.map((view) => ({ - ...view, - columns: columns.rows - .filter((col) => col.pureName == view.pureName && col.schemaName == view.schemaName) - .map(getColumnInfo), - })), - procedures: routines.rows.filter((x) => x.objectType == 'PROCEDURE'), - functions: routines.rows.filter((x) => x.objectType == 'FUNCTION'), - }, - (x) => `${x.objectTypeField}:${x.schemaName}.${x.pureName}` - ); - } - - async getModifications() { - const tableModificationsQueryData = await this.driver.query(this.pool, this.createQuery('tableModifications')); - const viewModificationsQueryData = await this.driver.query(this.pool, this.createQuery('viewModifications')); - const routineModificationsQueryData = await this.driver.query(this.pool, this.createQuery('routineModifications')); - - const allModifications = _.compact([ - ...tableModificationsQueryData.rows.map((x) => ({ ...x, objectTypeField: 'tables' })), - ...viewModificationsQueryData.rows.map((x) => ({ ...x, objectTypeField: 'views' })), - ...routineModificationsQueryData.rows - .filter((x) => x.objectType == 'PROCEDURE') - .map((x) => ({ ...x, objectTypeField: 'procedures' })), - ...routineModificationsQueryData.rows - .filter((x) => x.objectType == 'FUNCTION') - .map((x) => ({ ...x, objectTypeField: 'functions' })), - ]); - - const modifications = allModifications.map((x) => { - const { objectTypeField, hashCode, pureName, schemaName } = x; - - if (!objectTypeField || !this.structure[objectTypeField]) return null; - const obj = this.structure[objectTypeField].find((x) => x.pureName == pureName && x.schemaName == schemaName); - - // object not modified - if (obj && obj.hashCode == hashCode) return null; - - // console.log('MODIFICATION OF ', objectTypeField, schemaName, pureName); - - /** @type {import('dbgate-types').DatabaseModification} */ - const action = obj - ? { - newName: { schemaName, pureName }, - oldName: _.pick(obj, ['schemaName', 'pureName']), - action: 'change', - objectTypeField, - } - : { - newName: { schemaName, pureName }, - action: 'add', - objectTypeField, - }; - return action; - }); - - return [ - ..._.compact(modifications), - ...this.getDeletedObjects([...allModifications.map((x) => `${x.schemaName}.${x.pureName}`)]), - ]; - } - - getDeletedObjectsForField(nameArray, objectTypeField) { - return this.structure[objectTypeField] - .filter((x) => !nameArray.includes(`${x.schemaName}.${x.pureName}`)) - .map((x) => ({ - oldName: _.pick(x, ['schemaName', 'pureName']), - action: 'remove', - objectTypeField, - })); - } - - getDeletedObjects(nameArray) { - return [ - ...this.getDeletedObjectsForField(nameArray, 'tables'), - ...this.getDeletedObjectsForField(nameArray, 'views'), - ...this.getDeletedObjectsForField(nameArray, 'procedures'), - ...this.getDeletedObjectsForField(nameArray, 'functions'), - ...this.getDeletedObjectsForField(nameArray, 'triggers'), - ]; - } -} - -module.exports = PostgreAnalyser; diff --git a/packages/engines/postgres/PostgreDumper.js b/packages/engines/postgres/PostgreDumper.js deleted file mode 100644 index 12582b547..000000000 --- a/packages/engines/postgres/PostgreDumper.js +++ /dev/null @@ -1,30 +0,0 @@ -const SqlDumper = require('../default/SqlDumper'); - -class PostgreDumper extends SqlDumper { - /** @param type {import('dbgate-types').TransformType} */ - transform(type, dumpExpr) { - switch (type) { - case 'GROUP:YEAR': - case 'YEAR': - this.put('^extract(^year ^from %c)', dumpExpr); - break; - case 'MONTH': - this.put('^extract(^month ^from %c)', dumpExpr); - break; - case 'DAY': - this.put('^extract(^day ^from %c)', dumpExpr); - break; - case 'GROUP:MONTH': - this.put("^to_char(%c, '%s')", dumpExpr, 'YYYY-MM'); - break; - case 'GROUP:DAY': - this.put("^to_char(%c, '%s')", dumpExpr, 'YYYY-MM-DD'); - break; - default: - dumpExpr(); - break; - } - } -} - -module.exports = PostgreDumper; diff --git a/packages/engines/postgres/index.js b/packages/engines/postgres/index.js deleted file mode 100644 index bc09995b4..000000000 --- a/packages/engines/postgres/index.js +++ /dev/null @@ -1,185 +0,0 @@ -const _ = require('lodash'); -const driverBase = require('../default/driverBase'); -const PostgreAnalyser = require('./PostgreAnalyser'); -const PostgreDumper = require('./PostgreDumper'); - -/** @type {import('dbgate-types').SqlDialect} */ -const dialect = { - rangeSelect: true, - // stringEscapeChar: '\\', - stringEscapeChar: "'", - fallbackDataType: 'varchar', - quoteIdentifier(s) { - return '"' + s + '"'; - }, -}; - -/** @type {import('dbgate-types').EngineDriver} */ -const driver = { - ...driverBase, - analyserClass: PostgreAnalyser, - dumperClass: PostgreDumper, - - async connect(nativeModules, { server, port, user, password, database }) { - const client = new nativeModules.pg.Client({ - host: server, - port, - user, - password, - database: database || 'postgres', - }); - await client.connect(); - client._nativeModules = nativeModules; - return client; - }, - async query(client, sql) { - if (sql == null) { - return { - rows: [], - columns: [], - }; - } - const res = await client.query(sql); - return { rows: res.rows, columns: res.fields }; - }, - async stream(client, sql, options) { - const query = new client._nativeModules.pgQueryStream(sql); - const stream = client.query(query); - - // const handleInfo = (info) => { - // const { message, lineNumber, procName } = info; - // options.info({ - // message, - // line: lineNumber, - // procedure: procName, - // time: new Date(), - // severity: 'info', - // }); - // }; - - let wasHeader = false; - - const handleEnd = (result) => { - // console.log('RESULT', result); - options.done(result); - }; - - const handleReadable = () => { - let row = stream.read(); - if (!wasHeader && row) { - options.recordset(_.keys(row).map((columnName) => ({ columnName }))); - wasHeader = true; - } - - while (row) { - options.row(row); - row = stream.read(); - } - }; - - // const handleFields = (columns) => { - // // console.log('FIELDS', columns[0].name); - // options.recordset(columns); - // // options.recordset(extractColumns(columns)); - // }; - - const handleError = (error) => { - console.log('ERROR', error); - const { message, lineNumber, procName } = error; - options.info({ - message, - line: lineNumber, - procedure: procName, - time: new Date(), - severity: 'error', - }); - }; - - stream.on('error', handleError); - stream.on('readable', handleReadable); - // stream.on('result', handleRow) - // stream.on('data', handleRow) - stream.on('end', handleEnd); - - return stream; - }, - // async analyseSingleObject(pool, name, typeField = 'tables') { - // const analyser = new PostgreAnalyser(pool, this); - // analyser.singleObjectFilter = { ...name, typeField }; - // const res = await analyser.fullAnalysis(); - // return res.tables[0]; - // }, - // // @ts-ignore - // analyseSingleTable(pool, name) { - // return this.analyseSingleObject(pool, name, 'tables'); - // }, - async getVersion(client) { - const { rows } = await this.query(client, 'SELECT version()'); - const { version } = rows[0]; - return { version }; - }, - // async analyseFull(pool) { - // const analyser = new PostgreAnalyser(pool, this); - // return analyser.fullAnalysis(); - // }, - // async analyseIncremental(pool, structure) { - // const analyser = new PostgreAnalyser(pool, this); - // return analyser.incrementalAnalysis(structure); - // }, - async readQuery(client, sql, structure) { - const query = new client._nativeModules.pgQueryStream(sql); - const { stream } = client._nativeModules; - - const queryStream = client.query(query); - - let wasHeader = false; - - const pass = new stream.PassThrough({ - objectMode: true, - highWaterMark: 100, - }); - - const handleEnd = (result) => { - pass.end(); - }; - - const handleReadable = () => { - let row = queryStream.read(); - if (!wasHeader && row) { - pass.write( - structure || { - columns: _.keys(row).map((columnName) => ({ columnName })), - } - ); - wasHeader = true; - } - - while (row) { - pass.write(row); - row = queryStream.read(); - } - }; - - const handleError = (error) => { - console.error(error); - pass.end(); - }; - - queryStream.on('error', handleError); - queryStream.on('readable', handleReadable); - queryStream.on('end', handleEnd); - - return pass; - }, - // createDumper() { - // return new PostgreDumper(this); - // }, - async listDatabases(client) { - const { rows } = await this.query(client, 'SELECT datname AS name FROM pg_database WHERE datistemplate = false'); - return rows; - }, - dialect, - engine: 'postgres', -}; - -module.exports = driver; diff --git a/packages/engines/postgres/sql/columns.js b/packages/engines/postgres/sql/columns.js deleted file mode 100644 index 841eda6b9..000000000 --- a/packages/engines/postgres/sql/columns.js +++ /dev/null @@ -1,19 +0,0 @@ -module.exports = ` -select - table_schema as "schemaName", - table_name as "pureName", - column_name as "columnName", - is_nullable as "isNullable", - data_type as "dataType", - character_maximum_length as "charMaxLength", - numeric_precision as "numericPrecision", - numeric_scale as "numericScale", - column_default as "defaultValue" -from information_schema.columns -where - table_schema <> 'information_schema' - and table_schema <> 'pg_catalog' - and table_schema !~ '^pg_toast' - and 'tables:' || table_schema || '.' || table_name =OBJECT_ID_CONDITION -order by ordinal_position -`; \ No newline at end of file diff --git a/packages/engines/postgres/sql/foreignKeys.js b/packages/engines/postgres/sql/foreignKeys.js deleted file mode 100644 index fadcf5e78..000000000 --- a/packages/engines/postgres/sql/foreignKeys.js +++ /dev/null @@ -1,24 +0,0 @@ -module.exports = ` -select - fk.constraint_name as "constraintName", - fk.constraint_schema as "constraintSchema", - base.table_name as "pureName", - base.table_schema as "schemaName", - fk.update_rule as "updateAction", - fk.delete_rule as "deleteAction", - ref.table_name as "refTableName", - ref.table_schema as "refSchemaName", - basecol.column_name as "columnName", - refcol.column_name as "refColumnName" -from information_schema.referential_constraints fk -inner join information_schema.table_constraints base on fk.constraint_name = base.constraint_name and fk.constraint_schema = base.constraint_schema -inner join information_schema.table_constraints ref on fk.unique_constraint_name = ref.constraint_name and fk.unique_constraint_schema = ref.constraint_schema -inner join information_schema.key_column_usage basecol on base.table_name = basecol.table_name and base.constraint_name = basecol.constraint_name -inner join information_schema.key_column_usage refcol on ref.table_name = refcol.table_name and ref.constraint_name = refcol.constraint_name and basecol.ordinal_position = refcol.ordinal_position -where - base.table_schema <> 'information_schema' - and base.table_schema <> 'pg_catalog' - and base.table_schema !~ '^pg_toast' - and 'tables:' || base.table_schema || '.' || base.table_name =OBJECT_ID_CONDITION -order by basecol.ordinal_position -`; diff --git a/packages/engines/postgres/sql/index.js b/packages/engines/postgres/sql/index.js deleted file mode 100644 index 7cc2141e9..000000000 --- a/packages/engines/postgres/sql/index.js +++ /dev/null @@ -1,19 +0,0 @@ -const columns = require('./columns'); -const tableModifications = require('./tableModifications'); -const viewModifications = require('./viewModifications'); -const primaryKeys = require('./primaryKeys'); -const foreignKeys = require('./foreignKeys'); -const views = require('./views'); -const routines = require('./routines'); -const routineModifications = require('./routineModifications'); - -module.exports = { - columns, - tableModifications, - viewModifications, - primaryKeys, - foreignKeys, - views, - routines, - routineModifications, -}; diff --git a/packages/engines/postgres/sql/primaryKeys.js b/packages/engines/postgres/sql/primaryKeys.js deleted file mode 100644 index 204ff8478..000000000 --- a/packages/engines/postgres/sql/primaryKeys.js +++ /dev/null @@ -1,17 +0,0 @@ -module.exports = ` -select - table_constraints.constraint_schema as "constraintSchema", - table_constraints.constraint_name as "constraintName", - table_constraints.table_schema as "schemaName", - table_constraints.table_name as "pureName", - key_column_usage.column_name as "columnName" -from information_schema.table_constraints -inner join information_schema.key_column_usage on table_constraints.table_name = key_column_usage.table_name and table_constraints.constraint_name = key_column_usage.constraint_name -where - table_constraints.table_schema <> 'information_schema' - and table_constraints.table_schema <> 'pg_catalog' - and table_constraints.table_schema !~ '^pg_toast' - and table_constraints.constraint_type = 'PRIMARY KEY' - and 'tables:' || table_constraints.table_schema || '.' || table_constraints.table_name =OBJECT_ID_CONDITION -order by key_column_usage.ordinal_position -`; diff --git a/packages/engines/postgres/sql/routineModifications.js b/packages/engines/postgres/sql/routineModifications.js deleted file mode 100644 index 879a1195c..000000000 --- a/packages/engines/postgres/sql/routineModifications.js +++ /dev/null @@ -1,10 +0,0 @@ -module.exports = ` -select - routine_name as "pureName", - routine_schema as "schemaName", - md5(routine_definition) as "hashCode", - routine_type as "objectType" -from - information_schema.routines where routine_schema != 'information_schema' and routine_schema != 'pg_catalog' - and routine_type in ('PROCEDURE', 'FUNCTION') -`; diff --git a/packages/engines/postgres/sql/routines.js b/packages/engines/postgres/sql/routines.js deleted file mode 100644 index 80c189604..000000000 --- a/packages/engines/postgres/sql/routines.js +++ /dev/null @@ -1,15 +0,0 @@ -module.exports = ` -select - routine_name as "pureName", - routine_schema as "schemaName", - routine_definition as "createSql", - md5(routine_definition) as "hashCode", - routine_type as "objectType" -from - information_schema.routines where routine_schema != 'information_schema' and routine_schema != 'pg_catalog' - and ( - (routine_type = 'PROCEDURE' and ('procedures:' || routine_schema || '.' || routine_schema) =OBJECT_ID_CONDITION) - or - (routine_type = 'FUNCTION' and ('functions:' || routine_schema || '.' || routine_schema) =OBJECT_ID_CONDITION) - ) -`; diff --git a/packages/engines/postgres/sql/tableModifications.js b/packages/engines/postgres/sql/tableModifications.js deleted file mode 100644 index b34bf292b..000000000 --- a/packages/engines/postgres/sql/tableModifications.js +++ /dev/null @@ -1,52 +0,0 @@ -module.exports = ` -with pkey as -( - select cc.conrelid, format(E'create constraint %I primary key(%s);\\n', cc.conname, - string_agg(a.attname, ', ' - order by array_position(cc.conkey, a.attnum))) pkey - from pg_catalog.pg_constraint cc - join pg_catalog.pg_class c on c.oid = cc.conrelid - join pg_catalog.pg_attribute a on a.attrelid = cc.conrelid - and a.attnum = any(cc.conkey) - where cc.contype = 'p' - group by cc.conrelid, cc.conname -) - - -SELECT oid as "objectId", nspname as "schemaName", relname as "pureName", - md5('CREATE TABLE ' || nspname || '.' || relname || E'\\n(\\n' || - array_to_string( - array_agg( - ' ' || column_name || ' ' || type || ' '|| not_null - ) - , E',\\n' - ) || E'\\n);\\n' || coalesce((select pkey from pkey where pkey.conrelid = oid),'NO_PK')) as "hashCode" -from -( - SELECT - c.relname, a.attname AS column_name, c.oid, - n.nspname, - pg_catalog.format_type(a.atttypid, a.atttypmod) as type, - case - when a.attnotnull - then 'NOT NULL' - else 'NULL' - END as not_null - FROM pg_class c, - pg_namespace n, - pg_attribute a, - pg_type t - - WHERE c.relkind = 'r' - AND a.attnum > 0 - AND a.attrelid = c.oid - AND a.atttypid = t.oid - AND n.oid = c.relnamespace - AND n.nspname <> 'pg_catalog' - AND n.nspname <> 'information_schema' - AND n.nspname !~ '^pg_toast' - ORDER BY a.attnum -) as tabledefinition -where ('tables:' || nspname || '.' || relname) =OBJECT_ID_CONDITION -group by relname, nspname, oid -`; diff --git a/packages/engines/postgres/sql/viewModifications.js b/packages/engines/postgres/sql/viewModifications.js deleted file mode 100644 index a74c01a3f..000000000 --- a/packages/engines/postgres/sql/viewModifications.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = ` -select - table_name as "pureName", - table_schema as "schemaName", - md5(view_definition) as "hashCode" -from - information_schema.views where table_schema != 'information_schema' and table_schema != 'pg_catalog' -`; diff --git a/packages/engines/postgres/sql/views.js b/packages/engines/postgres/sql/views.js deleted file mode 100644 index 8d18ee557..000000000 --- a/packages/engines/postgres/sql/views.js +++ /dev/null @@ -1,11 +0,0 @@ -module.exports = ` -select - table_name as "pureName", - table_schema as "schemaName", - view_definition as "createSql", - md5(view_definition) as "hashCode" -from - information_schema.views -where table_schema != 'information_schema' and table_schema != 'pg_catalog' - and ('views:' || table_schema || '.' || table_name) =OBJECT_ID_CONDITION -`; diff --git a/packages/engines/tsconfig.json b/packages/engines/tsconfig.json deleted file mode 100644 index bdf53cd0f..000000000 --- a/packages/engines/tsconfig.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "compilerOptions": { - "module": "commonjs", - "allowJs": true, - "checkJs": true, - "noEmit": true, - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "moduleResolution": "node", - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], - }, - "include": [ - "." - ] -} diff --git a/packages/filterparser/src/cli.ts b/packages/filterparser/src/cli.ts deleted file mode 100644 index 5cee1d064..000000000 --- a/packages/filterparser/src/cli.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { parseFilter } from './parseFilter'; -import { FilterType } from './types'; -import engines from 'dbgate-engines'; -import { dumpSqlCondition, treeToSql } from 'dbgate-sqltree'; - -const ast = parseFilter(process.argv[2], process.argv[3] as FilterType); - -console.log(JSON.stringify(ast, null, ' ')); - -console.log('***************** MS SQL ******************'); -console.log(treeToSql(engines('mssql'), ast, dumpSqlCondition)); - -console.log('***************** MySql *******************'); -console.log(treeToSql(engines('mysql'), ast, dumpSqlCondition)); - -console.log('***************** Postgre *****************'); -console.log(treeToSql(engines('postgres'), ast, dumpSqlCondition)); diff --git a/packages/tools/package.json b/packages/tools/package.json index 9baa270e9..e8b487c04 100644 --- a/packages/tools/package.json +++ b/packages/tools/package.json @@ -1,23 +1,20 @@ { - "version": "1.0.1", + "version": "1.0.4", "name": "dbgate-tools", "main": "lib/index.js", "typings": "lib/index.d.ts", - "homepage": "https://dbgate.org/", "repository": { "type": "git", "url": "https://github.com/dbshell/dbgate.git" - }, + }, "funding": "https://www.paypal.com/paypalme/JanProchazkaCz/30eur", "author": "Jan Prochazka", "license": "GPL", - "keywords": [ "sql", "dbgate" ], - "scripts": { "prepare": "yarn build", "build": "tsc", @@ -28,14 +25,13 @@ "lib" ], "devDependencies": { - "dbgate-types": "^1.0.0", "@types/node": "^13.7.0", + "dbgate-types": "^1.0.0", "jest": "^24.9.0", "ts-jest": "^25.2.1", "typescript": "^3.7.5" }, "dependencies": { - "lodash": "^4.17.15", - "moment": "^2.24.0" + "lodash": "^4.17.15" } } diff --git a/packages/engines/default/DatabaseAnalyser.js b/packages/tools/src/DatabaseAnalyser.ts similarity index 51% rename from packages/engines/default/DatabaseAnalyser.js rename to packages/tools/src/DatabaseAnalyser.ts index 8874a725e..d876f8485 100644 --- a/packages/engines/default/DatabaseAnalyser.js +++ b/packages/tools/src/DatabaseAnalyser.ts @@ -1,21 +1,15 @@ -const _ = require('lodash'); -const fp = require('lodash/fp'); +import { DatabaseInfo, DatabaseModification, EngineDriver } from 'dbgate-types'; +import _sortBy from 'lodash/sortBy'; +import _groupBy from 'lodash/groupBy'; +import _pick from 'lodash/pick'; -class DatabaseAnalyser { - /** - * - * @param {import('dbgate-types').EngineDriver} driver - */ - constructor(pool, driver) { - this.pool = pool; - this.driver = driver; - // this.result = DatabaseAnalyser.createEmptyStructure(); - /** @type {import('dbgate-types').DatabaseInfo} */ - this.structure = null; - /** import('dbgate-types').DatabaseModification[]) */ - this.modifications = null; - this.singleObjectFilter = null; - } +const fp_pick = (arg) => (array) => _pick(array, arg); +export class DatabaseAnalyser { + structure: DatabaseInfo; + modifications: DatabaseModification[]; + singleObjectFilter: any; + + constructor(public pool, public driver: EngineDriver) {} async _runAnalysis() { return DatabaseAnalyser.createEmptyStructure(); @@ -62,7 +56,7 @@ class DatabaseAnalyser { const newArray = newlyAnalysed[field] || []; const addedChangedIds = newArray.map((x) => extractObjectId(x)); const removeAllIds = [...removedIds, ...addedChangedIds]; - res[field] = _.sortBy( + res[field] = _sortBy( [...this.structure[field].filter((x) => !removeAllIds.includes(extractObjectId(x))), ...newArray], (x) => x.pureName ); @@ -80,46 +74,46 @@ class DatabaseAnalyser { // findObjectById(id) { // return this.structure.tables.find((x) => x.objectId == id); // } + + static createEmptyStructure(): DatabaseInfo { + return { + tables: [], + views: [], + functions: [], + procedures: [], + triggers: [], + schemas: [], + }; + } + + static byTableFilter(table) { + return (x) => x.pureName == table.pureName && x.schemaName == x.schemaName; + } + + static extractPrimaryKeys(table, pkColumns) { + const filtered = pkColumns.filter(DatabaseAnalyser.byTableFilter(table)); + if (filtered.length == 0) return undefined; + return { + ..._pick(filtered[0], ['constraintName', 'schemaName', 'pureName']), + constraintType: 'primaryKey', + columns: filtered.map(fp_pick('columnName')), + }; + } + static extractForeignKeys(table, fkColumns) { + const grouped = _groupBy(fkColumns.filter(DatabaseAnalyser.byTableFilter(table)), 'constraintName'); + return Object.keys(grouped).map((constraintName) => ({ + constraintName, + constraintType: 'foreignKey', + ..._pick(grouped[constraintName][0], [ + 'constraintName', + 'schemaName', + 'pureName', + 'refSchemaName', + 'refTableName', + 'updateAction', + 'deleteAction', + ]), + columns: grouped[constraintName].map(fp_pick(['columnName', 'refColumnName'])), + })); + } } - -/** @returns {import('dbgate-types').DatabaseInfo} */ -DatabaseAnalyser.createEmptyStructure = () => ({ - tables: [], - views: [], - functions: [], - procedures: [], - triggers: [], - schemas: [], -}); - -DatabaseAnalyser.byTableFilter = (table) => (x) => x.pureName == table.pureName && x.schemaName == x.schemaName; - -DatabaseAnalyser.extractPrimaryKeys = (table, pkColumns) => { - const filtered = pkColumns.filter(DatabaseAnalyser.byTableFilter(table)); - if (filtered.length == 0) return undefined; - return { - ..._.pick(filtered[0], ['constraintName', 'schemaName', 'pureName']), - constraintType: 'primaryKey', - columns: filtered.map(fp.pick('columnName')), - }; -}; - -DatabaseAnalyser.extractForeignKeys = (table, fkColumns) => { - const grouped = _.groupBy(fkColumns.filter(DatabaseAnalyser.byTableFilter(table)), 'constraintName'); - return _.keys(grouped).map((constraintName) => ({ - constraintName, - constraintType: 'foreignKey', - ..._.pick(grouped[constraintName][0], [ - 'constraintName', - 'schemaName', - 'pureName', - 'refSchemaName', - 'refTableName', - 'updateAction', - 'deleteAction', - ]), - columns: grouped[constraintName].map(fp.pick(['columnName', 'refColumnName'])), - })); -}; - -module.exports = DatabaseAnalyser; diff --git a/packages/engines/default/SqlDumper.js b/packages/tools/src/SqlDumper.ts similarity index 80% rename from packages/engines/default/SqlDumper.js rename to packages/tools/src/SqlDumper.ts index e00b96ca1..310460ab5 100644 --- a/packages/engines/default/SqlDumper.js +++ b/packages/tools/src/SqlDumper.ts @@ -1,13 +1,25 @@ -const _ = require('lodash'); -const moment = require('moment'); +import { + ColumnInfo, + EngineDriver, + ForeignKeyInfo, + NamedObjectInfo, + SqlDialect, + TableInfo, + TransformType, +} from 'dbgate-types'; +import _isString from 'lodash/isString' +import _isNumber from 'lodash/isNumber' +import _isDate from 'lodash/isDate' -class SqlDumper { - /** @param driver {import('dbgate-types').EngineDriver} */ - constructor(driver) { - this.s = ''; +export class SqlDumper { + s = ''; + driver: EngineDriver; + dialect: SqlDialect; + indentLevel = 0; + + constructor(driver: EngineDriver) { this.driver = driver; this.dialect = driver.dialect; - this.indentLevel = 0; } endCommand() { this.putRaw(';\n'); @@ -36,9 +48,9 @@ class SqlDumper { if (value === null) this.putRaw('NULL'); if (value === true) this.putRaw('1'); if (value === false) this.putRaw('0'); - else if (_.isString(value)) this.putStringValue(value); - else if (_.isNumber(value)) this.putRaw(value.toString()); - else if (_.isDate(value)) this.putStringValue(moment(value).toISOString()); + else if (_isString(value)) this.putStringValue(value); + else if (_isNumber(value)) this.putRaw(value.toString()); + else if (_isDate(value)) this.putStringValue(new Date(value).toISOString()); } putCmd(format, ...args) { this.put(format, ...args); @@ -85,8 +97,7 @@ class SqlDumper { if (!collection) return; this.putCollection(', ', collection, (item) => this.putFormattedValue(c, item)); } - /** @param format {string} */ - put(format, ...args) { + put(format: string, ...args) { let i = 0; let argIndex = 0; const length = format.length; @@ -149,10 +160,7 @@ class SqlDumper { this.put(' ^auto_increment'); } - /** - * @param column {import('dbgate-types').ColumnInfo} - */ - columnDefinition(column, { includeDefault = true, includeNullable = true, includeCollate = true } = {}) { + columnDefinition(column: ColumnInfo, { includeDefault = true, includeNullable = true, includeCollate = true } = {}) { if (column.computedExpression) { this.put('^as %s', column.computedExpression); if (column.isPersisted) this.put(' ^persisted'); @@ -175,10 +183,7 @@ class SqlDumper { } } - /** - * @param column {import('dbgate-types').ColumnInfo} - */ - columnDefault(column) { + columnDefault(column: ColumnInfo) { if (column.defaultConstraint != null) { this.put(' ^constraint %i ^default %s ', column.defaultConstraint, column.defaultValue); } else { @@ -186,13 +191,7 @@ class SqlDumper { } } - /** - * @template T - * @param {string} delimiter - * @param {T[]} collection - * @param {(col: T) => void} lambda - */ - putCollection(delimiter, collection, lambda) { + putCollection(delimiter: string, collection: T[], lambda: (col: T) => void) { if (!collection) return; let first = true; for (const item of collection) { @@ -202,8 +201,7 @@ class SqlDumper { } } - /** @param table {import('dbgate-types').TableInfo} */ - createTable(table) { + createTable(table: TableInfo) { this.put('^create ^table %f ( &>&n', table); this.putCollection(',&n', table.columns, (col) => { this.put('%i ', col.columnName); @@ -245,8 +243,7 @@ class SqlDumper { // } } - /** @param fk {import('dbgate-types').ForeignKeyInfo} */ - createForeignKeyFore(fk) { + createForeignKeyFore(fk: ForeignKeyInfo) { if (fk.constraintName != null) this.put('^constraint %i ', fk.constraintName); this.put( '^foreign ^key (%,i) ^references %f (%,i)', @@ -258,16 +255,9 @@ class SqlDumper { if (fk.updateAction) this.put(' ^on ^update %k', fk.updateAction); } - /** @param type {import('dbgate-types').TransformType} */ - transform(type, dumpExpr) { + transform(type: TransformType, dumpExpr) { dumpExpr(); } - /** - * @param table {import('dbgate-types').NamedObjectInfo} - * @param allow {boolean} - */ - allowIdentityInsert(table, allow) {} + allowIdentityInsert(table: NamedObjectInfo, allow: boolean) {} } - -module.exports = SqlDumper; diff --git a/packages/engines/default/createBulkInsertStreamBase.js b/packages/tools/src/createBulkInsertStreamBase.ts similarity index 89% rename from packages/engines/default/createBulkInsertStreamBase.js rename to packages/tools/src/createBulkInsertStreamBase.ts index 12550104b..f8bb53a9d 100644 --- a/packages/engines/default/createBulkInsertStreamBase.js +++ b/packages/tools/src/createBulkInsertStreamBase.ts @@ -1,11 +1,8 @@ -const { prepareTableForImport } = require('dbgate-tools'); -const _ = require('lodash'); +import { EngineDriver } from 'dbgate-types'; +import _intersection from 'lodash/intersection'; +import { prepareTableForImport } from './tableTransforms'; -/** - * - * @param {import('dbgate-types').EngineDriver} driver - */ -function createBulkInsertStreamBase(driver, stream, pool, name, options) { +export function createBulkInsertStreamBase(driver, stream, pool, name, options): any { const fullNameQuoted = name.schemaName ? `${driver.dialect.quoteIdentifier(name.schemaName)}.${driver.dialect.quoteIdentifier(name.pureName)}` : driver.dialect.quoteIdentifier(name.pureName); @@ -46,7 +43,7 @@ function createBulkInsertStreamBase(driver, stream, pool, name, options) { await driver.query(pool, `TRUNCATE TABLE ${fullNameQuoted}`); } - this.columnNames = _.intersection( + this.columnNames = _intersection( structure.columns.map((x) => x.columnName), writable.structure.columns.map((x) => x.columnName) ); @@ -94,5 +91,3 @@ function createBulkInsertStreamBase(driver, stream, pool, name, options) { return writable; } - -module.exports = createBulkInsertStreamBase; diff --git a/packages/engines/default/driverBase.js b/packages/tools/src/driverBase.ts similarity index 69% rename from packages/engines/default/driverBase.js rename to packages/tools/src/driverBase.ts index 38c6a9599..9705b381b 100644 --- a/packages/engines/default/driverBase.js +++ b/packages/tools/src/driverBase.ts @@ -1,8 +1,20 @@ -const createBulkInsertStreamBase = require('./createBulkInsertStreamBase'); +import { SqlDumper } from "./SqlDumper"; -const driverBase = { +const dialect = { + limitSelect: true, + rangeSelect: true, + offsetFetchRangeSyntax: true, + stringEscapeChar: "'", + fallbackDataType: 'nvarchar(max)', + quoteIdentifier(s) { + return s; + }, +}; + +export const driverBase = { analyserClass: null, - dumperClass: null, + dumperClass: SqlDumper, + dialect, async analyseFull(pool) { const analyser = new this.analyserClass(pool, this); @@ -24,11 +36,4 @@ const driverBase = { createDumper() { return new this.dumperClass(this); }, - async writeTable(pool, name, options) { - const { stream, mssql } = pool._nativeModules; - // @ts-ignore - return createBulkInsertStreamBase(this, stream, pool, name, options); - }, }; - -module.exports = driverBase; diff --git a/packages/tools/src/index.ts b/packages/tools/src/index.ts index 3e77feffa..857fa0aec 100644 --- a/packages/tools/src/index.ts +++ b/packages/tools/src/index.ts @@ -1,3 +1,8 @@ export * from './commonTypeParser'; export * from './nameTools'; export * from './tableTransforms'; +export * from './packageTools'; +export * from './createBulkInsertStreamBase'; +export * from './DatabaseAnalyser'; +export * from './driverBase'; +export * from './SqlDumper'; diff --git a/packages/tools/src/packageTools.ts b/packages/tools/src/packageTools.ts new file mode 100644 index 000000000..6fd1d5639 --- /dev/null +++ b/packages/tools/src/packageTools.ts @@ -0,0 +1,40 @@ +import { EngineDriver, ExtensionsDirectory } from 'dbgate-types'; +import _camelCase from 'lodash/camelCase'; +import _isString from 'lodash/isString'; +import _isPlainObject from 'lodash/isPlainObject'; + +export function extractShellApiPlugins(functionName, props): string[] { + const res = []; + const nsMatch = functionName.match(/^([^@]+)@([^@]+)/); + if (nsMatch) { + res.push(nsMatch[2]); + } + if (props && props.connection && props.connection.engine) { + const nsMatchEngine = props.connection.engine.match(/^([^@]+)@([^@]+)/); + if (nsMatchEngine) { + res.push(nsMatchEngine[2]); + } + } + return res; +} + +export function extractShellApiFunctionName(functionName) { + const nsMatch = functionName.match(/^([^@]+)@([^@]+)/); + if (nsMatch) { + return `${_camelCase(nsMatch[2])}.shellApi.${nsMatch[1]}`; + } + return `dbgateApi.${functionName}`; +} + +export function findEngineDriver(connection, extensions: ExtensionsDirectory): EngineDriver { + if (_isString(connection)) { + return extensions.drivers.find((x) => x.engine == connection); + } + if (_isPlainObject(connection)) { + const { engine } = connection; + if (engine) { + return extensions.drivers.find((x) => x.engine == engine); + } + } + return null; +} diff --git a/packages/tools/src/tableTransforms.ts b/packages/tools/src/tableTransforms.ts index 5e3ea9ef2..994161b7a 100644 --- a/packages/tools/src/tableTransforms.ts +++ b/packages/tools/src/tableTransforms.ts @@ -1,8 +1,8 @@ import { TableInfo } from 'dbgate-types'; -import _ from 'lodash'; +import _cloneDeep from 'lodash/cloneDeep'; export function prepareTableForImport(table: TableInfo): TableInfo { - const res = _.cloneDeep(table); + const res = _cloneDeep(table); res.foreignKeys = []; if (res.primaryKey) res.primaryKey.constraintName = null; return res; diff --git a/packages/types/engines.d.ts b/packages/types/engines.d.ts index 9dc88dec3..59b2328dc 100644 --- a/packages/types/engines.d.ts +++ b/packages/types/engines.d.ts @@ -7,9 +7,9 @@ import { DatabaseInfo, NamedObjectInfo, TableInfo, ViewInfo, ProcedureInfo, Func export interface StreamOptions { recordset: (columns) => void; row: (row) => void; - error: (error) => void; - done: (result) => void; - info: (info) => void; + error?: (error) => void; + done?: (result) => void; + info?: (info) => void; } export interface WriteTableOptions { @@ -20,7 +20,8 @@ export interface WriteTableOptions { export interface EngineDriver { engine: string; - connect(nativeModules, { server, port, user, password, database }): any; + title: string; + connect({ server, port, user, password, database }): any; query(pool: any, sql: string): Promise; stream(pool: any, sql: string, options: StreamOptions); readQuery(pool: any, sql: string, structure?: TableInfo): Promise; diff --git a/packages/web/src/fileformats/types.ts b/packages/types/extensions.d.ts similarity index 56% rename from packages/web/src/fileformats/types.ts rename to packages/types/extensions.d.ts index de9929955..94476a4c4 100644 --- a/packages/web/src/fileformats/types.ts +++ b/packages/types/extensions.d.ts @@ -1,3 +1,5 @@ +import { EngineDriver } from "./engines"; + export interface FileFormatDefinition { storageType: string; extension: string; @@ -5,7 +7,7 @@ export interface FileFormatDefinition { readerFunc?: string; writerFunc?: string; args?: any[]; - addFilesToSourceList: ( + addFilesToSourceList?: ( file: { full: string; }, @@ -17,3 +19,15 @@ export interface FileFormatDefinition { getDefaultOutputName?: (sourceName, values) => string; getOutputParams?: (sourceName, values) => any; } + +export interface PluginDefinition { + packageName: string; + manifest: any; + content: any; +} + +export interface ExtensionsDirectory { + plugins: PluginDefinition[]; + fileFormats: FileFormatDefinition[]; + drivers: EngineDriver[]; +} diff --git a/packages/types/index.d.ts b/packages/types/index.d.ts index c8c9c73d8..6134439fb 100644 --- a/packages/types/index.d.ts +++ b/packages/types/index.d.ts @@ -39,3 +39,4 @@ export * from './query'; export * from './dialect'; export * from './dumper'; export * from './dbtypes'; +export * from './extensions'; diff --git a/packages/web/package.json b/packages/web/package.json index dc196cbb3..2d53268ce 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -4,9 +4,6 @@ "private": true, "dependencies": { "@ant-design/colors": "^5.0.0", - "dbgate-datalib": "^1.0.0", - "dbgate-engines": "^1.0.0", - "dbgate-sqltree": "^1.0.0", "@mdi/font": "^5.8.55", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", @@ -14,6 +11,9 @@ "ace-builds": "^1.4.8", "axios": "^0.19.0", "cross-env": "^6.0.3", + "dbgate-datalib": "^1.0.0", + "dbgate-sqltree": "^1.0.0", + "dbgate-tools": "^1.0.0", "eslint": "^6.8.0", "eslint-plugin-react": "^7.17.0", "formik": "^2.1.0", @@ -24,6 +24,7 @@ "react-dropzone": "^11.2.3", "react-helmet": "^6.1.0", "react-json-view": "^1.19.1", + "react-markdown": "^5.0.3", "react-modal": "^3.11.1", "react-scripts": "3.3.0", "react-select": "^3.1.0", @@ -54,9 +55,9 @@ ] }, "devDependencies": { - "dbgate-types": "^1.0.0", "@types/react": "^16.9.17", "@types/styled-components": "^4.4.2", + "dbgate-types": "^1.0.0", "typescript": "^3.7.4" } } diff --git a/packages/web/public/unknown.svg b/packages/web/public/unknown.svg new file mode 100644 index 000000000..ff30dd8cf --- /dev/null +++ b/packages/web/public/unknown.svg @@ -0,0 +1,15 @@ + + + + + + + + image/svg+xml + + + + + + ? + \ No newline at end of file diff --git a/packages/web/src/App.js b/packages/web/src/App.js index a02f8210c..28426f75f 100644 --- a/packages/web/src/App.js +++ b/packages/web/src/App.js @@ -16,6 +16,8 @@ import ConnectionsPinger from './utility/ConnectionsPinger'; import { ModalLayerProvider } from './modals/showModal'; import UploadsProvider from './utility/UploadsProvider'; import ThemeHelmet from './themes/ThemeHelmet'; +import PluginsProvider from './plugins/PluginsProvider'; +import { ExtensionsProvider } from './utility/useExtensions'; function App() { return ( @@ -27,16 +29,20 @@ function App() { - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/packages/web/src/DragAndDropFileTarget.js b/packages/web/src/DragAndDropFileTarget.js index 41e0211f0..85ece2ec1 100644 --- a/packages/web/src/DragAndDropFileTarget.js +++ b/packages/web/src/DragAndDropFileTarget.js @@ -1,8 +1,8 @@ import React from 'react'; import styled from 'styled-components'; -import { fileformats } from './fileformats'; import { FontIcon } from './icons'; import useTheme from './theme/useTheme'; +import useExtensions from './utility/useExtensions'; const TargetStyled = styled.div` position: fixed; @@ -40,6 +40,7 @@ const TitleWrapper = styled.div` export default function DragAndDropFileTarget({ isDragActive, inputProps }) { const theme = useTheme(); + const { fileFormats } = useExtensions(); return ( !!isDragActive && ( @@ -50,7 +51,7 @@ export default function DragAndDropFileTarget({ isDragActive, inputProps }) { Drop the files to upload to DbGate Supported file types:{' '} - {fileformats + {fileFormats .filter((x) => x.readerFunc) .map((x) => x.name) .join(', ')} diff --git a/packages/web/src/appobj/databaseAppObject.js b/packages/web/src/appobj/databaseAppObject.js index ae4598f49..7e3db8f17 100644 --- a/packages/web/src/appobj/databaseAppObject.js +++ b/packages/web/src/appobj/databaseAppObject.js @@ -3,9 +3,9 @@ import _ from 'lodash'; import { DropDownMenuItem } from '../modals/DropDownMenu'; import { openNewTab } from '../utility/common'; import ImportExportModal from '../modals/ImportExportModal'; -import { defaultFileFormat } from '../fileformats'; +import { getDefaultFileFormat } from '../utility/fileformats'; -function Menu({ data, setOpenedTabs, showModal }) { +function Menu({ data, setOpenedTabs, showModal, extensions }) { const { connection, name } = data; const tooltip = `${connection.displayName || connection.server}\n${name}`; @@ -27,7 +27,7 @@ function Menu({ data, setOpenedTabs, showModal }) { { @@ -75,7 +77,7 @@ export default function TableDataGrid({ return connection ? new TableGridDisplay( { schemaName, pureName }, - engines(connection), + findEngineDriver(connection, extensions), config, setConfig, cache || myCache, @@ -94,9 +96,10 @@ export default function TableDataGrid({ React.useEffect(() => { const newDisplay = createDisplay(); + if (!newDisplay) return; if (display && display.isLoadedCorrectly && !newDisplay.isLoadedCorrectly) return; setDisplay(newDisplay); - }, [connection, config, cache || myCache, conid, database, schemaName, pureName, dbinfo]); + }, [connection, config, cache || myCache, conid, database, schemaName, pureName, dbinfo, extensions]); const handleDatabaseStructureChanged = React.useCallback(() => { (setCache || setMyCache)(createGridCache()); diff --git a/packages/web/src/fileformats/csv.ts b/packages/web/src/fileformats/csv.ts deleted file mode 100644 index 1e7cd655d..000000000 --- a/packages/web/src/fileformats/csv.ts +++ /dev/null @@ -1,41 +0,0 @@ -import fileFormatBase from './fileFormatBase'; -import { FileFormatDefinition } from './types'; - -const csvFormat: FileFormatDefinition = { - ...fileFormatBase, - storageType: 'csv', - extension: 'csv', - name: 'CSV', - readerFunc: 'csvReader', - writerFunc: 'csvWriter', - args: [ - { - type: 'select', - name: 'delimiter', - label: 'Delimiter', - options: [ - { name: 'Comma (,)', value: ',' }, - { name: 'Semicolon (;)', value: ';' }, - { name: 'Tab', value: '\t' }, - { name: 'Pipe (|)', value: '|' }, - ], - apiName: 'delimiter', - }, - { - type: 'checkbox', - name: 'quoted', - label: 'Quoted', - apiName: 'quoted', - direction: 'target', - }, - { - type: 'checkbox', - name: 'header', - label: 'Has header row', - apiName: 'header', - default: true, - }, - ], -}; - -export default csvFormat; diff --git a/packages/web/src/fileformats/excel.ts b/packages/web/src/fileformats/excel.ts deleted file mode 100644 index 3b1920e80..000000000 --- a/packages/web/src/fileformats/excel.ts +++ /dev/null @@ -1,52 +0,0 @@ -import axios from '../utility/axios'; -import fileFormatBase from './fileFormatBase'; -import { FileFormatDefinition } from './types'; - -const excelFormat: FileFormatDefinition = { - ...fileFormatBase, - storageType: 'excel', - extension: 'xlsx', - name: 'MS Excel', - readerFunc: 'excelSheetReader', - writerFunc: 'excelSheetWriter', - - addFilesToSourceList: async (file, newSources, newValues) => { - const resp = await axios.get(`files/analyse-excel?filePath=${encodeURIComponent(file.full)}`); - const sheetNames = resp.data; - for (const sheetName of sheetNames) { - newSources.push(sheetName); - newValues[`sourceFile_${sheetName}`] = { - fileName: file.full, - sheetName, - }; - } - }, - - args: [ - { - type: 'checkbox', - name: 'singleFile', - label: 'Create single file', - direction: 'target', - }, - ], - - getDefaultOutputName: (sourceName, values) => { - if (values.target_excel_singleFile) { - return sourceName; - } - return null; - }, - - getOutputParams: (sourceName, values) => { - if (values.target_excel_singleFile) { - return { - sheetName: values[`targetName_${sourceName}`] || sourceName, - fileName:'data.xlsx' - }; - } - return null; - }, -}; - -export default excelFormat; diff --git a/packages/web/src/fileformats/fileFormatBase.ts b/packages/web/src/fileformats/fileFormatBase.ts deleted file mode 100644 index cb7bc1265..000000000 --- a/packages/web/src/fileformats/fileFormatBase.ts +++ /dev/null @@ -1,11 +0,0 @@ -const fileFormatBase = { - addFilesToSourceList: async (file, newSources, newValues) => { - const sourceName = file.name; - newSources.push(sourceName); - newValues[`sourceFile_${sourceName}`] = { - fileName: file.full, - }; - }, -}; - -export default fileFormatBase; diff --git a/packages/web/src/fileformats/index.ts b/packages/web/src/fileformats/index.ts deleted file mode 100644 index 2824490fe..000000000 --- a/packages/web/src/fileformats/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -import csv from './csv'; -import jsonl from './jsonl'; -import excel from './excel'; -import { FileFormatDefinition } from './types'; - -export const fileformats = [csv, jsonl, excel]; - -export function findFileFormat(storageType): FileFormatDefinition { - return fileformats.find((x) => x.storageType == storageType); -} - -export function getFileFormatDirections(format: FileFormatDefinition) { - if (!format) return []; - const res = []; - if (format.readerFunc) res.push('source'); - if (format.writerFunc) res.push('target'); - return res; -} - -export const defaultFileFormat = csv; \ No newline at end of file diff --git a/packages/web/src/fileformats/jsonl.ts b/packages/web/src/fileformats/jsonl.ts deleted file mode 100644 index 02882140c..000000000 --- a/packages/web/src/fileformats/jsonl.ts +++ /dev/null @@ -1,13 +0,0 @@ -import fileFormatBase from './fileFormatBase'; -import { FileFormatDefinition } from './types'; - -const jsonlFormat: FileFormatDefinition = { - ...fileFormatBase, - storageType: 'jsonl', - extension: 'jsonl', - name: 'JSON lines', - readerFunc: 'jsonLinesReader', - writerFunc: 'jsonLinesWriter', -}; - -export default jsonlFormat; diff --git a/packages/web/src/icons.js b/packages/web/src/icons.js index 790950042..038e5ab34 100644 --- a/packages/web/src/icons.js +++ b/packages/web/src/icons.js @@ -42,6 +42,7 @@ const iconNames = { 'icon run': 'mdi mdi-play', 'icon chevron-down': 'mdi mdi-chevron-down', + 'icon plugin': 'mdi mdi-toy-brick', 'img green-ok': 'mdi mdi-check-circle color-green-8', 'img alert': 'mdi mdi-alert-circle color-blue-6', diff --git a/packages/web/src/impexp/ImportExportConfigurator.js b/packages/web/src/impexp/ImportExportConfigurator.js index b416f3ca5..82c5d21e1 100644 --- a/packages/web/src/impexp/ImportExportConfigurator.js +++ b/packages/web/src/impexp/ImportExportConfigurator.js @@ -12,10 +12,10 @@ import { FormArchiveFolderSelect, FormArchiveFilesSelect, } from '../utility/forms'; -import { useArchiveFiles, useConnectionInfo, useDatabaseInfo } from '../utility/metadataLoaders'; +import { useArchiveFiles, useConnectionInfo, useDatabaseInfo, useInstalledPlugins } from '../utility/metadataLoaders'; import TableControl, { TableColumn } from '../utility/TableControl'; import { TextField, SelectField, CheckboxField } from '../utility/inputs'; -import { createPreviewReader, getActionOptions, getTargetName, isFileStorage } from './createImpExpScript'; +import { createPreviewReader, getActionOptions, getTargetName } from './createImpExpScript'; import getElectron from '../utility/getElectron'; import ErrorInfo from '../widgets/ErrorInfo'; import getAsArray from '../utility/getAsArray'; @@ -25,8 +25,9 @@ import SqlEditor from '../sqleditor/SqlEditor'; import { useUploadsProvider } from '../utility/UploadsProvider'; import { FontIcon } from '../icons'; import useTheme from '../theme/useTheme'; -import { fileformats, findFileFormat, getFileFormatDirections } from '../fileformats'; +import { findFileFormat, getFileFormatDirections } from '../utility/fileformats'; import FormArgumentList from '../utility/FormArgumentList'; +import useExtensions from '../utility/useExtensions'; const Container = styled.div` // max-height: 50vh; @@ -89,22 +90,30 @@ const Title = styled.div` margin: 10px 0px; `; -function getFileFilters(storageType) { +function getFileFilters(extensions, storageType) { const res = []; - const format = findFileFormat(storageType); + const format = findFileFormat(extensions, storageType); if (format) res.push({ name: format.name, extensions: [format.extension] }); res.push({ name: 'All Files', extensions: ['*'] }); return res; } -async function addFilesToSourceList(files, values, setValues, preferedStorageType, setPreviewSource) { +async function addFilesToSourceListDefault(file, newSources, newValues) { + const sourceName = file.name; + newSources.push(sourceName); + newValues[`sourceFile_${sourceName}`] = { + fileName: file.full, + }; +} + +async function addFilesToSourceList(extensions, files, values, setValues, preferedStorageType, setPreviewSource) { const newSources = []; const newValues = {}; const storage = preferedStorageType || values.sourceStorageType; for (const file of getAsArray(files)) { - const format = findFileFormat(storage); - if (format && format.addFilesToSourceList) { - await format.addFilesToSourceList(file, newSources, newValues); + const format = findFileFormat(extensions, storage); + if (format) { + await (format.addFilesToSourceList || addFilesToSourceListDefault)(file, newSources, newValues); } } newValues['sourceList'] = [...(values.sourceList || []).filter((x) => !newSources.includes(x)), ...newSources]; @@ -124,17 +133,19 @@ function ElectronFilesInput() { const { values, setValues } = useFormikContext(); const electron = getElectron(); const [isLoading, setIsLoading] = React.useState(false); + const extensions = useExtensions(); const handleClick = async () => { const files = electron.remote.dialog.showOpenDialogSync(electron.remote.getCurrentWindow(), { properties: ['openFile', 'multiSelections'], - filters: getFileFilters(values.sourceStorageType), + filters: getFileFilters(extensions, values.sourceStorageType), }); if (files) { const path = window.require('path'); try { setIsLoading(true); await addFilesToSourceList( + extensions, files.map((full) => ({ full, ...path.parse(full), @@ -175,6 +186,7 @@ function SourceTargetConfig({ tablesField = undefined, engine = undefined, }) { + const extensions = useExtensions(); const theme = useTheme(); const { values, setFieldValue } = useFormikContext(); const types = @@ -182,7 +194,7 @@ function SourceTargetConfig({ ? [{ value: 'jsldata', label: 'Query result data', directions: ['source'] }] : [ { value: 'database', label: 'Database', directions: ['source', 'target'] }, - ...fileformats.map((format) => ({ + ...extensions.fileFormats.map((format) => ({ value: format.storageType, label: `${format.name} files(s)`, directions: getFileFormatDirections(format), @@ -193,7 +205,7 @@ function SourceTargetConfig({ const storageType = values[storageTypeField]; const dbinfo = useDatabaseInfo({ conid: values[connectionIdField], database: values[databaseNameField] }); const archiveFiles = useArchiveFiles({ folder: values[archiveFolderField] }); - const format = findFileFormat(storageType); + const format = findFileFormat(extensions, storageType); return ( {direction == 'source' && ( @@ -340,10 +352,13 @@ export default function ImportExportConfigurator({ uploadedFile = undefined, onC const { setUploadListener } = useUploadsProvider(); const theme = useTheme(); const [previewSource, setPreviewSource] = React.useState(null); + const extensions = useExtensions(); const handleUpload = React.useCallback( (file) => { + console.log('UPLOAD', extensions); addFilesToSourceList( + extensions, [ { full: file.filePath, @@ -357,7 +372,7 @@ export default function ImportExportConfigurator({ uploadedFile = undefined, onC ); // setFieldValue('sourceList', [...(sourceList || []), file.originalName]); }, - [setFieldValue, sourceList, values] + [extensions, setFieldValue, sourceList, values] ); React.useEffect(() => { @@ -373,11 +388,11 @@ export default function ImportExportConfigurator({ uploadedFile = undefined, onC } }, []); - const supportsPreview = !!findFileFormat(values.sourceStorageType); + const supportsPreview = !!findFileFormat(extensions, values.sourceStorageType); const handleChangePreviewSource = async () => { if (previewSource && supportsPreview) { - const reader = await createPreviewReader(values, previewSource); + const reader = await createPreviewReader(extensions, values, previewSource); if (onChangePreview) onChangePreview(reader); } else { onChangePreview(null); @@ -436,8 +451,8 @@ export default function ImportExportConfigurator({ uploadedFile = undefined, onC header="Action" formatter={(row) => ( setFieldValue(`actionType_${row}`, e.target.value)} /> )} @@ -447,7 +462,7 @@ export default function ImportExportConfigurator({ uploadedFile = undefined, onC header="Target" formatter={(row) => ( setFieldValue(`targetName_${row}`, e.target.value)} /> )} diff --git a/packages/web/src/impexp/ScriptWriter.js b/packages/web/src/impexp/ScriptWriter.js index cf459d2e6..acd751ff3 100644 --- a/packages/web/src/impexp/ScriptWriter.js +++ b/packages/web/src/impexp/ScriptWriter.js @@ -1,6 +1,11 @@ +import _ from 'lodash'; +import { extractShellApiFunctionName, extractShellApiPlugins } from 'dbgate-tools'; + export default class ScriptWriter { constructor() { this.s = ''; + this.packageNames = []; + // this.engines = []; this.varCount = 0; } @@ -15,7 +20,8 @@ export default class ScriptWriter { } assign(variableName, functionName, props) { - this.put(`const ${variableName} = await dbgateApi.${functionName}(${JSON.stringify(props)});`); + this.put(`const ${variableName} = await ${extractShellApiFunctionName(functionName)}(${JSON.stringify(props)});`); + this.packageNames.push(...extractShellApiPlugins(functionName, props)); } copyStream(sourceVar, targetVar) { @@ -25,4 +31,17 @@ export default class ScriptWriter { comment(s) { this.put(`// ${s}`); } + + getScript(extensions) { + // if (this.packageNames.length > 0) { + // this.comment('@packages'); + // this.comment(JSON.stringify(this.packageNames)); + // } + // if (this.engines.length > 0) { + // this.comment('@engines'); + // this.comment(JSON.stringify(this.engines)); + // } + const packageNames = this.packageNames; + return _.uniq(packageNames).map((packageName) => `// @require ${packageName}\n`).join('') + '\n' + this.s; + } } diff --git a/packages/web/src/impexp/createImpExpScript.js b/packages/web/src/impexp/createImpExpScript.js index feb068550..1df26395e 100644 --- a/packages/web/src/impexp/createImpExpScript.js +++ b/packages/web/src/impexp/createImpExpScript.js @@ -2,14 +2,13 @@ import _ from 'lodash'; import ScriptWriter from './ScriptWriter'; import getAsArray from '../utility/getAsArray'; import { getConnectionInfo } from '../utility/metadataLoaders'; -import engines from 'dbgate-engines'; -import { findObjectLike } from 'dbgate-tools'; -import { findFileFormat } from '../fileformats'; +import { findEngineDriver, findObjectLike } from 'dbgate-tools'; +import { findFileFormat } from '../utility/fileformats'; -export function getTargetName(source, values) { +export function getTargetName(extensions, source, values) { const key = `targetName_${source}`; if (values[key]) return values[key]; - const format = findFileFormat(values.targetStorageType); + const format = findFileFormat(extensions, values.targetStorageType); if (format) { const res = format.getDefaultOutputName ? format.getDefaultOutputName(source, values) : null; if (res) return res; @@ -18,10 +17,6 @@ export function getTargetName(source, values) { return source; } -export function isFileStorage(storageType) { - return !!findFileFormat(storageType); -} - function extractApiParameters(values, direction, format) { const pairs = (format.args || []) .filter((arg) => arg.apiName) @@ -30,10 +25,10 @@ function extractApiParameters(values, direction, format) { return _.fromPairs(pairs); } -async function getConnection(storageType, conid, database) { +async function getConnection(extensions, storageType, conid, database) { if (storageType == 'database' || storageType == 'query') { const conn = await getConnectionInfo({ conid }); - const driver = engines(conn); + const driver = findEngineDriver(conn, extensions); return [ { ..._.pick(conn, ['server', 'engine', 'user', 'password', 'port']), @@ -45,7 +40,7 @@ async function getConnection(storageType, conid, database) { return [null, null]; } -function getSourceExpr(sourceName, values, sourceConnection, sourceDriver) { +function getSourceExpr(extensions, sourceName, values, sourceConnection, sourceDriver) { const { sourceStorageType } = values; if (sourceStorageType == 'database') { const fullName = { schemaName: values.sourceSchemaName, pureName: sourceName }; @@ -66,9 +61,9 @@ function getSourceExpr(sourceName, values, sourceConnection, sourceDriver) { }, ]; } - if (isFileStorage(sourceStorageType)) { + if (findFileFormat(extensions, sourceStorageType)) { const sourceFile = values[`sourceFile_${sourceName}`]; - const format = findFileFormat(sourceStorageType); + const format = findFileFormat(extensions, sourceStorageType); if (format && format.readerFunc) { return [ format.readerFunc, @@ -113,9 +108,9 @@ function getFlagsFroAction(action) { }; } -function getTargetExpr(sourceName, values, targetConnection, targetDriver) { +function getTargetExpr(extensions, sourceName, values, targetConnection, targetDriver) { const { targetStorageType } = values; - const format = findFileFormat(targetStorageType); + const format = findFileFormat(extensions, targetStorageType); if (format && format.writerFunc) { const outputParams = format.getOutputParams && format.getOutputParams(sourceName, values); return [ @@ -124,7 +119,7 @@ function getTargetExpr(sourceName, values, targetConnection, targetDriver) { ...(outputParams ? outputParams : { - fileName: getTargetName(sourceName, values), + fileName: getTargetName(extensions, sourceName, values), }), ...extractApiParameters(values, 'target', format), }, @@ -136,7 +131,7 @@ function getTargetExpr(sourceName, values, targetConnection, targetDriver) { { connection: targetConnection, schemaName: values.targetSchemaName, - pureName: getTargetName(sourceName, values), + pureName: getTargetName(extensions, sourceName, values), ...getFlagsFroAction(values[`actionType_${sourceName}`]), }, ]; @@ -146,7 +141,7 @@ function getTargetExpr(sourceName, values, targetConnection, targetDriver) { 'archiveWriter', { folderName: values.targetArchiveFolder, - fileName: getTargetName(sourceName, values), + fileName: getTargetName(extensions, sourceName, values), }, ]; } @@ -154,15 +149,17 @@ function getTargetExpr(sourceName, values, targetConnection, targetDriver) { throw new Error(`Unknown target storage type: ${targetStorageType}`); } -export default async function createImpExpScript(values, addEditorInfo = true) { +export default async function createImpExpScript(extensions, values, addEditorInfo = true) { const script = new ScriptWriter(); const [sourceConnection, sourceDriver] = await getConnection( + extensions, values.sourceStorageType, values.sourceConnectionId, values.sourceDatabaseName ); const [targetConnection, targetDriver] = await getConnection( + extensions, values.targetStorageType, values.targetConnectionId, values.targetDatabaseName @@ -172,11 +169,11 @@ export default async function createImpExpScript(values, addEditorInfo = true) { for (const sourceName of sourceList) { const sourceVar = script.allocVariable(); // @ts-ignore - script.assign(sourceVar, ...getSourceExpr(sourceName, values, sourceConnection, sourceDriver)); + script.assign(sourceVar, ...getSourceExpr(extensions, sourceName, values, sourceConnection, sourceDriver)); const targetVar = script.allocVariable(); // @ts-ignore - script.assign(targetVar, ...getTargetExpr(sourceName, values, targetConnection, targetDriver)); + script.assign(targetVar, ...getTargetExpr(extensions, sourceName, values, targetConnection, targetDriver)); script.copyStream(sourceVar, targetVar); script.put(); @@ -185,12 +182,12 @@ export default async function createImpExpScript(values, addEditorInfo = true) { script.comment('@ImportExportConfigurator'); script.comment(JSON.stringify(values)); } - return script.s; + return script.getScript(extensions); } -export function getActionOptions(source, values, targetDbinfo) { +export function getActionOptions(extensions, source, values, targetDbinfo) { const res = []; - const targetName = getTargetName(source, values); + const targetName = getTargetName(extensions, source, values); if (values.targetStorageType == 'database') { let existing = findObjectLike( { schemaName: values.targetSchemaName, pureName: targetName }, @@ -225,13 +222,14 @@ export function getActionOptions(source, values, targetDbinfo) { return res; } -export async function createPreviewReader(values, sourceName) { +export async function createPreviewReader(extensions, values, sourceName) { const [sourceConnection, sourceDriver] = await getConnection( + extensions, values.sourceStorageType, values.sourceConnectionId, values.sourceDatabaseName ); - const [functionName, props] = getSourceExpr(sourceName, values, sourceConnection, sourceDriver); + const [functionName, props] = getSourceExpr(extensions, sourceName, values, sourceConnection, sourceDriver); return { functionName, props: { diff --git a/packages/web/src/modals/ConnectionModal.js b/packages/web/src/modals/ConnectionModal.js index 4a2558258..f965637d1 100644 --- a/packages/web/src/modals/ConnectionModal.js +++ b/packages/web/src/modals/ConnectionModal.js @@ -7,10 +7,12 @@ import { Formik, Form } from 'formik'; import ModalHeader from './ModalHeader'; import ModalFooter from './ModalFooter'; import ModalContent from './ModalContent'; +import useExtensions from '../utility/useExtensions'; // import FormikForm from '../utility/FormikForm'; export default function ConnectionModal({ modalState, connection = undefined }) { const [sqlConnectResult, setSqlConnectResult] = React.useState('Not connected'); + const extensions = useExtensions(); const handleTest = async (values) => { const resp = await axios.post('connections/test', values); @@ -31,9 +33,15 @@ export default function ConnectionModal({ modalState, connection = undefined })
- + + {extensions.drivers.map((driver) => ( + + ))} + {/* - + */} diff --git a/packages/web/src/modals/ImportExportModal.js b/packages/web/src/modals/ImportExportModal.js index bd62384d8..4408724aa 100644 --- a/packages/web/src/modals/ImportExportModal.js +++ b/packages/web/src/modals/ImportExportModal.js @@ -22,7 +22,8 @@ import useSocket from '../utility/SocketProvider'; import LoadingInfo from '../widgets/LoadingInfo'; import { FontIcon } from '../icons'; import LargeButton from '../widgets/LargeButton'; -import { defaultFileFormat } from '../fileformats'; +import { getDefaultFileFormat } from '../utility/fileformats'; +import useExtensions from '../utility/useExtensions'; const headerHeight = '60px'; const footerHeight = '100px'; @@ -92,9 +93,10 @@ const FooterButtons = styled.div` function GenerateSctriptButton({ modalState }) { const setOpenedTabs = useSetOpenedTabs(); const { values } = useFormikContext(); + const extensions = useExtensions(); const handleGenerateScript = async () => { - const code = await createImpExpScript(values); + const code = await createImpExpScript(extensions, values); openNewTab(setOpenedTabs, { title: 'Shell', icon: 'img shell', @@ -141,6 +143,7 @@ export default function ImportExportModal({ const refreshArchiveFolderRef = React.useRef(null); const setArchive = useSetCurrentArchive(); const setCurrentWidget = useSetCurrentWidget(); + const extensions = useExtensions(); const [busy, setBusy] = React.useState(false); @@ -165,9 +168,9 @@ export default function ImportExportModal({ const handleExecute = async (values) => { if (busy) return; - + setBusy(true); - const script = await createImpExpScript(values); + const script = await createImpExpScript(extensions, values); setExecuteNumber((num) => num + 1); @@ -194,7 +197,7 @@ export default function ImportExportModal({ onSubmit={handleExecute} initialValues={{ sourceStorageType: 'database', - targetStorageType: importToArchive ? 'archive' : defaultFileFormat.storageType, + targetStorageType: importToArchive ? 'archive' : getDefaultFileFormat(extensions).storageType, sourceArchiveFolder: archive, targetArchiveFolder, ...initialValues, diff --git a/packages/web/src/plugins/PluginsList.js b/packages/web/src/plugins/PluginsList.js new file mode 100644 index 000000000..41b6de3a6 --- /dev/null +++ b/packages/web/src/plugins/PluginsList.js @@ -0,0 +1,87 @@ +import React from 'react'; +import styled from 'styled-components'; +import useTheme from '../theme/useTheme'; +import { openNewTab } from '../utility/common'; +import { useSetOpenedTabs } from '../utility/globalState'; +import { extractPluginIcon, extractPluginAuthor } from '../plugins/manifestExtractors'; + +const Wrapper = styled.div` + margin: 1px 3px 10px 5px; + display: flex; + align-items: center; + &:hover { + background-color: ${(props) => props.theme.left_background_blue[1]}; + } +`; + +const Texts = styled.div` + margin-left: 10px; +`; + +const Name = styled.div` + font-weight: bold; +`; + +const Line = styled.div` + display: flex; +`; + +const Icon = styled.img` + width: 50px; + height: 50px; +`; + +const Description = styled.div` + font-style: italic; +`; + +const Author = styled.div` + font-weight: bold; +`; + +const Version = styled.div` + margin-left: 5px; +`; + +function openPlugin(setOpenedTabs, packageManifest) { + openNewTab(setOpenedTabs, { + title: packageManifest.name, + icon: 'icon plugin', + tabComponent: 'PluginTab', + props: { + packageName: packageManifest.name, + }, + }); +} + +function PluginsListItem({ packageManifest }) { + const setOpenedTabs = useSetOpenedTabs(); + const theme = useTheme(); + return ( + openPlugin(setOpenedTabs, packageManifest)} theme={theme}> + + + + {packageManifest.name} + {packageManifest.version} + + + {packageManifest.description} + + + {extractPluginAuthor(packageManifest)} + + + + ); +} + +export default function PluginsList({ plugins }) { + return ( + <> + {plugins.map((packageManifest) => ( + + ))} + + ); +} diff --git a/packages/web/src/plugins/PluginsProvider.js b/packages/web/src/plugins/PluginsProvider.js new file mode 100644 index 000000000..0561afa85 --- /dev/null +++ b/packages/web/src/plugins/PluginsProvider.js @@ -0,0 +1,62 @@ +import React from 'react'; +import _ from 'lodash'; +import axios from '../utility/axios'; +import { useInstalledPlugins } from '../utility/metadataLoaders'; + +const PluginsContext = React.createContext(null); + +const dbgateEnv = { + axios, +}; + +export default function PluginsProvider({ children }) { + const installedPlugins = useInstalledPlugins(); + const [plugins, setPlugins] = React.useState({}); + const handleLoadPlugins = async () => { + const newPlugins = {}; + for (const installed of installedPlugins) { + if (!_.keys(plugins).includes(installed.name)) { + console.log('Loading module', installed.name); + const resp = await axios.request({ + method: 'get', + url: 'plugins/script', + params: { + packageName: installed.name, + }, + }); + const module = eval(`${resp.data}; plugin`); + console.log('Loaded plugin', module); + const moduleContent = module.__esModule ? module.default : module; + if (moduleContent.initialize) moduleContent.initialize(dbgateEnv); + newPlugins[installed.name] = moduleContent; + } + } + setPlugins((x) => + _.pick( + { ...x, ...newPlugins }, + installedPlugins.map((y) => y.name) + ) + ); + }; + React.useEffect(() => { + handleLoadPlugins(); + }, [installedPlugins]); + return {children}; +} + +export function usePlugins() { + const installed = useInstalledPlugins(); + const loaded = React.useContext(PluginsContext); + + return React.useMemo( + () => + installed + .map((manifest) => ({ + packageName: manifest.name, + manifest, + content: loaded[manifest.name], + })) + .filter((x) => x.content), + [installed, loaded] + ); +} diff --git a/packages/web/src/plugins/manifestExtractors.js b/packages/web/src/plugins/manifestExtractors.js new file mode 100644 index 000000000..32f62ef65 --- /dev/null +++ b/packages/web/src/plugins/manifestExtractors.js @@ -0,0 +1,21 @@ +import _ from 'lodash'; + +export function extractPluginIcon(packageManifest) { + const { links } = packageManifest || {}; + const { repository } = links || {}; + const homepage = (links && links.homepage) || packageManifest.homepage; + const tested = repository || homepage || packageManifest.homepage; + + if (tested) { + const match = tested.match(/https:\/\/github.com\/([^/]*)\/([^/]*)/); + if (match) { + return `https://raw.githubusercontent.com/${match[1]}/${match[2]}/master/icon.svg`; + } + } + // eslint-disable-next-line no-undef + return `${process.env.PUBLIC_URL}/unknown.svg`; +} + +export function extractPluginAuthor(packageManifest) { + return _.isPlainObject(packageManifest.author) ? packageManifest.author.name : packageManifest.author; +} diff --git a/packages/web/src/tabs/PluginTab.js b/packages/web/src/tabs/PluginTab.js new file mode 100644 index 000000000..24d2cbbf5 --- /dev/null +++ b/packages/web/src/tabs/PluginTab.js @@ -0,0 +1,122 @@ +import React from 'react'; +import styled from 'styled-components'; +import _ from 'lodash'; +import ReactMarkdown from 'react-markdown'; +import useTheme from '../theme/useTheme'; +import useFetch from '../utility/useFetch'; +import LoadingInfo from '../widgets/LoadingInfo'; +import { extractPluginIcon, extractPluginAuthor } from '../plugins/manifestExtractors'; +import FormStyledButton from '../widgets/FormStyledButton'; +import axios from '../utility/axios'; +import { useInstalledPlugins } from '../utility/metadataLoaders'; + +const WhitePage = styled.div` + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + background-color: ${(props) => props.theme.main_background}; + overflow: auto; + padding: 10px; +`; + +const Icon = styled.img` + width: 80px; + height: 80px; +`; + +const Header = styled.div` + display: flex; + border-bottom: 1px solid ${(props) => props.theme.border}; + margin-bottom: 20px; + padding-bottom: 20px; +`; + +const HeaderBody = styled.div` + margin-left: 10px; +`; + +const Title = styled.div` + font-size: 20pt; +`; + +const HeaderLine = styled.div` + margin-top: 5px; +`; + +const Author = styled.span` + font-weight: bold; +`; + +const Version = styled.span``; + +function Delimiter() { + return | ; +} + +function PluginTabCore({ packageName }) { + const theme = useTheme(); + const installed = useInstalledPlugins(); + const info = useFetch({ + params: { packageName }, + url: 'plugins/info', + defaultValue: null, + }); + let { readme, manifest } = info || {}; + const handleInstall = async () => { + axios.post('plugins/install', { packageName }); + }; + const handleUninstall = async () => { + axios.post('plugins/uninstall', { packageName }); + }; + + if (info == null) { + return ; + } + + if (manifest == null) { + const installedFound = installed.find((x) => x.name == packageName); + if (installedFound) { + manifest = installedFound; + readme = installedFound.readme; + } + if (manifest == null) { + return null; + } + } + + return ( + <> +
+ + + {packageName} + + {extractPluginAuthor(manifest)} + + {manifest.version && manifest.version} + + + {!installed.find((x) => x.name == packageName) && ( + + )} + {!!installed.find((x) => x.name == packageName) && ( + + )} + + +
+ {readme} + + ); +} + +export default function PluginTab({ packageName }) { + const theme = useTheme(); + return ( + + + + ); +} diff --git a/packages/web/src/tabs/QueryTab.js b/packages/web/src/tabs/QueryTab.js index cd8ddb212..c51eca95b 100644 --- a/packages/web/src/tabs/QueryTab.js +++ b/packages/web/src/tabs/QueryTab.js @@ -2,15 +2,8 @@ import React from 'react'; import ReactDOM from 'react-dom'; import _ from 'lodash'; import axios from '../utility/axios'; -import engines from 'dbgate-engines'; -import { - useConnectionInfo, - getTableInfo, - getDbCore, - getConnectionInfo, - getSqlObjectInfo, -} from '../utility/metadataLoaders'; +import { useConnectionInfo, getDbCore, getConnectionInfo, getSqlObjectInfo } from '../utility/metadataLoaders'; import SqlEditor from '../sqleditor/SqlEditor'; import { useUpdateDatabaseForTab, useSetOpenedTabs, useOpenedTabs } from '../utility/globalState'; import QueryToolbar from '../query/QueryToolbar'; @@ -24,15 +17,18 @@ import useSocket from '../utility/SocketProvider'; import SaveSqlFileModal from '../modals/SaveSqlFileModal'; import useModalState from '../modals/useModalState'; import sqlFormatter from 'sql-formatter'; +import useExtensions from '../utility/useExtensions'; +import { driverBase, findEngineDriver } from 'dbgate-tools'; function useSqlTemplate(sqlTemplate, props) { const [sql, setSql] = React.useState(''); + const extensions = useExtensions(); async function loadTemplate() { if (sqlTemplate == 'CREATE TABLE') { const tableInfo = await getDbCore(props, props.objectTypeField || 'tables'); const connection = await getConnectionInfo(props); - const driver = engines(connection.engine); + const driver = findEngineDriver(connection, extensions) || driverBase; const dmp = driver.createDumper(); if (tableInfo) dmp.createTable(tableInfo); setSql(dmp.s); @@ -48,7 +44,7 @@ function useSqlTemplate(sqlTemplate, props) { const procedureInfo = await getSqlObjectInfo(props); const connection = await getConnectionInfo(props); - const driver = engines(connection.engine); + const driver = findEngineDriver(connection, extensions) || driverBase; const dmp = driver.createDumper(); if (procedureInfo) dmp.put('^execute %f', procedureInfo); setSql(dmp.s); diff --git a/packages/web/src/tabs/ShellTab.js b/packages/web/src/tabs/ShellTab.js index 2197a8ef5..2a6c5150a 100644 --- a/packages/web/src/tabs/ShellTab.js +++ b/packages/web/src/tabs/ShellTab.js @@ -2,22 +2,11 @@ import React from 'react'; import ReactDOM from 'react-dom'; import _ from 'lodash'; import axios from '../utility/axios'; -import engines from 'dbgate-engines'; - -import { useConnectionInfo, getTableInfo, getConnectionInfo, getSqlObjectInfo } from '../utility/metadataLoaders'; -import SqlEditor from '../sqleditor/SqlEditor'; -import { useUpdateDatabaseForTab, useSetOpenedTabs, useOpenedTabs } from '../utility/globalState'; -import QueryToolbar from '../query/QueryToolbar'; -import SocketMessagesView from '../query/SocketMessagesView'; -import { TabPage } from '../widgets/TabControl'; -import ResultTabs from '../sqleditor/ResultTabs'; -import { VerticalSplitter, HorizontalSplitter } from '../widgets/Splitter'; +import { useSetOpenedTabs } from '../utility/globalState'; +import { VerticalSplitter } from '../widgets/Splitter'; import keycodes from '../utility/keycodes'; import { changeTab } from '../utility/common'; import useSocket from '../utility/SocketProvider'; -import SaveSqlFileModal from '../modals/SaveSqlFileModal'; -import useModalState from '../modals/useModalState'; -import sqlFormatter from 'sql-formatter'; import JavaScriptEditor from '../sqleditor/JavaScriptEditor'; import ShellToolbar from '../query/ShellToolbar'; import RunnerOutputPane from '../query/RunnerOutputPane'; diff --git a/packages/web/src/tabs/ViewDataTab.js b/packages/web/src/tabs/ViewDataTab.js index 65f751c4e..c3787fed3 100644 --- a/packages/web/src/tabs/ViewDataTab.js +++ b/packages/web/src/tabs/ViewDataTab.js @@ -2,18 +2,20 @@ import React from 'react'; import DataGrid from '../datagrid/DataGrid'; import { ViewGridDisplay, createGridCache, createChangeSet } from 'dbgate-datalib'; import { useConnectionInfo, useViewInfo } from '../utility/metadataLoaders'; -import engines from 'dbgate-engines'; import useUndoReducer from '../utility/useUndoReducer'; import usePropsCompare from '../utility/usePropsCompare'; import { useUpdateDatabaseForTab } from '../utility/globalState'; import useGridConfig from '../utility/useGridConfig'; import SqlDataGridCore from '../datagrid/SqlDataGridCore'; +import useExtensions from '../utility/useExtensions'; +import { findEngineDriver } from 'dbgate-tools'; export default function ViewDataTab({ conid, database, schemaName, pureName, tabVisible, toolbarPortalRef, tabid }) { const viewInfo = useViewInfo({ conid, database, schemaName, pureName }); const [config, setConfig] = useGridConfig(tabid); const [cache, setCache] = React.useState(createGridCache()); const [changeSetState, dispatchChangeSet] = useUndoReducer(createChangeSet()); + const extensions = useExtensions() useUpdateDatabaseForTab(tabVisible, conid, database); const connection = useConnectionInfo({ conid }); @@ -25,7 +27,7 @@ export default function ViewDataTab({ conid, database, schemaName, pureName, tab viewInfo && connection ? new ViewGridDisplay( viewInfo, - engines(connection), + findEngineDriver(connection, extensions), //@ts-ignore config, setConfig, diff --git a/packages/web/src/tabs/index.js b/packages/web/src/tabs/index.js index 97d46acd6..e77d0bc3e 100644 --- a/packages/web/src/tabs/index.js +++ b/packages/web/src/tabs/index.js @@ -6,6 +6,7 @@ import ShellTab from './ShellTab'; import InfoPageTab from './InfoPageTab'; import ArchiveFileTab from './ArchiveFileTab'; import FreeTableTab from './FreeTableTab'; +import PluginTab from './PluginTab'; export default { TableDataTab, @@ -16,4 +17,5 @@ export default { ShellTab, ArchiveFileTab, FreeTableTab, + PluginTab, }; diff --git a/packages/web/src/utility/UploadsProvider.js b/packages/web/src/utility/UploadsProvider.js index 81a5de7d2..065b442d0 100644 --- a/packages/web/src/utility/UploadsProvider.js +++ b/packages/web/src/utility/UploadsProvider.js @@ -1,9 +1,10 @@ import React from 'react'; import { useDropzone } from 'react-dropzone'; -import { findFileFormat } from '../fileformats'; import ImportExportModal from '../modals/ImportExportModal'; import useShowModal from '../modals/showModal'; +import { findFileFormat } from './fileformats'; import resolveApi from './resolveApi'; +import useExtensions from './useExtensions'; const UploadsContext = React.createContext(null); @@ -19,6 +20,7 @@ export function useUploadsProvider() { export function useUploadsZone() { const { uploadListener } = useUploadsProvider(); const showModal = useShowModal(); + const extensions = useExtensions(); const onDrop = React.useCallback( (files) => { @@ -28,6 +30,7 @@ export function useUploadsZone() { return; } + console.log('FILE', file); const formData = new FormData(); formData.append('data', file); @@ -40,10 +43,19 @@ export function useUploadsZone() { const resp = await fetch(`${apiBase}/uploads/upload`, fetchOptions); const fileData = await resp.json(); + fileData.shortName = file.name; + + for (const format of extensions.fileFormats) { + if (file.name.endsWith('.' + format.extension)) { + fileData.shortName = file.name.slice(0, -format.extension.length - 1); + fileData.storageType = format.storageType; + } + } + if (uploadListener) { uploadListener(fileData); } else { - if (findFileFormat(fileData.storageType)) { + if (findFileFormat(extensions, fileData.storageType)) { showModal((modalState) => ( x.storageType == storageType); +} + +export function getFileFormatDirections(format) { + if (!format) return []; + const res = []; + if (format.readerFunc) res.push('source'); + if (format.writerFunc) res.push('target'); + return res; +} + +export function getDefaultFileFormat(extensions) { + return extensions.fileFormats.find((x) => x.storageType == 'csv') || jsonlFormat; +} diff --git a/packages/web/src/utility/globalState.js b/packages/web/src/utility/globalState.js index 9a5f576f8..51ede5906 100644 --- a/packages/web/src/utility/globalState.js +++ b/packages/web/src/utility/globalState.js @@ -5,6 +5,7 @@ import { useConnectionInfo, useConfig } from './metadataLoaders'; import usePrevious from './usePrevious'; import useNewQuery from '../query/useNewQuery'; import useShowModal from '../modals/showModal'; +import useExtensions from './useExtensions'; function createGlobalState(defaultValue) { const Context = React.createContext(null); @@ -90,6 +91,7 @@ export function useAppObjectParams() { const currentArchive = useCurrentArchive(); const showModal = useShowModal(); const config = useConfig(); + const extensions = useExtensions(); return { setOpenedTabs, diff --git a/packages/web/src/utility/metadataLoaders.js b/packages/web/src/utility/metadataLoaders.js index 583075919..ffe8fc6c9 100644 --- a/packages/web/src/utility/metadataLoaders.js +++ b/packages/web/src/utility/metadataLoaders.js @@ -88,6 +88,12 @@ const connectionListLoader = () => ({ reloadTrigger: `connection-list-changed`, }); +const installedPluginsLoader = () => ({ + url: 'plugins/installed', + params: {}, + reloadTrigger: `installed-plugins-changed`, +}); + async function getCore(loader, args) { const { url, params, reloadTrigger, transform } = loader(args); const key = stableStringify({ url, ...params }); @@ -243,3 +249,10 @@ export function getArchiveFolders(args) { export function useArchiveFolders(args) { return useCore(archiveFoldersLoader, args); } + +export function getInstalledPlugins(args) { + return getCore(installedPluginsLoader, args) || []; +} +export function useInstalledPlugins(args) { + return useCore(installedPluginsLoader, args) || []; +} diff --git a/packages/web/src/utility/useExtensions.js b/packages/web/src/utility/useExtensions.js new file mode 100644 index 000000000..b428f8241 --- /dev/null +++ b/packages/web/src/utility/useExtensions.js @@ -0,0 +1,35 @@ +import React from 'react'; +import { usePlugins } from '../plugins/PluginsProvider'; +import { buildFileFormats } from './fileformats'; + +const ExtensionsContext = React.createContext(buildExtensions([])); + +export function ExtensionsProvider({ children }) { + const plugins = usePlugins(); + const extensions = React.useMemo(() => buildExtensions(plugins), [plugins]); + return {children}; +} + +function buildDrivers(plugins) { + const res = []; + for (const { content } of plugins) { + if (content.driver) res.push(content.driver); + if (content.drivers) res.push(...content.drivers); + } + return res; +} + +export function buildExtensions(plugins) { + /** @type {import('dbgate-types').ExtensionsDirectory} */ + const extensions = { + plugins, + fileFormats: buildFileFormats(plugins), + drivers: buildDrivers(plugins), + }; + return extensions; +} + +/** @returns {import('dbgate-types').ExtensionsDirectory} */ +export default function useExtensions() { + return React.useContext(ExtensionsContext); +} diff --git a/packages/web/src/widgets/ArchiveWidget.js b/packages/web/src/widgets/ArchiveWidget.js index c628a37a7..e73f1c0be 100644 --- a/packages/web/src/widgets/ArchiveWidget.js +++ b/packages/web/src/widgets/ArchiveWidget.js @@ -1,19 +1,10 @@ import React from 'react'; -import styled from 'styled-components'; import _ from 'lodash'; import { AppObjectList } from '../appobj/AppObjectList'; -import { useCurrentArchive, useOpenedTabs, useSavedSqlFiles, useSetCurrentArchive } from '../utility/globalState'; -import closedTabAppObject from '../appobj/closedTabAppObject'; -import { - SearchBoxWrapper, - WidgetsInnerContainer, - WidgetsMainContainer, - WidgetsOuterContainer, - WidgetTitle, -} from './WidgetStyles'; +import { useCurrentArchive, useSetCurrentArchive } from '../utility/globalState'; +import { SearchBoxWrapper, WidgetsInnerContainer } from './WidgetStyles'; import WidgetColumnBar, { WidgetColumnBarItem } from './WidgetColumnBar'; -import savedSqlFileAppObject from '../appobj/savedSqlFileAppObject'; import { useArchiveFiles, useArchiveFolders } from '../utility/metadataLoaders'; import archiveFolderAppObject from '../appobj/archiveFolderAppObject'; import archiveFileAppObject from '../appobj/archiveFileAppObject'; diff --git a/packages/web/src/widgets/PluginsWidget.js b/packages/web/src/widgets/PluginsWidget.js new file mode 100644 index 000000000..2419257a9 --- /dev/null +++ b/packages/web/src/widgets/PluginsWidget.js @@ -0,0 +1,65 @@ +import React from 'react'; +import _ from 'lodash'; +import { SearchBoxWrapper, WidgetsInnerContainer } from './WidgetStyles'; +import WidgetColumnBar, { WidgetColumnBarItem } from './WidgetColumnBar'; +import { useInstalledPlugins } from '../utility/metadataLoaders'; +import SearchInput from './SearchInput'; +import useFetch from '../utility/useFetch'; +import PluginsList from '../plugins/PluginsList'; + +function InstalledPluginsList() { + const plugins = useInstalledPlugins(); + + return ( + + + + ); +} + +function AvailablePluginsList() { + const [filter, setFilter] = React.useState(''); + const [search, setSearch] = React.useState(''); + + const plugins = useFetch({ + url: 'plugins/search', + params: { + filter: search, + }, + defaultValue: [], + }); + + const setDebouncedFilter = React.useRef( + // @ts-ignore + _.debounce((value) => setSearch(value), 500) + ); + + React.useEffect(() => { + // @ts-ignore + setDebouncedFilter.current(filter); + }, [filter]); + + return ( + <> + + + + + + + + ); +} + +export default function PluginsWidget() { + return ( + + + + + + + + + ); +} diff --git a/packages/web/src/widgets/Toolbar.js b/packages/web/src/widgets/Toolbar.js index d0c91089d..0c3aade65 100644 --- a/packages/web/src/widgets/Toolbar.js +++ b/packages/web/src/widgets/Toolbar.js @@ -10,7 +10,8 @@ import { openNewTab } from '../utility/common'; import useNewFreeTable from '../freetable/useNewFreeTable'; import ImportExportModal from '../modals/ImportExportModal'; import useShowModal from '../modals/showModal'; -import { defaultFileFormat } from '../fileformats'; +import useExtensions from '../utility/useExtensions'; +import { getDefaultFileFormat } from '../utility/fileformats'; const ToolbarContainer = styled.div` display: flex; @@ -28,6 +29,7 @@ export default function ToolBar({ toolbarPortalRef }) { const showModal = useShowModal(); const currentTheme = useCurrentTheme(); const setCurrentTheme = useSetCurrentTheme(); + const extensions = useExtensions(); React.useEffect(() => { window['dbgate_createNewConnection'] = modalState.open; @@ -41,7 +43,7 @@ export default function ToolBar({ toolbarPortalRef }) { modalState={modalState} importToArchive initialValues={{ - sourceStorageType: defaultFileFormat.storageType, + sourceStorageType: getDefaultFileFormat(extensions).storageType, // sourceConnectionId: data.conid, // sourceDatabaseName: data.database, // sourceSchemaName: data.schemaName, diff --git a/packages/web/src/widgets/WidgetContainer.js b/packages/web/src/widgets/WidgetContainer.js index 4ef38c466..9fa204db5 100644 --- a/packages/web/src/widgets/WidgetContainer.js +++ b/packages/web/src/widgets/WidgetContainer.js @@ -3,11 +3,13 @@ import { useCurrentWidget } from '../utility/globalState'; import ArchiveWidget from './ArchiveWidget'; import DatabaseWidget from './DatabaseWidget'; import FilesWidget from './FilesWidget'; +import PluginsWidget from './PluginsWidget'; export default function WidgetContainer() { const currentWidget = useCurrentWidget(); if (currentWidget === 'database') return ; if (currentWidget === 'file') return ; if (currentWidget === 'archive') return ; + if (currentWidget === 'plugins') return ; return null; } diff --git a/packages/web/src/widgets/WidgetIconPanel.js b/packages/web/src/widgets/WidgetIconPanel.js index ce78fd5c5..e00cbcd37 100644 --- a/packages/web/src/widgets/WidgetIconPanel.js +++ b/packages/web/src/widgets/WidgetIconPanel.js @@ -51,6 +51,11 @@ export default function WidgetIconPanel() { name: 'archive', title: 'Archive (saved tabular data)', }, + { + icon: 'icon plugin', + name: 'plugins', + title: 'Extensions & Plugins', + }, // { // icon: 'fa-cog', // name: 'settings', diff --git a/yarn.lock b/yarn.lock index 505cc1a98..7fffe40e6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9,34 +9,6 @@ dependencies: "@ctrl/tinycolor" "^3.1.6" -"@azure/ms-rest-azure-env@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@azure/ms-rest-azure-env/-/ms-rest-azure-env-1.1.2.tgz#8505873afd4a1227ec040894a64fdd736b4a101f" - integrity sha512-l7z0DPCi2Hp88w12JhDTtx5d0Y3+vhfE7JKJb9O7sEz71Cwp053N8piTtTnnk/tUor9oZHgEKi/p3tQQmLPjvA== - -"@azure/ms-rest-js@^1.8.7": - version "1.8.14" - resolved "https://registry.yarnpkg.com/@azure/ms-rest-js/-/ms-rest-js-1.8.14.tgz#657fc145db20b6eb3d58d1a2055473aa72eb609d" - integrity sha512-IrCPN22c8RbKWA06ZXuFwwEb15cSnr0zZ6J8Fspp9ns1SSNTERf7hv+gWvTIis1FlwHy42Mfk8hVu0/r3a0AWA== - dependencies: - "@types/tunnel" "0.0.0" - axios "^0.19.0" - form-data "^2.3.2" - tough-cookie "^2.4.3" - tslib "^1.9.2" - tunnel "0.0.6" - uuid "^3.2.1" - xml2js "^0.4.19" - -"@azure/ms-rest-nodeauth@2.0.2": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@azure/ms-rest-nodeauth/-/ms-rest-nodeauth-2.0.2.tgz#037e29540c5625eaec718b8fcc178dd7ad5dfb96" - integrity sha512-KmNNICOxt3EwViAJI3iu2VH8t8BQg5J2rSAyO4IUYLF9ZwlyYsP419pdvl4NBUhluAP2cgN7dfD2V6E6NOMZlQ== - dependencies: - "@azure/ms-rest-azure-env" "^1.1.2" - "@azure/ms-rest-js" "^1.8.7" - adal-node "^0.1.28" - "@babel/code-frame@7.8.3", "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.5.5", "@babel/code-frame@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e" @@ -1518,6 +1490,13 @@ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.149.tgz#1342d63d948c6062838fbf961012f74d4e638440" integrity sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ== +"@types/mdast@^3.0.0", "@types/mdast@^3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.3.tgz#2d7d671b1cd1ea3deb306ea75036c2a0407d2deb" + integrity sha512-SXPBMnFVQg1s00dlMCc/jCdvPqdE4mXaMMCeRlxLDmTAEoegHT53xKtkDnzDTOcmMHUfcjyf36/YYZ6SxRdnsw== + dependencies: + "@types/unist" "*" + "@types/minimatch@*": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" @@ -1528,21 +1507,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-13.7.0.tgz#b417deda18cf8400f278733499ad5547ed1abec4" integrity sha512-GnZbirvmqZUzMgkFn70c74OQpTTUcCzlhQliTzYjQMqg+hVKcDnxdL19Ne3UdYzdMA/+W3eb646FWn/ZaT1NfQ== -"@types/node@^12.12.17": - version "12.12.26" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.26.tgz#213e153babac0ed169d44a6d919501e68f59dea9" - integrity sha512-UmUm94/QZvU5xLcUlNR8hA7Ac+fGpO1EG/a8bcWVz0P0LqtxFmun9Y2bbtuckwGboWJIT70DoWq1r3hb56n3DA== - "@types/node@^13.7.0": version "13.7.7" resolved "https://registry.yarnpkg.com/@types/node/-/node-13.7.7.tgz#1628e6461ba8cc9b53196dfeaeec7b07fa6eea99" integrity sha512-Uo4chgKbnPNlxQwoFmYIwctkQVkMMmsAoGGU4JKwLuvBefF0pCq4FybNSnfkfRCpC7ZW7kttcC/TrRtAJsvGtg== -"@types/node@^8.0.47": - version "8.10.59" - resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.59.tgz#9e34261f30183f9777017a13d185dfac6b899e04" - integrity sha512-8RkBivJrDCyPpBXhVZcjh7cQxVBSmRk9QM7hOketZzp6Tg79c0N8kkpAIito9bnJ3HCVCHVYz+KHTEbfQNfeVQ== - "@types/parse-json@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" @@ -1585,14 +1554,6 @@ "@types/prop-types" "*" csstype "^2.2.0" -"@types/readable-stream@^2.3.5": - version "2.3.5" - resolved "https://registry.yarnpkg.com/@types/readable-stream/-/readable-stream-2.3.5.tgz#99c215f9c78563ebdfeff400246a724fb36bae4a" - integrity sha512-Mq2eLkGYamlcolW603FY2ROBvcl90jPF+3jLkjpBV6qS+2aVeJqlgRG0TVAa1oWbmPdb5yOWlOPVvQle76nUNw== - dependencies: - "@types/node" "*" - safe-buffer "*" - "@types/stack-utils@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" @@ -1623,12 +1584,10 @@ "@types/react-dom" "*" "@types/testing-library__dom" "*" -"@types/tunnel@0.0.0": - version "0.0.0" - resolved "https://registry.yarnpkg.com/@types/tunnel/-/tunnel-0.0.0.tgz#c2a42943ee63c90652a5557b8c4e56cda77f944e" - integrity sha512-FGDp0iBRiBdPjOgjJmn1NH0KDLN+Z8fRmo+9J7XGBhubq1DPrGrbmG4UTlGzrpbCpesMqD0sWkzi27EYkOMHyg== - dependencies: - "@types/node" "*" +"@types/unist@*", "@types/unist@^2.0.0", "@types/unist@^2.0.2", "@types/unist@^2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e" + integrity sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ== "@types/yargs-parser@*": version "15.0.0" @@ -1909,21 +1868,6 @@ acorn@^7.1.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.0.tgz#949d36f2c292535da602283586c2477c57eb2d6c" integrity sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ== -adal-node@^0.1.28: - version "0.1.28" - resolved "https://registry.yarnpkg.com/adal-node/-/adal-node-0.1.28.tgz#468c4bb3ebbd96b1270669f4b9cba4e0065ea485" - integrity sha1-RoxLs+u9lrEnBmn0ucuk4AZepIU= - dependencies: - "@types/node" "^8.0.47" - async ">=0.6.0" - date-utils "*" - jws "3.x.x" - request ">= 2.52.0" - underscore ">= 1.3.1" - uuid "^3.1.0" - xmldom ">= 0.1.x" - xpath.js "~1.1.0" - address@1.1.2, address@^1.0.1: version "1.1.2" resolved "https://registry.yarnpkg.com/address/-/address-1.1.2.tgz#bf1116c9c758c51b7a933d296b72c221ed9428b6" @@ -1940,14 +1884,6 @@ adjust-sourcemap-loader@2.0.0: object-path "0.11.4" regex-parser "2.2.10" -adler-32@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/adler-32/-/adler-32-1.2.0.tgz#6a3e6bf0a63900ba15652808cb15c6813d1a5f25" - integrity sha1-aj5r8KY5ALoVZSgIyxXGgT0aXyU= - dependencies: - exit-on-epipe "~1.0.1" - printj "~1.1.0" - after@0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" @@ -2238,11 +2174,6 @@ async@0.2.10: resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1" integrity sha1-trvgsGdLnXGXCMo43owjfLUmw9E= -async@>=0.6.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/async/-/async-3.1.1.tgz#dd3542db03de837979c9ebbca64ca01b06dc98df" - integrity sha512-X5Dj8hK1pJNC2Wzo2Rcp9FBVdJMGRR/S7V+lH46s8GVFhtbo5O4Le5GECCF/8PISVdkUA6mMPvgz7qTTD1rf1g== - async@^2.6.2: version "2.6.3" resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" @@ -2486,6 +2417,11 @@ backo2@1.0.2: resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" integrity sha1-MasayLEpNjRj41s+u2n038+6eUc= +bail@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.5.tgz#b6fa133404a392cbc1f8c4bf63f5953351e7a776" + integrity sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ== + balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" @@ -2555,11 +2491,6 @@ big.js@^5.2.2: resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== -bignumber.js@9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.0.tgz#805880f84a329b5eac6e7cb6f8274b6d82bdf075" - integrity sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A== - binary-extensions@^1.0.0: version "1.13.1" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" @@ -2584,13 +2515,6 @@ bindings@^1.5.0: dependencies: file-uri-to-path "1.0.0" -bl@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/bl/-/bl-3.0.0.tgz#3611ec00579fd18561754360b21e9f784500ff88" - integrity sha512-EUAyP5UHU5hxF8BPT0LKW8gjYLhq1DQIcneOX/pL/m2Alo+OYDQAJlHq+yseMP50Os2nHXOSic6Ss3vSQeyf4A== - dependencies: - readable-stream "^3.0.1" - blob@0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.5.tgz#d680eeef25f8cd91ad533f5b01eed48e64caf683" @@ -2782,11 +2706,6 @@ bser@2.1.1: dependencies: node-int64 "^0.4.0" -buffer-equal-constant-time@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" - integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= - buffer-from@1.x, buffer-from@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" @@ -2797,11 +2716,6 @@ buffer-indexof@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c" integrity sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g== -buffer-writer@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/buffer-writer/-/buffer-writer-2.0.0.tgz#ce7eb81a38f7829db09c873f2fbb792c0c98ec04" - integrity sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw== - buffer-xor@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" @@ -3009,15 +2923,6 @@ caseless@~0.12.0: resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= -cfb@^1.1.4: - version "1.2.0" - resolved "https://registry.yarnpkg.com/cfb/-/cfb-1.2.0.tgz#6a4d0872b525ed60349e1ef51fb4b0bf73eca9a8" - integrity sha512-sXMvHsKCICVR3Naq+J556K+ExBo9n50iKl6LGarlnvuA2035uMlGA/qVrc0wQtow5P1vJEw9UyrKLCbtIKz+TQ== - dependencies: - adler-32 "~1.2.0" - crc-32 "~1.2.0" - printj "~1.1.2" - chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -3046,6 +2951,21 @@ chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" +character-entities-legacy@^1.0.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz#94bc1845dce70a5bb9d2ecc748725661293d8fc1" + integrity sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA== + +character-entities@^1.0.0: + version "1.2.4" + resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-1.2.4.tgz#e12c3939b7eaf4e5b15e7ad4c5e28e1d48c5b16b" + integrity sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw== + +character-reference-invalid@^1.0.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz#083329cda0eae272ab3dbbf37e9a382c13af1560" + integrity sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg== + chardet@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" @@ -3090,6 +3010,11 @@ chownr@^1.1.1, chownr@^1.1.2: resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.3.tgz#42d837d5239688d55f303003a508230fa6727142" integrity sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw== +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + chrome-trace-event@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4" @@ -3211,14 +3136,6 @@ code-point-at@^1.0.0: resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= -codepage@~1.14.0: - version "1.14.0" - resolved "https://registry.yarnpkg.com/codepage/-/codepage-1.14.0.tgz#8cbe25481323559d7d307571b0fff91e7a1d2f99" - integrity sha1-jL4lSBMjVZ19MHVxsP/5HnodL5k= - dependencies: - commander "~2.14.1" - exit-on-epipe "~1.0.1" - collection-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" @@ -3274,7 +3191,7 @@ combined-stream@^1.0.6, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" -commander@2.17.x, commander@~2.17.1: +commander@2.17.x: version "2.17.1" resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg== @@ -3289,11 +3206,6 @@ commander@^4.0.0: resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== -commander@~2.14.1: - version "2.14.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.14.1.tgz#2235123e37af8ca3c65df45b026dbd357b01b9aa" - integrity sha512-+YR16o3rK53SmWHU3rEM3tPAh2rwb1yPcQX5irVn7mb0gXbwuCCrnkbV5+PBfETdfg1vui07nM6PCG1zndcjQw== - commander@~2.19.0: version "2.19.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" @@ -3553,14 +3465,6 @@ cosmiconfig@^6.0.0: path-type "^4.0.0" yaml "^1.7.2" -crc-32@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.0.tgz#cb2db6e29b88508e32d9dd0ec1693e7b41a18208" - integrity sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA== - dependencies: - exit-on-epipe "~1.0.1" - printj "~1.1.0" - create-ecdh@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff" @@ -3899,31 +3803,6 @@ csstype@^2.5.7, csstype@^2.6.7: resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.10.tgz#e63af50e66d7c266edb6b32909cfd0aabe03928b" integrity sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w== -csv-generate@^3.2.4: - version "3.2.4" - resolved "https://registry.yarnpkg.com/csv-generate/-/csv-generate-3.2.4.tgz#440dab9177339ee0676c9e5c16f50e2b3463c019" - integrity sha512-qNM9eqlxd53TWJeGtY1IQPj90b563Zx49eZs8e0uMyEvPgvNVmX1uZDtdzAcflB3PniuH9creAzcFOdyJ9YGvA== - -csv-parse@^4.8.8: - version "4.10.1" - resolved "https://registry.yarnpkg.com/csv-parse/-/csv-parse-4.10.1.tgz#1e26ba63d29c75e94d0eba6e9de9a8aaf89d72a6" - integrity sha512-gdDJVchi0oSLIcYXz1H/VSgLE6htHDqJyFsRU/vTkQgmVOZ3S0IR2LXnNbWUYG7VD76dYVwdfBLyx8AX9+An8A== - -csv-stringify@^5.3.6: - version "5.5.0" - resolved "https://registry.yarnpkg.com/csv-stringify/-/csv-stringify-5.5.0.tgz#0bdeaaf60d6e15b89c752a0eceb4b4c2c8af5a8a" - integrity sha512-G05575DSO/9vFzQxZN+Srh30cNyHk0SM0ePyiTChMD5WVt7GMTVPBQf4rtgMF6mqhNCJUPw4pN8LDe8MF9EYOA== - -csv@^5.3.2: - version "5.3.2" - resolved "https://registry.yarnpkg.com/csv/-/csv-5.3.2.tgz#50b344e25dfbb8c62684a1bcec18c22468b2161e" - integrity sha512-odDyucr9OgJTdGM2wrMbJXbOkJx3nnUX3Pt8SFOwlAMOpsUQlz1dywvLMXJWX/4Ib0rjfOsaawuuwfI5ucqBGQ== - dependencies: - csv-generate "^3.2.4" - csv-parse "^4.8.8" - csv-stringify "^5.3.6" - stream-transform "^2.0.1" - cyclist@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" @@ -3963,11 +3842,6 @@ date-fns@^2.0.1: resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.9.0.tgz#d0b175a5c37ed5f17b97e2272bbc1fa5aec677d2" integrity sha512-khbFLu/MlzLjEzy9Gh8oY1hNt/Dvxw3J6Rbc28cVoYWQaC1S3YI4xwkF9ZWcjDLscbZlY9hISMr66RFzZagLsA== -date-utils@*: - version "1.2.21" - resolved "https://registry.yarnpkg.com/date-utils/-/date-utils-1.2.21.tgz#61fb16cdc1274b3c9acaaffe9fc69df8720a2b64" - integrity sha1-YfsWzcEnSzyayq/+n8ad+HIKK2Q= - debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -3989,7 +3863,14 @@ debug@^3.0.0, debug@^3.1.1, debug@^3.2.5, debug@^3.2.6: dependencies: ms "^2.1.1" -debug@^4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@~4.1.0: +debug@^4.0.0: + version "4.3.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" + integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== + dependencies: + ms "2.1.2" + +debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@~4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== @@ -4088,11 +3969,6 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= -depd@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" - integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== - depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" @@ -4238,6 +4114,15 @@ dom-serializer@0: domelementtype "^2.0.1" entities "^2.0.0" +dom-serializer@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.1.0.tgz#5f7c828f1bfc44887dc2a315ab5c45691d544b58" + integrity sha512-ox7bvGXt2n+uLWtCRLybYx60IrOlWL/aCebWJk1T0d4m3y2tzf4U3ij9wBMUb6YJZpz06HCCYuyCDveE2xXmzQ== + dependencies: + domelementtype "^2.0.1" + domhandler "^3.0.0" + entities "^2.0.0" + domain-browser@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" @@ -4267,6 +4152,13 @@ domhandler@^2.3.0: dependencies: domelementtype "1" +domhandler@^3.0.0, domhandler@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-3.3.0.tgz#6db7ea46e4617eb15cf875df68b2b8524ce0037a" + integrity sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA== + dependencies: + domelementtype "^2.0.1" + domutils@1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" @@ -4283,6 +4175,15 @@ domutils@^1.5.1, domutils@^1.7.0: dom-serializer "0" domelementtype "1" +domutils@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.4.2.tgz#7ee5be261944e1ad487d9aa0616720010123922b" + integrity sha512-NKbgaM8ZJOecTZsIzW5gSuplsX2IWW2mIK7xVr8hTQF2v1CJWTmLZ1HOCh5sH+IzVPAGE5IucooOkvwBRAdowA== + dependencies: + dom-serializer "^1.0.1" + domelementtype "^2.0.1" + domhandler "^3.3.0" + dot-prop@^4.1.0, dot-prop@^4.1.1: version "4.2.0" resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.0.tgz#1f19e0c2e1aa0e32797c49799f2837ac6af69c57" @@ -4328,13 +4229,6 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" -ecdsa-sig-formatter@1.0.11: - version "1.0.11" - resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" - integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== - dependencies: - safe-buffer "^5.0.1" - ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -4845,11 +4739,6 @@ exenv@^1.2.0: resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d" integrity sha1-KueOhdmJQVhnCwPUe+wfA72Ru50= -exit-on-epipe@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz#0bdd92e87d5285d267daa8171d0eb06159689692" - integrity sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw== - exit@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" @@ -4959,7 +4848,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2: assign-symbols "^1.0.0" is-extendable "^1.0.1" -extend@~3.0.2: +extend@^3.0.0, extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== @@ -5309,15 +5198,6 @@ fork-ts-checker-webpack-plugin@3.1.1: tapable "^1.0.0" worker-rpc "^0.1.0" -form-data@^2.3.2: - version "2.5.1" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" - integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.6" - mime-types "^2.1.12" - form-data@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" @@ -5346,11 +5226,6 @@ forwarded@~0.1.2: resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= -frac@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/frac/-/frac-1.1.2.tgz#3d74f7f6478c88a1b5020306d747dc6313c74d0b" - integrity sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA== - fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" @@ -5837,6 +5712,16 @@ html-minifier@^3.5.20: relateurl "0.2.x" uglify-js "3.4.x" +html-to-react@^1.3.4: + version "1.4.5" + resolved "https://registry.yarnpkg.com/html-to-react/-/html-to-react-1.4.5.tgz#59091c11021d1ef315ef738460abb6a4a41fe1ce" + integrity sha512-KONZUDFPg5OodWaQu2ymfkDmU0JA7zB1iPfvyHehTmMUZnk0DS7/TyCMTzsLH6b4BvxX15g88qZCXFhJWktsmA== + dependencies: + domhandler "^3.3.0" + htmlparser2 "^5.0" + lodash.camelcase "^4.3.0" + ramda "^0.27.1" + html-webpack-plugin@4.0.0-beta.5: version "4.0.0-beta.5" resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-4.0.0-beta.5.tgz#2c53083c1151bfec20479b1f8aaf0039e77b5513" @@ -5861,6 +5746,16 @@ htmlparser2@^3.3.0: inherits "^2.0.1" readable-stream "^3.1.1" +htmlparser2@^5.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-5.0.1.tgz#7daa6fc3e35d6107ac95a4fc08781f091664f6e7" + integrity sha512-vKZZra6CSe9qsJzh0BjBGXo8dvzNsq/oGvsjfRdOrrryfeD9UOBEEQdeoqCRmKZchF5h2zOBMQ6YuQ0uRUmdbQ== + dependencies: + domelementtype "^2.0.1" + domhandler "^3.3.0" + domutils "^2.4.2" + entities "^2.0.0" + http-deceiver@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" @@ -5948,13 +5843,6 @@ iconv-lite@0.4.24, iconv-lite@^0.4.24: dependencies: safer-buffer ">= 2.1.2 < 3" -iconv-lite@^0.5.0: - version "0.5.1" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.5.1.tgz#b2425d3c7b18f7219f2ca663d103bddb91718d64" - integrity sha512-ONHr16SQvKZNSqjQT9gy5z24Jw+uqfO02/ngBSBoqChZ+W8qXX7GPRa1RoUnzGADw8K63R1BXUMzarCVQBpY8Q== - dependencies: - safer-buffer ">= 2.1.2 < 3" - iconv-lite@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.2.tgz#ce13d1875b0c3a674bd6a04b7f76b01b1b6ded01" @@ -6204,6 +6092,19 @@ is-accessor-descriptor@^1.0.0: dependencies: kind-of "^6.0.0" +is-alphabetical@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.4.tgz#9e7d6b94916be22153745d184c298cbf986a686d" + integrity sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg== + +is-alphanumerical@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz#7eb9a2431f855f6b1ef1a78e326df515696c4dbf" + integrity sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A== + dependencies: + is-alphabetical "^1.0.0" + is-decimal "^1.0.0" + is-arguments@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.0.4.tgz#3faf966c7cba0ff437fb31f6250082fcf0448cf3" @@ -6238,6 +6139,11 @@ is-buffer@^1.0.2, is-buffer@^1.1.5: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== +is-buffer@^2.0.0: + version "2.0.5" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" + integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== + is-callable@^1.1.4, is-callable@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab" @@ -6288,6 +6194,11 @@ is-date-object@^1.0.1: resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== +is-decimal@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.4.tgz#65a3a5958a1c5b63a706e1b333d7cd9f630d3fa5" + integrity sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw== + is-descriptor@^0.1.0: version "0.1.6" resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" @@ -6364,6 +6275,11 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" +is-hexadecimal@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7" + integrity sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw== + is-installed-globally@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80" @@ -6425,6 +6341,11 @@ is-plain-obj@^1.0.0: resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= +is-plain-obj@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" + integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== + is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" @@ -7012,11 +6933,6 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" -jsbi@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/jsbi/-/jsbi-3.1.1.tgz#8ea18b3e08d102c6cc09acaa9a099921d775f4fa" - integrity sha512-+HQESPaV0mRiH614z4JPVPAftcRC2p53x92lySPzUzFwJbJTMpzHz8OYUkcXPN3fOcHUe0NdVcHnCtX/1+eCrA== - jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" @@ -7177,23 +7093,6 @@ jsx-ast-utils@^2.2.1, jsx-ast-utils@^2.2.3: array-includes "^3.0.3" object.assign "^4.1.0" -jwa@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" - integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== - dependencies: - buffer-equal-constant-time "1.0.1" - ecdsa-sig-formatter "1.0.11" - safe-buffer "^5.0.1" - -jws@3.x.x: - version "3.2.2" - resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" - integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== - dependencies: - jwa "^1.4.1" - safe-buffer "^5.0.1" - killable@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892" @@ -7398,6 +7297,11 @@ lodash._reinterpolate@^3.0.0: resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= +lodash.camelcase@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" + integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= + lodash.curry@^4.0.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.curry/-/lodash.curry-4.1.1.tgz#248e36072ede906501d75966200a86dab8b23170" @@ -7557,6 +7461,28 @@ md5.js@^1.3.4: inherits "^2.0.1" safe-buffer "^5.1.2" +mdast-add-list-metadata@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mdast-add-list-metadata/-/mdast-add-list-metadata-1.0.1.tgz#95e73640ce2fc1fa2dcb7ec443d09e2bfe7db4cf" + integrity sha512-fB/VP4MJ0LaRsog7hGPxgOrSL3gE/2uEdZyDuSEnKCv/8IkYHiDkIQSbChiJoHyxZZXZ9bzckyRk+vNxFzh8rA== + dependencies: + unist-util-visit-parents "1.1.2" + +mdast-util-from-markdown@^0.8.0: + version "0.8.1" + resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-0.8.1.tgz#781371d493cac11212947226190270c15dc97116" + integrity sha512-qJXNcFcuCSPqUF0Tb0uYcFDIq67qwB3sxo9RPdf9vG8T90ViKnksFqdB/Coq2a7sTnxL/Ify2y7aIQXDkQFH0w== + dependencies: + "@types/mdast" "^3.0.0" + mdast-util-to-string "^1.0.0" + micromark "~2.10.0" + parse-entities "^2.0.0" + +mdast-util-to-string@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-1.1.0.tgz#27055500103f51637bd07d01da01eb1967a43527" + integrity sha512-jVU0Nr2B9X3MU4tSK7JP1CMkSvOj7X5l/GboG1tKRw52lLF1x2Ju92Ms9tNetCcbfX3hzlM73zYo2NKkWSfF/A== + mdn-data@2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b" @@ -7638,6 +7564,14 @@ microevent.ts@~0.1.1: resolved "https://registry.yarnpkg.com/microevent.ts/-/microevent.ts-0.1.1.tgz#70b09b83f43df5172d0205a63025bce0f7357fa0" integrity sha512-jo1OfR4TaEwd5HOrt5+tAZ9mqT4jmpNAusXtyfNzqVm9uiSYFZlKM1wYL4oU7azZW/PxQW53wM0S6OR1JHNa2g== +micromark@~2.10.0: + version "2.10.1" + resolved "https://registry.yarnpkg.com/micromark/-/micromark-2.10.1.tgz#cd73f54e0656f10e633073db26b663a221a442a7" + integrity sha512-fUuVF8sC1X7wsCS29SYQ2ZfIZYbTymp0EYr6sab3idFjigFFjGa5UwoniPlV9tAgntjuapW1t9U+S0yDYeGKHQ== + dependencies: + debug "^4.0.0" + parse-entities "^2.0.0" + micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4: version "3.1.10" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" @@ -7762,6 +7696,14 @@ minipass@^3.0.0, minipass@^3.1.1: dependencies: yallist "^4.0.0" +minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + mississippi@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022" @@ -7794,11 +7736,6 @@ mixin-object@^2.0.1: for-in "^0.1.3" is-extendable "^0.1.1" -mixme@^0.3.1: - version "0.3.5" - resolved "https://registry.yarnpkg.com/mixme/-/mixme-0.3.5.tgz#304652cdaf24a3df0487205e61ac6162c6906ddd" - integrity sha512-SyV9uPETRig5ZmYev0ANfiGeB+g6N2EnqqEfBbCGmmJ6MgZ3E4qv5aPbnHVdZ60KAHHXV+T3sXopdrnIXQdmjQ== - mkdirp@0.5.1, mkdirp@0.x, mkdirp@^0.5.1, mkdirp@~0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" @@ -7806,6 +7743,11 @@ mkdirp@0.5.1, mkdirp@0.x, mkdirp@^0.5.1, mkdirp@~0.5.1: dependencies: minimist "0.0.8" +mkdirp@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + moment@^2.24.0: version "2.24.0" resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" @@ -7833,20 +7775,11 @@ ms@2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== -ms@^2.1.1: +ms@2.1.2, ms@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -mssql@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/mssql/-/mssql-6.0.1.tgz#3aab69b7c19dd1b304495f27b748535d0110b477" - integrity sha512-7oPhbQjotFJBgMPlzLmPwWlnrryxrDltjCNCD9BK7Df7TA9wc6Mb/0Bcx04NJyHCMAhw8C/iWjbJoeDyUfRNUA== - dependencies: - debug "^4" - tarn "^1.1.5" - tedious "^6.6.2" - multicast-dns-service-types@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" @@ -7865,16 +7798,6 @@ mute-stream@0.0.8: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== -mysql@^2.17.1: - version "2.18.1" - resolved "https://registry.yarnpkg.com/mysql/-/mysql-2.18.1.tgz#2254143855c5a8c73825e4522baf2ea021766717" - integrity sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig== - dependencies: - bignumber.js "9.0.0" - readable-stream "2.3.7" - safe-buffer "5.1.2" - sqlstring "2.3.1" - nan@^2.12.1: version "2.14.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" @@ -7897,16 +7820,16 @@ nanomatch@^1.2.9: snapdragon "^0.8.1" to-regex "^3.0.1" -native-duplexpair@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/native-duplexpair/-/native-duplexpair-1.0.0.tgz#7899078e64bf3c8a3d732601b3d40ff05db58fa0" - integrity sha1-eJkHjmS/PIo9cyYBs9QP8F21j6A= - natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= +ncp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3" + integrity sha1-GVoh1sRuNh0vsSgbo4uR6d9727M= + nedb-promises@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/nedb-promises/-/nedb-promises-4.0.1.tgz#4d0bd1553d045acca5d6713ad76eb97aa830b390" @@ -8432,11 +8355,6 @@ package-json@^4.0.0: registry-url "^3.0.3" semver "^5.1.0" -packet-reader@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/packet-reader/-/packet-reader-1.0.0.tgz#9238e5480dedabacfe1fe3f2771063f164157d74" - integrity sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ== - pako@~1.0.5: version "1.0.11" resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" @@ -8477,6 +8395,18 @@ parse-asn1@^5.0.0: pbkdf2 "^3.0.3" safe-buffer "^5.1.1" +parse-entities@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-2.0.0.tgz#53c6eb5b9314a1f4ec99fa0fdf7ce01ecda0cbe8" + integrity sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ== + dependencies: + character-entities "^1.0.0" + character-entities-legacy "^1.0.0" + character-reference-invalid "^1.0.0" + is-alphanumerical "^1.0.0" + is-decimal "^1.0.0" + is-hexadecimal "^1.0.0" + parse-json@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" @@ -8656,70 +8586,6 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -pg-connection-string@0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-0.1.3.tgz#da1847b20940e42ee1492beaf65d49d91b245df7" - integrity sha1-2hhHsglA5C7hSSvq9l1J2RskXfc= - -pg-cursor@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/pg-cursor/-/pg-cursor-2.2.1.tgz#f0f35f9e729889d795c8191141a6b15c5f8b18a7" - integrity sha512-C0DKcb8do7Mv9tTQvrB+hxPYgJ6FCKnu1CjPMb0txYHW+zULpOH0B01MNtjQA4nrhHJ4Qs1Nf58BGEc158wXIA== - -pg-int8@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" - integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== - -pg-packet-stream@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/pg-packet-stream/-/pg-packet-stream-1.1.0.tgz#e45c3ae678b901a2873af1e17b92d787962ef914" - integrity sha512-kRBH0tDIW/8lfnnOyTwKD23ygJ/kexQVXZs7gEyBljw4FYqimZFxnMMx50ndZ8In77QgfGuItS5LLclC2TtjYg== - -pg-pool@^2.0.10: - version "2.0.10" - resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-2.0.10.tgz#842ee23b04e86824ce9d786430f8365082d81c4a" - integrity sha512-qdwzY92bHf3nwzIUcj+zJ0Qo5lpG/YxchahxIN8+ZVmXqkahKXsnl2aiJPHLYN9o5mB/leG+Xh6XKxtP7e0sjg== - -pg-query-stream@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/pg-query-stream/-/pg-query-stream-3.1.1.tgz#f73c5d5252dd0728bbf7f67e53c1ac09713ea245" - integrity sha512-jkIgIzBPWEHqePfA5dKbjsN9dCFIlGnLQ3pEIhU10OhgyOmi0CuP8cGLNgCbCnbbtZEaSuyCAYpe/rtwYMoL9w== - dependencies: - pg-cursor "^2.2.1" - -pg-types@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3" - integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA== - dependencies: - pg-int8 "1.0.1" - postgres-array "~2.0.0" - postgres-bytea "~1.0.0" - postgres-date "~1.0.4" - postgres-interval "^1.1.0" - -pg@^7.17.0: - version "7.18.1" - resolved "https://registry.yarnpkg.com/pg/-/pg-7.18.1.tgz#67f59c47a99456fcb34f9fe53662b79d4a992f6d" - integrity sha512-1KtKBKg/zWrjEEv//klBbVOPGucuc7HHeJf6OEMueVcUeyF3yueHf+DvhVwBjIAe9/97RAydO/lWjkcMwssuEw== - dependencies: - buffer-writer "2.0.0" - packet-reader "1.0.0" - pg-connection-string "0.1.3" - pg-packet-stream "^1.1.0" - pg-pool "^2.0.10" - pg-types "^2.1.0" - pgpass "1.x" - semver "4.3.2" - -pgpass@1.x: - version "1.0.2" - resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.2.tgz#2a7bb41b6065b67907e91da1b07c1847c877b306" - integrity sha1-Knu0G2BltnkH6R2hsHwYR8h3swY= - dependencies: - split "^1.0.0" - picomatch@^2.0.4, picomatch@^2.0.7: version "2.2.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.1.tgz#21bac888b6ed8601f831ce7816e335bc779f0a4a" @@ -9482,28 +9348,6 @@ postcss@^7, postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.16, po source-map "^0.6.1" supports-color "^6.1.0" -postgres-array@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e" - integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA== - -postgres-bytea@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35" - integrity sha1-AntTPAqokOJtFy1Hz5zOzFIazTU= - -postgres-date@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.4.tgz#1c2728d62ef1bff49abdd35c1f86d4bdf118a728" - integrity sha512-bESRvKVuTrjoBluEcpv2346+6kgB7UlnqWZsnbnCccTNq/pqfj1j6oBaN5+b/NrDXepYUT/HKadqv3iS9lJuVA== - -postgres-interval@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695" - integrity sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ== - dependencies: - xtend "^4.0.0" - prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" @@ -9547,11 +9391,6 @@ pretty-format@^25.1.0: ansi-styles "^4.0.0" react-is "^16.12.0" -printj@~1.1.0, printj@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/printj/-/printj-1.1.2.tgz#d90deb2975a8b9f600fb3a1c94e3f4c53c78a222" - integrity sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ== - private@^0.1.6: version "0.1.8" resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" @@ -9738,6 +9577,11 @@ raf@^3.4.1: dependencies: performance-now "^2.1.0" +ramda@^0.27.1: + version "0.27.1" + resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.27.1.tgz#66fc2df3ef873874ffc2da6aa8984658abacf5c9" + integrity sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw== + randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" @@ -9902,6 +9746,11 @@ react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.12.0.tgz#2cc0fe0fba742d97fd527c42a13bec4eeb06241c" integrity sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q== +react-is@^16.8.6: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + react-json-view@^1.19.1: version "1.19.1" resolved "https://registry.yarnpkg.com/react-json-view/-/react-json-view-1.19.1.tgz#95d8e59e024f08a25e5dc8f076ae304eed97cf5c" @@ -9917,6 +9766,22 @@ react-lifecycles-compat@^3.0.0, react-lifecycles-compat@^3.0.4: resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== +react-markdown@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-5.0.3.tgz#41040ea7a9324b564b328fb81dd6c04f2a5373ac" + integrity sha512-jDWOc1AvWn0WahpjW6NK64mtx6cwjM4iSsLHJPNBqoAgGOVoIdJMqaKX4++plhOtdd4JksdqzlDibgPx6B/M2w== + dependencies: + "@types/mdast" "^3.0.3" + "@types/unist" "^2.0.3" + html-to-react "^1.3.4" + mdast-add-list-metadata "1.0.1" + prop-types "^15.7.2" + react-is "^16.8.6" + remark-parse "^9.0.0" + unified "^9.0.0" + unist-util-visit "^2.0.0" + xtend "^4.0.1" + react-modal@^3.11.1: version "3.11.1" resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-3.11.1.tgz#2a0d6877c9e98f123939ea92d2bb4ad7fa5a17f9" @@ -10075,7 +9940,7 @@ read-pkg@^4.0.1: parse-json "^4.0.0" pify "^3.0.0" -"readable-stream@1 || 2", readable-stream@2.3.7, readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6: +"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -10088,7 +9953,7 @@ read-pkg@^4.0.1: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.0.1, readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0: +readable-stream@^3.0.6, readable-stream@^3.1.1: version "3.5.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.5.0.tgz#465d70e6d1087f6162d079cd0b5db7fbebfd1606" integrity sha512-gSz026xs2LfxBPudDuI41V1lka8cxg64E66SGe78zJlsUofOg/yqwezdIcdfwik6B4h8LFmWPA9ef9X3FiNFLA== @@ -10254,6 +10119,13 @@ relateurl@0.2.x: resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk= +remark-parse@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-9.0.0.tgz#4d20a299665880e4f4af5d90b7c7b8a935853640" + integrity sha512-geKatMwSzEXKHuzBNU1z676sGcDcFoChMK38TgdHJNAYfFtsfHDQG7MoJAjs6sgYMqyLduCYWDIWZIxiPeafEw== + dependencies: + mdast-util-from-markdown "^0.8.0" + remove-trailing-separator@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" @@ -10280,6 +10152,11 @@ repeat-string@^1.6.1: resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= +replace-ext@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb" + integrity sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs= + request-promise-core@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.3.tgz#e9a3c081b51380dfea677336061fea879a829ee9" @@ -10296,7 +10173,7 @@ request-promise-native@^1.0.5: stealthy-require "^1.1.1" tough-cookie "^2.3.3" -"request@>= 2.52.0", request@^2.87.0, request@^2.88.0: +request@^2.87.0, request@^2.88.0: version "2.88.0" resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== @@ -10508,16 +10385,16 @@ rxjs@^6.5.2, rxjs@^6.5.3: dependencies: tslib "^1.9.0" -safe-buffer@*, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" - integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== - safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== +safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" + integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== + safe-regex@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" @@ -10561,7 +10438,7 @@ sass-loader@8.0.0: schema-utils "^2.1.0" semver "^6.3.0" -sax@>=0.6.0, sax@^1.2.4, sax@~1.2.4: +sax@^1.2.4, sax@~1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== @@ -10622,11 +10499,6 @@ semver-diff@^2.0.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@4.3.2: - version "4.3.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.2.tgz#c7a07158a80bedd052355b770d82d6640f803be7" - integrity sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c= - semver@6.3.0, semver@^6.0.0, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" @@ -11034,18 +10906,6 @@ split-string@^3.0.1, split-string@^3.0.2: dependencies: extend-shallow "^3.0.0" -split@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9" - integrity sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg== - dependencies: - through "2" - -sprintf-js@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673" - integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug== - sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -11058,18 +10918,6 @@ sql-formatter@^2.3.3: dependencies: lodash "^4.16.0" -sqlstring@2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/sqlstring/-/sqlstring-2.3.1.tgz#475393ff9e91479aea62dcaf0ca3d14983a7fb40" - integrity sha1-R1OT/56RR5rqYtyvDKPRSYOn+0A= - -ssf@~0.11.2: - version "0.11.2" - resolved "https://registry.yarnpkg.com/ssf/-/ssf-0.11.2.tgz#0b99698b237548d088fc43cdf2b70c1a7512c06c" - integrity sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g== - dependencies: - frac "~1.1.2" - sshpk@^1.7.0: version "1.16.1" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" @@ -11160,13 +11008,6 @@ stream-shift@^1.0.0: resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== -stream-transform@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/stream-transform/-/stream-transform-2.0.2.tgz#3cb7a14c802eb39bc40caaab0535e584f3a65caf" - integrity sha512-J+D5jWPF/1oX+r9ZaZvEXFbu7znjxSkbNAHJ9L44bt/tCVuOEWZlDqU9qJk7N2xBU1S+K2DPpSKeR/MucmCA1Q== - dependencies: - mixme "^0.3.1" - streamsearch@0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" @@ -11463,27 +11304,17 @@ tapable@^1.0.0, tapable@^1.1.0, tapable@^1.1.3: resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== -tarn@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/tarn/-/tarn-1.1.5.tgz#7be88622e951738b9fa3fb77477309242cdddc2d" - integrity sha512-PMtJ3HCLAZeedWjJPgGnCvcphbCOMbtZpjKgLq3qM5Qq9aQud+XHrL0WlrlgnTyS8U+jrjGbEXprFcQrxPy52g== - -tedious@^6.6.2: - version "6.7.0" - resolved "https://registry.yarnpkg.com/tedious/-/tedious-6.7.0.tgz#ad02365f16f9e0416b216e13d3f83c53addd42ca" - integrity sha512-8qr7+sB0h4SZVQBRWUgHmYuOEflAOl2eihvxk0fVNvpvGJV4V5UC/YmSvebyfgyfwWcPO22/AnSbYVZZqf9wuQ== +tar@^6.0.5: + version "6.0.5" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.0.5.tgz#bde815086e10b39f1dcd298e89d596e1535e200f" + integrity sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg== dependencies: - "@azure/ms-rest-nodeauth" "2.0.2" - "@types/node" "^12.12.17" - "@types/readable-stream" "^2.3.5" - bl "^3.0.0" - depd "^2.0.0" - iconv-lite "^0.5.0" - jsbi "^3.1.1" - native-duplexpair "^1.0.0" - punycode "^2.1.0" - readable-stream "^3.4.0" - sprintf-js "^1.1.2" + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^3.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" term-size@^1.2.0: version "1.2.0" @@ -11558,7 +11389,7 @@ through2@^2.0.0, through2@^2.0.1: readable-stream "~2.3.6" xtend "~4.0.1" -through@2, through@^2.3.6: +through@^2.3.6: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= @@ -11661,7 +11492,7 @@ touch@^3.1.0: dependencies: nopt "~1.0.10" -tough-cookie@^2.3.3, tough-cookie@^2.3.4, tough-cookie@^2.4.3, tough-cookie@^2.5.0: +tough-cookie@^2.3.3, tough-cookie@^2.3.4, tough-cookie@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== @@ -11689,6 +11520,11 @@ tree-kill@^1.2.2: resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== +trough@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406" + integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA== + ts-jest@^25.2.1: version "25.2.1" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-25.2.1.tgz#49bf05da26a8b7fbfbc36b4ae2fcdc2fef35c85d" @@ -11710,7 +11546,7 @@ ts-pnp@1.1.5, ts-pnp@^1.1.2: resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.1.5.tgz#840e0739c89fce5f3abd9037bb091dbff16d9dec" integrity sha512-ti7OGMOUOzo66wLF3liskw6YQIaSsBgc4GOAlWRnIEj8htCxJUxskanMUoJOD6MDCRAXo36goXJZch+nOS0VMA== -tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.2: +tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0: version "1.10.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== @@ -11739,11 +11575,6 @@ tunnel-agent@^0.6.0: dependencies: safe-buffer "^5.0.1" -tunnel@0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c" - integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg== - tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" @@ -11809,11 +11640,6 @@ undefsafe@^2.0.2: dependencies: debug "^2.2.0" -"underscore@>= 1.3.1": - version "1.9.2" - resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.2.tgz#0c8d6f536d6f378a5af264a72f7bec50feb7cf2f" - integrity sha512-D39qtimx0c1fI3ya1Lnhk3E9nONswSKhnffBI0gME9C99fYOkNi04xs8K6pePLhvl1frbDemkaBQ5ikWllR2HQ== - underscore@~1.4.4: version "1.4.4" resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.4.4.tgz#61a6a32010622afa07963bf325203cf12239d604" @@ -11842,6 +11668,18 @@ unicode-property-aliases-ecmascript@^1.0.4: resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.5.tgz#a9cc6cc7ce63a0a3023fc99e341b94431d405a57" integrity sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw== +unified@^9.0.0: + version "9.2.0" + resolved "https://registry.yarnpkg.com/unified/-/unified-9.2.0.tgz#67a62c627c40589edebbf60f53edfd4d822027f8" + integrity sha512-vx2Z0vY+a3YoTj8+pttM3tiJHCwY5UFbYdiWrwBEbHmK8pvsPj2rtAX2BFfgXen8T39CJWblWRDT4L5WGXtDdg== + dependencies: + bail "^1.0.0" + extend "^3.0.0" + is-buffer "^2.0.0" + is-plain-obj "^2.0.0" + trough "^1.0.0" + vfile "^4.0.0" + union-value@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" @@ -11883,6 +11721,40 @@ unique-string@^1.0.0: dependencies: crypto-random-string "^1.0.0" +unist-util-is@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-4.0.3.tgz#e8b44db55fc20c43752b3346c116344d45d7c91d" + integrity sha512-bTofCFVx0iQM8Jqb1TBDVRIQW03YkD3p66JOd/aCWuqzlLyUtx1ZAGw/u+Zw+SttKvSVcvTiKYbfrtLoLefykw== + +unist-util-stringify-position@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz#cce3bfa1cdf85ba7375d1d5b17bdc4cada9bd9da" + integrity sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g== + dependencies: + "@types/unist" "^2.0.2" + +unist-util-visit-parents@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-1.1.2.tgz#f6e3afee8bdbf961c0e6f028ea3c0480028c3d06" + integrity sha512-yvo+MMLjEwdc3RhhPYSximset7rwjMrdt9E41Smmvg25UQIenzrN83cRnF1JMzoMi9zZOQeYXHSDf7p+IQkW3Q== + +unist-util-visit-parents@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz#65a6ce698f78a6b0f56aa0e88f13801886cdaef6" + integrity sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg== + dependencies: + "@types/unist" "^2.0.0" + unist-util-is "^4.0.0" + +unist-util-visit@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-2.0.3.tgz#c3703893146df47203bb8a9795af47d7b971208c" + integrity sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q== + dependencies: + "@types/unist" "^2.0.0" + unist-util-is "^4.0.0" + unist-util-visit-parents "^3.0.0" + universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" @@ -12033,7 +11905,7 @@ utils-merge@1.0.1: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= -uuid@^3.0.1, uuid@^3.1.0, uuid@^3.2.1, uuid@^3.3.2, uuid@^3.4.0: +uuid@^3.0.1, uuid@^3.3.2, uuid@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== @@ -12075,6 +11947,25 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" +vfile-message@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-2.0.4.tgz#5b43b88171d409eae58477d13f23dd41d52c371a" + integrity sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ== + dependencies: + "@types/unist" "^2.0.0" + unist-util-stringify-position "^2.0.0" + +vfile@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/vfile/-/vfile-4.2.0.tgz#26c78ac92eb70816b01d4565e003b7e65a2a0e01" + integrity sha512-a/alcwCvtuc8OX92rqqo7PflxiCgXRFjdyoGVuYV+qbgCb0GgZJRvIgCD4+U/Kl1yhaRsaTwksF88xbPyGsgpw== + dependencies: + "@types/unist" "^2.0.0" + is-buffer "^2.0.0" + replace-ext "1.0.0" + unist-util-stringify-position "^2.0.0" + vfile-message "^2.0.0" + vm-browserify@^1.0.1: version "1.1.2" resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" @@ -12367,21 +12258,11 @@ widest-line@^2.0.0: dependencies: string-width "^2.1.1" -wmf@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wmf/-/wmf-1.0.2.tgz#7d19d621071a08c2bdc6b7e688a9c435298cc2da" - integrity sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw== - word-wrap@~1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== -word@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/word/-/word-0.3.0.tgz#8542157e4f8e849f4a363a288992d47612db9961" - integrity sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA== - workbox-background-sync@^4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/workbox-background-sync/-/workbox-background-sync-4.3.1.tgz#26821b9bf16e9e37fd1d640289edddc08afd1950" @@ -12609,60 +12490,22 @@ xdg-basedir@^3.0.0: resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" integrity sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ= -xlsx@^0.16.8: - version "0.16.8" - resolved "https://registry.yarnpkg.com/xlsx/-/xlsx-0.16.8.tgz#5546de9b0ba15169b36770d4e43b24790d3ff1b8" - integrity sha512-qWub4YCn0xLEGHI7WWhk6IJ73MDu7sPSJQImxN6/LiI8wsHi0hUhICEDbyqBT+jgFgORZxrii0HvhNSwBNAPoQ== - dependencies: - adler-32 "~1.2.0" - cfb "^1.1.4" - codepage "~1.14.0" - commander "~2.17.1" - crc-32 "~1.2.0" - exit-on-epipe "~1.0.1" - ssf "~0.11.2" - wmf "~1.0.1" - word "~0.3.0" - xml-name-validator@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== -xml2js@^0.4.19: - version "0.4.23" - resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66" - integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug== - dependencies: - sax ">=0.6.0" - xmlbuilder "~11.0.0" - -xmlbuilder@~11.0.0: - version "11.0.1" - resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" - integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== - xmlchars@^2.1.1: version "2.2.0" resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== -"xmldom@>= 0.1.x": - version "0.2.1" - resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.2.1.tgz#cac9465066f161e1c3302793ea4dbe59c518274f" - integrity sha512-kXXiYvmblIgEemGeB75y97FyaZavx6SQhGppLw5TKWAD2Wd0KAly0g23eVLh17YcpxZpnFym1Qk/eaRjy1APPg== - xmlhttprequest-ssl@~1.5.4: version "1.5.5" resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e" integrity sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4= -xpath.js@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/xpath.js/-/xpath.js-1.1.0.tgz#3816a44ed4bb352091083d002a383dd5104a5ff1" - integrity sha512-jg+qkfS4K8E7965sqaUl8mRngXiKb3WZGfONgE18pr03FUQiuSV6G+Ej4tS55B+rIQSFEIw3phdVAQ4pPqNWfQ== - -xtend@^4.0.0, xtend@~4.0.1: +xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==