Merge branch 'master' of github.com:boazpoolman/strapi-plugin-config-sync
commit
d735151d9b
|
@ -26,6 +26,14 @@
|
|||
"strapi": true
|
||||
},
|
||||
"rules": {
|
||||
"import/no-unresolved": [2, {
|
||||
"ignore": [
|
||||
"@strapi/strapi/admin",
|
||||
"@strapi/icons/symbols",
|
||||
"@strapi/admin/strapi-admin"
|
||||
]
|
||||
}],
|
||||
|
||||
"template-curly-spacing" : "off",
|
||||
|
||||
"indent" : "off",
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 325 KiB After Width: | Height: | Size: 164 KiB |
|
@ -12,7 +12,7 @@ jobs:
|
|||
uses: actions/setup-node@v3
|
||||
with:
|
||||
always-auth: true
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
cache: 'yarn'
|
||||
registry-url: 'https://registry.npmjs.org/'
|
||||
- name: Install dependencies
|
||||
|
@ -31,3 +31,4 @@ jobs:
|
|||
with:
|
||||
commit_message: 'chore: Bump version to ${{ steps.get_version.outputs.VERSION }}'
|
||||
file_pattern: 'package.json'
|
||||
branch: master
|
||||
|
|
|
@ -8,6 +8,7 @@ on:
|
|||
branches:
|
||||
- master
|
||||
- develop
|
||||
- beta
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
|
@ -15,7 +16,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node: [14, 16, 18]
|
||||
node: [18, 20]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
|
@ -32,7 +33,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node: [14, 16, 18]
|
||||
node: [18, 20]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
|
@ -40,9 +41,13 @@ jobs:
|
|||
node-version: ${{ matrix.node }}
|
||||
cache: 'yarn'
|
||||
- name: Install dependencies plugin
|
||||
run: yarn --frozen-lockfile --unsafe-perm --production
|
||||
run: yarn --no-lockfile --unsafe-perm
|
||||
- name: Push the package to yalc
|
||||
run: yarn build
|
||||
- name: Add yalc package to the playground
|
||||
run: yarn playground:yalc-add
|
||||
- name: Install dependencies playground
|
||||
run: yarn playground:install --frozen-lockfile --unsafe-perm
|
||||
run: cd playground && yarn install --unsafe-perm
|
||||
- name: Build playground
|
||||
run: yarn playground:build
|
||||
- name: Run test
|
||||
|
@ -60,7 +65,7 @@ jobs:
|
|||
# runs-on: ubuntu-latest
|
||||
# strategy:
|
||||
# matrix:
|
||||
# node: [14, 16, 18]
|
||||
# node: [16, 18, 20]
|
||||
# steps:
|
||||
# - uses: actions/checkout@v2
|
||||
# - uses: actions/setup-node@v2
|
||||
|
|
|
@ -4,60 +4,55 @@ We want this community to be friendly and respectful to each other. Please follo
|
|||
|
||||
## Development Workflow
|
||||
|
||||
To get started with the project, make sure you have a local instance of Strapi running.
|
||||
See the [Strapi docs](https://github.com/strapi/strapi#getting-started) on how to setup a Strapi project.
|
||||
This plugin provides a local development instance of Strapi to develop it's features. We call this instance `playground` and it can be found in the playground folder in the root of the project. For that reason it is not needed to have your own Strapi instance running to work on this plugin. Just clone the repo and you're ready to go!
|
||||
|
||||
#### 1. Fork the [repository](https://github.com/boazpoolman/strapi-plugin-config-sync)
|
||||
#### 1. Fork the [repository](https://github.com/pluginpal/strapi-plugin-config-sync)
|
||||
|
||||
[Go to the repository](https://github.com/boazpoolman/strapi-plugin-config-sync) and fork it to your own GitHub account.
|
||||
[Go to the repository](https://github.com/pluginpal/strapi-plugin-config-sync) and fork it to your own GitHub account.
|
||||
|
||||
#### 2. Clone from your repository into the plugins folder
|
||||
#### 2. Clone the forked repository
|
||||
|
||||
```bash
|
||||
cd YOUR_STRAPI_PROJECT/src/plugins
|
||||
git clone git@github.com:YOUR_USERNAME/strapi-plugin-config-sync.git config-sync
|
||||
git clone git@github.com:YOUR_USERNAME/strapi-plugin-config-sync.git
|
||||
```
|
||||
|
||||
#### 3. Install the dependencies
|
||||
|
||||
Go to the plugin and install it's dependencies.
|
||||
Go to the folder and install the dependencies
|
||||
|
||||
```bash
|
||||
cd YOUR_STRAPI_PROJECT/src/plugins/config-sync/ && yarn plugin:install
|
||||
cd strapi-plugin-config-sync && yarn install
|
||||
```
|
||||
|
||||
#### 4. Enable the plugin
|
||||
#### 4. Install the playground dependencies
|
||||
|
||||
Add the following lines to the `config/plugins.js` file in your Strapi project.
|
||||
|
||||
```
|
||||
const path = require('path');
|
||||
// ...
|
||||
{
|
||||
'config-sync': {
|
||||
enabled: true,
|
||||
resolve: path.resolve(__dirname, '../src/plugins/config-sync'),
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
#### 5. Rebuild your Strapi project
|
||||
|
||||
Rebuild your strapi project to build the admin part of the plugin.
|
||||
Run this in the root of the repository
|
||||
|
||||
```bash
|
||||
cd YOUR_STRAPI_PROJECT && yarn build
|
||||
yarn playground:install
|
||||
```
|
||||
|
||||
#### 6. Running the administration panel in development mode
|
||||
#### 5. Run the compiler of the plugin
|
||||
|
||||
**Start the administration panel server for development**
|
||||
We use `yalc` to publish the package to a local registry. Run the following command o watch for changes and push to `yalc` every time a change is made:
|
||||
|
||||
```bash
|
||||
cd YOUR_STRAPI_PROJECT && yarn develop --watch-admin
|
||||
yarn develop
|
||||
```
|
||||
|
||||
The administration panel will be available at http://localhost:8080/admin
|
||||
#### 6. Start the playground instance
|
||||
|
||||
Leave the watcher running, open up a new terminal window and browse back to the root of the plugin repo. Run the following command:
|
||||
|
||||
```bash
|
||||
yarn playground:develop
|
||||
```
|
||||
|
||||
This will start the playground instance that will have the plugin installed from the `yalc` registry. Browse to http://localhost:1337 and create a test admin user to log in to the playground.
|
||||
|
||||
#### 7. Start your contribution!
|
||||
|
||||
You can now start working on your contribution. If you had trouble setting up this testing environment please feel free to report an issue on Github.
|
||||
|
||||
### Commit message convention
|
||||
|
||||
|
@ -82,12 +77,10 @@ The `package.json` file contains various scripts for common tasks:
|
|||
|
||||
- `yarn eslint`: lint files with ESLint.
|
||||
- `yarn eslint:fix`: auto-fix ESLint issues.
|
||||
- `yarn test:unit`: run unit tests with Jest.
|
||||
- `yarn test:integration`: run integration tests with Jest.
|
||||
|
||||
### Sending a pull request
|
||||
|
||||
> **Working on your first pull request?** You can learn how from this _free_ series: [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github).
|
||||
|
||||
When you're sending a pull request:
|
||||
|
||||
- Prefer small pull requests focused on one change.
|
||||
|
|
371
README.md
371
README.md
|
@ -1,7 +1,9 @@
|
|||
<div align="center">
|
||||
<h1>Strapi config-sync plugin</h1>
|
||||
|
||||
<p style="margin-top: 0;">This plugin is a multi-purpose tool to manage your Strapi database records through JSON files. Mostly used to version control <a href="#-config-types">config data</a> for automated deployment, automated tests and data sharing for collaboration purposes.</p>
|
||||
<p style="margin-top: 0;">This plugin is a multi-purpose tool to manage your Strapi database records through JSON files. Mostly used to version controlconfig data for automated deployment, automated tests and data sharing for collaboration purposes.</p>
|
||||
|
||||
<a href="https://docs.pluginpal.io/config-sync">Read the documentation</a>
|
||||
|
||||
<p>
|
||||
<a href="https://www.npmjs.org/package/strapi-plugin-config-sync">
|
||||
|
@ -19,19 +21,6 @@
|
|||
</p>
|
||||
</div>
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Features](#-features)
|
||||
- [Installation](#-installation)
|
||||
- [Requirements](#-requirements)
|
||||
- [Motivation](#-motivation)
|
||||
- [CLI](#-command-line-interface-cli)
|
||||
- [Admin panel](#%EF%B8%8F-admin-panel-gui)
|
||||
- [Usage / Workflow](#%EF%B8%8F-usage--workflow)
|
||||
- [Config types](#-config-types)
|
||||
- [Naming convention](#-naming-convention)
|
||||
- [Settings](#-settings)
|
||||
|
||||
## ✨ Features
|
||||
|
||||
- **CLI** - `config-sync` CLI for syncing the config from the command line
|
||||
|
@ -42,9 +31,9 @@
|
|||
- **Exclusion** - Exclude single config entries or all entries of a given type
|
||||
- **Diff viewer** - A git-style diff viewer to inspect the config changes
|
||||
|
||||
## ⏳ Installation
|
||||
## ⏳ Getting started
|
||||
|
||||
Install the plugin in your Strapi project.
|
||||
[Read the Getting Started tutorial](https://docs.pluginpal.io/config-sync) or follow the steps below:
|
||||
|
||||
```bash
|
||||
# using yarn
|
||||
|
@ -81,353 +70,15 @@ npm run develop
|
|||
|
||||
The **Config Sync** plugin should now appear in the **Settings** section of your Strapi app.
|
||||
|
||||
To start tracking your config changes you have to make the first export. This will dump all your configuration data to the `/config/sync` directory. You can export either through [the CLI](#-command-line-interface-cli) or [Strapi admin panel](#%EF%B8%8F-admin-panel-gui)
|
||||
To start tracking your config changes you have to make the first export. This will dump all your configuration data to the `/config/sync` directory. You can export either through [the CLI](https://docs.pluginpal.io/config-sync/cli) or [Strapi admin panel](https://docs.pluginpal.io/config-sync/admin-gui)
|
||||
|
||||
Enjoy 🎉
|
||||
|
||||
## 🖐 Requirements
|
||||
## 📓 Documentation
|
||||
|
||||
Complete installation requirements are the exact same as for Strapi itself and can be found in the [Strapi documentation](https://strapi.io/documentation).
|
||||
|
||||
**Supported Strapi versions**:
|
||||
|
||||
- Strapi 4.3.2 (recently tested)
|
||||
- Strapi ^4.x (use `strapi-plugin-config-sync@^1.0.0`)
|
||||
- Strapi ^3.4.x (use `strapi-plugin-config-sync@0.1.6`)
|
||||
|
||||
## 💡 Motivation
|
||||
In Strapi we come across what I would call config types. These are models of which the records are stored in our database, just like content types. Though the big difference here is that your code ofter relies on the database records of these types.
|
||||
|
||||
Having said that, it makes sense that these records can be exported, added to git, and be migrated across environments. This way we can make sure we have all the data our code relies on, on each environment.
|
||||
|
||||
Examples of these types are:
|
||||
|
||||
- Admin roles _(admin::role)_
|
||||
- User roles _(plugin::users-permissions.role)_
|
||||
- Admin settings _(strapi::core-store)_
|
||||
- I18n locale _(plugin::i18n.locale)_
|
||||
|
||||
This plugin gives you the tools to sync this data. You can export the data as JSON files on one env, and import them on every other env. By writing this data as JSON files you can easily track them in your version control system (git).
|
||||
|
||||
_With great power comes great responsibility - Spider-Man_
|
||||
|
||||
## 🔌 Command line interface (CLI)
|
||||
|
||||
Add the `config-sync` command as a script to the `package.json` of your Strapi project:
|
||||
|
||||
```
|
||||
"scripts": {
|
||||
// ...
|
||||
"cs": "config-sync"
|
||||
},
|
||||
```
|
||||
|
||||
You can now run all the `config-sync` commands like this:
|
||||
|
||||
```bash
|
||||
# using yarn
|
||||
yarn cs --help
|
||||
|
||||
# using npm
|
||||
npm run cs --help
|
||||
```
|
||||
|
||||
### ⬆️ Import ⬇️ Export
|
||||
|
||||
> _Command:_ `import` _Alias:_ `i`
|
||||
>
|
||||
> _Command:_ `export` _Alias:_ `e`
|
||||
|
||||
These commands are used to sync the config in your Strapi project.
|
||||
|
||||
_Example:_
|
||||
|
||||
```bash
|
||||
# using yarn
|
||||
yarn cs import
|
||||
yarn cs export
|
||||
|
||||
# using npm
|
||||
npm run cs import
|
||||
npm run cs export
|
||||
```
|
||||
|
||||
##### Flag: `-y`, `--yes`
|
||||
|
||||
Use this flag to skip the confirm prompt and go straight to syncing the config.
|
||||
|
||||
```bash
|
||||
[command] --yes
|
||||
```
|
||||
|
||||
##### Flag: `-t`, `--type`
|
||||
|
||||
Use this flag to specify the type of config you want to sync.
|
||||
|
||||
```bash
|
||||
[command] --type user-role
|
||||
```
|
||||
|
||||
##### Flag: `-p`, `--partial`
|
||||
|
||||
Use this flag to sync a specific set of configs by giving the CLI a comma-separated string of config names.
|
||||
|
||||
```bash
|
||||
[command] --partial user-role.public,i18n-locale.en
|
||||
```
|
||||
|
||||
##### Flag: `-f`, `--force`
|
||||
|
||||
If you're using the soft setting to gracefully import config, you can use this flag to ignore the setting for the current command and forcefully import all changes anyway.
|
||||
|
||||
```bash
|
||||
[command] --force
|
||||
```
|
||||
|
||||
### ↔️ Diff
|
||||
|
||||
> _Command:_ `diff` | _Alias:_ `d`
|
||||
|
||||
This command is used to see the difference between the config as found in the sync directory, and the config as found in the database.
|
||||
|
||||
_Example:_
|
||||
|
||||
```bash
|
||||
# using yarn
|
||||
yarn cs diff
|
||||
|
||||
# using npm
|
||||
npm run cs diff
|
||||
```
|
||||
|
||||
##### Argument: `<single>`
|
||||
|
||||
Add a single config name as the argument of the `diff` command to see the difference of that single file in a git-style diff viewer.
|
||||
|
||||
_Example:_
|
||||
|
||||
```bash
|
||||
# using yarn
|
||||
yarn cs diff user-role.public
|
||||
|
||||
# using npm
|
||||
npm run cs diff user-role.public
|
||||
```
|
||||
|
||||
## 🖥️ Admin panel (GUI)
|
||||
This plugin ships with a React app which can be accessed from the settings page in Strapi admin panel. On this page you can pretty much do the same as you can from the CLI. You can import, export and see the difference between the config as found in the sync directory, and the config as found in the database.
|
||||
|
||||
**Pro tip:**
|
||||
By clicking on one of the items in the diff table you can see the exact difference between sync dir and database in a git-style diff viewer.
|
||||
|
||||
<img src="https://raw.githubusercontent.com/boazpoolman/strapi-plugin-config-sync/master/.github/config-diff.png" alt="Config diff in admin" />
|
||||
|
||||
## ⌨️ Usage / Workflow
|
||||
This plugin works best when you use `git` for the version control of your Strapi project.
|
||||
|
||||
_The following workflows are assuming you're using `git`._
|
||||
|
||||
### Intro
|
||||
All database records tracked with this plugin will be exported to JSON files. Once exported each change to the file or the record will be tracked. Meaning you can now do one of two things:
|
||||
|
||||
- Change the file(s), and run an import. You have now imported from filesystem -> database.
|
||||
- Change the record(s), and run an export. You have now exported from database -> filesystem.
|
||||
|
||||
### Local development
|
||||
When building a new feature locally for your Strapi project you'd use the following workflow:
|
||||
|
||||
- Build the feature.
|
||||
- Export the config.
|
||||
- Commit and push the files to git.
|
||||
|
||||
### Deployment
|
||||
When deploying the newly created feature - to either a server, or a co-worker's machine - you'd use the following workflow:
|
||||
|
||||
- Pull the latest file changes to the environment.
|
||||
- (Re)start your Strapi instance.
|
||||
- Import the config.
|
||||
|
||||
### Production deployment
|
||||
The production deployment will be the same as a regular deployment. You just have to be careful before running the import. Ideally making sure the are no open changes before you pull the new code to the environment.
|
||||
|
||||
## 🚀 Config types
|
||||
|
||||
By default the plugin will track 4 (official) types.
|
||||
|
||||
To track your own custom types you can register them by setting some plugin config.
|
||||
|
||||
### Default types
|
||||
|
||||
These 4 types are by default registered in the sync process.
|
||||
|
||||
#### Admin role
|
||||
|
||||
> Config name: `admin-role` | UID: `code` | Query string: `admin::role`
|
||||
|
||||
#### User role
|
||||
|
||||
> Config name: `user-role` | UID: `type` | Query string: `plugin::users-permissions.role`
|
||||
|
||||
#### Core store
|
||||
|
||||
> Config name: `core-store` | UID: `key` | Query string: `strapi::core-store`
|
||||
|
||||
#### I18n locale
|
||||
|
||||
> Config name: `i18n-locale` | UID: `code` | Query string: `plugin::i18n.locale`
|
||||
|
||||
### Custom types
|
||||
|
||||
Your custom types can be registered through the `customTypes` plugin config. This is a setting that can be set in the `config/plugins.js` file in your project.
|
||||
|
||||
_Read more about the `config/plugins.js` file [here](#-settings)._
|
||||
|
||||
You can register a type by giving the `customTypes` array an object which contains at least the following 3 properties:
|
||||
|
||||
```
|
||||
customTypes: [{
|
||||
configName: 'webhook',
|
||||
queryString: 'webhook',
|
||||
uid: 'name',
|
||||
}],
|
||||
```
|
||||
|
||||
_The example above will register the Strapi webhook type._
|
||||
|
||||
#### Config name
|
||||
|
||||
The name of the config type. This value will be used as the first part of the filename for all config of this type. It should be unique from the other types and is preferably written in kebab-case.
|
||||
|
||||
###### Key: `configName`
|
||||
|
||||
> `required:` YES | `type:` string
|
||||
|
||||
#### Query string
|
||||
|
||||
This is the query string of the type. Each type in Strapi has its own query string you can use to programatically preform CRUD actions on the entries of the type. Often for custom types in Strapi the format is something like `api::custom-api.custom-type`.
|
||||
|
||||
###### Key: `queryString`
|
||||
|
||||
> `required:` YES | `type:` string
|
||||
|
||||
#### UID
|
||||
|
||||
The UID represents a field on the registered type. The value of this field will act as a unique identifier to identify the entries across environments. Therefore it should be unique and preferably un-editable after initial creation.
|
||||
|
||||
Mind that you can not use an auto-incremental value like the `id` as auto-increment does not play nice when you try to match entries across different databases.
|
||||
|
||||
If you do not have a single unique value, you can also pass in a array of keys for a combined uid key. This is for example the case for all content types which use i18n features (An example config would be `uid: ['productId', 'locale']`).
|
||||
|
||||
###### Key: `uid`
|
||||
|
||||
> `required:` YES | `type:` string | string[]
|
||||
|
||||
#### JSON fields
|
||||
|
||||
This property can accept an array of field names from the type. It is meant to specify the JSON fields on the type so the plugin can better format the field values when calculating the config difference.
|
||||
|
||||
###### Key: `jsonFields`
|
||||
|
||||
> `required:` NO | `type:` array
|
||||
|
||||
|
||||
## 🔍 Naming convention
|
||||
All the config files written in the sync directory have the same naming convention. It goes as follows:
|
||||
|
||||
[config-type].[identifier].json
|
||||
|
||||
- `config-type` - Corresponds to the `configName` of the config type.
|
||||
- `identifier` - Corresponds to the value of the `uid` field of the config type.
|
||||
|
||||
## 🔧 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.
|
||||
|
||||
##### `config/plugins.js`:
|
||||
module.exports = ({ env }) => ({
|
||||
// ...
|
||||
'config-sync': {
|
||||
enabled: true,
|
||||
config: {
|
||||
syncDir: "config/sync/",
|
||||
minify: false,
|
||||
soft: false,
|
||||
importOnBootstrap: false,
|
||||
customTypes: [],
|
||||
excludedTypes: [],
|
||||
excludedConfig: [
|
||||
"core-store.plugin_users-permissions_grant",
|
||||
"core-store.plugin_upload_metrics",
|
||||
"core-store.strapi_content_types_schema",
|
||||
"core-store.ee_information",
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
### Sync dir
|
||||
|
||||
The path for reading and writing the sync files.
|
||||
|
||||
###### Key: `syncDir`
|
||||
|
||||
> `required:` YES | `type:` string | `default:` `config/sync/`
|
||||
|
||||
### Minify
|
||||
|
||||
When enabled all the exported JSON files will be minified.
|
||||
|
||||
###### Key: `minify`
|
||||
|
||||
> `required:` NO | `type:` bool | `default:` `false`
|
||||
|
||||
### Soft
|
||||
|
||||
When enabled the import action will be limited to only create new entries. Entries to be deleted, or updated will be skipped from the import process and will remain in it's original state.
|
||||
|
||||
###### Key: `soft`
|
||||
|
||||
> `required:` NO | `type:` bool | `default:` `false`
|
||||
|
||||
### Import on bootstrap
|
||||
|
||||
Allows you to let the config be imported automaticly when strapi is bootstrapping (on `strapi start`). This setting can't be used locally and should be handled very carefully as it can unintendedly overwrite the changes in your database. **PLEASE USE WITH CARE**.
|
||||
|
||||
###### Key: `importOnBootstrap`
|
||||
|
||||
> `required:` NO | `type:` bool | `default:` `false`
|
||||
|
||||
### Custom types
|
||||
|
||||
With this setting you can register your own custom config types. This is an array which expects objects with at least the `configName`, `queryString` and `uid` properties. Read more about registering custom types in the [Custom config types](#custom-types) documentation.
|
||||
|
||||
###### Key: `customTypes`
|
||||
|
||||
> `required:` NO | `type:` array | `default:` `[]`
|
||||
|
||||
### Excluded types
|
||||
|
||||
This setting will exclude all the config from a given type from the syncing process. The config types are specified by the `configName` of the type.
|
||||
|
||||
For example:
|
||||
|
||||
```
|
||||
excludedTypes: ['admin-role']
|
||||
```
|
||||
|
||||
###### Key: `excludedTypes`
|
||||
|
||||
> `required:` NO | `type:` array | `default:` `[]`
|
||||
|
||||
### Excluded config
|
||||
|
||||
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.
|
||||
|
||||
###### Key: `excludedConfig`
|
||||
|
||||
> `required:` NO | `type:` array | `default:` `['core-store.plugin_users-permissions_grant', 'core-store.plugin_upload_metrics', 'core-store.strapi_content_types_schema', 'core-store.ee_information',]`
|
||||
See our dedicated [repository](https://github.com/pluginpal/docs) for all of PluginPal's documentation, or view the Config Sync documentation live:
|
||||
|
||||
- [Config Sync documentation](https://docs.pluginpal.io/config-sync)
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
|
@ -439,8 +90,10 @@ Give a star if this project helped you.
|
|||
|
||||
## 🔗 Links
|
||||
|
||||
- [PluginPal marketplace](https://www.pluginpal.io/plugin/config-sync)
|
||||
- [NPM package](https://www.npmjs.com/package/strapi-plugin-config-sync)
|
||||
- [GitHub repository](https://github.com/boazpoolman/strapi-plugin-config-sync)
|
||||
- [Strapi marketplace](https://market.strapi.io/plugins/strapi-plugin-config-sync)
|
||||
|
||||
## 🌎 Community support
|
||||
|
||||
|
@ -449,4 +102,4 @@ Give a star if this project helped you.
|
|||
|
||||
## 📝 Resources
|
||||
|
||||
- [MIT License](LICENSE.md)
|
||||
- [MIT License](https://github.com/pluginpal/strapi-plugin-config-sync/blob/master/LICENSE.md)
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
import React, { useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { Button } from '@strapi/design-system';
|
||||
import { Map } from 'immutable';
|
||||
import { useNotification } from '@strapi/helper-plugin';
|
||||
|
||||
import ConfirmModal from '../ConfirmModal';
|
||||
import { downloadZip, exportAllConfig, importAllConfig } from '../../state/actions/Config';
|
||||
|
||||
const ActionButtons = () => {
|
||||
const dispatch = useDispatch();
|
||||
const toggleNotification = useNotification();
|
||||
const [modalIsOpen, setModalIsOpen] = useState(false);
|
||||
const [actionType, setActionType] = useState('');
|
||||
const partialDiff = useSelector((state) => state.getIn(['config', 'partialDiff'], Map({}))).toJS();
|
||||
|
||||
const closeModal = () => {
|
||||
setActionType('');
|
||||
setModalIsOpen(false);
|
||||
};
|
||||
|
||||
const openModal = (type) => {
|
||||
setActionType(type);
|
||||
setModalIsOpen(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<ActionButtonsStyling>
|
||||
<Button disabled={isEmpty(partialDiff)} onClick={() => openModal('import')}>Import</Button>
|
||||
<Button disabled={isEmpty(partialDiff)} onClick={() => openModal('export')}>Export</Button>
|
||||
<Button onClick={() => dispatch(downloadZip(toggleNotification))}>Download Config</Button>
|
||||
{!isEmpty(partialDiff) && (
|
||||
<h4 style={{ display: 'inline' }}>{Object.keys(partialDiff).length} {Object.keys(partialDiff).length === 1 ? "config change" : "config changes"}</h4>
|
||||
)}
|
||||
<ConfirmModal
|
||||
isOpen={modalIsOpen}
|
||||
onClose={closeModal}
|
||||
type={actionType}
|
||||
onSubmit={(force) => actionType === 'import' ? dispatch(importAllConfig(partialDiff, force, toggleNotification)) : dispatch(exportAllConfig(partialDiff, toggleNotification))}
|
||||
/>
|
||||
</ActionButtonsStyling>
|
||||
);
|
||||
};
|
||||
|
||||
const ActionButtonsStyling = styled.div`
|
||||
padding: 10px 0 20px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
> button {
|
||||
margin-right: 10px;
|
||||
}
|
||||
`;
|
||||
|
||||
export default ActionButtons;
|
|
@ -0,0 +1,57 @@
|
|||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { Button, Typography } from '@strapi/design-system';
|
||||
import { Map } from 'immutable';
|
||||
import { getFetchClient, useNotification } from '@strapi/strapi/admin';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
||||
import ConfirmModal from '../ConfirmModal';
|
||||
import { exportAllConfig, importAllConfig } from '../../state/actions/Config';
|
||||
|
||||
const ActionButtons = () => {
|
||||
const { post, get } = getFetchClient();
|
||||
const dispatch = useDispatch();
|
||||
const { toggleNotification } = useNotification();
|
||||
const partialDiff = useSelector((state) => state.getIn(['config', 'partialDiff'], Map({}))).toJS();
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return (
|
||||
<ActionButtonsStyling>
|
||||
<ConfirmModal
|
||||
type="import"
|
||||
trigger={(
|
||||
<Button disabled={isEmpty(partialDiff)}>
|
||||
{formatMessage({ id: 'config-sync.Buttons.Import' })}
|
||||
</Button>
|
||||
)}
|
||||
onSubmit={(force) => dispatch(importAllConfig(partialDiff, force, toggleNotification, formatMessage, post, get))}
|
||||
/>
|
||||
<ConfirmModal
|
||||
type="export"
|
||||
trigger={(
|
||||
<Button disabled={isEmpty(partialDiff)}>
|
||||
{formatMessage({ id: 'config-sync.Buttons.Export' })}
|
||||
</Button>
|
||||
)}
|
||||
onSubmit={(force) => dispatch(exportAllConfig(partialDiff, toggleNotification, formatMessage, post, get))}
|
||||
/>
|
||||
{!isEmpty(partialDiff) && (
|
||||
<Typography variant="epsilon">{Object.keys(partialDiff).length} {Object.keys(partialDiff).length === 1 ? "config change" : "config changes"}</Typography>
|
||||
)}
|
||||
</ActionButtonsStyling>
|
||||
);
|
||||
};
|
||||
|
||||
const ActionButtonsStyling = styled.div`
|
||||
padding: 10px 0 20px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
> button {
|
||||
margin-right: 10px;
|
||||
}
|
||||
`;
|
||||
|
||||
export default ActionButtons;
|
|
@ -1,48 +0,0 @@
|
|||
import React from 'react';
|
||||
import ReactDiffViewer, { DiffMethod } from 'react-diff-viewer-continued';
|
||||
|
||||
import {
|
||||
ModalLayout,
|
||||
ModalBody,
|
||||
ModalHeader,
|
||||
Grid,
|
||||
GridItem,
|
||||
Typography,
|
||||
} from '@strapi/design-system';
|
||||
|
||||
const ConfigDiff = ({ isOpen, onClose, oldValue, newValue, configName }) => {
|
||||
if (!isOpen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ModalLayout
|
||||
onClose={onClose}
|
||||
labelledBy="title"
|
||||
>
|
||||
<ModalHeader>
|
||||
<Typography variant="omega" fontWeight="bold" textColor="neutral800">
|
||||
Config changes for {configName}
|
||||
</Typography>
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<Grid paddingBottom={4} style={{ textAlign: 'center' }}>
|
||||
<GridItem col={6}>
|
||||
<Typography variant="delta">Sync directory</Typography>
|
||||
</GridItem>
|
||||
<GridItem col={6}>
|
||||
<Typography variant="delta">Database</Typography>
|
||||
</GridItem>
|
||||
</Grid>
|
||||
<ReactDiffViewer
|
||||
oldValue={JSON.stringify(oldValue, null, 2)}
|
||||
newValue={JSON.stringify(newValue, null, 2)}
|
||||
splitView
|
||||
compareMethod={DiffMethod.WORDS}
|
||||
/>
|
||||
</ModalBody>
|
||||
</ModalLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConfigDiff;
|
|
@ -0,0 +1,48 @@
|
|||
import React from 'react';
|
||||
import ReactDiffViewer, { DiffMethod } from 'react-diff-viewer-continued';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
||||
import {
|
||||
Modal,
|
||||
Grid,
|
||||
Typography,
|
||||
} from '@strapi/design-system';
|
||||
|
||||
const ConfigDiff = ({ oldValue, newValue, configName, trigger }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return (
|
||||
<Modal.Root>
|
||||
<Modal.Trigger>
|
||||
{trigger}
|
||||
</Modal.Trigger>
|
||||
<Modal.Content>
|
||||
<Modal.Header>
|
||||
<Typography variant="omega" fontWeight="bold" textColor="neutral800">
|
||||
{formatMessage({ id: 'config-sync.ConfigDiff.Title' })} {configName}
|
||||
</Typography>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<Grid.Root paddingBottom={4} style={{ textAlign: 'center' }}>
|
||||
<Grid.Item col={6}>
|
||||
<Typography variant="delta" style={{ width: '100%' }}>{formatMessage({ id: 'config-sync.ConfigDiff.SyncDirectory' })}</Typography>
|
||||
</Grid.Item>
|
||||
<Grid.Item col={6}>
|
||||
<Typography variant="delta" style={{ width: '100%' }}>{formatMessage({ id: 'config-sync.ConfigDiff.Database' })}</Typography>
|
||||
</Grid.Item>
|
||||
</Grid.Root>
|
||||
<Typography variant="pi">
|
||||
<ReactDiffViewer
|
||||
oldValue={JSON.stringify(oldValue, null, 2)}
|
||||
newValue={JSON.stringify(newValue, null, 2)}
|
||||
splitView
|
||||
compareMethod={DiffMethod.WORDS}
|
||||
/>
|
||||
</Typography>
|
||||
</Modal.Body>
|
||||
</Modal.Content>
|
||||
</Modal.Root>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConfigDiff;
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import { Tr, Td, BaseCheckbox } from '@strapi/design-system';
|
||||
import { Tr, Td, Checkbox, Typography } from '@strapi/design-system';
|
||||
|
||||
const CustomRow = ({ row, checked, updateValue }) => {
|
||||
const CustomRow = ({ row, checked, updateValue, ...props }) => {
|
||||
const { configName, configType, state, onClick } = row;
|
||||
|
||||
const stateStyle = (stateStr) => {
|
||||
|
@ -34,6 +34,7 @@ const CustomRow = ({ row, checked, updateValue }) => {
|
|||
|
||||
return (
|
||||
<Tr
|
||||
{...props}
|
||||
onClick={(e) => {
|
||||
if (e.target.type !== 'checkbox') {
|
||||
onClick(configType, configName);
|
||||
|
@ -42,20 +43,20 @@ const CustomRow = ({ row, checked, updateValue }) => {
|
|||
style={{ cursor: 'pointer' }}
|
||||
>
|
||||
<Td>
|
||||
<BaseCheckbox
|
||||
<Checkbox
|
||||
aria-label={`Select ${configName}`}
|
||||
value={checked}
|
||||
onValueChange={updateValue}
|
||||
checked={checked}
|
||||
onCheckedChange={updateValue}
|
||||
/>
|
||||
</Td>
|
||||
<Td>
|
||||
<p>{configName}</p>
|
||||
<Td onClick={(e) => props.onClick(e)}>
|
||||
<Typography variant="omega">{configName}</Typography>
|
||||
</Td>
|
||||
<Td>
|
||||
<p>{configType}</p>
|
||||
<Td onClick={(e) => props.onClick(e)}>
|
||||
<Typography variant="omega">{configType}</Typography>
|
||||
</Td>
|
||||
<Td>
|
||||
<p style={stateStyle(state)}>{state}</p>
|
||||
<Td onClick={(e) => props.onClick(e)}>
|
||||
<Typography variant="omega" style={stateStyle(state)}>{state}</Typography>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
|
@ -1,4 +1,5 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
|
@ -9,7 +10,7 @@ import {
|
|||
Tr,
|
||||
Th,
|
||||
Typography,
|
||||
BaseCheckbox,
|
||||
Checkbox,
|
||||
Loader,
|
||||
} from '@strapi/design-system';
|
||||
|
||||
|
@ -19,31 +20,32 @@ import NoChanges from '../NoChanges';
|
|||
import ConfigListRow from './ConfigListRow';
|
||||
import { setConfigPartialDiffInState } from '../../state/actions/Config';
|
||||
|
||||
|
||||
const ConfigList = ({ diff, isLoading }) => {
|
||||
const [openModal, setOpenModal] = useState(false);
|
||||
const [originalConfig, setOriginalConfig] = useState({});
|
||||
const [newConfig, setNewConfig] = useState({});
|
||||
const [cName, setCname] = useState('');
|
||||
const [rows, setRows] = useState([]);
|
||||
const [checkedItems, setCheckedItems] = useState([]);
|
||||
const dispatch = useDispatch();
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
const getConfigState = (configName) => {
|
||||
if (
|
||||
diff.fileConfig[configName]
|
||||
&& diff.databaseConfig[configName]
|
||||
) {
|
||||
return 'Different';
|
||||
return formatMessage({ id: 'config-sync.ConfigList.Different' });
|
||||
} else if (
|
||||
diff.fileConfig[configName]
|
||||
&& !diff.databaseConfig[configName]
|
||||
) {
|
||||
return 'Only in sync dir';
|
||||
return formatMessage({ id: 'config-sync.ConfigList.OnlyDir' });
|
||||
} else if (
|
||||
!diff.fileConfig[configName]
|
||||
&& diff.databaseConfig[configName]
|
||||
) {
|
||||
return 'Only in DB';
|
||||
return formatMessage({ id: 'config-sync.ConfigList.OnlyDB' });
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -69,7 +71,6 @@ const ConfigList = ({ diff, isLoading }) => {
|
|||
setOriginalConfig(diff.fileConfig[`${configType}.${configName}`]);
|
||||
setNewConfig(diff.databaseConfig[`${configType}.${configName}`]);
|
||||
setCname(`${configType}.${configName}`);
|
||||
setOpenModal(true);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -86,17 +87,10 @@ const ConfigList = ({ diff, isLoading }) => {
|
|||
dispatch(setConfigPartialDiffInState(newPartialDiff));
|
||||
}, [checkedItems]);
|
||||
|
||||
const closeModal = () => {
|
||||
setOriginalConfig({});
|
||||
setNewConfig({});
|
||||
setCname('');
|
||||
setOpenModal(false);
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div style={{ textAlign: 'center', marginTop: 40 }}>
|
||||
<Loader>Loading content...</Loader>
|
||||
<Loader>{formatMessage({ id: 'config-sync.ConfigList.Loading' })}</Loader>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -114,39 +108,36 @@ const ConfigList = ({ diff, isLoading }) => {
|
|||
|
||||
return (
|
||||
<div>
|
||||
<ConfigDiff
|
||||
isOpen={openModal}
|
||||
oldValue={originalConfig}
|
||||
newValue={newConfig}
|
||||
onClose={closeModal}
|
||||
configName={cName}
|
||||
/>
|
||||
<Table colCount={4} rowCount={rows.length + 1}>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>
|
||||
<BaseCheckbox
|
||||
aria-label="Select all entries"
|
||||
indeterminate={isIndeterminate}
|
||||
onValueChange={(value) => setCheckedItems(checkedItems.map(() => value))}
|
||||
value={allChecked}
|
||||
<Checkbox
|
||||
aria-label={formatMessage({ id: 'config-sync.ConfigList.SelectAll' })}
|
||||
checked={isIndeterminate ? "indeterminate" : allChecked}
|
||||
onCheckedChange={(value) => setCheckedItems(checkedItems.map(() => value))}
|
||||
/>
|
||||
</Th>
|
||||
<Th>
|
||||
<Typography variant="sigma">Config name</Typography>
|
||||
<Typography variant="sigma">{formatMessage({ id: 'config-sync.ConfigList.ConfigName' })}</Typography>
|
||||
</Th>
|
||||
<Th>
|
||||
<Typography variant="sigma">Config type</Typography>
|
||||
<Typography variant="sigma">{formatMessage({ id: 'config-sync.ConfigList.ConfigType' })}</Typography>
|
||||
</Th>
|
||||
<Th>
|
||||
<Typography variant="sigma">State</Typography>
|
||||
<Typography variant="sigma">{formatMessage({ id: 'config-sync.ConfigList.State' })}</Typography>
|
||||
</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{rows.map((row, index) => (
|
||||
<ConfigListRow
|
||||
<ConfigDiff
|
||||
key={row.configName}
|
||||
oldValue={originalConfig}
|
||||
newValue={newConfig}
|
||||
configName={cName}
|
||||
trigger={(
|
||||
<ConfigListRow
|
||||
row={row}
|
||||
checked={checkedItems[index]}
|
||||
updateValue={() => {
|
||||
|
@ -154,6 +145,8 @@ const ConfigList = ({ diff, isLoading }) => {
|
|||
setCheckedItems([...checkedItems]);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
|
@ -1,83 +0,0 @@
|
|||
import React, { useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import {
|
||||
Dialog,
|
||||
DialogBody,
|
||||
DialogFooter,
|
||||
Flex,
|
||||
Typography,
|
||||
Stack,
|
||||
Button,
|
||||
Checkbox,
|
||||
Divider,
|
||||
Box,
|
||||
} from '@strapi/design-system';
|
||||
import { ExclamationMarkCircle } from '@strapi/icons';
|
||||
|
||||
const ConfirmModal = ({ isOpen, onClose, onSubmit, type }) => {
|
||||
const soft = useSelector((state) => state.getIn(['config', 'appEnv', 'config', 'soft'], false));
|
||||
const [force, setForce] = useState(false);
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
onClose={onClose}
|
||||
title="Confirmation"
|
||||
isOpen={isOpen}
|
||||
>
|
||||
<DialogBody icon={<ExclamationMarkCircle />}>
|
||||
<Stack size={2}>
|
||||
<Flex justifyContent="center">
|
||||
<Typography variant="omega" id="confirm-description" style={{ textAlign: 'center' }}>
|
||||
{formatMessage({ id: `config-sync.popUpWarning.warning.${type}_1` })}<br />
|
||||
{formatMessage({ id: `config-sync.popUpWarning.warning.${type}_2` })}
|
||||
</Typography>
|
||||
</Flex>
|
||||
</Stack>
|
||||
</DialogBody>
|
||||
{(soft && type === 'import') && (
|
||||
<React.Fragment>
|
||||
<Divider />
|
||||
<Box padding={4}>
|
||||
<Checkbox
|
||||
onValueChange={(value) => setForce(value)}
|
||||
value={force}
|
||||
name="force"
|
||||
hint="Check this to ignore the soft setting."
|
||||
>
|
||||
{formatMessage({ id: 'config-sync.popUpWarning.force' })}
|
||||
</Checkbox>
|
||||
</Box>
|
||||
</React.Fragment>
|
||||
)}
|
||||
<DialogFooter
|
||||
startAction={(
|
||||
<Button
|
||||
onClick={() => {
|
||||
onClose();
|
||||
}}
|
||||
variant="tertiary"
|
||||
>
|
||||
{formatMessage({ id: 'config-sync.popUpWarning.button.cancel' })}
|
||||
</Button>
|
||||
)}
|
||||
endAction={(
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
onClose();
|
||||
onSubmit(force);
|
||||
}}
|
||||
>
|
||||
{formatMessage({ id: `config-sync.popUpWarning.button.${type}` })}
|
||||
</Button>
|
||||
)} />
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConfirmModal;
|
|
@ -0,0 +1,80 @@
|
|||
import React, { useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import {
|
||||
Dialog,
|
||||
Flex,
|
||||
Typography,
|
||||
Button,
|
||||
Checkbox,
|
||||
Divider,
|
||||
Box,
|
||||
Field,
|
||||
} from '@strapi/design-system';
|
||||
import { WarningCircle } from '@strapi/icons';
|
||||
|
||||
const ConfirmModal = ({ onClose, onSubmit, type, trigger }) => {
|
||||
const soft = useSelector((state) => state.getIn(['config', 'appEnv', 'config', 'soft'], false));
|
||||
const [force, setForce] = useState(false);
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return (
|
||||
<Dialog.Root>
|
||||
<Dialog.Trigger>
|
||||
{trigger}
|
||||
</Dialog.Trigger>
|
||||
<Dialog.Content>
|
||||
<Dialog.Header>{formatMessage({ id: "config-sync.popUpWarning.Confirmation" })}</Dialog.Header>
|
||||
<Dialog.Body>
|
||||
<WarningCircle fill="danger600" width="32px" height="32px" />
|
||||
<Flex size={2}>
|
||||
<Flex justifyContent="center">
|
||||
<Typography variant="omega" id="confirm-description" style={{ textAlign: 'center' }}>
|
||||
{formatMessage({ id: `config-sync.popUpWarning.warning.${type}_1` })}<br />
|
||||
{formatMessage({ id: `config-sync.popUpWarning.warning.${type}_2` })}
|
||||
</Typography>
|
||||
</Flex>
|
||||
</Flex>
|
||||
{(soft && type === 'import') && (
|
||||
<Box width="100%">
|
||||
<Divider marginTop={4} />
|
||||
<Box paddingTop={6}>
|
||||
<Field.Root hint="Check this to ignore the soft setting.">
|
||||
<Checkbox
|
||||
onValueChange={(value) => setForce(value)}
|
||||
value={force}
|
||||
name="force"
|
||||
>
|
||||
{formatMessage({ id: 'config-sync.popUpWarning.force' })}
|
||||
</Checkbox>
|
||||
<Field.Hint />
|
||||
</Field.Root>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</Dialog.Body>
|
||||
<Dialog.Footer>
|
||||
<Dialog.Cancel>
|
||||
<Button fullWidth variant="tertiary">
|
||||
{formatMessage({ id: 'config-sync.popUpWarning.button.cancel' })}
|
||||
</Button>
|
||||
</Dialog.Cancel>
|
||||
<Dialog.Action>
|
||||
<Button
|
||||
fullWidth
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
onSubmit(force);
|
||||
}}
|
||||
>
|
||||
{formatMessage({ id: `config-sync.popUpWarning.button.${type}` })}
|
||||
</Button>
|
||||
</Dialog.Action>
|
||||
</Dialog.Footer>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConfirmModal;
|
|
@ -1,34 +0,0 @@
|
|||
import React, { useState } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { NoContent, useNotification } from '@strapi/helper-plugin';
|
||||
import { Button } from '@strapi/design-system';
|
||||
|
||||
import { exportAllConfig } from '../../state/actions/Config';
|
||||
import ConfirmModal from '../ConfirmModal';
|
||||
|
||||
const FirstExport = () => {
|
||||
const toggleNotification = useNotification();
|
||||
const dispatch = useDispatch();
|
||||
const [modalIsOpen, setModalIsOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ConfirmModal
|
||||
isOpen={modalIsOpen}
|
||||
onClose={() => setModalIsOpen(false)}
|
||||
type="export"
|
||||
onSubmit={() => dispatch(exportAllConfig([], toggleNotification))}
|
||||
/>
|
||||
<NoContent
|
||||
content={{
|
||||
id: 'emptyState',
|
||||
defaultMessage:
|
||||
'Looks like this is your first time using config-sync for this project.',
|
||||
}}
|
||||
action={<Button onClick={() => setModalIsOpen(true)}>Make the initial export</Button>}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FirstExport;
|
|
@ -0,0 +1,37 @@
|
|||
import React from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { getFetchClient, useNotification } from '@strapi/strapi/admin';
|
||||
import { Button, EmptyStateLayout } from '@strapi/design-system';
|
||||
import { EmptyDocuments } from '@strapi/icons/symbols';
|
||||
|
||||
|
||||
import { exportAllConfig } from '../../state/actions/Config';
|
||||
import ConfirmModal from '../ConfirmModal';
|
||||
|
||||
const FirstExport = () => {
|
||||
const { post, get } = getFetchClient();
|
||||
const { toggleNotification } = useNotification();
|
||||
const dispatch = useDispatch();
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<EmptyStateLayout
|
||||
content={formatMessage({ id: 'config-sync.FirstExport.Message' })}
|
||||
action={(
|
||||
<ConfirmModal
|
||||
type="export"
|
||||
onSubmit={() => dispatch(exportAllConfig([], toggleNotification, formatMessage, post, get))}
|
||||
trigger={(
|
||||
<Button>{formatMessage({ id: 'config-sync.FirstExport.Button' })}</Button>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
icon={<EmptyDocuments width={160} />}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FirstExport;
|
|
@ -7,14 +7,15 @@
|
|||
import React, { memo } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
||||
import { HeaderLayout, Box } from '@strapi/design-system';
|
||||
import { Layouts } from '@strapi/admin/strapi-admin';
|
||||
import { Box } from '@strapi/design-system';
|
||||
|
||||
const HeaderComponent = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return (
|
||||
<Box background="neutral100">
|
||||
<HeaderLayout
|
||||
<Layouts.Header
|
||||
title={formatMessage({ id: 'config-sync.Header.Title' })}
|
||||
subtitle={formatMessage({ id: 'config-sync.Header.Description' })}
|
||||
as="h2"
|
|
@ -1,14 +0,0 @@
|
|||
import React from 'react';
|
||||
import { NoContent } from '@strapi/helper-plugin';
|
||||
|
||||
const NoChanges = () => (
|
||||
<NoContent
|
||||
content={{
|
||||
id: 'emptyState',
|
||||
defaultMessage:
|
||||
'No differences between DB and sync directory. You are up-to-date!',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
export default NoChanges;
|
|
@ -0,0 +1,16 @@
|
|||
import React from 'react';
|
||||
import { EmptyStateLayout } from '@strapi/design-system';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { EmptyDocuments } from '@strapi/icons/symbols';
|
||||
|
||||
const NoChanges = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
return (
|
||||
<EmptyStateLayout
|
||||
content={formatMessage({ id: 'config-sync.NoChanges.Message', defaultMessage: 'No differences between DB and sync directory. You are up-to-date!' })}
|
||||
icon={<EmptyDocuments width={160} />}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default NoChanges;
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { CheckPagePermissions } from '@strapi/helper-plugin';
|
||||
import { Page } from '@strapi/strapi/admin';
|
||||
|
||||
import pluginPermissions from '../../permissions';
|
||||
import Header from '../../components/Header';
|
||||
|
@ -16,12 +16,12 @@ import ConfigPage from '../ConfigPage';
|
|||
|
||||
const App = () => {
|
||||
return (
|
||||
<CheckPagePermissions permissions={pluginPermissions.settings}>
|
||||
<Page.Protect permissions={pluginPermissions.settings}>
|
||||
<Provider store={store}>
|
||||
<Header />
|
||||
<ConfigPage />
|
||||
</Provider>
|
||||
</CheckPagePermissions>
|
||||
</Page.Protect>
|
||||
);
|
||||
};
|
||||
|
|
@ -3,30 +3,34 @@ import { useDispatch, useSelector } from 'react-redux';
|
|||
import { Map } from 'immutable';
|
||||
import {
|
||||
Box,
|
||||
ContentLayout,
|
||||
Alert,
|
||||
Typography,
|
||||
} from '@strapi/design-system';
|
||||
import { useNotification } from '@strapi/helper-plugin';
|
||||
import { useNotification } from '@strapi/strapi/admin';
|
||||
import { getFetchClient, Layouts } from '@strapi/admin/strapi-admin';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
||||
import { getAllConfigDiff, getAppEnv } from '../../state/actions/Config';
|
||||
import ConfigList from '../../components/ConfigList';
|
||||
import ActionButtons from '../../components/ActionButtons';
|
||||
|
||||
const ConfigPage = () => {
|
||||
const toggleNotification = useNotification();
|
||||
const { toggleNotification } = useNotification();
|
||||
const { get } = getFetchClient();
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const isLoading = useSelector((state) => state.getIn(['config', 'isLoading'], Map({})));
|
||||
const configDiff = useSelector((state) => state.getIn(['config', 'configDiff'], Map({})));
|
||||
const appEnv = useSelector((state) => state.getIn(['config', 'appEnv', 'env']));
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getAllConfigDiff(toggleNotification));
|
||||
dispatch(getAppEnv(toggleNotification));
|
||||
dispatch(getAllConfigDiff(toggleNotification, formatMessage, get));
|
||||
dispatch(getAppEnv(toggleNotification, formatMessage, get));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ContentLayout paddingBottom={8}>
|
||||
<Layouts.Content paddingBottom={8}>
|
||||
{appEnv === 'production' && (
|
||||
<Box paddingBottom={4}>
|
||||
<Alert variant="danger">
|
||||
|
@ -38,7 +42,7 @@ const ConfigPage = () => {
|
|||
)}
|
||||
<ActionButtons />
|
||||
<ConfigList isLoading={isLoading} diff={configDiff.toJS()} />
|
||||
</ContentLayout>
|
||||
</Layouts.Content>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
const pluginPkg = require('../../../package.json');
|
||||
import pluginPkg from '../../../package.json';
|
||||
|
||||
const pluginId = pluginPkg.name.replace(
|
||||
/^strapi-plugin-/i,
|
||||
'',
|
||||
);
|
||||
|
||||
module.exports = pluginId;
|
||||
export default pluginId;
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
const prefixPluginTranslations = (trad, pluginId) => {
|
||||
if (!pluginId) {
|
||||
throw new TypeError("pluginId can't be empty");
|
||||
}
|
||||
return Object.keys(trad).reduce((acc, current) => {
|
||||
acc[`${pluginId}.${current}`] = trad[current];
|
||||
return acc;
|
||||
}, {});
|
||||
};
|
||||
|
||||
export { prefixPluginTranslations };
|
|
@ -1,6 +1,6 @@
|
|||
import { prefixPluginTranslations } from '@strapi/helper-plugin';
|
||||
import pluginPkg from '../../package.json';
|
||||
import pluginId from './helpers/pluginId';
|
||||
import { prefixPluginTranslations } from './helpers/prefixPluginTranslations';
|
||||
import pluginPermissions from './permissions';
|
||||
// import pluginIcon from './components/PluginIcon';
|
||||
// import getTrad from './helpers/getTrad';
|
||||
|
|
|
@ -3,20 +3,18 @@
|
|||
* Main actions
|
||||
*
|
||||
*/
|
||||
|
||||
import { request } from '@strapi/helper-plugin';
|
||||
import { saveAs } from 'file-saver';
|
||||
|
||||
export function getAllConfigDiff(toggleNotification) {
|
||||
export function getAllConfigDiff(toggleNotification, formatMessage, get) {
|
||||
return async function(dispatch) {
|
||||
dispatch(setLoadingState(true));
|
||||
try {
|
||||
const configDiff = await request('/config-sync/diff', { method: 'GET' });
|
||||
const configDiff = await get('/config-sync/diff');
|
||||
dispatch(setConfigPartialDiffInState([]));
|
||||
dispatch(setConfigDiffInState(configDiff));
|
||||
dispatch(setConfigDiffInState(configDiff.data));
|
||||
dispatch(setLoadingState(false));
|
||||
} catch (err) {
|
||||
toggleNotification({ type: 'warning', message: { id: 'notification.error' } });
|
||||
toggleNotification({ type: 'warning', message: formatMessage({ id: 'notification.error' }) });
|
||||
dispatch(setLoadingState(false));
|
||||
}
|
||||
};
|
||||
|
@ -38,31 +36,26 @@ export function setConfigPartialDiffInState(config) {
|
|||
};
|
||||
}
|
||||
|
||||
export function exportAllConfig(partialDiff, toggleNotification) {
|
||||
export function exportAllConfig(partialDiff, toggleNotification, formatMessage, post, get) {
|
||||
return async function(dispatch) {
|
||||
dispatch(setLoadingState(true));
|
||||
try {
|
||||
const { message } = await request('/config-sync/export', {
|
||||
method: 'POST',
|
||||
body: partialDiff,
|
||||
});
|
||||
toggleNotification({ type: 'success', message });
|
||||
dispatch(getAllConfigDiff(toggleNotification));
|
||||
const response = await post('/config-sync/export', partialDiff);
|
||||
toggleNotification({ type: 'success', message: response.data.message });
|
||||
dispatch(getAllConfigDiff(toggleNotification, formatMessage, get));
|
||||
dispatch(setLoadingState(false));
|
||||
} catch (err) {
|
||||
toggleNotification({ type: 'warning', message: { id: 'notification.error' } });
|
||||
toggleNotification({ type: 'warning', message: formatMessage({ id: 'notification.error' }) });
|
||||
dispatch(setLoadingState(false));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function downloadZip(toggleNotification) {
|
||||
export function downloadZip(toggleNotification, formatMessage, post, get) {
|
||||
return async function (dispatch) {
|
||||
dispatch(setLoadingState(true));
|
||||
try {
|
||||
const { message, base64Data, name } = await request('/config-sync/zip', {
|
||||
method: 'GET'
|
||||
});
|
||||
const { message, base64Data, name } = await get('/config-sync/zip');
|
||||
toggleNotification({ type: 'success', message });
|
||||
if (base64Data) {
|
||||
function b64toBlob(dataURI) {
|
||||
|
@ -78,28 +71,25 @@ export function downloadZip(toggleNotification) {
|
|||
}
|
||||
dispatch(setLoadingState(false));
|
||||
} catch (err) {
|
||||
toggleNotification({ type: 'warning', message: { id: 'notification.error' } });
|
||||
toggleNotification({ type: 'warning', message: formatMessage({ id: 'notification.error' }) });
|
||||
dispatch(setLoadingState(false));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function importAllConfig(partialDiff, force, toggleNotification) {
|
||||
export function importAllConfig(partialDiff, force, toggleNotification, formatMessage, post, get) {
|
||||
return async function(dispatch) {
|
||||
dispatch(setLoadingState(true));
|
||||
try {
|
||||
const { message } = await request('/config-sync/import', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
const response = await post('/config-sync/import', {
|
||||
force,
|
||||
config: partialDiff,
|
||||
},
|
||||
});
|
||||
toggleNotification({ type: 'success', message });
|
||||
dispatch(getAllConfigDiff(toggleNotification));
|
||||
toggleNotification({ type: 'success', message: response.data.message });
|
||||
dispatch(getAllConfigDiff(toggleNotification, formatMessage, get));
|
||||
dispatch(setLoadingState(false));
|
||||
} catch (err) {
|
||||
toggleNotification({ type: 'warning', message: { id: 'notification.error' } });
|
||||
toggleNotification({ type: 'warning', message: formatMessage({ id: 'notification.error' }) });
|
||||
dispatch(setLoadingState(false));
|
||||
}
|
||||
};
|
||||
|
@ -113,15 +103,13 @@ export function setLoadingState(value) {
|
|||
};
|
||||
}
|
||||
|
||||
export function getAppEnv(toggleNotification) {
|
||||
export function getAppEnv(toggleNotification, formatMessage, get) {
|
||||
return async function(dispatch) {
|
||||
try {
|
||||
const envVars = await request('/config-sync/app-env', {
|
||||
method: 'GET',
|
||||
});
|
||||
dispatch(setAppEnvInState(envVars));
|
||||
const envVars = await get('/config-sync/app-env');
|
||||
dispatch(setAppEnvInState(envVars.data));
|
||||
} catch (err) {
|
||||
toggleNotification({ type: 'warning', message: { id: 'notification.error' } });
|
||||
toggleNotification({ type: 'warning', message: formatMessage({ id: 'notification.error' }) });
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -7,10 +7,32 @@
|
|||
"popUpWarning.button.export": "Yes, export",
|
||||
"popUpWarning.button.cancel": "Cancel",
|
||||
"popUpWarning.force": "Force",
|
||||
"popUpWarning.Confirmation": "Confirmation",
|
||||
|
||||
"Header.Title": "Config Sync",
|
||||
"Header.Description": "Manage your database config across environments.",
|
||||
|
||||
"ConfigList.Loading": "Loading content...",
|
||||
"ConfigList.SelectAll": "Select all entries",
|
||||
"ConfigList.ConfigName": "Config name",
|
||||
"ConfigList.ConfigType": "Config type",
|
||||
"ConfigList.State": "State",
|
||||
"ConfigList.Different": "Different",
|
||||
"ConfigList.OnlyDir": "Only in sync dir",
|
||||
"ConfigList.OnlyDB": "Only in DB",
|
||||
|
||||
"NoChanges.Message": "No differences between DB and sync directory. You are up-to-date!",
|
||||
|
||||
"ConfigDiff.Title": "Config changes for",
|
||||
"ConfigDiff.SyncDirectory": "Sync directory",
|
||||
"ConfigDiff.Database": "Database",
|
||||
|
||||
"Buttons.Export": "Export",
|
||||
"Buttons.Import": "Import",
|
||||
|
||||
"FirstExport.Message": "Looks like this is your first time using config-sync for this project.",
|
||||
"FirstExport.Button": "Make the initial export",
|
||||
|
||||
"Settings.Tool.Title": "Interface",
|
||||
|
||||
"plugin.name": "Config Sync"
|
||||
|
|
|
@ -1 +1,39 @@
|
|||
{}
|
||||
{
|
||||
"popUpWarning.warning.import_1": "Si continuas todos tus ficheros de configuración locales",
|
||||
"popUpWarning.warning.import_2": "se importarán a la base de datos.",
|
||||
"popUpWarning.warning.export_1": "Si continuas las configuraciones de tu base de datos",
|
||||
"popUpWarning.warning.export_2": "se escribirán en ficheros de configuración locales.",
|
||||
"popUpWarning.button.import": "Sí, importar",
|
||||
"popUpWarning.button.export": "Sí, exportar",
|
||||
"popUpWarning.button.cancel": "Cancelar",
|
||||
"popUpWarning.force": "Forzar",
|
||||
"popUpWarning.Confirmation": "Confirmación",
|
||||
|
||||
"Header.Title": "Config Sync",
|
||||
"Header.Description": "Gestiona las configuraciones de tu base de datos entre diferentes entornos o instancias.",
|
||||
|
||||
"ConfigList.Loading": "Cargando contenido...",
|
||||
"ConfigList.SelectAll": "Seleccionar todas las entradas",
|
||||
"ConfigList.ConfigName": "Nombre",
|
||||
"ConfigList.ConfigType": "Tipo",
|
||||
"ConfigList.State": "Estado",
|
||||
"ConfigList.Different": "Diferentes",
|
||||
"ConfigList.OnlyDir": "Sólo en directorio de sincronización",
|
||||
"ConfigList.OnlyDB": "Sólo en la base de datos",
|
||||
|
||||
"NoChanges.Message": "No hay diferencia entre la base de datos y el directorio de sincronización. ¡Estás actualizado!",
|
||||
|
||||
"ConfigDiff.Title": "Cambios en la configuración para",
|
||||
"ConfigDiff.SyncDirectory": "Directorio de sincronización",
|
||||
"ConfigDiff.Database": "Base de datos",
|
||||
|
||||
"Buttons.Import": "Importar",
|
||||
"Buttons.Export": "Exportar",
|
||||
|
||||
"FirstExport.Message": "Parece ser la primera vez que se usa config-sync en este proyecto.",
|
||||
"FirstExport.Button": "Hacer la exportación inicial",
|
||||
|
||||
"Settings.Tool.Title": "Interfaz",
|
||||
|
||||
"plugin.name": "Config Sync"
|
||||
}
|
41
package.json
41
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "strapi-plugin-config-sync",
|
||||
"version": "1.1.2",
|
||||
"version": "2.0.0-beta.4",
|
||||
"description": "Migrate your config data across environments using the CLI or Strapi admin panel.",
|
||||
"strapi": {
|
||||
"displayName": "Config Sync",
|
||||
|
@ -14,12 +14,15 @@
|
|||
"config-sync": "./bin/config-sync"
|
||||
},
|
||||
"scripts": {
|
||||
"develop": "nodemon -e js,jsx --ignore playground --exec \"yalc publish && yalc push\"",
|
||||
"build": "yalc push --publish",
|
||||
"eslint": "eslint --max-warnings=0 './**/*.{js,jsx}'",
|
||||
"eslint:fix": "eslint --fix './**/*.{js,jsx}'",
|
||||
"test:unit": "jest --verbose",
|
||||
"test:integration": "cd playground && node_modules/.bin/jest --verbose",
|
||||
"plugin:install": "yarn install && rm -rf node_modules/@strapi/helper-plugin",
|
||||
"playground:install": "cd playground && yarn install",
|
||||
"test:integration": "cd playground && node_modules/.bin/jest --verbose --forceExit --detectOpenHandles",
|
||||
"playground:install": "yarn playground:yalc-add-link && cd playground && yarn install",
|
||||
"playground:yalc-add": "cd playground && yalc add strapi-plugin-config-sync",
|
||||
"playground:yalc-add-link": "cd playground && yalc add --link strapi-plugin-config-sync",
|
||||
"playground:build": "cd playground && yarn build",
|
||||
"playground:develop": "cd playground && yarn develop"
|
||||
},
|
||||
|
@ -31,19 +34,20 @@
|
|||
"git-diff": "^2.0.6",
|
||||
"immutable": "^3.8.2",
|
||||
"inquirer": "^8.2.0",
|
||||
"react-diff-viewer-continued": "^3.2.3",
|
||||
"react-diff-viewer-continued": "3.2.6",
|
||||
"react-intl": "6.6.2",
|
||||
"redux-immutable": "^4.0.0",
|
||||
"redux-thunk": "^2.3.0"
|
||||
},
|
||||
"author": {
|
||||
"name": "Boaz Poolman",
|
||||
"email": "info@boazpoolman.nl",
|
||||
"email": "boaz@pluginpal.io",
|
||||
"url": "https://github.com/boazpoolman"
|
||||
},
|
||||
"maintainers": [
|
||||
{
|
||||
"name": "Boaz Poolman",
|
||||
"email": "info@boazpoolman.nl",
|
||||
"email": "boaz@pluginpal.io",
|
||||
"url": "https://github.com/boazpoolman"
|
||||
}
|
||||
],
|
||||
|
@ -55,13 +59,13 @@
|
|||
"strapi-server.js"
|
||||
],
|
||||
"peerDependencies": {
|
||||
"@strapi/strapi": "^4.0.0"
|
||||
"@strapi/strapi": "^5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@strapi/design-system": "^1.3.1",
|
||||
"@strapi/helper-plugin": "^4.5.5",
|
||||
"@strapi/icons": "^1.3.1",
|
||||
"@strapi/utils": "^4.5.2",
|
||||
"@strapi/design-system": "2.0.0-rc.11",
|
||||
"@strapi/icons": "2.0.0-rc.11",
|
||||
"@strapi/strapi": "5.0.4",
|
||||
"@strapi/utils": "5.0.4",
|
||||
"babel-eslint": "9.0.0",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-airbnb": "^18.2.1",
|
||||
|
@ -74,22 +78,23 @@
|
|||
"eslint-plugin-jsx-a11y": "^6.4.1",
|
||||
"eslint-plugin-react": "^7.21.5",
|
||||
"eslint-plugin-react-hooks": "^2.3.0",
|
||||
"jest": "^29.3.1",
|
||||
"jest": "^29.7.0",
|
||||
"jest-cli": "^29.3.1",
|
||||
"jest-styled-components": "^7.0.2",
|
||||
"lodash": "^4.17.11",
|
||||
"nodemon": "^3.1.7",
|
||||
"react": "^17.0.2",
|
||||
"react-intl": "^5.20.12",
|
||||
"react-redux": "^7.2.2",
|
||||
"redux": "^4.0.5",
|
||||
"styled-components": "^5.2.3"
|
||||
"styled-components": "^5.2.3",
|
||||
"yalc": "^1.0.0-pre.53"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/boazpoolman/strapi-plugin-config-sync/issues"
|
||||
"url": "https://github.com/pluginpal/strapi-plugin-config-sync/issues"
|
||||
},
|
||||
"homepage": "https://github.com/boazpoolman/strapi-plugin-config-sync#readme",
|
||||
"homepage": "https://www.pluginpal.io/plugin/config-sync",
|
||||
"engines": {
|
||||
"node": ">=10.0.0",
|
||||
"node": ">=18.0.0",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"license": "MIT",
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
HOST=0.0.0.0
|
||||
PORT=1337
|
||||
APP_KEYS=SIwLyqu+IpSHIuUBDQfPZg==,Nzqbq2C3ATsR19u5XEAJQA==,/Agk5Sn8M4EzfoSiIHcDlQ==,gSxT2T0k2zbQatKXUV0zCA==
|
||||
API_TOKEN_SALT=reQcUBbGXD2KWG2QpRn7DA==
|
||||
ADMIN_JWT_SECRET= 69mzgwRGfEBUhPEaas8EBA==
|
||||
JWT_SECRET=E0TTVdsr+M/FXAjfrNIgXA==
|
||||
APP_KEYS=ujfpKPEst1tv0WDxJEhjJw==,MOnFjWYKbWYmtrBZ3cQTFQ==,zQpX70tJw/Mw+Y656kXfVA==,xJT1vbsiz3cgabfgpLu72w==
|
||||
API_TOKEN_SALT=5FoJkYoZV8IA6+NnZJDzng==
|
||||
ADMIN_JWT_SECRET=tkeg3+HqE+QmTd2ITEivtA==
|
||||
TRANSFER_TOKEN_SALT=UUMCRQ2cx9qvKw/RkB815Q==
|
||||
# Database
|
||||
DATABASE_CLIENT=sqlite
|
||||
DATABASE_FILENAME=.tmp/data.db
|
||||
JWT_SECRET=Dn/nUGQsREUw4/lfQYOScw==
|
||||
|
|
|
@ -1,2 +1,7 @@
|
|||
HOST=0.0.0.0
|
||||
PORT=1337
|
||||
APP_KEYS="toBeModified1,toBeModified2"
|
||||
API_TOKEN_SALT=tobemodified
|
||||
ADMIN_JWT_SECRET=tobemodified
|
||||
TRANSFER_TOKEN_SALT=tobemodified
|
||||
JWT_SECRET=tobemodified
|
||||
|
|
|
@ -114,3 +114,7 @@ exports
|
|||
*.cache
|
||||
build
|
||||
.strapi-updater.json
|
||||
|
||||
# yalc
|
||||
.yalc
|
||||
yalc.lock
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
/**
|
||||
* This file was automatically generated by Strapi.
|
||||
* Any modifications made will be discarded.
|
||||
*/
|
||||
import strapiCloud from "@strapi/plugin-cloud/strapi-admin";
|
||||
import usersPermissions from "@strapi/plugin-users-permissions/strapi-admin";
|
||||
import configSync from "strapi-plugin-config-sync/strapi-admin";
|
||||
import { renderAdmin } from "@strapi/strapi/admin";
|
||||
|
||||
renderAdmin(document.getElementById("strapi"), {
|
||||
plugins: {
|
||||
"strapi-cloud": strapiCloud,
|
||||
"users-permissions": usersPermissions,
|
||||
"config-sync": configSync,
|
||||
},
|
||||
});
|
|
@ -0,0 +1,63 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<!--
|
||||
This file was automatically generated by Strapi.
|
||||
Any modifications made will be discarded.
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, viewport-fit=cover"
|
||||
/>
|
||||
<meta name="robots" content="noindex" />
|
||||
<meta name="referrer" content="same-origin" />
|
||||
<title>Strapi Admin</title>
|
||||
<style>
|
||||
html,
|
||||
body,
|
||||
#strapi {
|
||||
height: 100%;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="strapi"></div>
|
||||
<noscript
|
||||
><div class="strapi--root">
|
||||
<div class="strapi--no-js">
|
||||
<style type="text/css">
|
||||
.strapi--root {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.strapi--no-js {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
text-align: center;
|
||||
font-family: helvetica, arial, sans-serif;
|
||||
}
|
||||
</style>
|
||||
<h1>JavaScript disabled</h1>
|
||||
<p>
|
||||
Please
|
||||
<a href="https://www.enable-javascript.com/">enable JavaScript</a>
|
||||
in your browser and reload the page to proceed.
|
||||
</p>
|
||||
</div>
|
||||
</div></noscript
|
||||
>
|
||||
<script type="module" src="/.strapi/client/app.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,3 +1,57 @@
|
|||
# Strapi application
|
||||
# 🚀 Getting started with Strapi
|
||||
|
||||
A quick description of your strapi application
|
||||
Strapi comes with a full featured [Command Line Interface](https://docs.strapi.io/dev-docs/cli) (CLI) which lets you scaffold and manage your project in seconds.
|
||||
|
||||
### `develop`
|
||||
|
||||
Start your Strapi application with autoReload enabled. [Learn more](https://docs.strapi.io/dev-docs/cli#strapi-develop)
|
||||
|
||||
```
|
||||
npm run develop
|
||||
# or
|
||||
yarn develop
|
||||
```
|
||||
|
||||
### `start`
|
||||
|
||||
Start your Strapi application with autoReload disabled. [Learn more](https://docs.strapi.io/dev-docs/cli#strapi-start)
|
||||
|
||||
```
|
||||
npm run start
|
||||
# or
|
||||
yarn start
|
||||
```
|
||||
|
||||
### `build`
|
||||
|
||||
Build your admin panel. [Learn more](https://docs.strapi.io/dev-docs/cli#strapi-build)
|
||||
|
||||
```
|
||||
npm run build
|
||||
# or
|
||||
yarn build
|
||||
```
|
||||
|
||||
## ⚙️ Deployment
|
||||
|
||||
Strapi gives you many possible deployment options for your project including [Strapi Cloud](https://cloud.strapi.io). Browse the [deployment section of the documentation](https://docs.strapi.io/dev-docs/deployment) to find the best solution for your use case.
|
||||
|
||||
## 📚 Learn more
|
||||
|
||||
- [Resource center](https://strapi.io/resource-center) - Strapi resource center.
|
||||
- [Strapi documentation](https://docs.strapi.io) - Official Strapi documentation.
|
||||
- [Strapi tutorials](https://strapi.io/tutorials) - List of tutorials made by the core team and the community.
|
||||
- [Strapi blog](https://strapi.io/blog) - Official Strapi blog containing articles made by the Strapi team and the community.
|
||||
- [Changelog](https://strapi.io/changelog) - Find out about the Strapi product updates, new features and general improvements.
|
||||
|
||||
Feel free to check out the [Strapi GitHub repository](https://github.com/strapi/strapi). Your feedback and contributions are welcome!
|
||||
|
||||
## ✨ Community
|
||||
|
||||
- [Discord](https://discord.strapi.io) - Come chat with the Strapi community including the core team.
|
||||
- [Forum](https://forum.strapi.io/) - Place to discuss, ask questions and find answers, show your Strapi project and get feedback or just talk with other Community members.
|
||||
- [Awesome Strapi](https://github.com/strapi/awesome-strapi) - A curated list of awesome things related to Strapi.
|
||||
|
||||
---
|
||||
|
||||
<sub>🤫 Psst! [Strapi is hiring](https://strapi.io/careers).</sub>
|
||||
|
|
|
@ -6,7 +6,6 @@ const exec = util.promisify(require('child_process').exec);
|
|||
jest.setTimeout(20000);
|
||||
|
||||
describe('Test the config-sync CLI', () => {
|
||||
|
||||
afterAll(async () => {
|
||||
// Remove the generated files and the DB.
|
||||
await exec('rm -rf config/sync');
|
||||
|
@ -14,20 +13,41 @@ describe('Test the config-sync CLI', () => {
|
|||
});
|
||||
|
||||
test('Export', async () => {
|
||||
const { stdout } = await exec('yarn cs export -y');
|
||||
expect(stdout).toContain('Finished export');
|
||||
const { stdout: exportOutput } = await exec('yarn cs export -y');
|
||||
expect(exportOutput).toContain('Finished export');
|
||||
const { stdout: diffOutput } = await exec('yarn cs diff');
|
||||
expect(diffOutput).toContain('No differences between DB and sync directory');
|
||||
});
|
||||
test('Import', async () => {
|
||||
await exec('rm -rf config/sync/admin-role.strapi-editor.json');
|
||||
const { stdout } = await exec('yarn cs import -y');
|
||||
expect(stdout).toContain('Finished import');
|
||||
|
||||
test('Import (delete)', async () => {
|
||||
// Remove a file to trigger a delete.
|
||||
await exec('mv config/sync/admin-role.strapi-editor.json .tmp');
|
||||
const { stdout: importOutput } = await exec('yarn cs import -y');
|
||||
expect(importOutput).toContain('Finished import');
|
||||
const { stdout: diffOutput } = await exec('yarn cs diff');
|
||||
expect(diffOutput).toContain('No differences between DB and sync directory');
|
||||
});
|
||||
test('Diff', async () => {
|
||||
const { stdout } = await exec('yarn cs diff');
|
||||
expect(stdout).toContain('No differences between DB and sync directory');
|
||||
test('Import (update)', async () => {
|
||||
// Update a core-store file.
|
||||
await exec('sed -i \'s/"description":"",/"description":"test",/g\' config/sync/core-store.plugin_content_manager_configuration_content_types##plugin##users-permissions.user.json');
|
||||
// Update a file that has relations.
|
||||
await exec('sed -i \'s/{"action":"plugin::users-permissions.auth.register"},//g\' config/sync/user-role.public.json');
|
||||
const { stdout: importOutput } = await exec('yarn cs import -y');
|
||||
expect(importOutput).toContain('Finished import');
|
||||
const { stdout: diffOutput } = await exec('yarn cs diff');
|
||||
expect(diffOutput).toContain('No differences between DB and sync directory');
|
||||
});
|
||||
test('Import (create)', async () => {
|
||||
// Add a file to trigger a creation.
|
||||
await exec('mv .tmp/admin-role.strapi-editor.json config/sync/');
|
||||
const { stdout: importOutput } = await exec('yarn cs import -y');
|
||||
expect(importOutput).toContain('Finished import');
|
||||
const { stdout: diffOutput } = await exec('yarn cs diff');
|
||||
expect(diffOutput).toContain('No differences between DB and sync directory');
|
||||
});
|
||||
|
||||
test('Non-empty diff returns 1', async () => {
|
||||
await exec('rm -rf config/sync/admin-role.strapi-author.json');
|
||||
await exec('rm -rf config/sync/admin-role.strapi-editor.json');
|
||||
// Work around Jest not supporting custom error matching.
|
||||
// https://github.com/facebook/jest/issues/8140
|
||||
let error;
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
const fs = require('fs');
|
||||
const { createStrapi, compileStrapi } = require('@strapi/strapi');
|
||||
|
||||
let instance;
|
||||
|
||||
async function setupStrapi() {
|
||||
if (!instance) {
|
||||
const appContext = await compileStrapi();
|
||||
await createStrapi(appContext).load();
|
||||
instance = strapi;
|
||||
|
||||
await instance.server.mount();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
async function cleanupStrapi() {
|
||||
const dbSettings = strapi.config.get('database.connection');
|
||||
|
||||
// close server to release the db-file.
|
||||
await strapi.server.httpServer.close();
|
||||
|
||||
// close the connection to the database before deletion.
|
||||
await strapi.db.connection.destroy();
|
||||
|
||||
// delete test database after all tests have completed.
|
||||
if (dbSettings && dbSettings.connection && dbSettings.connection.filename) {
|
||||
const tmpDbFile = dbSettings.connection.filename;
|
||||
if (fs.existsSync(tmpDbFile)) {
|
||||
fs.unlinkSync(tmpDbFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { setupStrapi, cleanupStrapi };
|
|
@ -0,0 +1,48 @@
|
|||
const util = require('util');
|
||||
const exec = util.promisify(require('child_process').exec);
|
||||
|
||||
const { setupStrapi, cleanupStrapi } = require('./helpers');
|
||||
|
||||
jest.setTimeout(20000);
|
||||
|
||||
afterEach(async () => {
|
||||
// Disable importOnBootstrap
|
||||
await exec('sed -i "s/importOnBootstrap: true/importOnBootstrap: false/g" config/plugins.js');
|
||||
|
||||
await cleanupStrapi();
|
||||
await exec('rm -rf config/sync');
|
||||
});
|
||||
|
||||
describe('Test the importOnBootstrap feature', () => {
|
||||
test('Without a database', async () => {
|
||||
// Do the initial export and remove the database.
|
||||
await exec('yarn cs export -y');
|
||||
await exec('rm -rf .tmp');
|
||||
|
||||
// Manually change the plugins.js to enable importOnBoostrap.
|
||||
await exec('sed -i "s/importOnBootstrap: false/importOnBootstrap: true/g" config/plugins.js');
|
||||
|
||||
// Start up Strapi to initiate the importOnBootstrap function.
|
||||
await setupStrapi();
|
||||
|
||||
expect(strapi).toBeDefined();
|
||||
});
|
||||
|
||||
test('With a database', async () => {
|
||||
// Delete any existing database and do an export.
|
||||
await exec('rm -rf .tmp');
|
||||
await exec('yarn cs export -y');
|
||||
|
||||
// Manually change the plugins.js to enable importOnBoostrap.
|
||||
await exec('sed -i "s/importOnBootstrap: false/importOnBootstrap: true/g" config/plugins.js');
|
||||
|
||||
// Remove a config file to make sure the importOnBoostrap
|
||||
// function actually attempts to import.
|
||||
await exec('rm -rf config/sync/admin-role.strapi-editor.json');
|
||||
|
||||
// Start up Strapi to initiate the importOnBootstrap function.
|
||||
await setupStrapi();
|
||||
|
||||
expect(strapi).toBeDefined();
|
||||
});
|
||||
});
|
|
@ -1,8 +1,20 @@
|
|||
module.exports = ({ env }) => ({
|
||||
auth: {
|
||||
secret: env('ADMIN_JWT_SECRET', 'c27c3833823a12b0761e32b22dc0113a'),
|
||||
secret: env('ADMIN_JWT_SECRET'),
|
||||
},
|
||||
apiToken: {
|
||||
salt: env('API_TOKEN_SALT'),
|
||||
},
|
||||
transfer: {
|
||||
token: {
|
||||
salt: env('TRANSFER_TOKEN_SALT'),
|
||||
},
|
||||
},
|
||||
flags: {
|
||||
nps: env.bool('FLAG_NPS', true),
|
||||
promoteEE: env.bool('FLAG_PROMOTE_EE', true),
|
||||
},
|
||||
watchIgnoreFiles: [
|
||||
'**/config/sync/**',
|
||||
],
|
||||
'!**/.yalc/**/server/**',
|
||||
]
|
||||
});
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
module.exports = [
|
||||
'strapi::logger',
|
||||
'strapi::errors',
|
||||
'strapi::security',
|
||||
'strapi::cors',
|
||||
'strapi::poweredBy',
|
||||
'strapi::logger',
|
||||
'strapi::query',
|
||||
'strapi::body',
|
||||
'strapi::session',
|
||||
'strapi::favicon',
|
||||
'strapi::public',
|
||||
];
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
module.exports = {
|
||||
'config-sync': {
|
||||
enabled: true,
|
||||
config: {
|
||||
importOnBootstrap: false,
|
||||
minify: true,
|
||||
},
|
||||
},
|
||||
};
|
|
@ -1,4 +1,10 @@
|
|||
module.exports = ({ env }) => ({
|
||||
host: env('HOST', '0.0.0.0'),
|
||||
port: env.int('PORT', 1337),
|
||||
app: {
|
||||
keys: env.array('APP_KEYS'),
|
||||
},
|
||||
webhooks: {
|
||||
populateRelations: env.bool('WEBHOOKS_POPULATE_RELATIONS', false),
|
||||
},
|
||||
});
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 3.6 KiB |
Binary file not shown.
After Width: | Height: | Size: 497 B |
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "nodenext",
|
||||
"target": "ES2021",
|
||||
"checkJs": true,
|
||||
"allowJs": true
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "playground",
|
||||
"name": "strapi-5-beta",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"description": "A Strapi application",
|
||||
|
@ -11,24 +11,30 @@
|
|||
"cs": "config-sync"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "^26.0.1",
|
||||
"jest-cli": "^26.0.1"
|
||||
"jest": "^29.7.0",
|
||||
"jest-cli": "^29.7.0",
|
||||
"supertest": "^6.3.3",
|
||||
"yalc": "^1.0.0-pre.53"
|
||||
},
|
||||
"dependencies": {
|
||||
"@strapi/plugin-i18n": "^4.0.0",
|
||||
"@strapi/plugin-users-permissions": "^4.0.0",
|
||||
"@strapi/strapi": "^4.0.0",
|
||||
"better-sqlite3": "7.4.6",
|
||||
"strapi-plugin-config-sync": "./.."
|
||||
"@strapi/plugin-cloud": "5.0.4",
|
||||
"@strapi/plugin-users-permissions": "5.0.4",
|
||||
"@strapi/strapi": "5.0.4",
|
||||
"better-sqlite3": "9.4.3",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0",
|
||||
"react-router-dom": "^6.0.0",
|
||||
"strapi-plugin-config-sync": "link:.yalc/strapi-plugin-config-sync",
|
||||
"styled-components": "^6.0.0"
|
||||
},
|
||||
"author": {
|
||||
"name": "A Strapi developer"
|
||||
},
|
||||
"strapi": {
|
||||
"uuid": "2e84e366-1e09-43c2-a99f-a0d0acbc2ca5"
|
||||
"uuid": "edadddbd-0f25-4da7-833b-d4cd7dcae2fc"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.x.x <=18.x.x",
|
||||
"node": ">=18.0.0 <=20.x.x",
|
||||
"npm": ">=6.0.0"
|
||||
},
|
||||
"license": "MIT"
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"kind": "singleType",
|
||||
"collectionName": "homes",
|
||||
"info": {
|
||||
"singularName": "home",
|
||||
"pluralName": "homes",
|
||||
"displayName": "Home",
|
||||
"description": ""
|
||||
},
|
||||
"options": {
|
||||
"draftAndPublish": false
|
||||
},
|
||||
"pluginOptions": {},
|
||||
"attributes": {
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"slug": {
|
||||
"type": "uid",
|
||||
"targetField": "title"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* home controller
|
||||
*/
|
||||
|
||||
const { createCoreController } = require('@strapi/strapi').factories;
|
||||
|
||||
module.exports = createCoreController('api::home.home');
|
|
@ -0,0 +1,9 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* home router
|
||||
*/
|
||||
|
||||
const { createCoreRouter } = require('@strapi/strapi').factories;
|
||||
|
||||
module.exports = createCoreRouter('api::home.home');
|
|
@ -0,0 +1,9 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* home service
|
||||
*/
|
||||
|
||||
const { createCoreService } = require('@strapi/strapi').factories;
|
||||
|
||||
module.exports = createCoreService('api::home.home');
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"kind": "collectionType",
|
||||
"collectionName": "pages",
|
||||
"info": {
|
||||
"singularName": "page",
|
||||
"pluralName": "pages",
|
||||
"displayName": "Page"
|
||||
},
|
||||
"options": {
|
||||
"draftAndPublish": false
|
||||
},
|
||||
"pluginOptions": {},
|
||||
"attributes": {
|
||||
"title": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* page controller
|
||||
*/
|
||||
|
||||
const { createCoreController } = require('@strapi/strapi').factories;
|
||||
|
||||
module.exports = createCoreController('api::page.page');
|
|
@ -0,0 +1,9 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* page router
|
||||
*/
|
||||
|
||||
const { createCoreRouter } = require('@strapi/strapi').factories;
|
||||
|
||||
module.exports = createCoreRouter('api::page.page');
|
|
@ -0,0 +1,9 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* page service
|
||||
*/
|
||||
|
||||
const { createCoreService } = require('@strapi/strapi').factories;
|
||||
|
||||
module.exports = createCoreService('api::page.page');
|
|
@ -0,0 +1,3 @@
|
|||
/*
|
||||
* The app doesn't have any components yet.
|
||||
*/
|
|
@ -0,0 +1,924 @@
|
|||
import type { Struct, Schema } from '@strapi/strapi';
|
||||
|
||||
export interface ApiHomeHome extends Struct.SingleTypeSchema {
|
||||
collectionName: 'homes';
|
||||
info: {
|
||||
singularName: 'home';
|
||||
pluralName: 'homes';
|
||||
displayName: 'Home';
|
||||
description: '';
|
||||
};
|
||||
options: {
|
||||
draftAndPublish: false;
|
||||
};
|
||||
attributes: {
|
||||
title: Schema.Attribute.String;
|
||||
slug: Schema.Attribute.UID<'title'>;
|
||||
createdAt: Schema.Attribute.DateTime;
|
||||
updatedAt: Schema.Attribute.DateTime;
|
||||
publishedAt: Schema.Attribute.DateTime;
|
||||
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
|
||||
Schema.Attribute.Private;
|
||||
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
|
||||
Schema.Attribute.Private;
|
||||
locale: Schema.Attribute.String;
|
||||
localizations: Schema.Attribute.Relation<'oneToMany', 'api::home.home'>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ApiPagePage extends Struct.CollectionTypeSchema {
|
||||
collectionName: 'pages';
|
||||
info: {
|
||||
singularName: 'page';
|
||||
pluralName: 'pages';
|
||||
displayName: 'Page';
|
||||
};
|
||||
options: {
|
||||
draftAndPublish: false;
|
||||
};
|
||||
attributes: {
|
||||
title: Schema.Attribute.String & Schema.Attribute.Required;
|
||||
createdAt: Schema.Attribute.DateTime;
|
||||
updatedAt: Schema.Attribute.DateTime;
|
||||
publishedAt: Schema.Attribute.DateTime;
|
||||
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
|
||||
Schema.Attribute.Private;
|
||||
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
|
||||
Schema.Attribute.Private;
|
||||
locale: Schema.Attribute.String;
|
||||
localizations: Schema.Attribute.Relation<'oneToMany', 'api::page.page'>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface PluginUploadFile extends Struct.CollectionTypeSchema {
|
||||
collectionName: 'files';
|
||||
info: {
|
||||
singularName: 'file';
|
||||
pluralName: 'files';
|
||||
displayName: 'File';
|
||||
description: '';
|
||||
};
|
||||
options: {
|
||||
draftAndPublish: false;
|
||||
};
|
||||
pluginOptions: {
|
||||
'content-manager': {
|
||||
visible: false;
|
||||
};
|
||||
'content-type-builder': {
|
||||
visible: false;
|
||||
};
|
||||
};
|
||||
attributes: {
|
||||
name: Schema.Attribute.String & Schema.Attribute.Required;
|
||||
alternativeText: Schema.Attribute.String;
|
||||
caption: Schema.Attribute.String;
|
||||
width: Schema.Attribute.Integer;
|
||||
height: Schema.Attribute.Integer;
|
||||
formats: Schema.Attribute.JSON;
|
||||
hash: Schema.Attribute.String & Schema.Attribute.Required;
|
||||
ext: Schema.Attribute.String;
|
||||
mime: Schema.Attribute.String & Schema.Attribute.Required;
|
||||
size: Schema.Attribute.Decimal & Schema.Attribute.Required;
|
||||
url: Schema.Attribute.String & Schema.Attribute.Required;
|
||||
previewUrl: Schema.Attribute.String;
|
||||
provider: Schema.Attribute.String & Schema.Attribute.Required;
|
||||
provider_metadata: Schema.Attribute.JSON;
|
||||
related: Schema.Attribute.Relation<'morphToMany'>;
|
||||
folder: Schema.Attribute.Relation<'manyToOne', 'plugin::upload.folder'> &
|
||||
Schema.Attribute.Private;
|
||||
folderPath: Schema.Attribute.String &
|
||||
Schema.Attribute.Required &
|
||||
Schema.Attribute.Private &
|
||||
Schema.Attribute.SetMinMaxLength<{
|
||||
minLength: 1;
|
||||
}>;
|
||||
createdAt: Schema.Attribute.DateTime;
|
||||
updatedAt: Schema.Attribute.DateTime;
|
||||
publishedAt: Schema.Attribute.DateTime;
|
||||
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
|
||||
Schema.Attribute.Private;
|
||||
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
|
||||
Schema.Attribute.Private;
|
||||
locale: Schema.Attribute.String;
|
||||
localizations: Schema.Attribute.Relation<
|
||||
'oneToMany',
|
||||
'plugin::upload.file'
|
||||
>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface PluginUploadFolder extends Struct.CollectionTypeSchema {
|
||||
collectionName: 'upload_folders';
|
||||
info: {
|
||||
singularName: 'folder';
|
||||
pluralName: 'folders';
|
||||
displayName: 'Folder';
|
||||
};
|
||||
options: {
|
||||
draftAndPublish: false;
|
||||
};
|
||||
pluginOptions: {
|
||||
'content-manager': {
|
||||
visible: false;
|
||||
};
|
||||
'content-type-builder': {
|
||||
visible: false;
|
||||
};
|
||||
};
|
||||
attributes: {
|
||||
name: Schema.Attribute.String &
|
||||
Schema.Attribute.Required &
|
||||
Schema.Attribute.SetMinMaxLength<{
|
||||
minLength: 1;
|
||||
}>;
|
||||
pathId: Schema.Attribute.Integer &
|
||||
Schema.Attribute.Required &
|
||||
Schema.Attribute.Unique;
|
||||
parent: Schema.Attribute.Relation<'manyToOne', 'plugin::upload.folder'>;
|
||||
children: Schema.Attribute.Relation<'oneToMany', 'plugin::upload.folder'>;
|
||||
files: Schema.Attribute.Relation<'oneToMany', 'plugin::upload.file'>;
|
||||
path: Schema.Attribute.String &
|
||||
Schema.Attribute.Required &
|
||||
Schema.Attribute.SetMinMaxLength<{
|
||||
minLength: 1;
|
||||
}>;
|
||||
createdAt: Schema.Attribute.DateTime;
|
||||
updatedAt: Schema.Attribute.DateTime;
|
||||
publishedAt: Schema.Attribute.DateTime;
|
||||
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
|
||||
Schema.Attribute.Private;
|
||||
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
|
||||
Schema.Attribute.Private;
|
||||
locale: Schema.Attribute.String;
|
||||
localizations: Schema.Attribute.Relation<
|
||||
'oneToMany',
|
||||
'plugin::upload.folder'
|
||||
>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface PluginI18NLocale extends Struct.CollectionTypeSchema {
|
||||
collectionName: 'i18n_locale';
|
||||
info: {
|
||||
singularName: 'locale';
|
||||
pluralName: 'locales';
|
||||
collectionName: 'locales';
|
||||
displayName: 'Locale';
|
||||
description: '';
|
||||
};
|
||||
options: {
|
||||
draftAndPublish: false;
|
||||
};
|
||||
pluginOptions: {
|
||||
'content-manager': {
|
||||
visible: false;
|
||||
};
|
||||
'content-type-builder': {
|
||||
visible: false;
|
||||
};
|
||||
};
|
||||
attributes: {
|
||||
name: Schema.Attribute.String &
|
||||
Schema.Attribute.SetMinMax<
|
||||
{
|
||||
min: 1;
|
||||
max: 50;
|
||||
},
|
||||
number
|
||||
>;
|
||||
code: Schema.Attribute.String & Schema.Attribute.Unique;
|
||||
createdAt: Schema.Attribute.DateTime;
|
||||
updatedAt: Schema.Attribute.DateTime;
|
||||
publishedAt: Schema.Attribute.DateTime;
|
||||
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
|
||||
Schema.Attribute.Private;
|
||||
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
|
||||
Schema.Attribute.Private;
|
||||
locale: Schema.Attribute.String;
|
||||
localizations: Schema.Attribute.Relation<
|
||||
'oneToMany',
|
||||
'plugin::i18n.locale'
|
||||
>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface PluginContentReleasesRelease
|
||||
extends Struct.CollectionTypeSchema {
|
||||
collectionName: 'strapi_releases';
|
||||
info: {
|
||||
singularName: 'release';
|
||||
pluralName: 'releases';
|
||||
displayName: 'Release';
|
||||
};
|
||||
options: {
|
||||
draftAndPublish: false;
|
||||
};
|
||||
pluginOptions: {
|
||||
'content-manager': {
|
||||
visible: false;
|
||||
};
|
||||
'content-type-builder': {
|
||||
visible: false;
|
||||
};
|
||||
};
|
||||
attributes: {
|
||||
name: Schema.Attribute.String & Schema.Attribute.Required;
|
||||
releasedAt: Schema.Attribute.DateTime;
|
||||
scheduledAt: Schema.Attribute.DateTime;
|
||||
timezone: Schema.Attribute.String;
|
||||
status: Schema.Attribute.Enumeration<
|
||||
['ready', 'blocked', 'failed', 'done', 'empty']
|
||||
> &
|
||||
Schema.Attribute.Required;
|
||||
actions: Schema.Attribute.Relation<
|
||||
'oneToMany',
|
||||
'plugin::content-releases.release-action'
|
||||
>;
|
||||
createdAt: Schema.Attribute.DateTime;
|
||||
updatedAt: Schema.Attribute.DateTime;
|
||||
publishedAt: Schema.Attribute.DateTime;
|
||||
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
|
||||
Schema.Attribute.Private;
|
||||
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
|
||||
Schema.Attribute.Private;
|
||||
locale: Schema.Attribute.String;
|
||||
localizations: Schema.Attribute.Relation<
|
||||
'oneToMany',
|
||||
'plugin::content-releases.release'
|
||||
>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface PluginContentReleasesReleaseAction
|
||||
extends Struct.CollectionTypeSchema {
|
||||
collectionName: 'strapi_release_actions';
|
||||
info: {
|
||||
singularName: 'release-action';
|
||||
pluralName: 'release-actions';
|
||||
displayName: 'Release Action';
|
||||
};
|
||||
options: {
|
||||
draftAndPublish: false;
|
||||
};
|
||||
pluginOptions: {
|
||||
'content-manager': {
|
||||
visible: false;
|
||||
};
|
||||
'content-type-builder': {
|
||||
visible: false;
|
||||
};
|
||||
};
|
||||
attributes: {
|
||||
type: Schema.Attribute.Enumeration<['publish', 'unpublish']> &
|
||||
Schema.Attribute.Required;
|
||||
contentType: Schema.Attribute.String & Schema.Attribute.Required;
|
||||
entryDocumentId: Schema.Attribute.String;
|
||||
locale: Schema.Attribute.String;
|
||||
release: Schema.Attribute.Relation<
|
||||
'manyToOne',
|
||||
'plugin::content-releases.release'
|
||||
>;
|
||||
isEntryValid: Schema.Attribute.Boolean;
|
||||
createdAt: Schema.Attribute.DateTime;
|
||||
updatedAt: Schema.Attribute.DateTime;
|
||||
publishedAt: Schema.Attribute.DateTime;
|
||||
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
|
||||
Schema.Attribute.Private;
|
||||
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
|
||||
Schema.Attribute.Private;
|
||||
localizations: Schema.Attribute.Relation<
|
||||
'oneToMany',
|
||||
'plugin::content-releases.release-action'
|
||||
>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface PluginReviewWorkflowsWorkflow
|
||||
extends Struct.CollectionTypeSchema {
|
||||
collectionName: 'strapi_workflows';
|
||||
info: {
|
||||
name: 'Workflow';
|
||||
description: '';
|
||||
singularName: 'workflow';
|
||||
pluralName: 'workflows';
|
||||
displayName: 'Workflow';
|
||||
};
|
||||
options: {
|
||||
draftAndPublish: false;
|
||||
};
|
||||
pluginOptions: {
|
||||
'content-manager': {
|
||||
visible: false;
|
||||
};
|
||||
'content-type-builder': {
|
||||
visible: false;
|
||||
};
|
||||
};
|
||||
attributes: {
|
||||
name: Schema.Attribute.String &
|
||||
Schema.Attribute.Required &
|
||||
Schema.Attribute.Unique;
|
||||
stages: Schema.Attribute.Relation<
|
||||
'oneToMany',
|
||||
'plugin::review-workflows.workflow-stage'
|
||||
>;
|
||||
contentTypes: Schema.Attribute.JSON &
|
||||
Schema.Attribute.Required &
|
||||
Schema.Attribute.DefaultTo<'[]'>;
|
||||
createdAt: Schema.Attribute.DateTime;
|
||||
updatedAt: Schema.Attribute.DateTime;
|
||||
publishedAt: Schema.Attribute.DateTime;
|
||||
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
|
||||
Schema.Attribute.Private;
|
||||
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
|
||||
Schema.Attribute.Private;
|
||||
locale: Schema.Attribute.String;
|
||||
localizations: Schema.Attribute.Relation<
|
||||
'oneToMany',
|
||||
'plugin::review-workflows.workflow'
|
||||
>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface PluginReviewWorkflowsWorkflowStage
|
||||
extends Struct.CollectionTypeSchema {
|
||||
collectionName: 'strapi_workflows_stages';
|
||||
info: {
|
||||
name: 'Workflow Stage';
|
||||
description: '';
|
||||
singularName: 'workflow-stage';
|
||||
pluralName: 'workflow-stages';
|
||||
displayName: 'Stages';
|
||||
};
|
||||
options: {
|
||||
version: '1.1.0';
|
||||
draftAndPublish: false;
|
||||
};
|
||||
pluginOptions: {
|
||||
'content-manager': {
|
||||
visible: false;
|
||||
};
|
||||
'content-type-builder': {
|
||||
visible: false;
|
||||
};
|
||||
};
|
||||
attributes: {
|
||||
name: Schema.Attribute.String;
|
||||
color: Schema.Attribute.String & Schema.Attribute.DefaultTo<'#4945FF'>;
|
||||
workflow: Schema.Attribute.Relation<
|
||||
'manyToOne',
|
||||
'plugin::review-workflows.workflow'
|
||||
>;
|
||||
permissions: Schema.Attribute.Relation<'manyToMany', 'admin::permission'>;
|
||||
createdAt: Schema.Attribute.DateTime;
|
||||
updatedAt: Schema.Attribute.DateTime;
|
||||
publishedAt: Schema.Attribute.DateTime;
|
||||
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
|
||||
Schema.Attribute.Private;
|
||||
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
|
||||
Schema.Attribute.Private;
|
||||
locale: Schema.Attribute.String;
|
||||
localizations: Schema.Attribute.Relation<
|
||||
'oneToMany',
|
||||
'plugin::review-workflows.workflow-stage'
|
||||
>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface PluginUsersPermissionsPermission
|
||||
extends Struct.CollectionTypeSchema {
|
||||
collectionName: 'up_permissions';
|
||||
info: {
|
||||
name: 'permission';
|
||||
description: '';
|
||||
singularName: 'permission';
|
||||
pluralName: 'permissions';
|
||||
displayName: 'Permission';
|
||||
};
|
||||
options: {
|
||||
draftAndPublish: false;
|
||||
};
|
||||
pluginOptions: {
|
||||
'content-manager': {
|
||||
visible: false;
|
||||
};
|
||||
'content-type-builder': {
|
||||
visible: false;
|
||||
};
|
||||
};
|
||||
attributes: {
|
||||
action: Schema.Attribute.String & Schema.Attribute.Required;
|
||||
role: Schema.Attribute.Relation<
|
||||
'manyToOne',
|
||||
'plugin::users-permissions.role'
|
||||
>;
|
||||
createdAt: Schema.Attribute.DateTime;
|
||||
updatedAt: Schema.Attribute.DateTime;
|
||||
publishedAt: Schema.Attribute.DateTime;
|
||||
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
|
||||
Schema.Attribute.Private;
|
||||
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
|
||||
Schema.Attribute.Private;
|
||||
locale: Schema.Attribute.String;
|
||||
localizations: Schema.Attribute.Relation<
|
||||
'oneToMany',
|
||||
'plugin::users-permissions.permission'
|
||||
>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface PluginUsersPermissionsRole
|
||||
extends Struct.CollectionTypeSchema {
|
||||
collectionName: 'up_roles';
|
||||
info: {
|
||||
name: 'role';
|
||||
description: '';
|
||||
singularName: 'role';
|
||||
pluralName: 'roles';
|
||||
displayName: 'Role';
|
||||
};
|
||||
options: {
|
||||
draftAndPublish: false;
|
||||
};
|
||||
pluginOptions: {
|
||||
'content-manager': {
|
||||
visible: false;
|
||||
};
|
||||
'content-type-builder': {
|
||||
visible: false;
|
||||
};
|
||||
};
|
||||
attributes: {
|
||||
name: Schema.Attribute.String &
|
||||
Schema.Attribute.Required &
|
||||
Schema.Attribute.SetMinMaxLength<{
|
||||
minLength: 3;
|
||||
}>;
|
||||
description: Schema.Attribute.String;
|
||||
type: Schema.Attribute.String & Schema.Attribute.Unique;
|
||||
permissions: Schema.Attribute.Relation<
|
||||
'oneToMany',
|
||||
'plugin::users-permissions.permission'
|
||||
>;
|
||||
users: Schema.Attribute.Relation<
|
||||
'oneToMany',
|
||||
'plugin::users-permissions.user'
|
||||
>;
|
||||
createdAt: Schema.Attribute.DateTime;
|
||||
updatedAt: Schema.Attribute.DateTime;
|
||||
publishedAt: Schema.Attribute.DateTime;
|
||||
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
|
||||
Schema.Attribute.Private;
|
||||
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
|
||||
Schema.Attribute.Private;
|
||||
locale: Schema.Attribute.String;
|
||||
localizations: Schema.Attribute.Relation<
|
||||
'oneToMany',
|
||||
'plugin::users-permissions.role'
|
||||
>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface PluginUsersPermissionsUser
|
||||
extends Struct.CollectionTypeSchema {
|
||||
collectionName: 'up_users';
|
||||
info: {
|
||||
name: 'user';
|
||||
description: '';
|
||||
singularName: 'user';
|
||||
pluralName: 'users';
|
||||
displayName: 'User';
|
||||
};
|
||||
options: {
|
||||
timestamps: true;
|
||||
draftAndPublish: false;
|
||||
};
|
||||
attributes: {
|
||||
username: Schema.Attribute.String &
|
||||
Schema.Attribute.Required &
|
||||
Schema.Attribute.Unique &
|
||||
Schema.Attribute.SetMinMaxLength<{
|
||||
minLength: 3;
|
||||
}>;
|
||||
email: Schema.Attribute.Email &
|
||||
Schema.Attribute.Required &
|
||||
Schema.Attribute.SetMinMaxLength<{
|
||||
minLength: 6;
|
||||
}>;
|
||||
provider: Schema.Attribute.String;
|
||||
password: Schema.Attribute.Password &
|
||||
Schema.Attribute.Private &
|
||||
Schema.Attribute.SetMinMaxLength<{
|
||||
minLength: 6;
|
||||
}>;
|
||||
resetPasswordToken: Schema.Attribute.String & Schema.Attribute.Private;
|
||||
confirmationToken: Schema.Attribute.String & Schema.Attribute.Private;
|
||||
confirmed: Schema.Attribute.Boolean & Schema.Attribute.DefaultTo<false>;
|
||||
blocked: Schema.Attribute.Boolean & Schema.Attribute.DefaultTo<false>;
|
||||
role: Schema.Attribute.Relation<
|
||||
'manyToOne',
|
||||
'plugin::users-permissions.role'
|
||||
>;
|
||||
createdAt: Schema.Attribute.DateTime;
|
||||
updatedAt: Schema.Attribute.DateTime;
|
||||
publishedAt: Schema.Attribute.DateTime;
|
||||
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
|
||||
Schema.Attribute.Private;
|
||||
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
|
||||
Schema.Attribute.Private;
|
||||
locale: Schema.Attribute.String;
|
||||
localizations: Schema.Attribute.Relation<
|
||||
'oneToMany',
|
||||
'plugin::users-permissions.user'
|
||||
>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface AdminPermission extends Struct.CollectionTypeSchema {
|
||||
collectionName: 'admin_permissions';
|
||||
info: {
|
||||
name: 'Permission';
|
||||
description: '';
|
||||
singularName: 'permission';
|
||||
pluralName: 'permissions';
|
||||
displayName: 'Permission';
|
||||
};
|
||||
options: {
|
||||
draftAndPublish: false;
|
||||
};
|
||||
pluginOptions: {
|
||||
'content-manager': {
|
||||
visible: false;
|
||||
};
|
||||
'content-type-builder': {
|
||||
visible: false;
|
||||
};
|
||||
};
|
||||
attributes: {
|
||||
action: Schema.Attribute.String &
|
||||
Schema.Attribute.Required &
|
||||
Schema.Attribute.SetMinMaxLength<{
|
||||
minLength: 1;
|
||||
}>;
|
||||
actionParameters: Schema.Attribute.JSON & Schema.Attribute.DefaultTo<{}>;
|
||||
subject: Schema.Attribute.String &
|
||||
Schema.Attribute.SetMinMaxLength<{
|
||||
minLength: 1;
|
||||
}>;
|
||||
properties: Schema.Attribute.JSON & Schema.Attribute.DefaultTo<{}>;
|
||||
conditions: Schema.Attribute.JSON & Schema.Attribute.DefaultTo<[]>;
|
||||
role: Schema.Attribute.Relation<'manyToOne', 'admin::role'>;
|
||||
createdAt: Schema.Attribute.DateTime;
|
||||
updatedAt: Schema.Attribute.DateTime;
|
||||
publishedAt: Schema.Attribute.DateTime;
|
||||
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
|
||||
Schema.Attribute.Private;
|
||||
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
|
||||
Schema.Attribute.Private;
|
||||
locale: Schema.Attribute.String;
|
||||
localizations: Schema.Attribute.Relation<'oneToMany', 'admin::permission'>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface AdminUser extends Struct.CollectionTypeSchema {
|
||||
collectionName: 'admin_users';
|
||||
info: {
|
||||
name: 'User';
|
||||
description: '';
|
||||
singularName: 'user';
|
||||
pluralName: 'users';
|
||||
displayName: 'User';
|
||||
};
|
||||
options: {
|
||||
draftAndPublish: false;
|
||||
};
|
||||
pluginOptions: {
|
||||
'content-manager': {
|
||||
visible: false;
|
||||
};
|
||||
'content-type-builder': {
|
||||
visible: false;
|
||||
};
|
||||
};
|
||||
attributes: {
|
||||
firstname: Schema.Attribute.String &
|
||||
Schema.Attribute.SetMinMaxLength<{
|
||||
minLength: 1;
|
||||
}>;
|
||||
lastname: Schema.Attribute.String &
|
||||
Schema.Attribute.SetMinMaxLength<{
|
||||
minLength: 1;
|
||||
}>;
|
||||
username: Schema.Attribute.String;
|
||||
email: Schema.Attribute.Email &
|
||||
Schema.Attribute.Required &
|
||||
Schema.Attribute.Private &
|
||||
Schema.Attribute.Unique &
|
||||
Schema.Attribute.SetMinMaxLength<{
|
||||
minLength: 6;
|
||||
}>;
|
||||
password: Schema.Attribute.Password &
|
||||
Schema.Attribute.Private &
|
||||
Schema.Attribute.SetMinMaxLength<{
|
||||
minLength: 6;
|
||||
}>;
|
||||
resetPasswordToken: Schema.Attribute.String & Schema.Attribute.Private;
|
||||
registrationToken: Schema.Attribute.String & Schema.Attribute.Private;
|
||||
isActive: Schema.Attribute.Boolean &
|
||||
Schema.Attribute.Private &
|
||||
Schema.Attribute.DefaultTo<false>;
|
||||
roles: Schema.Attribute.Relation<'manyToMany', 'admin::role'> &
|
||||
Schema.Attribute.Private;
|
||||
blocked: Schema.Attribute.Boolean &
|
||||
Schema.Attribute.Private &
|
||||
Schema.Attribute.DefaultTo<false>;
|
||||
preferedLanguage: Schema.Attribute.String;
|
||||
createdAt: Schema.Attribute.DateTime;
|
||||
updatedAt: Schema.Attribute.DateTime;
|
||||
publishedAt: Schema.Attribute.DateTime;
|
||||
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
|
||||
Schema.Attribute.Private;
|
||||
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
|
||||
Schema.Attribute.Private;
|
||||
locale: Schema.Attribute.String;
|
||||
localizations: Schema.Attribute.Relation<'oneToMany', 'admin::user'>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface AdminRole extends Struct.CollectionTypeSchema {
|
||||
collectionName: 'admin_roles';
|
||||
info: {
|
||||
name: 'Role';
|
||||
description: '';
|
||||
singularName: 'role';
|
||||
pluralName: 'roles';
|
||||
displayName: 'Role';
|
||||
};
|
||||
options: {
|
||||
draftAndPublish: false;
|
||||
};
|
||||
pluginOptions: {
|
||||
'content-manager': {
|
||||
visible: false;
|
||||
};
|
||||
'content-type-builder': {
|
||||
visible: false;
|
||||
};
|
||||
};
|
||||
attributes: {
|
||||
name: Schema.Attribute.String &
|
||||
Schema.Attribute.Required &
|
||||
Schema.Attribute.Unique &
|
||||
Schema.Attribute.SetMinMaxLength<{
|
||||
minLength: 1;
|
||||
}>;
|
||||
code: Schema.Attribute.String &
|
||||
Schema.Attribute.Required &
|
||||
Schema.Attribute.Unique &
|
||||
Schema.Attribute.SetMinMaxLength<{
|
||||
minLength: 1;
|
||||
}>;
|
||||
description: Schema.Attribute.String;
|
||||
users: Schema.Attribute.Relation<'manyToMany', 'admin::user'>;
|
||||
permissions: Schema.Attribute.Relation<'oneToMany', 'admin::permission'>;
|
||||
createdAt: Schema.Attribute.DateTime;
|
||||
updatedAt: Schema.Attribute.DateTime;
|
||||
publishedAt: Schema.Attribute.DateTime;
|
||||
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
|
||||
Schema.Attribute.Private;
|
||||
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
|
||||
Schema.Attribute.Private;
|
||||
locale: Schema.Attribute.String;
|
||||
localizations: Schema.Attribute.Relation<'oneToMany', 'admin::role'>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface AdminApiToken extends Struct.CollectionTypeSchema {
|
||||
collectionName: 'strapi_api_tokens';
|
||||
info: {
|
||||
name: 'Api Token';
|
||||
singularName: 'api-token';
|
||||
pluralName: 'api-tokens';
|
||||
displayName: 'Api Token';
|
||||
description: '';
|
||||
};
|
||||
options: {
|
||||
draftAndPublish: false;
|
||||
};
|
||||
pluginOptions: {
|
||||
'content-manager': {
|
||||
visible: false;
|
||||
};
|
||||
'content-type-builder': {
|
||||
visible: false;
|
||||
};
|
||||
};
|
||||
attributes: {
|
||||
name: Schema.Attribute.String &
|
||||
Schema.Attribute.Required &
|
||||
Schema.Attribute.Unique &
|
||||
Schema.Attribute.SetMinMaxLength<{
|
||||
minLength: 1;
|
||||
}>;
|
||||
description: Schema.Attribute.String &
|
||||
Schema.Attribute.SetMinMaxLength<{
|
||||
minLength: 1;
|
||||
}> &
|
||||
Schema.Attribute.DefaultTo<''>;
|
||||
type: Schema.Attribute.Enumeration<['read-only', 'full-access', 'custom']> &
|
||||
Schema.Attribute.Required &
|
||||
Schema.Attribute.DefaultTo<'read-only'>;
|
||||
accessKey: Schema.Attribute.String &
|
||||
Schema.Attribute.Required &
|
||||
Schema.Attribute.SetMinMaxLength<{
|
||||
minLength: 1;
|
||||
}>;
|
||||
lastUsedAt: Schema.Attribute.DateTime;
|
||||
permissions: Schema.Attribute.Relation<
|
||||
'oneToMany',
|
||||
'admin::api-token-permission'
|
||||
>;
|
||||
expiresAt: Schema.Attribute.DateTime;
|
||||
lifespan: Schema.Attribute.BigInteger;
|
||||
createdAt: Schema.Attribute.DateTime;
|
||||
updatedAt: Schema.Attribute.DateTime;
|
||||
publishedAt: Schema.Attribute.DateTime;
|
||||
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
|
||||
Schema.Attribute.Private;
|
||||
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
|
||||
Schema.Attribute.Private;
|
||||
locale: Schema.Attribute.String;
|
||||
localizations: Schema.Attribute.Relation<'oneToMany', 'admin::api-token'>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface AdminApiTokenPermission extends Struct.CollectionTypeSchema {
|
||||
collectionName: 'strapi_api_token_permissions';
|
||||
info: {
|
||||
name: 'API Token Permission';
|
||||
description: '';
|
||||
singularName: 'api-token-permission';
|
||||
pluralName: 'api-token-permissions';
|
||||
displayName: 'API Token Permission';
|
||||
};
|
||||
options: {
|
||||
draftAndPublish: false;
|
||||
};
|
||||
pluginOptions: {
|
||||
'content-manager': {
|
||||
visible: false;
|
||||
};
|
||||
'content-type-builder': {
|
||||
visible: false;
|
||||
};
|
||||
};
|
||||
attributes: {
|
||||
action: Schema.Attribute.String &
|
||||
Schema.Attribute.Required &
|
||||
Schema.Attribute.SetMinMaxLength<{
|
||||
minLength: 1;
|
||||
}>;
|
||||
token: Schema.Attribute.Relation<'manyToOne', 'admin::api-token'>;
|
||||
createdAt: Schema.Attribute.DateTime;
|
||||
updatedAt: Schema.Attribute.DateTime;
|
||||
publishedAt: Schema.Attribute.DateTime;
|
||||
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
|
||||
Schema.Attribute.Private;
|
||||
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
|
||||
Schema.Attribute.Private;
|
||||
locale: Schema.Attribute.String;
|
||||
localizations: Schema.Attribute.Relation<
|
||||
'oneToMany',
|
||||
'admin::api-token-permission'
|
||||
>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface AdminTransferToken extends Struct.CollectionTypeSchema {
|
||||
collectionName: 'strapi_transfer_tokens';
|
||||
info: {
|
||||
name: 'Transfer Token';
|
||||
singularName: 'transfer-token';
|
||||
pluralName: 'transfer-tokens';
|
||||
displayName: 'Transfer Token';
|
||||
description: '';
|
||||
};
|
||||
options: {
|
||||
draftAndPublish: false;
|
||||
};
|
||||
pluginOptions: {
|
||||
'content-manager': {
|
||||
visible: false;
|
||||
};
|
||||
'content-type-builder': {
|
||||
visible: false;
|
||||
};
|
||||
};
|
||||
attributes: {
|
||||
name: Schema.Attribute.String &
|
||||
Schema.Attribute.Required &
|
||||
Schema.Attribute.Unique &
|
||||
Schema.Attribute.SetMinMaxLength<{
|
||||
minLength: 1;
|
||||
}>;
|
||||
description: Schema.Attribute.String &
|
||||
Schema.Attribute.SetMinMaxLength<{
|
||||
minLength: 1;
|
||||
}> &
|
||||
Schema.Attribute.DefaultTo<''>;
|
||||
accessKey: Schema.Attribute.String &
|
||||
Schema.Attribute.Required &
|
||||
Schema.Attribute.SetMinMaxLength<{
|
||||
minLength: 1;
|
||||
}>;
|
||||
lastUsedAt: Schema.Attribute.DateTime;
|
||||
permissions: Schema.Attribute.Relation<
|
||||
'oneToMany',
|
||||
'admin::transfer-token-permission'
|
||||
>;
|
||||
expiresAt: Schema.Attribute.DateTime;
|
||||
lifespan: Schema.Attribute.BigInteger;
|
||||
createdAt: Schema.Attribute.DateTime;
|
||||
updatedAt: Schema.Attribute.DateTime;
|
||||
publishedAt: Schema.Attribute.DateTime;
|
||||
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
|
||||
Schema.Attribute.Private;
|
||||
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
|
||||
Schema.Attribute.Private;
|
||||
locale: Schema.Attribute.String;
|
||||
localizations: Schema.Attribute.Relation<
|
||||
'oneToMany',
|
||||
'admin::transfer-token'
|
||||
>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface AdminTransferTokenPermission
|
||||
extends Struct.CollectionTypeSchema {
|
||||
collectionName: 'strapi_transfer_token_permissions';
|
||||
info: {
|
||||
name: 'Transfer Token Permission';
|
||||
description: '';
|
||||
singularName: 'transfer-token-permission';
|
||||
pluralName: 'transfer-token-permissions';
|
||||
displayName: 'Transfer Token Permission';
|
||||
};
|
||||
options: {
|
||||
draftAndPublish: false;
|
||||
};
|
||||
pluginOptions: {
|
||||
'content-manager': {
|
||||
visible: false;
|
||||
};
|
||||
'content-type-builder': {
|
||||
visible: false;
|
||||
};
|
||||
};
|
||||
attributes: {
|
||||
action: Schema.Attribute.String &
|
||||
Schema.Attribute.Required &
|
||||
Schema.Attribute.SetMinMaxLength<{
|
||||
minLength: 1;
|
||||
}>;
|
||||
token: Schema.Attribute.Relation<'manyToOne', 'admin::transfer-token'>;
|
||||
createdAt: Schema.Attribute.DateTime;
|
||||
updatedAt: Schema.Attribute.DateTime;
|
||||
publishedAt: Schema.Attribute.DateTime;
|
||||
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
|
||||
Schema.Attribute.Private;
|
||||
updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
|
||||
Schema.Attribute.Private;
|
||||
locale: Schema.Attribute.String;
|
||||
localizations: Schema.Attribute.Relation<
|
||||
'oneToMany',
|
||||
'admin::transfer-token-permission'
|
||||
>;
|
||||
};
|
||||
}
|
||||
|
||||
declare module '@strapi/strapi' {
|
||||
export module Public {
|
||||
export interface ContentTypeSchemas {
|
||||
'api::home.home': ApiHomeHome;
|
||||
'api::page.page': ApiPagePage;
|
||||
'plugin::upload.file': PluginUploadFile;
|
||||
'plugin::upload.folder': PluginUploadFolder;
|
||||
'plugin::i18n.locale': PluginI18NLocale;
|
||||
'plugin::content-releases.release': PluginContentReleasesRelease;
|
||||
'plugin::content-releases.release-action': PluginContentReleasesReleaseAction;
|
||||
'plugin::review-workflows.workflow': PluginReviewWorkflowsWorkflow;
|
||||
'plugin::review-workflows.workflow-stage': PluginReviewWorkflowsWorkflowStage;
|
||||
'plugin::users-permissions.permission': PluginUsersPermissionsPermission;
|
||||
'plugin::users-permissions.role': PluginUsersPermissionsRole;
|
||||
'plugin::users-permissions.user': PluginUsersPermissionsUser;
|
||||
'admin::permission': AdminPermission;
|
||||
'admin::user': AdminUser;
|
||||
'admin::role': AdminRole;
|
||||
'admin::api-token': AdminApiToken;
|
||||
'admin::api-token-permission': AdminApiTokenPermission;
|
||||
'admin::transfer-token': AdminTransferToken;
|
||||
'admin::transfer-token-permission': AdminTransferTokenPermission;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,21 +23,21 @@ module.exports = async () => {
|
|||
|
||||
// The default types provided by the plugin.
|
||||
defaultTypes(strapi).map((type) => {
|
||||
if (!strapi.config.get('plugin.config-sync.excludedTypes').includes(type.configName)) {
|
||||
if (!strapi.config.get('plugin::config-sync.excludedTypes').includes(type.configName)) {
|
||||
types[type.configName] = new ConfigType(type);
|
||||
}
|
||||
});
|
||||
|
||||
// The types provided by other plugins.
|
||||
strapi.plugin('config-sync').pluginTypes.map((type) => {
|
||||
if (!strapi.config.get('plugin.config-sync.excludedTypes').includes(type.configName)) {
|
||||
if (!strapi.config.get('plugin::config-sync.excludedTypes').includes(type.configName)) {
|
||||
types[type.configName] = new ConfigType(type);
|
||||
}
|
||||
});
|
||||
|
||||
// The custom types provided by the user.
|
||||
strapi.config.get('plugin.config-sync.customTypes').map((type) => {
|
||||
if (!strapi.config.get('plugin.config-sync.excludedTypes').includes(type.configName)) {
|
||||
strapi.config.get('plugin::config-sync.customTypes').map((type) => {
|
||||
if (!strapi.config.get('plugin::config-sync.excludedTypes').includes(type.configName)) {
|
||||
types[type.configName] = new ConfigType(type);
|
||||
}
|
||||
});
|
||||
|
@ -47,10 +47,12 @@ module.exports = async () => {
|
|||
strapi.plugin('config-sync').types = registerTypes();
|
||||
|
||||
// Import on bootstrap.
|
||||
if (strapi.config.get('plugin.config-sync.importOnBootstrap')) {
|
||||
if (strapi.config.get('plugin::config-sync.importOnBootstrap')) {
|
||||
if (strapi.server.app.env === 'development') {
|
||||
strapi.log.warn(logMessage(`You can't use the 'importOnBootstrap' setting in the development env.`));
|
||||
} else if (fs.existsSync(strapi.config.get('plugin.config-sync.syncDir'))) {
|
||||
} else if (process.env.CONFIG_SYNC_CLI === 'true') {
|
||||
strapi.log.warn(logMessage(`The 'importOnBootstrap' setting was ignored because Strapi was started from the config-sync CLI itself.`));
|
||||
} else if (fs.existsSync(strapi.config.get('plugin::config-sync.syncDir'))) {
|
||||
await strapi.plugin('config-sync').service('main').importAllConfig();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ const Table = require('cli-table');
|
|||
const chalk = require('chalk');
|
||||
const inquirer = require('inquirer');
|
||||
const { isEmpty } = require('lodash');
|
||||
const strapi = require('@strapi/strapi'); // eslint-disable-line
|
||||
const { createStrapi, compileStrapi } = require('@strapi/strapi');
|
||||
const gitDiff = require('git-diff');
|
||||
|
||||
const warnings = require('./warnings');
|
||||
|
@ -15,31 +15,11 @@ const packageJSON = require('../package.json');
|
|||
const program = new Command();
|
||||
|
||||
const getStrapiApp = async () => {
|
||||
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);
|
||||
const alreadyCompiled = await fs.existsSync(outDir);
|
||||
|
||||
if (isTSProject && !alreadyCompiled) {
|
||||
await tsUtils.compile(appDir, {
|
||||
watch: false,
|
||||
configOptions: { options: { incremental: true } },
|
||||
});
|
||||
}
|
||||
|
||||
const distDir = isTSProject ? outDir : appDir;
|
||||
|
||||
const app = await strapi({ appDir, distDir }).load();
|
||||
process.env.CONFIG_SYNC_CLI = 'true';
|
||||
|
||||
const appContext = await compileStrapi();
|
||||
const app = await createStrapi(appContext).load();
|
||||
return app;
|
||||
} catch (e) {
|
||||
// Fallback for pre Strapi 4.2.
|
||||
const app = await strapi().load();
|
||||
return app;
|
||||
}
|
||||
};
|
||||
|
||||
const initTable = (head) => {
|
||||
|
@ -98,7 +78,7 @@ const getConfigState = (diff, configName, syncType) => {
|
|||
|
||||
const handleAction = async (syncType, skipConfirm, configType, partials, force) => {
|
||||
const app = await getStrapiApp();
|
||||
const hasSyncDir = fs.existsSync(app.config.get('plugin.config-sync.syncDir'));
|
||||
const hasSyncDir = fs.existsSync(app.config.get('plugin::config-sync.syncDir'));
|
||||
|
||||
// No import with empty sync dir.
|
||||
if (!hasSyncDir && syncType === 'import') {
|
||||
|
|
|
@ -11,6 +11,7 @@ module.exports = {
|
|||
excludedConfig: [
|
||||
"core-store.plugin_users-permissions_grant",
|
||||
"core-store.plugin_upload_metrics",
|
||||
"core-store.plugin_upload_api-folder",
|
||||
"core-store.strapi_content_types_schema",
|
||||
"core-store.ee_information",
|
||||
],
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
const { isEmpty } = require('lodash');
|
||||
const { logMessage, sanitizeConfig, dynamicSort, noLimit, getCombinedUid, getCombinedUidWhereFilter, getUidParamsFromName } = require('../utils');
|
||||
const { difference, same } = require('../utils/getArrayDiff');
|
||||
const queryFallBack = require('../utils/queryFallBack');
|
||||
|
||||
const ConfigType = class ConfigType {
|
||||
constructor({ queryString, configName, uid, jsonFields, relations }) {
|
||||
constructor({ queryString, configName, uid, jsonFields, relations, components }) {
|
||||
if (!configName) {
|
||||
strapi.log.error(logMessage('A config type was registered without a config name.'));
|
||||
process.exit(0);
|
||||
|
@ -25,6 +26,7 @@ const ConfigType = class ConfigType {
|
|||
this.configPrefix = configName;
|
||||
this.jsonFields = jsonFields || [];
|
||||
this.relations = relations || [];
|
||||
this.components = components || null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -37,10 +39,10 @@ const ConfigType = class ConfigType {
|
|||
*/
|
||||
importSingle = async (configName, configContent, force) => {
|
||||
// Check if the config should be excluded.
|
||||
const shouldExclude = !isEmpty(strapi.config.get('plugin.config-sync.excludedConfig').filter((option) => `${this.configPrefix}.${configName}`.startsWith(option)));
|
||||
const shouldExclude = !isEmpty(strapi.config.get('plugin::config-sync.excludedConfig').filter((option) => `${this.configPrefix}.${configName}`.startsWith(option)));
|
||||
if (shouldExclude) return;
|
||||
|
||||
const softImport = strapi.config.get('plugin.config-sync.soft');
|
||||
const softImport = strapi.config.get('plugin::config-sync.soft');
|
||||
const queryAPI = strapi.query(this.queryString);
|
||||
const uidParams = getUidParamsFromName(this.uidKeys, configName);
|
||||
const combinedUidWhereFilter = getCombinedUidWhereFilter(this.uidKeys, uidParams);
|
||||
|
@ -68,15 +70,15 @@ const ConfigType = class ConfigType {
|
|||
});
|
||||
|
||||
await Promise.all(relations.map(async (relation) => {
|
||||
await strapi.query(queryString).delete({
|
||||
where: { id: relation.id },
|
||||
});
|
||||
await queryFallBack.delete(queryString, { where: {
|
||||
id: relation.id,
|
||||
}});
|
||||
}));
|
||||
}));
|
||||
|
||||
await queryAPI.delete({
|
||||
where: { id: existingConfig.id },
|
||||
});
|
||||
await queryFallBack.delete(this.queryString, { where: {
|
||||
id: existingConfig.id,
|
||||
}});
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -89,15 +91,17 @@ const ConfigType = class ConfigType {
|
|||
|
||||
// Create entity.
|
||||
this.relations.map(({ relationName }) => delete query[relationName]);
|
||||
const newEntity = await queryAPI.create({ data: query });
|
||||
const newEntity = await queryFallBack.create(this.queryString, {
|
||||
data: query,
|
||||
});
|
||||
|
||||
// Create relation entities.
|
||||
await Promise.all(this.relations.map(async ({ queryString, relationName, parentName }) => {
|
||||
const relationQueryApi = strapi.query(queryString);
|
||||
|
||||
await Promise.all(configContent[relationName].map(async (relationEntity) => {
|
||||
const relationQuery = { ...relationEntity, [parentName]: newEntity };
|
||||
await relationQueryApi.create({ data: relationQuery });
|
||||
await queryFallBack.create(queryString, {
|
||||
data: relationQuery,
|
||||
});
|
||||
}));
|
||||
}));
|
||||
} else { // Config does exist in DB --> update config in DB
|
||||
|
@ -105,19 +109,22 @@ const ConfigType = class ConfigType {
|
|||
if (softImport && !force) return false;
|
||||
|
||||
// Format JSON fields.
|
||||
configContent = sanitizeConfig(configContent);
|
||||
configContent = sanitizeConfig({
|
||||
config: configContent,
|
||||
configName,
|
||||
});
|
||||
const query = { ...configContent };
|
||||
this.jsonFields.map((field) => query[field] = JSON.stringify(configContent[field]));
|
||||
|
||||
// Update entity.
|
||||
this.relations.map(({ relationName }) => delete query[relationName]);
|
||||
const entity = await queryAPI.update({ where: combinedUidWhereFilter, data: query });
|
||||
const entity = await queryFallBack.update(this.queryString, { where: combinedUidWhereFilter, data: query });
|
||||
|
||||
// Delete/create relations.
|
||||
await Promise.all(this.relations.map(async ({ queryString, relationName, parentName, relationSortFields }) => {
|
||||
const relationQueryApi = strapi.query(queryString);
|
||||
existingConfig = sanitizeConfig(existingConfig, relationName, relationSortFields);
|
||||
configContent = sanitizeConfig(configContent, relationName, relationSortFields);
|
||||
existingConfig = sanitizeConfig({ config: existingConfig, configName, relation: relationName, relationSortFields });
|
||||
configContent = sanitizeConfig({ config: configContent, configName, relation: relationName, relationSortFields });
|
||||
|
||||
const configToAdd = difference(configContent[relationName], existingConfig[relationName], relationSortFields);
|
||||
const configToDelete = difference(existingConfig[relationName], configContent[relationName], relationSortFields);
|
||||
|
@ -137,7 +144,7 @@ const ConfigType = class ConfigType {
|
|||
}));
|
||||
|
||||
await Promise.all(configToAdd.map(async (config) => {
|
||||
await relationQueryApi.create({
|
||||
await queryFallBack.create(queryString, {
|
||||
data: { ...config, [parentName]: entity.id },
|
||||
});
|
||||
}));
|
||||
|
@ -170,7 +177,7 @@ const ConfigType = class ConfigType {
|
|||
const formattedDiff = await strapi.plugin('config-sync').service('main').getFormattedDiff(this.configPrefix);
|
||||
|
||||
// Check if the config should be excluded.
|
||||
const shouldExclude = !isEmpty(strapi.config.get('plugin.config-sync.excludedConfig').filter((option) => configName.startsWith(option)));
|
||||
const shouldExclude = !isEmpty(strapi.config.get('plugin::config-sync.excludedConfig').filter((option) => configName.startsWith(option)));
|
||||
if (shouldExclude) return;
|
||||
|
||||
const currentConfig = formattedDiff.databaseConfig[configName];
|
||||
|
@ -203,10 +210,12 @@ const ConfigType = class ConfigType {
|
|||
* @returns {object} Object with key value pairs of configs.
|
||||
*/
|
||||
getAllFromDatabase = async () => {
|
||||
const AllConfig = await noLimit(strapi.query(this.queryString), {});
|
||||
const AllConfig = await noLimit(strapi.query(this.queryString), {
|
||||
populate: this.components,
|
||||
});
|
||||
const configs = {};
|
||||
|
||||
await Promise.all(Object.values(AllConfig).map(async (config) => {
|
||||
await Promise.all(Object.entries(AllConfig).map(async ([configName, config]) => {
|
||||
const combinedUid = getCombinedUid(this.uidKeys, config);
|
||||
const combinedUidWhereFilter = getCombinedUidWhereFilter(this.uidKeys, config);
|
||||
|
||||
|
@ -216,16 +225,16 @@ const ConfigType = class ConfigType {
|
|||
}
|
||||
|
||||
// Check if the config should be excluded.
|
||||
const shouldExclude = !isEmpty(strapi.config.get('plugin.config-sync.excludedConfig').filter((option) => `${this.configPrefix}.${combinedUid}`.startsWith(option)));
|
||||
const shouldExclude = !isEmpty(strapi.config.get('plugin::config-sync.excludedConfig').filter((option) => `${this.configPrefix}.${combinedUid}`.startsWith(option)));
|
||||
if (shouldExclude) return;
|
||||
|
||||
const formattedConfig = { ...sanitizeConfig(config) };
|
||||
const formattedConfig = { ...sanitizeConfig({ config, configName }) };
|
||||
await Promise.all(this.relations.map(async ({ queryString, relationName, relationSortFields, parentName }) => {
|
||||
const relations = await noLimit(strapi.query(queryString), {
|
||||
where: { [parentName]: combinedUidWhereFilter },
|
||||
});
|
||||
|
||||
relations.map((relation) => sanitizeConfig(relation));
|
||||
relations.map((relation) => sanitizeConfig({ config: relation, configName: relationName }));
|
||||
relationSortFields.map((sortField) => {
|
||||
relations.sort(dynamicSort(sortField));
|
||||
});
|
||||
|
|
|
@ -25,7 +25,7 @@ module.exports = {
|
|||
|
||||
|
||||
ctx.send({
|
||||
message: `Config was successfully exported to ${strapi.config.get('plugin.config-sync.syncDir')}.`,
|
||||
message: `Config was successfully exported to ${strapi.config.get('plugin::config-sync.syncDir')}.`,
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -37,7 +37,7 @@ module.exports = {
|
|||
*/
|
||||
importAll: async (ctx) => {
|
||||
// Check for existance of the config file sync dir.
|
||||
if (!fs.existsSync(strapi.config.get('plugin.config-sync.syncDir'))) {
|
||||
if (!fs.existsSync(strapi.config.get('plugin::config-sync.syncDir'))) {
|
||||
ctx.send({
|
||||
message: 'No config files were found.',
|
||||
});
|
||||
|
@ -73,7 +73,7 @@ module.exports = {
|
|||
*/
|
||||
getDiff: async (ctx) => {
|
||||
// Check for existance of the config file sync dir.
|
||||
if (!fs.existsSync(strapi.config.get('plugin.config-sync.syncDir'))) {
|
||||
if (!fs.existsSync(strapi.config.get('plugin::config-sync.syncDir'))) {
|
||||
ctx.send({
|
||||
message: 'No config files were found.',
|
||||
});
|
||||
|
@ -104,7 +104,7 @@ module.exports = {
|
|||
getAppEnv: async () => {
|
||||
return {
|
||||
env: strapi.server.app.env,
|
||||
config: strapi.config.get('plugin.config-sync'),
|
||||
config: strapi.config.get('plugin::config-sync'),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
|
|
@ -22,23 +22,23 @@ module.exports = () => ({
|
|||
*/
|
||||
writeConfigFile: async (configType, configName, fileContents) => {
|
||||
// Check if the config should be excluded.
|
||||
const shouldExclude = !isEmpty(strapi.config.get('plugin.config-sync.excludedConfig').filter((option) => `${configType}.${configName}`.startsWith(option)));
|
||||
const shouldExclude = !isEmpty(strapi.config.get('plugin::config-sync.excludedConfig').filter((option) => `${configType}.${configName}`.startsWith(option)));
|
||||
if (shouldExclude) return;
|
||||
|
||||
// Replace reserved characters in filenames.
|
||||
configName = configName.replace(/:/g, "#").replace(/\//g, "$");
|
||||
|
||||
// Check if the JSON content should be minified.
|
||||
const json = !strapi.config.get('plugin.config-sync').minify
|
||||
const json = !strapi.config.get('plugin::config-sync').minify
|
||||
? JSON.stringify(fileContents, null, 2)
|
||||
: JSON.stringify(fileContents);
|
||||
|
||||
if (!fs.existsSync(strapi.config.get('plugin.config-sync.syncDir'))) {
|
||||
fs.mkdirSync(strapi.config.get('plugin.config-sync.syncDir'), { recursive: true });
|
||||
if (!fs.existsSync(strapi.config.get('plugin::config-sync.syncDir'))) {
|
||||
fs.mkdirSync(strapi.config.get('plugin::config-sync.syncDir'), { recursive: true });
|
||||
}
|
||||
|
||||
const writeFile = util.promisify(fs.writeFile);
|
||||
await writeFile(`${strapi.config.get('plugin.config-sync.syncDir')}${configType}.${configName}.json`, json)
|
||||
await writeFile(`${strapi.config.get('plugin::config-sync.syncDir')}${configType}.${configName}.json`, json)
|
||||
.then(() => {
|
||||
// @TODO:
|
||||
// Add logging for successfull config export.
|
||||
|
@ -57,13 +57,13 @@ module.exports = () => ({
|
|||
*/
|
||||
deleteConfigFile: async (configName) => {
|
||||
// Check if the config should be excluded.
|
||||
const shouldExclude = !isEmpty(strapi.config.get('plugin.config-sync.excludedConfig').filter((option) => configName.startsWith(option)));
|
||||
const shouldExclude = !isEmpty(strapi.config.get('plugin::config-sync.excludedConfig').filter((option) => configName.startsWith(option)));
|
||||
if (shouldExclude) return;
|
||||
|
||||
// Replace reserved characters in filenames.
|
||||
configName = configName.replace(/:/g, "#").replace(/\//g, "$");
|
||||
|
||||
fs.unlinkSync(`${strapi.config.get('plugin.config-sync.syncDir')}${configName}.json`);
|
||||
fs.unlinkSync(`${strapi.config.get('plugin::config-sync.syncDir')}${configName}.json`);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -95,7 +95,7 @@ module.exports = () => ({
|
|||
configName = configName.replace(/:/g, "#").replace(/\//g, "$");
|
||||
|
||||
const readFile = util.promisify(fs.readFile);
|
||||
return readFile(`${strapi.config.get('plugin.config-sync.syncDir')}${configType}.${configName}.json`)
|
||||
return readFile(`${strapi.config.get('plugin::config-sync.syncDir')}${configType}.${configName}.json`)
|
||||
.then((data) => {
|
||||
return JSON.parse(data);
|
||||
})
|
||||
|
@ -112,11 +112,11 @@ module.exports = () => ({
|
|||
* @returns {object} Object with key value pairs of configs.
|
||||
*/
|
||||
getAllConfigFromFiles: async (configType = null) => {
|
||||
if (!fs.existsSync(strapi.config.get('plugin.config-sync.syncDir'))) {
|
||||
if (!fs.existsSync(strapi.config.get('plugin::config-sync.syncDir'))) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const configFiles = fs.readdirSync(strapi.config.get('plugin.config-sync.syncDir'));
|
||||
const configFiles = fs.readdirSync(strapi.config.get('plugin::config-sync.syncDir'));
|
||||
|
||||
const getConfigs = async () => {
|
||||
const fileConfigs = {};
|
||||
|
@ -131,7 +131,7 @@ module.exports = () => ({
|
|||
if (
|
||||
configType && configType !== type
|
||||
|| !strapi.plugin('config-sync').types[type]
|
||||
|| !isEmpty(strapi.config.get('plugin.config-sync.excludedConfig').filter((option) => `${type}.${name}`.startsWith(option)))
|
||||
|| !isEmpty(strapi.config.get('plugin::config-sync.excludedConfig').filter((option) => `${type}.${name}`.startsWith(option)))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
@ -237,7 +237,7 @@ module.exports = () => ({
|
|||
*/
|
||||
importSingleConfig: async (configName, onSuccess, force) => {
|
||||
// Check if the config should be excluded.
|
||||
const shouldExclude = !isEmpty(strapi.config.get('plugin.config-sync.excludedConfig').filter((option) => configName.startsWith(option)));
|
||||
const shouldExclude = !isEmpty(strapi.config.get('plugin::config-sync.excludedConfig').filter((option) => configName.startsWith(option)));
|
||||
if (shouldExclude) return;
|
||||
|
||||
const type = configName.split('.')[0]; // Grab the first part of the filename.
|
||||
|
@ -262,7 +262,7 @@ module.exports = () => ({
|
|||
*/
|
||||
exportSingleConfig: async (configName, onSuccess) => {
|
||||
// Check if the config should be excluded.
|
||||
const shouldExclude = !isEmpty(strapi.config.get('plugin.config-sync.excludedConfig').filter((option) => configName.startsWith(option)));
|
||||
const shouldExclude = !isEmpty(strapi.config.get('plugin::config-sync.excludedConfig').filter((option) => configName.startsWith(option)));
|
||||
if (shouldExclude) return;
|
||||
|
||||
const type = configName.split('.')[0]; // Grab the first part of the filename.
|
||||
|
|
|
@ -46,11 +46,17 @@ const dynamicSort = (property) => {
|
|||
};
|
||||
};
|
||||
|
||||
const sanitizeConfig = (config, relation, relationSortFields) => {
|
||||
const sanitizeConfig = ({
|
||||
config,
|
||||
relation,
|
||||
relationSortFields,
|
||||
configName,
|
||||
}) => {
|
||||
delete config._id;
|
||||
delete config.id;
|
||||
delete config.updatedAt;
|
||||
delete config.createdAt;
|
||||
delete config.publishedAt;
|
||||
|
||||
if (relation) {
|
||||
const formattedRelations = [];
|
||||
|
@ -59,6 +65,7 @@ const sanitizeConfig = (config, relation, relationSortFields) => {
|
|||
delete relationEntity._id;
|
||||
delete relationEntity.id;
|
||||
delete relationEntity.updatedAt;
|
||||
delete config.publishedAt;
|
||||
delete relationEntity.createdAt;
|
||||
relationEntity = sortByKeys(relationEntity);
|
||||
|
||||
|
@ -74,6 +81,26 @@ const sanitizeConfig = (config, relation, relationSortFields) => {
|
|||
config[relation] = formattedRelations;
|
||||
}
|
||||
|
||||
// We recursively sanitize the config to remove environment specific data.
|
||||
// Except for the plugin_content_manager_configuration.
|
||||
// This is because that stores the "edit the view" data which includes only configuration, not content.
|
||||
if (configName && !configName.startsWith('plugin_content_manager_configuration_')) {
|
||||
const recursiveSanitizeConfig = (recursivedSanitizedConfig) => {
|
||||
delete recursivedSanitizedConfig._id;
|
||||
delete recursivedSanitizedConfig.id;
|
||||
delete recursivedSanitizedConfig.updatedAt;
|
||||
delete recursivedSanitizedConfig.createdAt;
|
||||
|
||||
Object.keys(recursivedSanitizedConfig).map((key, index) => {
|
||||
if (recursivedSanitizedConfig[key] && typeof recursivedSanitizedConfig[key] === "object") {
|
||||
recursiveSanitizeConfig(recursivedSanitizedConfig[key]);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
recursiveSanitizeConfig(config);
|
||||
}
|
||||
|
||||
return config;
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
const queryFallBack = {
|
||||
create: async (queryString, options) => {
|
||||
try {
|
||||
const newEntity = await strapi.documents(queryString).create(options);
|
||||
|
||||
return newEntity;
|
||||
} catch (e) {
|
||||
return strapi.query(queryString).create(options);
|
||||
}
|
||||
},
|
||||
update: async (queryString, options) => {
|
||||
try {
|
||||
const entity = await strapi.query(queryString).findOne(options);
|
||||
const updatedEntity = await strapi.documents(queryString).update({
|
||||
documentId: entity.documentId,
|
||||
...options,
|
||||
});
|
||||
|
||||
return updatedEntity;
|
||||
} catch (e) {
|
||||
return strapi.query(queryString).update(options);
|
||||
}
|
||||
},
|
||||
delete: async (queryString, options) => {
|
||||
try {
|
||||
const entity = await strapi.query(queryString).findOne(options);
|
||||
await strapi.documents(queryString).delete({
|
||||
documentId: entity.documentId,
|
||||
});
|
||||
} catch (e) {
|
||||
await strapi.query(queryString).delete(options);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = queryFallBack;
|
|
@ -1,3 +1,3 @@
|
|||
'use strict';
|
||||
import admin from './admin/src';
|
||||
|
||||
module.exports = require('./admin/src').default;
|
||||
export default admin;
|
||||
|
|
Loading…
Reference in New Issue