2021-11-02 19:03:58 +01:00
|
|
|
#!/usr/bin/env node
|
|
|
|
|
2022-03-16 22:10:24 +01:00
|
|
|
const fs = require('fs');
|
2021-11-02 19:03:58 +01:00
|
|
|
const { Command } = require('commander');
|
|
|
|
const Table = require('cli-table');
|
|
|
|
const chalk = require('chalk');
|
|
|
|
const inquirer = require('inquirer');
|
2021-11-10 16:22:52 +01:00
|
|
|
const { isEmpty } = require('lodash');
|
2022-02-12 23:03:47 +01:00
|
|
|
const strapi = require('@strapi/strapi'); // eslint-disable-line
|
2022-04-24 14:07:39 +02:00
|
|
|
const gitDiff = require('git-diff');
|
2021-11-02 19:03:58 +01:00
|
|
|
|
2021-12-28 23:46:48 +01:00
|
|
|
const warnings = require('./warnings');
|
2021-11-02 19:03:58 +01:00
|
|
|
const packageJSON = require('../package.json');
|
|
|
|
|
|
|
|
const program = new Command();
|
|
|
|
|
2022-06-08 22:43:55 +02:00
|
|
|
const getStrapiApp = async () => {
|
2022-06-14 18:38:05 +02:00
|
|
|
try {
|
|
|
|
const tsUtils = require('@strapi/typescript-utils'); // eslint-disable-line
|
|
|
|
|
|
|
|
const appDir = process.cwd();
|
|
|
|
const isTSProject = await tsUtils.isUsingTypeScript(appDir);
|
|
|
|
const outDir = await tsUtils.resolveOutDir(appDir);
|
2022-06-08 22:43:55 +02:00
|
|
|
|
2022-06-14 18:38:05 +02:00
|
|
|
if (isTSProject) {
|
|
|
|
await tsUtils.compile(appDir, {
|
|
|
|
watch: false,
|
|
|
|
configOptions: { options: { incremental: true } },
|
|
|
|
});
|
|
|
|
}
|
2022-06-08 22:43:55 +02:00
|
|
|
|
2022-06-14 18:38:05 +02:00
|
|
|
const distDir = isTSProject ? outDir : appDir;
|
2022-06-08 22:43:55 +02:00
|
|
|
|
2022-06-14 18:38:05 +02:00
|
|
|
const app = await strapi({ appDir, distDir }).load();
|
|
|
|
|
|
|
|
return app;
|
|
|
|
} catch (e) {
|
|
|
|
// Fallback for pre Strapi 4.2.
|
|
|
|
const app = await strapi().load();
|
|
|
|
return app;
|
|
|
|
}
|
2022-06-08 22:43:55 +02:00
|
|
|
};
|
|
|
|
|
2021-11-10 16:22:52 +01:00
|
|
|
const initTable = (head) => {
|
2021-11-02 19:03:58 +01:00
|
|
|
return new Table({
|
2021-11-10 16:22:52 +01:00
|
|
|
head: [chalk.green('Name'), chalk.green(head || 'State')],
|
2021-11-10 16:43:33 +01:00
|
|
|
colWidths: [65, 20],
|
2021-11-02 19:03:58 +01:00
|
|
|
chars: { top: '═',
|
|
|
|
'top-mid': '╤',
|
|
|
|
'top-left': '╔',
|
|
|
|
'top-right': '╗',
|
|
|
|
bottom: '═',
|
|
|
|
'bottom-mid': '╧',
|
|
|
|
'bottom-left': '╚',
|
|
|
|
'bottom-right': '╝',
|
|
|
|
left: '║',
|
|
|
|
'left-mid': '╟',
|
|
|
|
mid: '─',
|
|
|
|
'mid-mid': '┼',
|
|
|
|
right: '║',
|
|
|
|
'right-mid': '╢',
|
|
|
|
middle: '│',
|
|
|
|
},
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2021-11-10 16:22:52 +01:00
|
|
|
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');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-11-18 22:45:20 +01:00
|
|
|
const handleAction = async (syncType, skipConfirm, configType, partials) => {
|
2022-06-08 22:43:55 +02:00
|
|
|
const app = await getStrapiApp();
|
2022-03-16 22:10:24 +01:00
|
|
|
const hasSyncDir = fs.existsSync(app.config.get('plugin.config-sync.syncDir'));
|
|
|
|
|
|
|
|
// No import with empty sync dir.
|
|
|
|
if (!hasSyncDir && syncType === 'import') {
|
|
|
|
console.log(`${chalk.yellow.bold('[warning]')} You can't import an empty sync directory. Please export before continuing.`);
|
|
|
|
process.exit(0);
|
|
|
|
}
|
|
|
|
|
2021-11-10 16:22:52 +01:00
|
|
|
const diff = await app.plugin('config-sync').service('main').getFormattedDiff();
|
|
|
|
|
|
|
|
// No changes.
|
|
|
|
if (isEmpty(diff.diff)) {
|
2021-12-28 23:50:25 +01:00
|
|
|
console.log(`${chalk.cyan.bold('[notice]')} There are no changes to ${syncType}.`);
|
2021-11-10 16:22:52 +01:00
|
|
|
process.exit(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Init table.
|
|
|
|
const table = initTable('Action');
|
2021-11-18 22:45:20 +01:00
|
|
|
const configNames = partials && partials.split(',');
|
|
|
|
const partialDiff = {};
|
|
|
|
|
|
|
|
// Fill partialDiff with arguments.
|
|
|
|
if (configNames) {
|
|
|
|
configNames.map((name) => {
|
|
|
|
if (diff.diff[name]) partialDiff[name] = diff.diff[name];
|
|
|
|
});
|
|
|
|
}
|
|
|
|
if (configType) {
|
|
|
|
Object.keys(diff.diff).map((name) => {
|
|
|
|
if (configType === name.split('.')[0]) {
|
|
|
|
partialDiff[name] = diff.diff[name];
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// No changes for partial diff.
|
|
|
|
if ((partials || configType) && isEmpty(partialDiff)) {
|
2021-12-28 23:50:25 +01:00
|
|
|
console.log(`${chalk.cyan.bold('[notice]')} There are no changes for the specified config.`);
|
2021-11-18 22:45:20 +01:00
|
|
|
process.exit(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
const finalDiff = (partials || configType) && partialDiff ? partialDiff : diff.diff;
|
2021-11-10 16:22:52 +01:00
|
|
|
|
|
|
|
// Add diff to table.
|
2021-11-18 22:45:20 +01:00
|
|
|
Object.keys(finalDiff).map((configName) => {
|
|
|
|
table.push([configName, getConfigState(diff, configName, syncType)]);
|
2021-11-10 16:22:52 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
// Print table.
|
2022-03-16 22:10:24 +01:00
|
|
|
if (hasSyncDir) {
|
|
|
|
console.log(table.toString(), '\n');
|
|
|
|
}
|
2021-11-10 16:22:52 +01:00
|
|
|
|
|
|
|
// Prompt to confirm.
|
|
|
|
let answer = {};
|
|
|
|
if (!skipConfirm) {
|
|
|
|
answer = await inquirer.prompt([{
|
|
|
|
type: 'confirm',
|
|
|
|
name: 'confirm',
|
2021-11-18 22:45:20 +01:00
|
|
|
message: `Are you sure you want to ${syncType} the config changes?`,
|
2021-11-10 16:22:52 +01:00
|
|
|
}]);
|
|
|
|
console.log('');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Preform the action.
|
|
|
|
if (skipConfirm || answer.confirm) {
|
2021-11-18 22:45:20 +01:00
|
|
|
if (syncType === 'import') {
|
2021-12-29 00:05:01 +01:00
|
|
|
const onSuccess = (name) => console.log(`${chalk.cyan.bold('[notice]')} Imported ${name}`);
|
2021-11-10 16:22:52 +01:00
|
|
|
try {
|
2021-11-18 22:45:20 +01:00
|
|
|
await Promise.all(Object.keys(finalDiff).map(async (name) => {
|
2021-12-28 23:46:48 +01:00
|
|
|
let warning;
|
|
|
|
if (
|
|
|
|
getConfigState(diff, name, syncType) === chalk.red('Delete')
|
|
|
|
&& warnings.delete[name]
|
|
|
|
) warning = warnings.delete[name];
|
|
|
|
|
2021-11-18 22:45:20 +01:00
|
|
|
await app.plugin('config-sync').service('main').importSingleConfig(name, onSuccess);
|
2021-12-28 23:46:48 +01:00
|
|
|
if (warning) console.log(`${chalk.yellow.bold('[warning]')} ${warning}`);
|
2021-11-18 22:45:20 +01:00
|
|
|
}));
|
2021-12-29 00:02:38 +01:00
|
|
|
console.log(`${chalk.green.bold('[success]')} Finished import`);
|
2021-11-10 16:22:52 +01:00
|
|
|
} catch (e) {
|
2022-08-15 13:22:31 +02:00
|
|
|
console.log(`${chalk.red.bold('[error]')} ${e}`);
|
2021-11-10 16:22:52 +01:00
|
|
|
}
|
|
|
|
}
|
2021-11-18 22:45:20 +01:00
|
|
|
if (syncType === 'export') {
|
2021-12-29 00:05:01 +01:00
|
|
|
const onSuccess = (name) => console.log(`${chalk.cyan.bold('[notice]')} Exported ${name}`);
|
2021-11-20 14:28:17 +01:00
|
|
|
|
2021-11-10 16:22:52 +01:00
|
|
|
try {
|
2021-11-20 14:28:17 +01:00
|
|
|
await Promise.all(Object.keys(finalDiff).map(async (name) => {
|
|
|
|
await app.plugin('config-sync').service('main').exportSingleConfig(name, onSuccess);
|
|
|
|
}));
|
2021-12-29 00:02:38 +01:00
|
|
|
console.log(`${chalk.green.bold('[success]')} Finished export`);
|
2021-11-10 16:22:52 +01:00
|
|
|
} catch (e) {
|
2022-08-15 13:22:31 +02:00
|
|
|
console.log(`${chalk.red.bold('[error]')} ${e}`);
|
2021-11-10 16:22:52 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
process.exit(0);
|
|
|
|
};
|
|
|
|
|
2021-11-02 19:03:58 +01:00
|
|
|
// Initial program setup
|
|
|
|
program.storeOptionsAsProperties(false).allowUnknownOption(true);
|
|
|
|
|
|
|
|
program.helpOption('-h, --help', 'Display help for command');
|
|
|
|
program.addHelpCommand('help [command]', 'Display help for command');
|
|
|
|
|
|
|
|
// `$ config-sync version` (--version synonym)
|
|
|
|
program.version(packageJSON.version, '-v, --version', 'Output the version number');
|
|
|
|
program
|
|
|
|
.command('version')
|
|
|
|
.description('Output your version of the config-sync plugin')
|
|
|
|
.action(() => {
|
|
|
|
process.stdout.write(`${packageJSON.version}\n`);
|
|
|
|
process.exit(0);
|
|
|
|
});
|
|
|
|
|
|
|
|
// `$ config-sync import`
|
|
|
|
program
|
|
|
|
.command('import')
|
2021-11-10 16:48:17 +01:00
|
|
|
.alias('i')
|
2021-11-20 14:28:17 +01:00
|
|
|
.option('-t, --type <type>', 'The type of config')
|
|
|
|
.option('-p, --partial <partials>', 'A comma separated string of configs')
|
2021-12-28 22:10:04 +01:00
|
|
|
.option('-y, --yes', 'Skip the confirm prompt')
|
2021-11-02 19:03:58 +01:00
|
|
|
.description('Import the config')
|
2021-12-28 22:10:04 +01:00
|
|
|
.action(async ({ yes, type, partial }) => {
|
|
|
|
return handleAction('import', yes, type, partial);
|
2021-11-02 19:03:58 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
// `$ config-sync export`
|
|
|
|
program
|
|
|
|
.command('export')
|
2021-11-10 16:48:17 +01:00
|
|
|
.alias('e')
|
2021-11-20 14:28:17 +01:00
|
|
|
.option('-t, --type <type>', 'The type of config')
|
|
|
|
.option('-p, --partial <partials>', 'A comma separated string of configs')
|
2021-12-28 22:10:04 +01:00
|
|
|
.option('-y, --yes', 'Skip the confirm prompt')
|
2021-11-02 19:03:58 +01:00
|
|
|
.description('Export the config')
|
2021-12-28 22:10:04 +01:00
|
|
|
.action(async ({ yes, type, partial }) => {
|
|
|
|
return handleAction('export', yes, type, partial);
|
2021-11-10 16:22:52 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
// `$ config-sync diff`
|
|
|
|
program
|
|
|
|
.command('diff')
|
2021-11-10 16:48:17 +01:00
|
|
|
.alias('d')
|
2021-11-10 16:22:52 +01:00
|
|
|
.description('The config diff')
|
2022-04-24 14:07:39 +02:00
|
|
|
.action(async (options, { args }) => {
|
|
|
|
const single = args[0];
|
2022-06-08 22:43:55 +02:00
|
|
|
const app = await getStrapiApp();
|
2021-11-10 16:22:52 +01:00
|
|
|
const diff = await app.plugin('config-sync').service('main').getFormattedDiff();
|
2021-11-02 19:03:58 +01:00
|
|
|
|
2021-11-10 16:36:30 +01:00
|
|
|
// No changes.
|
|
|
|
if (isEmpty(diff.diff)) {
|
2021-12-28 23:50:25 +01:00
|
|
|
console.log(`${chalk.cyan.bold('[notice]')} No differences between DB and sync directory.`);
|
2021-11-10 16:36:30 +01:00
|
|
|
process.exit(0);
|
|
|
|
}
|
|
|
|
|
2022-04-24 14:07:39 +02:00
|
|
|
// Single config diff.
|
|
|
|
if (single) {
|
|
|
|
// No changes.
|
|
|
|
if (!diff.fileConfig[single] && !diff.databaseConfig[single]) {
|
|
|
|
console.log(`${chalk.cyan.bold('[notice]')} No differences between DB and sync directory for ${single}.`);
|
|
|
|
process.exit(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Git diff.
|
|
|
|
console.log(gitDiff(
|
|
|
|
JSON.stringify(diff.fileConfig[single], null, 2),
|
|
|
|
JSON.stringify(diff.databaseConfig[single], null, 2),
|
|
|
|
{ color: true },
|
|
|
|
));
|
|
|
|
|
|
|
|
process.exit(0);
|
|
|
|
}
|
|
|
|
|
2021-11-02 19:03:58 +01:00
|
|
|
// Init table.
|
|
|
|
const table = initTable();
|
|
|
|
|
2021-11-10 16:22:52 +01:00
|
|
|
// Add diff to table.
|
|
|
|
Object.keys(diff.diff).map((configName) => {
|
|
|
|
table.push([configName, getConfigState(diff, configName)]);
|
|
|
|
});
|
2021-11-02 19:03:58 +01:00
|
|
|
|
|
|
|
// Print table.
|
|
|
|
console.log(table.toString());
|
|
|
|
|
2021-11-10 16:22:52 +01:00
|
|
|
process.exit(0);
|
2021-11-02 19:03:58 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
program.parseAsync(process.argv);
|