commit
b9e6a37df8
63
README.md
63
README.md
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
A lot of configuration of your Strapi project is stored in the database. Like core_store, user permissions, user roles & webhooks. Things you might want to have the same on all environments. But when you update them locally, you will have to manually update them on all other environments too.
|
A lot of configuration of your Strapi project is stored in the database. Like core_store, user permissions, user roles & webhooks. Things you might want to have the same on all environments. But when you update them locally, you will have to manually update them on all other environments too.
|
||||||
|
|
||||||
That's where this plugin comes in to play. It allows you to export these configs as individual JSON files for each config, and write them somewhere in your project. With the configs written in your filesystem your can keep track of them through version control (git), and easily pull and import them across environments.
|
That's where this plugin comes in to play. It allows you to export these configs as individual JSON files for each config, and write them somewhere in your project. With the configs written in your filesystem you can keep track of them through version control (git), and easily pull and import them across environments.
|
||||||
|
|
||||||
Importing, exporting and keeping track of config changes is done in the admin page of the plugin.
|
Importing, exporting and keeping track of config changes is done in the admin page of the plugin.
|
||||||
|
|
||||||
|
@ -27,44 +27,50 @@ This way your app won't reload when you export the config in development.
|
||||||
|
|
||||||
admin: {
|
admin: {
|
||||||
auth: {
|
auth: {
|
||||||
...
|
// ...
|
||||||
},
|
},
|
||||||
watchIgnoreFiles: [
|
watchIgnoreFiles: [
|
||||||
'**/config-sync/files/**',
|
'**/config-sync/files/**',
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
## Settings
|
## Settings
|
||||||
Some settings for the plugin are able to be modified by creating a file `extensions/config-sync/config/config.json` and overwriting the default settings.
|
The settings of the plugin can be overridden in the `config/plugins.js` file.
|
||||||
|
In the example below you can see how, and also what the default settings are.
|
||||||
|
|
||||||
#### Default settings:
|
##### `config/plugins.js`:
|
||||||
{
|
module.exports = ({ env }) => ({
|
||||||
"destination": "extensions/config-sync/files/",
|
// ...
|
||||||
"minify": false,
|
'config-sync': {
|
||||||
"importOnBootstrap": false,
|
destination: "extensions/config-sync/files/",
|
||||||
"include": [
|
minify: false,
|
||||||
|
importOnBootstrap: false,
|
||||||
|
include: [
|
||||||
"core-store",
|
"core-store",
|
||||||
"role-permissions",
|
"role-permissions"
|
||||||
"webhooks"
|
|
||||||
],
|
],
|
||||||
"exclude": []
|
exclude: [
|
||||||
}
|
"core-store.plugin_users-permissions_grant"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
|
||||||
| Property | Default | Description |
|
| Property | Type | Description |
|
||||||
| -------- | ------- | ----------- |
|
| -------- | ---- | ----------- |
|
||||||
| destination | extensions/config-sync/files/ | The path for reading and writing the config JSON files. |
|
| destination | string | The path for reading and writing the sync files. |
|
||||||
| minify | false | Setting to minify the JSON that's being exported. It defaults to false for better readability in git commits. |
|
| minify | bool | When enabled all the exported JSON files will be minified. |
|
||||||
| importOnBootstrap | false | Allows you to let the config be imported automaticly when strapi is bootstrapping (on `yarn start`). This setting should only be used in production, and should be handled very carefully as it can unintendedly overwrite the changes in your database. PLEASE USE WITH CARE. |
|
| importOnBootstrap | bool | Allows you to let the config be imported automaticly when strapi is bootstrapping (on `strapi start`). This setting should only be used in production, and should be handled very carefully as it can unintendedly overwrite the changes in your database. PLEASE USE WITH CARE. |
|
||||||
| include | ["core-store", "role-permissions", "webhooks"] | Configs you want to include. Allowed values: `core-store`, `role-permissions`, `webhooks`. |
|
| include | array | Configs types you want to include in the syncing process. Allowed values: `core-store`, `role-permissions`, `webhooks`. |
|
||||||
| exclude | [] | You might not want all your database config exported and managed in git. This settings allows you to add an array of config names which should not be tracked by the config-sync plugin. *Currently not working* |
|
| exclude | array | Specify the names of configs you want to exclude from the syncing process. By default the API tokens for users-permissions, which are stored in core_store, are excluded. This setting expects the config names to comply with the naming convention. |
|
||||||
|
|
||||||
## Naming convention
|
## Naming convention
|
||||||
All the config files written in the file destination have the same naming convention. It goes as follows:
|
All the config files written in the file destination have the same naming convention. It goes as follows:
|
||||||
|
|
||||||
[config-type].[config-name].json
|
[config-type].[config-name].json
|
||||||
|
|
||||||
- `config-type` - Corresponds to the value in from the config.include setting.
|
- `config-type` - Corresponds to the value in from the include setting.
|
||||||
- `config-name` - The unique identifier of the config.
|
- `config-name` - The unique identifier of the config.
|
||||||
- For `core-store` config this is the `key` value.
|
- For `core-store` config this is the `key` value.
|
||||||
- For `role-permissions` config this is the `type` value.
|
- For `role-permissions` config this is the `type` value.
|
||||||
|
@ -78,7 +84,14 @@ All the config files written in the file destination have the same naming conven
|
||||||
- Exporting of EE roles & permissions
|
- Exporting of EE roles & permissions
|
||||||
- Add partial import/export functionality
|
- Add partial import/export functionality
|
||||||
- Add CLI commands for importing/exporting
|
- Add CLI commands for importing/exporting
|
||||||
- Track config deletions
|
- ~~Track config deletions~~
|
||||||
|
|
||||||
|
## ⭐️ Show your support
|
||||||
|
|
||||||
|
Give a star if this project helped you.
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
Shout out to [@ScottAgirs](https://github.com/ScottAgirs) for making [strapi-plugin-migrate](https://github.com/ijsto/strapi-plugin-migrate) as it was a big help while making the config-sync plugin.
|
||||||
|
|
||||||
## Resources
|
## Resources
|
||||||
|
|
||||||
|
@ -88,7 +101,3 @@ All the config files written in the file destination have the same naming conven
|
||||||
|
|
||||||
- [NPM package](https://www.npmjs.com/package/strapi-plugin-config-sync)
|
- [NPM package](https://www.npmjs.com/package/strapi-plugin-config-sync)
|
||||||
- [GitHub repository](https://github.com/boazpoolman/strapi-plugin-config-sync)
|
- [GitHub repository](https://github.com/boazpoolman/strapi-plugin-config-sync)
|
||||||
|
|
||||||
## ⭐️ Show your support
|
|
||||||
|
|
||||||
Give a star if this project helped you.
|
|
||||||
|
|
|
@ -23,8 +23,11 @@ const ActionButtons = ({ diff }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ActionButtonsStyling>
|
<ActionButtonsStyling>
|
||||||
<Button disabled={isEmpty(diff.fileConfig)} color="primary" label="Import" onClick={() => openModal('import')} />
|
<Button disabled={isEmpty(diff.diff)} color="primary" label="Import" onClick={() => openModal('import')} />
|
||||||
<Button disabled={isEmpty(diff.fileConfig)} color="primary" label="Export" onClick={() => openModal('export')} />
|
<Button disabled={isEmpty(diff.diff)} color="primary" label="Export" onClick={() => openModal('export')} />
|
||||||
|
{!isEmpty(diff.diff) && (
|
||||||
|
<h4 style={{ display: 'inline' }}>{Object.keys(diff.diff).length} {Object.keys(diff.diff).length === 1 ? "config change" : "config changes"}</h4>
|
||||||
|
)}
|
||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
isOpen={modalIsOpen}
|
isOpen={modalIsOpen}
|
||||||
onClose={closeModal}
|
onClose={closeModal}
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const CustomRow = ({ row }) => {
|
||||||
|
const { config_name, config_type, state, onClick } = row;
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr onClick={() => onClick(config_type, config_name)}>
|
||||||
|
<td>
|
||||||
|
<p>{config_name}</p>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p>{config_type}</p>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p style={stateStyle(state)}>{state}</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const stateStyle = (state) => {
|
||||||
|
let style = {
|
||||||
|
display: 'inline-flex',
|
||||||
|
padding: '0 10px',
|
||||||
|
borderRadius: '12px',
|
||||||
|
height: '24px',
|
||||||
|
alignItems: 'center',
|
||||||
|
fontWeight: '500',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (state === 'Only in DB') {
|
||||||
|
style.backgroundColor = '#cbf2d7';
|
||||||
|
style.color = '#1b522b';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state === 'Only in sync dir') {
|
||||||
|
style.backgroundColor = '#f0cac7';
|
||||||
|
style.color = '#3d302f';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state === 'Different') {
|
||||||
|
style.backgroundColor = '#e8e6b7';
|
||||||
|
style.color = '#4a4934';
|
||||||
|
}
|
||||||
|
|
||||||
|
return style;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export default CustomRow
|
|
@ -3,6 +3,7 @@ import { Table } from '@buffetjs/core';
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
import ConfigDiff from '../ConfigDiff';
|
import ConfigDiff from '../ConfigDiff';
|
||||||
import FirstExport from '../FirstExport';
|
import FirstExport from '../FirstExport';
|
||||||
|
import ConfigListRow from './ConfigListRow';
|
||||||
|
|
||||||
const headers = [
|
const headers = [
|
||||||
{
|
{
|
||||||
|
@ -14,8 +15,8 @@ const headers = [
|
||||||
value: 'config_type',
|
value: 'config_type',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Change',
|
name: 'State',
|
||||||
value: 'change_type',
|
value: 'state',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -26,6 +27,25 @@ const ConfigList = ({ diff, isLoading }) => {
|
||||||
const [configName, setConfigName] = useState('');
|
const [configName, setConfigName] = useState('');
|
||||||
const [rows, setRows] = useState([]);
|
const [rows, setRows] = useState([]);
|
||||||
|
|
||||||
|
const getConfigState = (configName) => {
|
||||||
|
if (
|
||||||
|
diff.fileConfig[configName] &&
|
||||||
|
diff.databaseConfig[configName]
|
||||||
|
) {
|
||||||
|
return 'Different'
|
||||||
|
} else if (
|
||||||
|
diff.fileConfig[configName] &&
|
||||||
|
!diff.databaseConfig[configName]
|
||||||
|
) {
|
||||||
|
return 'Only in sync dir'
|
||||||
|
} else if (
|
||||||
|
!diff.fileConfig[configName] &&
|
||||||
|
diff.databaseConfig[configName]
|
||||||
|
) {
|
||||||
|
return 'Only in DB'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isEmpty(diff.diff)) {
|
if (isEmpty(diff.diff)) {
|
||||||
setRows([]);
|
setRows([]);
|
||||||
|
@ -33,14 +53,20 @@ const ConfigList = ({ diff, isLoading }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
let formattedRows = [];
|
let formattedRows = [];
|
||||||
Object.keys(diff.fileConfig).map((configName) => {
|
Object.keys(diff.diff).map((configName) => {
|
||||||
const type = configName.split('.')[0]; // Grab the first part of the filename.
|
const type = configName.split('.')[0]; // Grab the first part of the filename.
|
||||||
const name = configName.split(/\.(.+)/)[1]; // Grab the rest of the filename minus the file extension.
|
const name = configName.split(/\.(.+)/)[1]; // Grab the rest of the filename minus the file extension.
|
||||||
|
|
||||||
formattedRows.push({
|
formattedRows.push({
|
||||||
config_name: name,
|
config_name: name,
|
||||||
config_type: type,
|
config_type: type,
|
||||||
change_type: ''
|
state: getConfigState(configName),
|
||||||
|
onClick: (config_type, config_name) => {
|
||||||
|
setOriginalConfig(diff.fileConfig[`${config_type}.${config_name}`]);
|
||||||
|
setNewConfig(diff.databaseConfig[`${config_type}.${config_name}`]);
|
||||||
|
setConfigName(`${config_type}.${config_name}`);
|
||||||
|
setOpenModal(true);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -70,12 +96,7 @@ const ConfigList = ({ diff, isLoading }) => {
|
||||||
/>
|
/>
|
||||||
<Table
|
<Table
|
||||||
headers={headers}
|
headers={headers}
|
||||||
onClickRow={(e, { config_type, config_name }) => {
|
customRow={ConfigListRow}
|
||||||
setOriginalConfig(diff.fileConfig[`${config_type}.${config_name}`]);
|
|
||||||
setNewConfig(diff.databaseConfig[`${config_type}.${config_name}`]);
|
|
||||||
setConfigName(`${config_type}.${config_name}`);
|
|
||||||
setOpenModal(true);
|
|
||||||
}}
|
|
||||||
rows={!isLoading ? rows : []}
|
rows={!isLoading ? rows : []}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
tableEmptyText="No config changes. You are up to date!"
|
tableEmptyText="No config changes. You are up to date!"
|
||||||
|
|
|
@ -23,7 +23,6 @@ export default strapi => {
|
||||||
blockerComponent: null,
|
blockerComponent: null,
|
||||||
blockerComponentProps: {},
|
blockerComponentProps: {},
|
||||||
description: pluginDescription,
|
description: pluginDescription,
|
||||||
icon: pluginPkg.strapi.icon,
|
|
||||||
id: pluginId,
|
id: pluginId,
|
||||||
initializer: Initializer,
|
initializer: Initializer,
|
||||||
injectedComponents: [],
|
injectedComponents: [],
|
||||||
|
|
|
@ -4,8 +4,9 @@
|
||||||
"importOnBootstrap": false,
|
"importOnBootstrap": false,
|
||||||
"include": [
|
"include": [
|
||||||
"core-store",
|
"core-store",
|
||||||
"role-permissions",
|
"role-permissions"
|
||||||
"webhooks"
|
|
||||||
],
|
],
|
||||||
"exclude": []
|
"exclude": [
|
||||||
|
"core-store.plugin_users-permissions_grant"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,7 +70,8 @@ module.exports = {
|
||||||
const fileConfig = await strapi.plugins['config-sync'].services.main.getAllConfigFromFiles();
|
const fileConfig = await strapi.plugins['config-sync'].services.main.getAllConfigFromFiles();
|
||||||
const databaseConfig = await strapi.plugins['config-sync'].services.main.getAllConfigFromDatabase();
|
const databaseConfig = await strapi.plugins['config-sync'].services.main.getAllConfigFromDatabase();
|
||||||
|
|
||||||
const diff = difference(fileConfig, databaseConfig);
|
const diff = difference(databaseConfig, fileConfig);
|
||||||
|
|
||||||
formattedDiff.diff = diff;
|
formattedDiff.diff = diff;
|
||||||
|
|
||||||
Object.keys(diff).map((changedConfigName) => {
|
Object.keys(diff).map((changedConfigName) => {
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
{
|
{
|
||||||
"name": "strapi-plugin-config-sync",
|
"name": "strapi-plugin-config-sync",
|
||||||
"version": "0.1.3",
|
"version": "0.1.4",
|
||||||
"description": "Manage your Strapi database configuration as partial json files which can be imported/exported across environments. ",
|
"description": "Manage your Strapi database configuration as partial json files which can be imported/exported across environments. ",
|
||||||
"strapi": {
|
"strapi": {
|
||||||
"name": "config-sync",
|
"name": "config-sync",
|
||||||
"icon": "plug",
|
"icon": "sync",
|
||||||
"description": "Manage your Strapi database configuration as partial json files which can be imported/exported across environments. "
|
"description": "Manage your Strapi database configuration as partial json files which can be imported/exported across environments. "
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
const coreStoreQueryString = 'core_store';
|
const coreStoreQueryString = 'core_store';
|
||||||
const configPrefix = 'core-store'; // Should be the same as the filename.
|
const configPrefix = 'core-store'; // Should be the same as the filename.
|
||||||
|
const difference = require('../utils/getObjectDiff');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Import/Export for core-store configs.
|
* Import/Export for core-store configs.
|
||||||
|
@ -14,11 +15,38 @@ module.exports = {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
exportAll: async () => {
|
exportAll: async () => {
|
||||||
const coreStore = await strapi.query(coreStoreQueryString).find({ _limit: -1 });
|
const formattedDiff = {
|
||||||
|
fileConfig: {},
|
||||||
|
databaseConfig: {},
|
||||||
|
diff: {}
|
||||||
|
};
|
||||||
|
|
||||||
await Promise.all(Object.values(coreStore).map(async ({ id, ...config }) => {
|
const fileConfig = await strapi.plugins['config-sync'].services.main.getAllConfigFromFiles(configPrefix);
|
||||||
config.value = JSON.parse(config.value);
|
const databaseConfig = await strapi.plugins['config-sync'].services.main.getAllConfigFromDatabase(configPrefix);
|
||||||
await strapi.plugins['config-sync'].services.main.writeConfigFile(configPrefix, config.key, config);
|
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.
|
||||||
|
const shouldExclude = strapi.plugins['config-sync'].config.exclude.includes(`${configName}`);
|
||||||
|
if (shouldExclude) return;
|
||||||
|
|
||||||
|
const currentConfig = formattedDiff.databaseConfig[configName];
|
||||||
|
|
||||||
|
if (
|
||||||
|
!currentConfig &&
|
||||||
|
formattedDiff.fileConfig[configName]
|
||||||
|
) {
|
||||||
|
await strapi.plugins['config-sync'].services.main.deleteConfigFile(configName);
|
||||||
|
} else {
|
||||||
|
await strapi.plugins['config-sync'].services.main.writeConfigFile(configPrefix, currentConfig.key, currentConfig);
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -30,11 +58,22 @@ module.exports = {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
importSingle: async (configName, configContent) => {
|
importSingle: async (configName, configContent) => {
|
||||||
const { value, ...fileContent } = configContent;
|
// Check if the config should be excluded.
|
||||||
|
const shouldExclude = strapi.plugins['config-sync'].config.exclude.includes(`${configPrefix}.${configName}`);
|
||||||
|
if (shouldExclude) return;
|
||||||
|
|
||||||
const coreStoreAPI = strapi.query(coreStoreQueryString);
|
const coreStoreAPI = strapi.query(coreStoreQueryString);
|
||||||
|
|
||||||
const configExists = await coreStoreAPI
|
const configExists = await coreStoreAPI
|
||||||
.findOne({ key: configName, environment: fileContent.environment });
|
.findOne({ key: configName });
|
||||||
|
|
||||||
|
if (configExists && configContent === null) {
|
||||||
|
await coreStoreAPI.delete({ key: configName });
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { value, ...fileContent } = configContent;
|
||||||
|
|
||||||
if (!configExists) {
|
if (!configExists) {
|
||||||
await coreStoreAPI.create({ value: JSON.stringify(value), ...fileContent });
|
await coreStoreAPI.create({ value: JSON.stringify(value), ...fileContent });
|
||||||
|
@ -53,6 +92,10 @@ module.exports = {
|
||||||
let configs = {};
|
let configs = {};
|
||||||
|
|
||||||
Object.values(coreStore).map( ({ id, value, key, ...config }) => {
|
Object.values(coreStore).map( ({ id, value, key, ...config }) => {
|
||||||
|
// Check if the config should be excluded.
|
||||||
|
const shouldExclude = strapi.plugins['config-sync'].config.exclude.includes(`${configPrefix}.${key}`);
|
||||||
|
if (shouldExclude) return;
|
||||||
|
|
||||||
configs[`${configPrefix}.${key}`] = { key, value: JSON.parse(value), ...config };
|
configs[`${configPrefix}.${key}`] = { key, value: JSON.parse(value), ...config };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const util = require('util');
|
const util = require('util');
|
||||||
|
const difference = require('../utils/getObjectDiff');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main services for config import/export.
|
* Main services for config import/export.
|
||||||
|
@ -43,6 +44,20 @@ module.exports = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete config file.
|
||||||
|
*
|
||||||
|
* @param {string} configName - The name of the config file.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
deleteConfigFile: async (configName) => {
|
||||||
|
// Check if the config should be excluded.
|
||||||
|
const shouldExclude = strapi.plugins['config-sync'].config.exclude.includes(`${configName}`);
|
||||||
|
if (shouldExclude) return;
|
||||||
|
|
||||||
|
fs.unlinkSync(`${strapi.plugins['config-sync'].config.destination}${configName}.json`);
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read from a config file.
|
* Read from a config file.
|
||||||
*
|
*
|
||||||
|
@ -67,7 +82,11 @@ module.exports = {
|
||||||
*
|
*
|
||||||
* @returns {object} Object with key value pairs of configs.
|
* @returns {object} Object with key value pairs of configs.
|
||||||
*/
|
*/
|
||||||
getAllConfigFromFiles: async () => {
|
getAllConfigFromFiles: async (configType = null) => {
|
||||||
|
if (!fs.existsSync(strapi.plugins['config-sync'].config.destination)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
const configFiles = fs.readdirSync(strapi.plugins['config-sync'].config.destination);
|
const configFiles = fs.readdirSync(strapi.plugins['config-sync'].config.destination);
|
||||||
|
|
||||||
const getConfigs = async () => {
|
const getConfigs = async () => {
|
||||||
|
@ -76,6 +95,15 @@ module.exports = {
|
||||||
await Promise.all(configFiles.map(async (file) => {
|
await Promise.all(configFiles.map(async (file) => {
|
||||||
const type = file.split('.')[0]; // Grab the first part of the filename.
|
const type = file.split('.')[0]; // Grab the first part of the filename.
|
||||||
const name = file.split(/\.(.+)/)[1].split('.').slice(0, -1).join('.'); // Grab the rest of the filename minus the file extension.
|
const name = file.split(/\.(.+)/)[1].split('.').slice(0, -1).join('.'); // Grab the rest of the filename minus the file extension.
|
||||||
|
|
||||||
|
if (
|
||||||
|
configType && configType !== type ||
|
||||||
|
!strapi.plugins['config-sync'].config.include.includes(type) ||
|
||||||
|
strapi.plugins['config-sync'].config.exclude.includes(`${type}.${name}`)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const fileContents = await strapi.plugins['config-sync'].services.main.readConfigFile(type, name);
|
const fileContents = await strapi.plugins['config-sync'].services.main.readConfigFile(type, name);
|
||||||
fileConfigs[`${type}.${name}`] = fileContents;
|
fileConfigs[`${type}.${name}`] = fileContents;
|
||||||
}));
|
}));
|
||||||
|
@ -116,11 +144,14 @@ module.exports = {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
importAllConfig: async (configType = null) => {
|
importAllConfig: async (configType = null) => {
|
||||||
const configFiles = fs.readdirSync(strapi.plugins['config-sync'].config.destination);
|
const fileConfig = await strapi.plugins['config-sync'].services.main.getAllConfigFromFiles();
|
||||||
|
const databaseConfig = await strapi.plugins['config-sync'].services.main.getAllConfigFromDatabase();
|
||||||
|
|
||||||
configFiles.map((file) => {
|
const diff = difference(databaseConfig, fileConfig);
|
||||||
|
|
||||||
|
Object.keys(diff).map((file) => {
|
||||||
const type = file.split('.')[0]; // Grab the first part of the filename.
|
const type = file.split('.')[0]; // Grab the first part of the filename.
|
||||||
const name = file.split(/\.(.+)/)[1].split('.').slice(0, -1).join('.'); // Grab the rest of the filename minus the file extension.
|
const name = file.split(/\.(.+)/)[1]; // Grab the rest of the filename minus the file extension.
|
||||||
|
|
||||||
if (configType && configType !== type) {
|
if (configType && configType !== type) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const { sanitizeEntity } = require('strapi-utils');
|
const { sanitizeEntity } = require('strapi-utils');
|
||||||
|
const difference = require('../utils/getObjectDiff');
|
||||||
|
|
||||||
const configPrefix = 'role-permissions'; // Should be the same as the filename.
|
const configPrefix = 'role-permissions'; // Should be the same as the filename.
|
||||||
|
|
||||||
|
@ -15,26 +16,39 @@ module.exports = {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
exportAll: async () => {
|
exportAll: async () => {
|
||||||
const service =
|
const formattedDiff = {
|
||||||
strapi.plugins['users-permissions'].services.userspermissions;
|
fileConfig: {},
|
||||||
|
databaseConfig: {},
|
||||||
|
diff: {}
|
||||||
|
};
|
||||||
|
|
||||||
const [roles, plugins] = await Promise.all([
|
const fileConfig = await strapi.plugins['config-sync'].services.main.getAllConfigFromFiles(configPrefix);
|
||||||
service.getRoles(),
|
const databaseConfig = await strapi.plugins['config-sync'].services.main.getAllConfigFromDatabase(configPrefix);
|
||||||
service.getPlugins(),
|
const diff = difference(databaseConfig, fileConfig);
|
||||||
]);
|
|
||||||
|
|
||||||
const rolesWithPermissions = await Promise.all(
|
formattedDiff.diff = diff;
|
||||||
roles.map(async role => service.getRole(role.id, plugins))
|
|
||||||
);
|
|
||||||
|
|
||||||
const sanitizedRolesArray = rolesWithPermissions.map(role =>
|
Object.keys(diff).map((changedConfigName) => {
|
||||||
sanitizeEntity(role, {
|
formattedDiff.fileConfig[changedConfigName] = fileConfig[changedConfigName];
|
||||||
model: strapi.plugins['users-permissions'].models.role,
|
formattedDiff.databaseConfig[changedConfigName] = databaseConfig[changedConfigName];
|
||||||
})
|
})
|
||||||
);
|
|
||||||
|
|
||||||
await Promise.all(sanitizedRolesArray.map(async (config) => {
|
await Promise.all(Object.entries(diff).map(async ([configName, config]) => {
|
||||||
await strapi.plugins['config-sync'].services.main.writeConfigFile(configPrefix, config.type, config);
|
// Check if the config should be excluded.
|
||||||
|
const shouldExclude = strapi.plugins['config-sync'].config.exclude.includes(`${configName}`);
|
||||||
|
if (shouldExclude) return;
|
||||||
|
|
||||||
|
const currentConfig = formattedDiff.databaseConfig[configName];
|
||||||
|
|
||||||
|
if (
|
||||||
|
!currentConfig &&
|
||||||
|
formattedDiff.fileConfig[configName]
|
||||||
|
) {
|
||||||
|
await strapi.plugins['config-sync'].services.main.deleteConfigFile(configName);
|
||||||
|
} else {
|
||||||
|
await strapi.plugins['config-sync'].services.main.writeConfigFile(configPrefix, currentConfig.type, currentConfig);
|
||||||
|
}
|
||||||
|
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -46,6 +60,10 @@ module.exports = {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
importSingle: async (configName, configContent) => {
|
importSingle: async (configName, configContent) => {
|
||||||
|
// Check if the config should be excluded.
|
||||||
|
const shouldExclude = strapi.plugins['config-sync'].config.exclude.includes(`${configPrefix}.${configName}`);
|
||||||
|
if (shouldExclude) return;
|
||||||
|
|
||||||
const service =
|
const service =
|
||||||
strapi.plugins['users-permissions'].services.userspermissions;
|
strapi.plugins['users-permissions'].services.userspermissions;
|
||||||
|
|
||||||
|
@ -53,6 +71,15 @@ module.exports = {
|
||||||
.query('role', 'users-permissions')
|
.query('role', 'users-permissions')
|
||||||
.findOne({ type: configName });
|
.findOne({ type: configName });
|
||||||
|
|
||||||
|
if (role && configContent === null) {
|
||||||
|
const publicRole = await strapi.query('role', 'users-permissions').findOne({ type: 'public' });
|
||||||
|
const publicRoleID = publicRole.id;
|
||||||
|
|
||||||
|
await service.deleteRole(role.id, publicRoleID);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const users = role ? role.users : [];
|
const users = role ? role.users : [];
|
||||||
configContent.users = users;
|
configContent.users = users;
|
||||||
|
|
||||||
|
@ -89,7 +116,11 @@ module.exports = {
|
||||||
|
|
||||||
let configs = {};
|
let configs = {};
|
||||||
|
|
||||||
Object.values(sanitizedRolesArray).map((config) => {
|
Object.values(sanitizedRolesArray).map(({ id, ...config }) => {
|
||||||
|
// Check if the config should be excluded.
|
||||||
|
const shouldExclude = strapi.plugins['config-sync'].config.exclude.includes(`${configPrefix}.${config.type}`);
|
||||||
|
if (shouldExclude) return;
|
||||||
|
|
||||||
configs[`${configPrefix}.${config.type}`] = config;
|
configs[`${configPrefix}.${config.type}`] = config;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
const webhookQueryString = 'strapi_webhooks';
|
const webhookQueryString = 'strapi_webhooks';
|
||||||
const configPrefix = 'webhooks'; // Should be the same as the filename.
|
const configPrefix = 'webhooks'; // Should be the same as the filename.
|
||||||
|
const difference = require('../utils/getObjectDiff');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
/**
|
/**
|
||||||
|
@ -14,10 +15,38 @@ module.exports = {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
exportAll: async () => {
|
exportAll: async () => {
|
||||||
const webhooks = await strapi.query(webhookQueryString).find({ _limit: -1 });
|
const formattedDiff = {
|
||||||
|
fileConfig: {},
|
||||||
|
databaseConfig: {},
|
||||||
|
diff: {}
|
||||||
|
};
|
||||||
|
|
||||||
await Promise.all(Object.values(webhooks).map(async (config) => {
|
const fileConfig = await strapi.plugins['config-sync'].services.main.getAllConfigFromFiles(configPrefix);
|
||||||
await strapi.plugins['config-sync'].services.main.writeConfigFile(configPrefix, config.id, config);
|
const databaseConfig = await strapi.plugins['config-sync'].services.main.getAllConfigFromDatabase(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.
|
||||||
|
const shouldExclude = strapi.plugins['config-sync'].config.exclude.includes(`${configName}`);
|
||||||
|
if (shouldExclude) return;
|
||||||
|
|
||||||
|
const currentConfig = formattedDiff.databaseConfig[configName];
|
||||||
|
|
||||||
|
if (
|
||||||
|
!currentConfig &&
|
||||||
|
formattedDiff.fileConfig[configName]
|
||||||
|
) {
|
||||||
|
await strapi.plugins['config-sync'].services.main.deleteConfigFile(configName);
|
||||||
|
} else {
|
||||||
|
await strapi.plugins['config-sync'].services.main.writeConfigFile(configPrefix, currentConfig.id, currentConfig);
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -29,6 +58,10 @@ module.exports = {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
importSingle: async (configName, configContent) => {
|
importSingle: async (configName, configContent) => {
|
||||||
|
// Check if the config should be excluded.
|
||||||
|
const shouldExclude = strapi.plugins['config-sync'].config.exclude.includes(`${configPrefix}.${configName}`);
|
||||||
|
if (shouldExclude) return;
|
||||||
|
|
||||||
const webhookAPI = strapi.query(webhookQueryString);
|
const webhookAPI = strapi.query(webhookQueryString);
|
||||||
|
|
||||||
const configExists = await webhookAPI.findOne({ id: configName });
|
const configExists = await webhookAPI.findOne({ id: configName });
|
||||||
|
@ -36,6 +69,9 @@ module.exports = {
|
||||||
if (!configExists) {
|
if (!configExists) {
|
||||||
await webhookAPI.create(configContent);
|
await webhookAPI.create(configContent);
|
||||||
} else {
|
} else {
|
||||||
|
if (configContent === null) {
|
||||||
|
await webhookAPI.delete({ id: configName });
|
||||||
|
}
|
||||||
await webhookAPI.update({ id: configName }, configContent);
|
await webhookAPI.update({ id: configName }, configContent);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -50,6 +86,10 @@ module.exports = {
|
||||||
let configs = {};
|
let configs = {};
|
||||||
|
|
||||||
Object.values(webhooks).map( (config) => {
|
Object.values(webhooks).map( (config) => {
|
||||||
|
// Check if the config should be excluded.
|
||||||
|
const shouldExclude = strapi.plugins['config-sync'].config.exclude.includes(`${configPrefix}.${config.id}`);
|
||||||
|
if (shouldExclude) return;
|
||||||
|
|
||||||
configs[`${configPrefix}.${config.id}`] = config;
|
configs[`${configPrefix}.${config.id}`] = config;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -10,13 +10,23 @@ const { transform, isEqual, isArray, isObject } = require('lodash');
|
||||||
const difference = (origObj, newObj) => {
|
const difference = (origObj, newObj) => {
|
||||||
function changes(newObj, origObj) {
|
function changes(newObj, origObj) {
|
||||||
let arrayIndexCounter = 0
|
let arrayIndexCounter = 0
|
||||||
return transform(newObj, function (result, value, key) {
|
|
||||||
|
const newObjChange = transform(newObj, function (result, value, key) {
|
||||||
if (!isEqual(value, origObj[key])) {
|
if (!isEqual(value, origObj[key])) {
|
||||||
let resultKey = isArray(origObj) ? arrayIndexCounter++ : key
|
let resultKey = isArray(origObj) ? arrayIndexCounter++ : key
|
||||||
result[resultKey] = (isObject(value) && isObject(origObj[key])) ? changes(value, origObj[key]) : value
|
result[resultKey] = (isObject(value) && isObject(origObj[key])) ? changes(value, origObj[key]) : value
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
const origObjChange = transform(origObj, function (result, value, key) {
|
||||||
|
if (!isEqual(value, newObj[key])) {
|
||||||
|
let resultKey = isArray(newObj) ? arrayIndexCounter++ : key
|
||||||
|
result[resultKey] = (isObject(value) && isObject(newObj[key])) ? changes(value, newObj[key]) : value
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return Object.assign(newObjChange, origObjChange);
|
||||||
|
}
|
||||||
|
|
||||||
return changes(newObj, origObj)
|
return changes(newObj, origObj)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue