diff --git a/packages/strapi/bin/strapi.js b/packages/strapi/bin/strapi.js index 5226900af8..524183ee42 100755 --- a/packages/strapi/bin/strapi.js +++ b/packages/strapi/bin/strapi.js @@ -49,7 +49,16 @@ const getLocalScript = name => (...args) => { process.exit(1); } - return require(cmdPath)(...args); + const script = require(cmdPath); + + Promise.resolve() + .then(() => { + return script(...args); + }) + .catch(error => { + console.error(`Error while running command ${name}: ${error.message}`); + process.exit(1); + }); }; /** @@ -207,9 +216,15 @@ program program .command('configuration:dump') - .option('-f, --file ', 'file to output to') + .option('-f, --file ', 'Output file, default output is stdout') .action(getLocalScript('configurationDump')); +program + .command('configuration:restore') + .option('-f, --file ', 'Input file, default input is stdin') + .option('-s, --strategy ', 'Strategy name, one of: "replace", "merge", "keep"') + .action(getLocalScript('configurationRestore')); + /** * Normalize help argument */ diff --git a/packages/strapi/lib/commands/configurationDump.js b/packages/strapi/lib/commands/configurationDump.js index c9480b0907..cd144d0d4f 100644 --- a/packages/strapi/lib/commands/configurationDump.js +++ b/packages/strapi/lib/commands/configurationDump.js @@ -1,25 +1,52 @@ 'use strict'; const fs = require('fs'); -const { logger } = require('strapi-utils'); -const loadConfiguration = require('../core/app-configuration'); const strapi = require('../index'); +const CHUNK_SIZE = 100; + +/** + * Will dump configurations to a file or stdout + * @param {string} file filepath to use as output + */ module.exports = async function({ file }) { const output = file ? fs.createWriteStream(file) : process.stdout; - output.write('this is a test'); - const app = strapi(); await app.load(); - const confs = await app.query('core_store').find({ - key_contains: 'plugin', - }); + const count = await app.query('core_store').count(); - console.log(confs); + const exportData = []; + const pageCount = Math.ceil(count / 100); + + for (let page = 0; page < pageCount; page++) { + const results = await app + .query('core_store') + .find({ _limit: CHUNK_SIZE, _start: page * CHUNK_SIZE }); + + results + .filter(result => result.key.startsWith('plugin_')) + .forEach(result => { + exportData.push({ + key: result.key, + value: result.value, + type: result.type, + environment: result.environment, + tag: result.tag, + }); + }); + } + + output.write(JSON.stringify(exportData)); output.write('\n'); output.end(); + + // log success only when writting to file + if (file) { + console.log(`Successfully exported ${exportData.length} configuration entries`); + } + process.exit(0); }; diff --git a/packages/strapi/lib/commands/configurationRestore.js b/packages/strapi/lib/commands/configurationRestore.js new file mode 100644 index 0000000000..1e94717fb0 --- /dev/null +++ b/packages/strapi/lib/commands/configurationRestore.js @@ -0,0 +1,113 @@ +'use strict'; + +const _ = require('lodash'); +const fs = require('fs'); +const strapi = require('../index'); + +/** + * Will restore configurations. It reads from a file or stdin + * @param {string} file filepath to use as input + * @param {string} strategy import strategy. one of (replace, merge, keep, default: replace) + */ +module.exports = async function({ file, strategy = 'replace' }) { + const input = file ? fs.readFileSync(file) : await readStdin(process.stdin); + + const app = strapi(); + await app.load(); + + let dataToImport; + try { + dataToImport = JSON.parse(input); + + if (!Array.isArray(dataToImport)) { + throw new Error(`Invalid input data. Expected a valid JSON array.`); + } + } catch (error) { + throw new Error(`Invalid input data: ${error.message}. Expected a valid JSON array.`); + } + + const importer = createImporter(app.db, strategy); + + for (const config of dataToImport) { + await importer.import(config); + } + + console.log(`Successfully imported ${dataToImport.length} configuration entries`); + process.exit(0); +}; + +const readStdin = () => { + const { stdin } = process; + let result = ''; + + if (stdin.isTTY) return Promise.resolve(result); + + return new Promise((resolve, reject) => { + stdin.setEncoding('utf8'); + stdin.on('readable', () => { + let chunk; + while ((chunk = stdin.read())) { + result += chunk; + } + }); + + stdin.on('end', () => { + resolve(result); + }); + + stdin.on('error', reject); + }); +}; + +const createImporter = (db, strategy) => { + switch (strategy) { + case 'replace': + return new ReplaceImporter(db); + case 'merge': + return new MergeImporter(db); + case 'keep': + return new KeepImporter(db); + default: + throw new Error(`No importer available for strategy "${strategy}"`); + } +}; + +function ReplaceImporter(db) { + return { + async import(conf) { + const matching = await db.query('core_store').count({ key: conf.key }); + if (matching > 0) { + await db.query('core_store').update({ key: conf.key }, conf); + } else { + await db.query('core_store').create(conf); + } + }, + }; +} + +function MergeImporter(db) { + return { + async import(conf) { + const existingConf = await db.query('core_store').find({ key: conf.key }); + if (existingConf) { + await db.query('core_store').update({ key: conf.key }, _.merge(existingConf, conf)); + } else { + await db.query('core_store').create(conf); + } + }, + }; +} + +function KeepImporter(db) { + return { + async import(conf) { + const matching = await db.query('core_store').count({ key: conf.key }); + if (matching > 0) { + // if configuration already exists do not overwrite it + return; + } + + await db.query('core_store').create(conf); + }, + }; +}