Merge pull request #25 from boazpoolman/feature/cli

feat: CLI 🎉
pull/26/head
Boaz Poolman 2021-11-10 16:31:47 +01:00 committed by GitHub
commit e1a9e016e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 6451 additions and 510 deletions

7
bin/config-sync Normal file
View File

@ -0,0 +1,7 @@
#!/usr/bin/env node
'use strict';
const cli = require('../server/cli');
cli();

View File

@ -1,5 +0,0 @@
#!/usr/bin/env node
'use strict';
require('../server/cli')(process.argv);

View File

@ -10,9 +10,8 @@
"required": false, "required": false,
"kind": "plugin" "kind": "plugin"
}, },
"main": "server/cli.js",
"bin": { "bin": {
"config-sync": "./bin/config-sync.js" "config-sync": "./bin/config-sync"
}, },
"scripts": { "scripts": {
"eslint": "eslint --max-warnings=0 './**/*.{js,jsx}'", "eslint": "eslint --max-warnings=0 './**/*.{js,jsx}'",
@ -48,10 +47,11 @@
], ],
"devDependencies": { "devDependencies": {
"@fortawesome/react-fontawesome": "^0.1.16", "@fortawesome/react-fontawesome": "^0.1.16",
"@strapi/design-system": "0.0.1-alpha.51", "@strapi/design-system": "0.0.1-alpha.64",
"@strapi/helper-plugin": "4.0.0-beta.6", "@strapi/helper-plugin": "4.0.0-beta.12",
"@strapi/icons": "0.0.1-alpha.51", "@strapi/icons": "0.0.1-alpha.64",
"@strapi/utils": "4.0.0-beta.6", "@strapi/strapi": "^4.0.0-beta.12",
"@strapi/utils": "4.0.0-beta.12",
"babel-eslint": "9.0.0", "babel-eslint": "9.0.0",
"codecov": "^3.8.3", "codecov": "^3.8.3",
"eslint": "^5.16.0", "eslint": "^5.16.0",

View File

@ -4,14 +4,17 @@ const { Command } = require('commander');
const Table = require('cli-table'); const Table = require('cli-table');
const chalk = require('chalk'); const chalk = require('chalk');
const inquirer = require('inquirer'); const inquirer = require('inquirer');
const { isEmpty } = require('lodash');
const strapi = require('@strapi/strapi');
const packageJSON = require('../package.json'); const packageJSON = require('../package.json');
const program = new Command(); const program = new Command();
const initTable = () => { const initTable = (head) => {
return new Table({ return new Table({
head: [chalk.green('Name'), chalk.green('State')], head: [chalk.green('Name'), chalk.green(head || 'State')],
colWidths: [50, 15],
chars: { top: '═', chars: { top: '═',
'top-mid': '╤', 'top-mid': '╤',
'top-left': '╔', 'top-left': '╔',
@ -31,6 +34,93 @@ const initTable = () => {
}); });
}; };
const getConfigState = (diff, configName, syncType) => {
if (
diff.fileConfig[configName]
&& diff.databaseConfig[configName]
) {
return chalk.yellow(syncType ? 'Update' : 'Different');
} else if (
diff.fileConfig[configName]
&& !diff.databaseConfig[configName]
) {
if (syncType === 'import') {
return chalk.green('Create');
} else if (syncType === 'export') {
return chalk.red('Delete');
} else {
return chalk.red('Only in sync dir');
}
} else if (
!diff.fileConfig[configName]
&& diff.databaseConfig[configName]
) {
if (syncType === 'import') {
return chalk.red('Delete');
} else if (syncType === 'export') {
return chalk.green('Create');
} else {
return chalk.green('Only in DB');
}
}
};
const handleAction = async (type, skipConfirm) => {
const app = await strapi().load();
const diff = await app.plugin('config-sync').service('main').getFormattedDiff();
// No changes.
if (isEmpty(diff.diff)) {
console.log(`${chalk.cyan('[notice]')} There are no changes to ${type}`);
process.exit(0);
}
// Init table.
const table = initTable('Action');
// Add diff to table.
Object.keys(diff.diff).map((configName) => {
table.push([configName, getConfigState(diff, configName, type)]);
});
// Print table.
console.log(table.toString(), '\n');
// Prompt to confirm.
let answer = {};
if (!skipConfirm) {
answer = await inquirer.prompt([{
type: 'confirm',
name: 'confirm',
message: `Are you sure you want to ${type} the config changes?`,
}]);
console.log('');
}
// Preform the action.
if (skipConfirm || answer.confirm) {
if (type === 'import') {
const onSuccess = (name) => console.log(`${chalk.green('[success]')} Imported ${name}`);
try {
await app.plugin('config-sync').service('main').importAllConfig(null, onSuccess);
} catch (e) {
console.log(`${chalk.red('[error]')} Something went wrong during the import. ${e}`);
}
}
if (type === 'export') {
try {
await app.plugin('config-sync').service('main').exportAllConfig();
console.log(`${chalk.green('[success]')} Exported config`);
} catch (e) {
console.log(`${chalk.red('[error]')} Something went wrong during the export. ${e}`);
}
}
}
process.exit(0);
};
// Initial program setup // Initial program setup
program.storeOptionsAsProperties(false).allowUnknownOption(true); program.storeOptionsAsProperties(false).allowUnknownOption(true);
@ -50,88 +140,44 @@ program
// `$ config-sync import` // `$ config-sync import`
program program
.command('import') .command('import')
.option('-t, --type <type>', 'The type of config') // .option('-t, --type <type>', 'The type of config') // TODO: partial import
.option('-y', 'Skip the confirm prompt')
.description('Import the config') .description('Import the config')
.action(async (args) => { .action(async ({ y, type }) => {
console.log('import', args); return handleAction('import', y, type);
// Init table.
const table = initTable();
// Fill table.
table.push(
['admin-role.author', chalk.yellow('different')],
['core-store.plugin_i18n_default_locale', chalk.green('Only in DB')],
);
// Print table.
console.log(table.toString());
// Prompt to confirm.
const answer = await inquirer.prompt([{
type: 'confirm',
name: 'confirm',
message: 'Are you sure you want to import the config changes?',
}]);
// Make the import.
if (answer.confirm) {
console.log(chalk.magenta('IMPORT!!'));
}
}); });
// `$ config-sync export` // `$ config-sync export`
program program
.command('export') .command('export')
.option('-t, --type <type>', 'The type of config') // .option('-t, --type <type>', 'The type of config') // TODO: partial export
.option('-y', 'Skip the confirm prompt')
.description('Export the config') .description('Export the config')
.action(async (args) => { .action(async ({ y, type }) => {
console.log('export', args); return handleAction('export', y, type);
// Init table.
const table = initTable();
// Fill table.
table.push(
['admin-role.author', chalk.yellow('different')],
['core-store.plugin_i18n_default_locale', chalk.green('Only in DB')],
);
// Print table.
console.log(table.toString());
// Prompt to confirm.
const answer = await inquirer.prompt([{
type: 'confirm',
name: 'confirm',
message: 'Are you sure you want to export the config changes?',
}]);
// Make the export.
if (answer.confirm) {
console.log(chalk.magenta('EXPORT!!'));
}
}); });
// `$ config-sync diff` // `$ config-sync diff`
program program
.command('diff') .command('diff')
.option('-t, --type <type>', 'The type of config') // .option('-t, --type <type>', 'The type of config') // TODO: partial diff
.description('The config diff') .description('The config diff')
.action(async (args) => { .action(async ({ type }) => {
console.log('diff', args); const app = await strapi().load();
const diff = await app.plugin('config-sync').service('main').getFormattedDiff();
// Init table. // Init table.
const table = initTable(); const table = initTable();
// Fill table. // Add diff to table.
table.push( Object.keys(diff.diff).map((configName) => {
['admin-role.author', chalk.yellow('different')], table.push([configName, getConfigState(diff, configName)]);
['core-store.plugin_i18n_default_locale', chalk.green('Only in DB')], });
);
// Print table. // Print table.
console.log(table.toString()); console.log(table.toString());
process.exit(0);
}); });
program.parseAsync(process.argv); program.parseAsync(process.argv);

View File

@ -1,7 +1,6 @@
'use strict'; 'use strict';
const fs = require('fs'); const fs = require('fs');
const difference = require('../utils/getObjectDiff');
/** /**
* Main controllers for config import/export. * Main controllers for config import/export.
@ -64,24 +63,6 @@ module.exports = {
return; return;
} }
const formattedDiff = { return strapi.plugin('config-sync').service('main').getFormattedDiff();
fileConfig: {},
databaseConfig: {},
diff: {},
};
const fileConfig = await strapi.plugin('config-sync').service('main').getAllConfigFromFiles();
const databaseConfig = await strapi.plugin('config-sync').service('main').getAllConfigFromDatabase();
const diff = difference(databaseConfig, fileConfig);
formattedDiff.diff = diff;
Object.keys(diff).map((changedConfigName) => {
formattedDiff.fileConfig[changedConfigName] = fileConfig[changedConfigName];
formattedDiff.databaseConfig[changedConfigName] = databaseConfig[changedConfigName];
});
return formattedDiff;
}, },
}; };

View File

@ -144,9 +144,10 @@ module.exports = () => ({
* Import all config files into the db. * Import all config files into the db.
* *
* @param {string} configType - Type of config to impor. Leave empty to import all config. * @param {string} configType - Type of config to impor. Leave empty to import all config.
* @param {object} onSuccess - Success callback to run on each single successfull import.
* @returns {void} * @returns {void}
*/ */
importAllConfig: async (configType = null) => { importAllConfig: async (configType = null, onSuccess) => {
const fileConfig = await strapi.plugin('config-sync').service('main').getAllConfigFromFiles(); const fileConfig = await strapi.plugin('config-sync').service('main').getAllConfigFromFiles();
const databaseConfig = await strapi.plugin('config-sync').service('main').getAllConfigFromDatabase(); const databaseConfig = await strapi.plugin('config-sync').service('main').getAllConfigFromDatabase();
@ -160,7 +161,14 @@ module.exports = () => ({
return; return;
} }
try {
await strapi.plugin('config-sync').service('main').importSingleConfig(type, name); await strapi.plugin('config-sync').service('main').importSingleConfig(type, name);
if (onSuccess) {
onSuccess(`${type}.${name}`);
}
} catch (e) {
throw new Error(e);
}
})); }));
}, },
@ -207,4 +215,33 @@ module.exports = () => ({
exportSingleConfig: async (configType, configName) => { exportSingleConfig: async (configType, configName) => {
}, },
/**
* Get the formatted diff.
*
* @param {string} configType - Type of config to get the diff of. Leave empty to get the diff of all config.
*
* @returns {object} - the formatted diff.
*/
getFormattedDiff: async (configType = null) => {
const formattedDiff = {
fileConfig: {},
databaseConfig: {},
diff: {},
};
const fileConfig = await strapi.plugin('config-sync').service('main').getAllConfigFromFiles(configType);
const databaseConfig = await strapi.plugin('config-sync').service('main').getAllConfigFromDatabase(configType);
const diff = difference(databaseConfig, fileConfig);
formattedDiff.diff = diff;
Object.keys(diff).map((changedConfigName) => {
formattedDiff.fileConfig[changedConfigName] = fileConfig[changedConfigName];
formattedDiff.databaseConfig[changedConfigName] = databaseConfig[changedConfigName];
});
return formattedDiff;
},
}); });

View File

@ -1,7 +1,5 @@
const { logMessage, sanitizeConfig, dynamicSort } = require('../utils'); const { logMessage, sanitizeConfig, dynamicSort } = require('../utils');
const difference = require('../utils/getObjectDiff'); const difference = require('../utils/getArrayDiff');
const arrayDifference = require('../utils/getArrayDiff');
const ConfigType = class ConfigType { const ConfigType = class ConfigType {
constructor(queryString, configPrefix, uid, jsonFields, relations) { constructor(queryString, configPrefix, uid, jsonFields, relations) {
@ -21,24 +19,9 @@ const ConfigType = class ConfigType {
* @returns {void} * @returns {void}
*/ */
exportAll = async () => { exportAll = async () => {
const formattedDiff = { const formattedDiff = await strapi.plugin('config-sync').service('main').getFormattedDiff(this.configPrefix);
fileConfig: {},
databaseConfig: {},
diff: {},
};
const fileConfig = await strapi.plugin('config-sync').service('main').getAllConfigFromFiles(this.configPrefix); await Promise.all(Object.entries(formattedDiff.diff).map(async ([configName, config]) => {
const databaseConfig = await strapi.plugin('config-sync').service('main').getAllConfigFromDatabase(this.configPrefix);
const diff = difference(databaseConfig, fileConfig);
formattedDiff.diff = diff;
Object.keys(diff).map((changedConfigName) => {
formattedDiff.fileConfig[changedConfigName] = fileConfig[changedConfigName];
formattedDiff.databaseConfig[changedConfigName] = databaseConfig[changedConfigName];
});
await Promise.all(Object.entries(diff).map(async ([configName, config]) => {
// Check if the config should be excluded. // Check if the config should be excluded.
const shouldExclude = strapi.config.get('plugin.config-sync.exclude').includes(`${configName}`); const shouldExclude = strapi.config.get('plugin.config-sync.exclude').includes(`${configName}`);
if (shouldExclude) return; if (shouldExclude) return;
@ -132,29 +115,29 @@ const ConfigType = class ConfigType {
const entity = await queryAPI.update({ where: { [this.uid]: configName }, data: query }); const entity = await queryAPI.update({ where: { [this.uid]: configName }, data: query });
// Delete/create relations. // Delete/create relations.
this.relations.map(async ({ queryString, relationName, parentName, relationSortField }) => { await Promise.all(this.relations.map(async ({ queryString, relationName, parentName, relationSortField }) => {
const relationQueryApi = strapi.query(queryString); const relationQueryApi = strapi.query(queryString);
existingConfig = sanitizeConfig(existingConfig, relationName, relationSortField); existingConfig = sanitizeConfig(existingConfig, relationName, relationSortField);
configContent = sanitizeConfig(configContent, relationName, relationSortField); configContent = sanitizeConfig(configContent, relationName, relationSortField);
const configToAdd = arrayDifference(configContent[relationName], existingConfig[relationName], relationSortField); const configToAdd = difference(configContent[relationName], existingConfig[relationName], relationSortField);
const configToDelete = arrayDifference(existingConfig[relationName], configContent[relationName], relationSortField); const configToDelete = difference(existingConfig[relationName], configContent[relationName], relationSortField);
configToDelete.map(async (config) => { await Promise.all(configToDelete.map(async (config) => {
await relationQueryApi.delete({ await relationQueryApi.delete({
where: { where: {
[relationSortField]: config[relationSortField], [relationSortField]: config[relationSortField],
[parentName]: entity.id, [parentName]: entity.id,
}, },
}); });
}); }));
configToAdd.map(async (config) => { await Promise.all(configToAdd.map(async (config) => {
await relationQueryApi.create({ await relationQueryApi.create({
data: { ...config, [parentName]: entity.id }, data: { ...config, [parentName]: entity.id },
}); });
}); }));
}); }));
} }
} }

6648
yarn.lock

File diff suppressed because it is too large Load Diff