feat: CLI 🎉

pull/25/head
Boaz Poolman 2021-11-10 16:22:52 +01:00
parent dea1963199
commit cd8a71f584
8 changed files with 438 additions and 820 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,
"kind": "plugin"
},
"main": "server/cli.js",
"bin": {
"config-sync": "./bin/config-sync.js"
"config-sync": "./bin/config-sync"
},
"scripts": {
"eslint": "eslint --max-warnings=0 './**/*.{js,jsx}'",
@ -48,10 +47,10 @@
],
"devDependencies": {
"@fortawesome/react-fontawesome": "^0.1.16",
"@strapi/design-system": "0.0.1-alpha.51",
"@strapi/helper-plugin": "4.0.0-beta.6",
"@strapi/icons": "0.0.1-alpha.51",
"@strapi/utils": "4.0.0-beta.6",
"@strapi/design-system": "0.0.1-alpha.64",
"@strapi/helper-plugin": "4.0.0-beta.12",
"@strapi/icons": "0.0.1-alpha.64",
"@strapi/utils": "4.0.0-beta.12",
"babel-eslint": "9.0.0",
"codecov": "^3.8.3",
"eslint": "^5.16.0",

View File

@ -4,14 +4,17 @@ const { Command } = require('commander');
const Table = require('cli-table');
const chalk = require('chalk');
const inquirer = require('inquirer');
const { isEmpty } = require('lodash');
const strapi = require('@strapi/strapi');
const packageJSON = require('../package.json');
const program = new Command();
const initTable = () => {
const initTable = (head) => {
return new Table({
head: [chalk.green('Name'), chalk.green('State')],
head: [chalk.green('Name'), chalk.green(head || 'State')],
colWidths: [50, 15],
chars: { top: '═',
'top-mid': '╤',
'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
program.storeOptionsAsProperties(false).allowUnknownOption(true);
@ -50,88 +140,44 @@ program
// `$ config-sync import`
program
.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')
.action(async (args) => {
console.log('import', args);
// 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!!'));
}
.action(async ({ y, type }) => {
return handleAction('import', y, type);
});
// `$ config-sync export`
program
.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')
.action(async (args) => {
console.log('export', args);
// 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!!'));
}
.action(async ({ y, type }) => {
return handleAction('export', y, type);
});
// `$ config-sync diff`
program
.command('diff')
.option('-t, --type <type>', 'The type of config')
.description('The config diff')
.action(async (args) => {
console.log('diff', args);
.command('diff')
// .option('-t, --type <type>', 'The type of config') // TODO: partial diff
.description('The config diff')
.action(async ({ type }) => {
const app = await strapi().load();
const diff = await app.plugin('config-sync').service('main').getFormattedDiff();
// Init table.
const table = initTable();
// 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')],
);
// Add diff to table.
Object.keys(diff.diff).map((configName) => {
table.push([configName, getConfigState(diff, configName)]);
});
// Print table.
console.log(table.toString());
});
// Print table.
console.log(table.toString());
process.exit(0);
});
program.parseAsync(process.argv);

View File

@ -64,24 +64,6 @@ module.exports = {
return;
}
const formattedDiff = {
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;
return strapi.plugin('config-sync').service('main').getFormattedDiff();
},
};

View File

@ -144,9 +144,10 @@ module.exports = () => ({
* Import all config files into the db.
*
* @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}
*/
importAllConfig: async (configType = null) => {
importAllConfig: async (configType = null, onSuccess) => {
const fileConfig = await strapi.plugin('config-sync').service('main').getAllConfigFromFiles();
const databaseConfig = await strapi.plugin('config-sync').service('main').getAllConfigFromDatabase();
@ -160,7 +161,17 @@ module.exports = () => ({
return;
}
await strapi.plugin('config-sync').service('main').importSingleConfig(type, name);
try {
await strapi.plugin('config-sync').service('main').importSingleConfig(type, name);
if (onSuccess) {
onSuccess(`${type}.${name}`);
}
} catch (e) {
console.log(e);
// if (onSuccess) {
// onSuccess(name);
// }
}
}));
},
@ -207,4 +218,33 @@ module.exports = () => ({
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

@ -21,24 +21,9 @@ const ConfigType = class ConfigType {
* @returns {void}
*/
exportAll = async () => {
const formattedDiff = {
fileConfig: {},
databaseConfig: {},
diff: {},
};
const formattedDiff = await strapi.plugin('config-sync').service('main').getFormattedDiff(this.configPrefix);
const fileConfig = await strapi.plugin('config-sync').service('main').getAllConfigFromFiles(this.configPrefix);
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]) => {
await Promise.all(Object.entries(formattedDiff.diff).map(async ([configName, config]) => {
// Check if the config should be excluded.
const shouldExclude = strapi.config.get('plugin.config-sync.exclude').includes(`${configName}`);
if (shouldExclude) return;
@ -132,7 +117,7 @@ const ConfigType = class ConfigType {
const entity = await queryAPI.update({ where: { [this.uid]: configName }, data: query });
// 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);
existingConfig = sanitizeConfig(existingConfig, relationName, relationSortField);
configContent = sanitizeConfig(configContent, relationName, relationSortField);
@ -140,21 +125,21 @@ const ConfigType = class ConfigType {
const configToAdd = arrayDifference(configContent[relationName], existingConfig[relationName], relationSortField);
const configToDelete = arrayDifference(existingConfig[relationName], configContent[relationName], relationSortField);
configToDelete.map(async (config) => {
await Promise.all(configToDelete.map(async (config) => {
await relationQueryApi.delete({
where: {
[relationSortField]: config[relationSortField],
[parentName]: entity.id,
},
});
});
}));
configToAdd.map(async (config) => {
await Promise.all(configToAdd.map(async (config) => {
await relationQueryApi.create({
data: { ...config, [parentName]: entity.id },
});
});
});
}));
}));
}
}

952
yarn.lock

File diff suppressed because it is too large Load Diff