Repository: michaelsogos/pg-diff Branch: master Commit: 7288e652dc9c Files: 18 Total size: 32.2 KB Directory structure: gitextract_ccrp0_jt/ ├── .eslintrc.json ├── .gitignore ├── .vscode/ │ ├── .gitignore │ ├── launch.json │ └── settings.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── db_migration/ │ └── .gitignore ├── jsconfig.json ├── main.js ├── note.txt ├── package.json ├── pg-diff-config.json └── src/ ├── CLI.js ├── ConfigHandler.js └── enums/ ├── actions.js └── options.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .eslintrc.json ================================================ { "env": { "commonjs": true, "es6": true, "node": true }, "extends": "eslint:recommended", "globals": { "Atomics": "readonly", "SharedArrayBuffer": "readonly" }, "parserOptions": { "ecmaVersion": 11 }, "rules": { } } ================================================ FILE: .gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* # Runtime data pids *.pid *.seed *.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage # nyc test coverage .nyc_output # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower dependency directory (https://bower.io/) bower_components # node-waf configuration .lock-wscript # Compiled binary addons (https://nodejs.org/api/addons.html) build/Release # Dependency directories node_modules/ jspm_packages/ # TypeScript v1 declaration files typings/ # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variables file .env # parcel-bundler cache (https://parceljs.org/) .cache # next.js build output .next # nuxt.js build output .nuxt # vuepress build output .vuepress/dist # Serverless directories /pg-diff-config_hpos.json *.lock ================================================ FILE: .vscode/.gitignore ================================================ /settings.json ================================================ FILE: .vscode/launch.json ================================================ { // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "type": "node", "request": "launch", "name": "Launch Compare", "program": "${workspaceFolder}\\main.js", "args": ["-c", "${input:configName}", "test"], "protocol": "auto", "console": "integratedTerminal" }, { "type": "node", "request": "launch", "name": "Launch Migration", "program": "${workspaceFolder}\\main.js", "args": ["${input:migrationType}", "${input:configName}"], "protocol": "auto", "console": "integratedTerminal" }, { "type": "node", "request": "launch", "name": "Launch Save Patch", "program": "${workspaceFolder}\\main.js", "args": ["-s", "${input:configName}", "${input:patchName}"], "protocol": "auto", "console": "integratedTerminal" } ], "inputs": [ { "id": "configName", "type": "promptString", "default": "development", "description": "The program name" }, { "id": "patchName", "type": "promptString", "default": "", "description": "The patch name" }, { "type": "pickString", "id": "migrationType", "description": "The migration type", "options": ["--migrate-to-target", "--migrate-to-source"], "default": "--migrate-to-target" } ] } ================================================ FILE: .vscode/settings.json ================================================ { "todo-tree.flat": false, "todo-tree.grouped": false, "todo-tree.expanded": true, "workbench.colorCustomizations": {}, "todo-tree.tree.expanded": true, "todo-tree.tree.flat": false, "todo-tree.tree.grouped": false } ================================================ FILE: CHANGELOG.md ================================================ # Changelog #### ver 2.0.0 * Updated CHANGELOG * Added JS config for better intellisense in VSCode * Refactoring entire tool in order to integrate pg-diff-api * Added ESLING config * Improved logs messages * Fixed bug on config validation * Preparing first release 2.x #### ver 1.2.13 * Fixed a bug to retrieve the COLUMN DEFAULT VALUE because pg_attrdef.adsrc is deprecated * Updated package "pg" to 8.1.0 * Fixed bug with field "relhasoids" because is deprecated in PG v12.x * Fixed a bug to not include functions coming from external library (tested with pg_crypto and posgis) * Excluded from compare objects created by EXTENSIONS * Added initial support for POSGIS and PG_CRYPTO extensions (should be valid also for other extensions) * Improved support for USER DEFINED data types (also if coming from extensions) #### ver 1.2.12 * Improved support for postgres 9.6 in order to not fail looking for IDENTITY COLUMN #### ver 1.2.11 * Added DEEP compare for JSON and JSONB objects #### ver 1.2.10 * Added support for UUID data type #### ver 1.2.9 * Added support to JSON and XML fields * Improved VSCODE debugger launcher #### ver 1.2.8 * Fixed issue when comparing data records without using TABLE SCHEMA from configuration file #### ver 1.2.7 * Fix issue on saving SQL ERROR on migration history table #### ver 1.2.6 * Fixed issues when comparing STRING value containing ' which wasn't properly escaped #### ver 1.2.5 * Added support to DROP VIEW when missing * Added support to DROP MATERIALIZED VIEW when missing * Added support to DROP FUNCTION when missing #### ver 1.2.4 * Added support to DROP missing table; #3 * Improved output messages * Fixed a bug which miss a semicolumn creating a new SEQUENCE; #2 * Fixed a bug which a SEQUENCE is going to be RENAMED wrongly; #4 * Cleaned up CONSOLE.LOG #### ver 1.2.3 * Fixed a bug on handling properly the sequence name on RENAME #### ver 1.2.2 * Fixed a bug when RENAME SEQUENCE using full sequence name instead of just the its name #### ver 1.2.1 * Added support to keep in memory schema changes useful to know during data compare * Added support to include UPDATE SCRIPT during data compare for not yet available table columns because coming from ALTER TABLE during schema compare #### ver 1.2.0 - Added support for SEQUENCES - Fixed an issue which rebase sequences's next value even when tables records under data compare don't have differences - Added support for SEQUENCE RENAME owned by table column - Added support for SEQUENCE CACHE also for pgsql 9.x versions - Fixed a bug on handling sequence privileges #### ver 1.1.3 - Fixed a bug while comparing FUNCTIONS #### ver 1.1.2 - Added the option to SAVE\REGISTER the patch on migration history table without executing the script, it is useful to keep updated SOURCE DATABASE from own created scripts and avoid issues when applying team member patches - Added compatibility check between different PGSQL servers versions - Fixed a bug when comparing DATETIME object #### ver 1.1.1 - Fixed datatime issue between PGSQL timestamp data type and NodeJS Date object - Fixed an issue when rebasing sequences #### ver 1.1.0 - Fixed a bug comparing data between tables - Improved sql patch generator evaluating objects dependencies - Small code refactoring - Improved data type recognition - Refactored sql path generator to divide commands between DROP and CREATE, removed CHANGE script generator - Added MIGRATION STRATEGY for patch execution - Added USING expression for casting on ALTER COLUMN data type - Improved and re-organized configuration file - Improved data compare script generator, now with a single statement is possible to merge existing records on same table but in different database #### ver 1.0.4 - Improved package information for NPM repository #### ver 1.0.3 - Added option to generate idempotent sql code #### ver 1.0.2 - Fixed small bugs - Added records comparing and relative patch generator #### ver 1.0.1 - Fix issue when publish on NPM repository #### ver 1.0.0 - First working version to compare e generate patch file for (TABLES, INDEXES, VIEWS, MATERIALIZED VIEWS, FUNCTIONS) ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2018 Michael Sogos Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # pg-diff PostgreSQL schema and data comparing tool [Documentation is here](https://michaelsogos.github.io/pg-diff/) ## If you like ... [![Support this project via PayPal](https://cdn.rawgit.com/twolfson/paypal-github-button/1.0.0/dist/button.svg)](https://www.paypal.me/michaelsogos) ================================================ FILE: db_migration/.gitignore ================================================ *.sql *.txt ================================================ FILE: jsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "target": "es2016", "jsx": "preserve" }, "exclude": [ "node_modules", "**/node_modules/*" ] } ================================================ FILE: main.js ================================================ #!/usr/bin/env node const CLI = require("./src/CLI").CLI; const ConfigHandler = require("./src/ConfigHandler").ConfigHandler; const PgDiffApi = require("pg-diff-api").PgDiff; const chalk = require("chalk"); const { Progress } = require("clui"); const stdout = require("readline"); const pjson = require("./package.json"); const log = console.log; const actions = require("./src/enums/actions"); const options = require("./src/enums/options"); //pgTypes.setTypeParser(1114, value => new Date(Date.parse(`${value}+0000`))); CLI.PrintIntro(pjson); Run() .then(() => { process.exitCode = 0; process.exit(); }) .catch((err) => { HandleError(err); process.exitCode = -1; process.exit(); }); /** * * @param {Error} e */ function HandleError(e, nostack = false) { log(); if (!nostack) log(chalk.red(e.stack)); if (!nostack) process.stderr.write(e.message); else log(chalk.red(e.message)); process.stderr.write("\n"); switch (e.code) { case "MODULE_NOT_FOUND": log( chalk.red( 'Please create the configuration file "pg-diff-config.json" in the same folder where you run pg-diff or specify config file full path with option parameter "-f"!' ) ); break; } } /** * * @param {String} lastAction * @param {String} currentAction */ function CheckDoubleActionError(lastAction, currentAction) { if (lastAction) { HandleError(new Error(`Too many execution options specified! "${lastAction}" and "${currentAction}" cannot co-exists.`)); CLI.PrintHelp(); process.exit(); } } /** * Run the tool */ async function Run() { var args = process.argv.slice(2); if (args.length <= 0) { HandleError(new Error("Options not specified!"), true); } let action = null; let lastOption = null; let breakLoop = false; /** @type {Map} */ let optionParams = new Map(); let parsingOptionParams = false; for (const arg of args) { if (arg.startsWith("-")) { parsingOptionParams = true; breakLoop = false; lastOption = null; switch (arg) { case "-h": case "--help": CheckDoubleActionError(action, arg); action = actions.HELP; breakLoop = true; parsingOptionParams = false; break; case "-c": case "--compare": CheckDoubleActionError(action, arg); action = actions.COMPARE; lastOption = actions.COMPARE; if (!optionParams.has(lastOption)) optionParams.set(lastOption, []); break; case "-ms": case "--migrate-to-source": CheckDoubleActionError(action, arg); action = actions.MIGRATE_TO_SOURCE; lastOption = actions.MIGRATE_TO_SOURCE; if (!optionParams.has(lastOption)) optionParams.set(lastOption, []); break; case "-mt": case "--migrate-to-target": CheckDoubleActionError(action, arg); action = actions.MIGRATE_TO_TARGET; lastOption = actions.MIGRATE_TO_TARGET; if (!optionParams.has(lastOption)) optionParams.set(lastOption, []); break; case "-s": case "--save": CheckDoubleActionError(action, arg); action = actions.SAVE; lastOption = actions.SAVE; if (!optionParams.has(lastOption)) optionParams.set(lastOption, []); break; case "-p": case "--patch-folder": lastOption = options.PATCH_FOLDER; if (!optionParams.has(lastOption)) optionParams.set(lastOption, []); break; case "-f": case "--config-file": lastOption = options.CONFIG_FILEPATH; if (!optionParams.has(lastOption)) optionParams.set(lastOption, []); break; case "-g": case "--generate-config": CheckDoubleActionError(action, arg); action = actions.GENERATE_CONFIG; lastOption = actions.GENERATE_CONFIG; if (!optionParams.has(lastOption)) optionParams.set(lastOption, []); break; default: HandleError(new Error(`Invalid parameter "${arg}"!`)); CLI.PrintHelp(); process.exit(); } } else if (parsingOptionParams) { optionParams.get(lastOption).push(arg); } if (breakLoop) break; } if (!action) { HandleError(new Error(`Missing execution options! Please specify one between -c, -ms, -mt, -s, -g execution option.`), true); CLI.PrintHelp(); process.exit(); } switch (action) { case actions.HELP: { CLI.PrintHelp(); process.exit(); break; } case actions.COMPARE: { if (!optionParams.has(actions.COMPARE) || optionParams.get(actions.COMPARE).length != 2) { HandleError(new Error("Missing or invalid arguments for option 'COMPARE'!")); CLI.PrintHelp(); process.exit(); } const params = optionParams.get(actions.COMPARE); let config = ConfigHandler.LoadConfig(params[0], ConfigHandler.GetConfigFilePath(optionParams)); ConfigHandler.ValidateCompareConfig(optionParams, config); CLI.PrintOptions(config); let progressBar = new Progress(20); let pgDiff = new PgDiffApi(config); pgDiff.events.on("compare", function (message, percentage) { stdout.clearLine(process.stdout); stdout.cursorTo(process.stdout, 0); process.stdout.write(progressBar.update(percentage / 100) + " - " + chalk.whiteBright(message)); }); let scriptFilePath = await pgDiff.compare(params[1]); log(); if (scriptFilePath) log(chalk.whiteBright("SQL patch file has been created succesfully at: ") + chalk.green(scriptFilePath)); else log(chalk.yellow("No patch has been created because no differences have been found!")); } break; case actions.MIGRATE_TO_SOURCE: case actions.MIGRATE_TO_TARGET: { let configName = null; let toSourceClient = false; if (action == actions.MIGRATE_TO_SOURCE) { if (!optionParams.has(actions.MIGRATE_TO_SOURCE) || optionParams.get(actions.MIGRATE_TO_SOURCE).length != 1) { HandleError(new Error("Missing or invalid arguments for option 'MIGRATE TO SOURCE'!")); CLI.PrintHelp(); process.exit(); } else { configName = optionParams.get(actions.MIGRATE_TO_SOURCE)[0]; toSourceClient = true; } } if (action == actions.MIGRATE_TO_TARGET) { if (!optionParams.has(actions.MIGRATE_TO_TARGET) || optionParams.get(actions.MIGRATE_TO_TARGET).length != 1) { HandleError(new Error("Missing or invalid arguments for option 'MIGRATE TO TARGET'!")); CLI.PrintHelp(); process.exit(); } else configName = optionParams.get(actions.MIGRATE_TO_TARGET)[0]; } let config = ConfigHandler.LoadConfig(configName, ConfigHandler.GetConfigFilePath(optionParams)); ConfigHandler.ValidateMigrationConfig(optionParams, config); CLI.PrintOptions(config); let progressBar = new Progress(20); let pgDiff = new PgDiffApi(config); pgDiff.events.on("migrate", function (message, percentage) { stdout.clearLine(process.stdout); stdout.cursorTo(process.stdout, 0); process.stdout.write(progressBar.update(percentage / 100) + " - " + chalk.whiteBright(message)); }); let patches = await pgDiff.migrate(true, toSourceClient); log(); if (patches.length <= 0) log(chalk.yellow("No new db patches have been found.")); else { log(chalk.green("Following db patches have been applied:")); for (let patch of patches) { log(chalk.green(`- Patch "${patch.name}" version "${patch.version}"`)); } } } break; case actions.SAVE: { if (!optionParams.has(actions.SAVE) || optionParams.get(actions.SAVE).length != 2) { HandleError(new Error("Missing or invalid arguments for option 'SAVE'!")); CLI.PrintHelp(); process.exit(); } const params = optionParams.get(actions.SAVE); let config = ConfigHandler.LoadConfig(params[0], ConfigHandler.GetConfigFilePath(optionParams)); CLI.PrintOptions(config); let pgDiff = new PgDiffApi(config); await pgDiff.save(params[1]); log(); log(chalk.green(`Patch ${params[1]} has been saved!`)); } break; case actions.GENERATE_CONFIG: { if (!optionParams.has(actions.GENERATE_CONFIG) || optionParams.get(actions.GENERATE_CONFIG).length > 1) { HandleError(new Error("Invalid arguments for option 'GENERATE CONFIG'!")); CLI.PrintHelp(); process.exit(); } const params = optionParams.get(actions.GENERATE_CONFIG); await CLI.GenerateConfig(params[0]); } break; default: { HandleError(new Error(`Not implemented yet execution option "${action}"!`)); CLI.PrintHelp(); process.exit(); } } } ================================================ FILE: note.txt ================================================ - TODO: compare the pg-diff-config between api and cli project to check diff - Move ConfigHandler validation in pg-diff-api - check if it is possible to list extension and try to install it ================================================ FILE: package.json ================================================ { "name": "pg-diff-cli", "version": "3.0.0", "description": "PostgreSQL schema and data comparing tool", "pgver": "9.6+", "main": "main.js", "preferGlobal": true, "homepage": "https://michaelsogos.github.io/pg-diff/", "bugs": { "url": "https://github.com/michaelsogos/pg-diff/issues", "email": "michael.sogos@gurustudioweb.it" }, "repository": { "type": "git", "url": "https://github.com/michaelsogos/pg-diff" }, "engines": { "node": ">=8.11.1" }, "scripts": { "debug": "node main.js", "debug-g": "node main.js -g", "debug-g-custom": "node main.js -g my-config" }, "bin": { "pg-diff": "main.js" }, "keywords": [ "pg", "pgsql", "postgre", "postgresql", "sql", "diff", "schema", "data", "compare", "tool", "cli" ], "author": "Michael Sogos (https://github.com/michaelsogos)", "contributors": [ "angulion" ], "license": "MIT", "dependencies": { "@inquirer/prompts": "7.2.3", "chalk": "4.1.2", "clui": "0.3.6", "figlet": "1.8.0", "pg-diff-api": "1.5.3" } } ================================================ FILE: pg-diff-config.json ================================================ { "development": { "sourceClient": { "host": "localhost", "port": 5432, "database": "pg_diff_test1", "user": "postgres", "password": "postgres", "applicationName": "pg-diff-cli", "ssl": false }, "targetClient": { "host": "localhost", "port": 5432, "database": "pg_diff_test2", "user": "postgres", "password": "postgres", "applicationName": "pg-diff-cli", "ssl": false }, "compareOptions": { "author": "@MSO - Michael Sogos", "outputDirectory": "db_migration", "getAuthorFromGit": true, "schemaCompare": { "namespaces": [ "public", "schema_one" ], "dropMissingTable": true, "dropMissingView": true, "dropMissingFunction": true, "dropMissingAggregate": true, "roles": [] }, "dataCompare": { "enable": false, "tables": [ { "tableName": "test_generic", "tableSchema": "public", "tableKeyFields": [ "id" ] } ] } }, "migrationOptions": { "patchesDirectory": "db_migration", "historyTableName": "migrations", "historyTableSchema": "public" } } } ================================================ FILE: src/CLI.js ================================================ const chalk = require("chalk"); const figlet = require("figlet"); const path = require("path"); const inquirer = require("@inquirer/prompts"); const log = console.log; const fs = require("fs"); class CLI { /** * Print initial CLI information * @param {Object} pjson The package json */ static PrintIntro(pjson) { log(chalk.yellow(figlet.textSync(pjson.name, { horizontalLayout: "full" }))); log(); log(chalk.blue(" Author: ") + chalk.green(pjson.author)); log(chalk.blue(" Version: ") + chalk.green(pjson.version)); log(chalk.blue(" PostgreSQL: ") + chalk.green(pjson.pgver)); log(chalk.blue(" License: ") + chalk.green(pjson.license)); log(chalk.blue("Description: ") + chalk.green(pjson.description)); log(); } /** * Print help documentation */ static PrintHelp() { log(); log(); log(chalk.magenta("==============================")); log(chalk.magenta("=== pg-diff-cli HELP ===")); log(chalk.magenta("==============================")); log(); log(chalk.gray("OPTION \t\tDESCRIPTION")); log(chalk.green("-h, --help \t\t") + chalk.blue("To show this help.")); log(chalk.green("-c, --compare \t\t") + chalk.blue("To run compare and generate a patch file.")); log(chalk.green("-ms, --migrate-to-source \t\t") + chalk.blue("To run migration applying all missing patch files to SOURCE CLIENT.")); log(chalk.green("-mt, --migrate-to-target \t\t") + chalk.blue("To run migration applying all missing patch files to TARGET CLIENT.")); log( chalk.green("-f, --config-file \t\t") + chalk.blue("To specify where to find config file, otherwise looks for 'pg-diff-config.json' on current working directory.") ); log( chalk.green("-p, --patch-folder \t\t") + chalk.blue("To set patch folder where save\\retrieve patches (it will override configuration).") ); log( chalk.green("-s, --save \t\t") + chalk.blue("To save\\register patch on migration history table without executing the script.") ); log(chalk.green("-g, --generate-config \t\t") + chalk.blue("To generate a new config file.")); log(); log(); log(chalk.gray("TO GENERATE CONFIG FILE: ") + chalk.yellow("pg-diff ") + chalk.gray("-g ") + chalk.cyan("[configuration-file-name]")); log(chalk.gray(" EXAMPLE: ") + chalk.yellow("pg-diff ") + chalk.gray("-g ")); log(chalk.gray(" EXAMPLE: ") + chalk.yellow("pg-diff ") + chalk.gray("-g ") + chalk.cyan("my-config")); log(); log(chalk.gray(" TO COMPARE: ") + chalk.yellow("pg-diff ") + chalk.gray("-c ") + chalk.cyan("configuration-name script-name")); log(chalk.gray(" EXAMPLE: ") + chalk.yellow("pg-diff ") + chalk.gray("-c ") + chalk.cyan("development my-script")); log(); log(chalk.gray(" TO MIGRATE: ") + chalk.yellow("pg-diff ") + chalk.gray("[-ms | -mt] ") + chalk.cyan("configuration-name")); log(chalk.gray(" EXAMPLE: ") + chalk.yellow("pg-diff ") + chalk.gray("-ms ") + chalk.cyan("development")); log(chalk.gray(" EXAMPLE: ") + chalk.yellow("pg-diff ") + chalk.gray("-mt ") + chalk.cyan("development")); log(); log( chalk.gray(" TO REGISTER: ") + chalk.yellow("pg-diff ") + chalk.gray("-s ") + chalk.cyan("configuration-name patch-file-name") ); log( chalk.gray(" EXAMPLE: ") + chalk.yellow("pg-diff ") + chalk.gray("-s ") + chalk.cyan("development 20182808103040999_my-script.sql") ); log(); log(); } /** * Print configuration options * * @param {Object} config The configuration */ static PrintOptions(config) { log(); log(chalk.gray("CONFIGURED OPTIONS")); log(chalk.yellow(" Script Author: ") + chalk.green(config.compareOptions.author)); log(chalk.yellow(" Output Directory: ") + chalk.green(path.resolve(process.cwd(), config.compareOptions.outputDirectory))); let schemasMessage = config.compareOptions.schemaCompare.namespaces && config.compareOptions.schemaCompare.namespaces.length ? config.compareOptions.schemaCompare.namespaces : "will be retrieve dynamically from database"; log(chalk.yellow(" Schema Namespaces: ") + chalk.green(schemasMessage)); log(chalk.yellow(" Data Compare: ") + chalk.green(config.compareOptions.dataCompare.enable ? "ENABLED" : "DISABLED")); log(); } static async GenerateConfig(filename = "pg-diff-config") { const configName = await inquirer.input({ message: "Type a name for this configuration:", required: true, }); const patchesFolderName = await inquirer.input({ message: "Type the patches folder name:", default: "db_migration", required: true }); let configGenerated = {}; configGenerated[configName] = { sourceClient: { host: await inquirer.input({ message: "Type the SOURCE host name:", default: "localhost", required: true }), port: await inquirer.number({ message: "Type the SOURCE port number:", default: 5432, required: true }), database: await inquirer.input({ message: "Type the SOURCE database name:", required: true }), user: await inquirer.input({ message: "Type the SOURCE username:", default: "postgres", required: true }), password: await inquirer.password({ message: "Type the SOURCE password:", required: true, mask: true }), applicationName: await inquirer.input({ message: "Type the SOURCE application name:", default: "pg-diff-cli", required: true }), ssl: await inquirer.confirm({ message: "Does connection to SOURCE require SSL?", required: true }), }, targetClient: { host: await inquirer.input({ message: "Type the TARGET host name:", default: "localhost", required: true }), port: await inquirer.number({ message: "Type the TARGET port number:", default: 5432, required: true }), database: await inquirer.input({ message: "Type the TARGET database name:", required: true }), user: await inquirer.input({ message: "Type the TARGET username:", default: "postgres", required: true }), password: await inquirer.password({ message: "Type the TARGET password:", required: true, mask: true }), applicationName: await inquirer.input({ message: "Type the TARGET application name:", default: "pg-diff-cli", required: true }), ssl: await inquirer.confirm({ message: "Does connection to TARGET require SSL?", required: true }), }, compareOptions: { author: await inquirer.input({ message: "Type the author of sql patches:" }), getAuthorFromGit: await inquirer.confirm({ message: "Do you like to get the author from GIT user?" }), outputDirectory: patchesFolderName, schemaCompare: { namespaces: [], dropMissingTable: await inquirer.confirm({ message: "Do you want to enable DROP TABLE when exists on TARGET only?", required: true, }), dropMissingView: await inquirer.confirm({ message: "Do you want to enable DROP VIEW when exists on TARGET only?", required: true, }), dropMissingFunction: await inquirer.confirm({ message: "Do you want to enable DROP FUNCTION when exists on TARGET only?", required: true, }), dropMissingAggregate: await inquirer.confirm({ message: "Do you want to enable DROP AGGREGATE when exists on TARGET only?", required: true, }), roles: [], }, dataCompare: { enable: await inquirer.confirm({ message: "Do you want to enable data compare?", required: true }), tables: [ { tableName: "my_table_example", tableSchema: "public", tableKeyFields: ["id"], }, ], }, }, migrationOptions: { patchesDirectory: patchesFolderName, historyTableName: await inquirer.input({ message: "Type the table name will persist the patches applied:", default: "migrations", required: true, }), historyTableSchema: await inquirer.input({ message: "Type the schema name for the table will persist the patches applied:", default: "public", required: true, }), }, }; fs.writeFileSync(`${filename}.json`, JSON.stringify(configGenerated, null, 4), "utf-8"); } } module.exports.CLI = CLI; ================================================ FILE: src/ConfigHandler.js ================================================ const path = require("path"); const options = require("./enums/options"); class ConfigHandler { /** * Load configurations * * @param {String} configName The configuration name * @returns {import("pg-diff-api/src/models/config")} Return the specified configuration */ static LoadConfig(configName, configPath) { const absoluteFilePath = path.resolve(configPath || "pg-diff-config.json"); if (!path.extname(absoluteFilePath) || path.extname(absoluteFilePath).toLocaleLowerCase() !== ".json") throw new Error(`The configuration file path "${absoluteFilePath}" not include file name or it isn't a JSON file!`); let configFile = require(absoluteFilePath); if (!configFile[configName]) throw new Error(`Impossible to find the configuration with name ${configName} !`); /** @type {import("pg-diff-api/src/models/config")} */ let config = configFile[configName]; if (!config.sourceClient) throw new Error('The configuration doesn\'t contains the section "sourceClient {object}" !'); if (!config.targetClient) throw new Error('The configuration doesn\'t contains the section "targetClient {object}" !'); return config; } /** * Validate the configuration schema * * @param {Map} optionParams * @param {Object} config */ static ValidateCompareConfig(optionParams, config) { this.ValidatePatchFolderOption(optionParams, config); if (!config.compareOptions) throw new Error('The configuration doesn\'t contains the section "compareOptions {object}" !'); if (!config.compareOptions.outputDirectory) throw new Error('The configuration section "compareOptions" must contains property "outputDirectory {string}" !'); if (!config.compareOptions.schemaCompare) throw new Error('The configuration section "compareOptions" must contains property "schemaCompare {object}" !'); // if ( // !config.compareOptions.schemaCompare.namespaces || // !Array.isArray(config.compareOptions.schemaCompare.namespaces) || // config.compareOptions.schemaCompare.namespaces.length <= 0 // ) // throw new Error('The configuration section "compareOptions.schemaCompare" must contains property "namespaces (array of strings}" !'); if (!config.compareOptions.schemaCompare.roles || !Array.isArray(config.compareOptions.schemaCompare.roles)) throw new Error('The configuration section "compareOptions.schemaCompare" must contains property "roles (array of strings}" !'); if (!config.compareOptions.dataCompare) throw new Error('The configuration section "optcompareOptionsions" must contains property "dataCompare (object}" !'); if (!Object.prototype.hasOwnProperty.call(config.compareOptions.dataCompare, "enable")) throw new Error('The configuration section "compareOptions.dataCompare" must contains property "enable (boolean}" !'); } /** * Validate the migration configuration schema * * @param {Map} optionParams * @param {Object} config */ static ValidateMigrationConfig(optionParams, config) { this.ValidatePatchFolderOption(optionParams, config); if (!config.migrationOptions) throw new Error('The configuration doesn\'t contains the section "migrationOptions {object}" !'); if (!config.migrationOptions.historyTableSchema) throw new Error('The configuration section "migrationOptions" must contains property "historyTableSchema {string}" !'); if (!config.migrationOptions.historyTableName) throw new Error('The configuration section "migrationOptions" must contains property "historyTableName {string}" !'); if (!config.migrationOptions.patchesDirectory) throw new Error('The configuration section "migrationOptions" must contains property "patchesDirectory {string}" !'); } /** * * @param {Map} optionParams * @param {import("pg-diff-api/src/models/config")} config */ static ValidatePatchFolderOption(optionParams, config) { if (optionParams.has(options.PATCH_FOLDER)) if (optionParams.get(options.PATCH_FOLDER).length == 1 && optionParams.get(options.PATCH_FOLDER)[0]) config.compareOptions.outputDirectory = optionParams.get(options.PATCH_FOLDER)[0]; else throw new Error("Missing or invalid arguments for option 'PATCH FOLDER'!"); } /** * * @param {Map} optionParams */ static GetConfigFilePath(optionParams) { if (optionParams.has(options.CONFIG_FILEPATH)) if (optionParams.get(options.CONFIG_FILEPATH).length == 1 && optionParams.get(options.CONFIG_FILEPATH)[0]) { const configFilePath = optionParams.get(options.CONFIG_FILEPATH)[0]; if (typeof configFilePath === "string" || configFilePath instanceof String) return configFilePath; else throw new Error("Missing or invalid arguments for option 'CONFIG FILEPATH'!"); } else throw new Error("Missing or invalid arguments for option 'CONFIG FILEPATH'!"); else return ""; } } module.exports.ConfigHandler = ConfigHandler; ================================================ FILE: src/enums/actions.js ================================================ const actions = { COMPARE: "COMPARE", MIGRATE_TO_SOURCE: "MIGRATE_TO_SOURCE", MIGRATE_TO_TARGET: "MIGRATE_TO_TARGET", SAVE: "SAVE", HELP: "HELP", GENERATE_CONFIG: "GENERATE_CONFIG", }; module.exports = actions; ================================================ FILE: src/enums/options.js ================================================ const options = { CONFIG_FILEPATH: "CONFIG_FILEPATH", PATCH_FOLDER: "PATCH_FOLDER", }; module.exports = options;