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