Add configuration:dump & configuration:restore commands

Signed-off-by: Alexandre Bodin <bodin.alex@gmail.com>
This commit is contained in:
Alexandre Bodin 2020-05-06 18:49:36 +02:00
parent 34026fd88c
commit 7da81fef9e
3 changed files with 165 additions and 10 deletions

View File

@ -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>', 'file to output to')
.option('-f, --file <file>', 'Output file, default output is stdout')
.action(getLocalScript('configurationDump'));
program
.command('configuration:restore')
.option('-f, --file <file>', 'Input file, default input is stdin')
.option('-s, --strategy <strategy>', 'Strategy name, one of: "replace", "merge", "keep"')
.action(getLocalScript('configurationRestore'));
/**
* Normalize help argument
*/

View File

@ -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);
};

View File

@ -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);
},
};
}