├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md ├── config-diff.png └── workflows │ ├── deploy-docs.yml │ ├── publish.yml │ └── tests.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── admin └── src │ ├── components │ ├── .gitkeep │ ├── ActionButtons │ │ └── index.jsx │ ├── ConfigDiff │ │ └── index.jsx │ ├── ConfigList │ │ ├── ConfigListRow │ │ │ └── index.jsx │ │ └── index.jsx │ ├── ConfirmModal │ │ └── index.jsx │ ├── FirstExport │ │ └── index.jsx │ ├── Header │ │ └── index.jsx │ └── NoChanges │ │ └── index.jsx │ ├── config │ ├── constants.js │ └── logger.js │ ├── containers │ ├── App │ │ └── index.jsx │ ├── ConfigPage │ │ └── index.jsx │ └── Initializer │ │ └── index.jsx │ ├── helpers │ ├── blob.js │ ├── configureStore.js │ ├── getTrad.js │ ├── pluginId.js │ └── prefixPluginTranslations.js │ ├── index.cy.jsx │ ├── index.js │ ├── permissions.js │ ├── state │ ├── actions │ │ └── Config.js │ └── reducers │ │ ├── Config │ │ └── index.js │ │ └── index.js │ └── translations │ ├── ar.json │ ├── cs.json │ ├── de.json │ ├── en.json │ ├── es.json │ ├── fr.json │ ├── id.json │ ├── index.js │ ├── it.json │ ├── ko.json │ ├── ms.json │ ├── nl.json │ ├── pl.json │ ├── pt-BR.json │ ├── pt.json │ ├── ru.json │ ├── sk.json │ ├── th.json │ ├── tr.json │ ├── uk.json │ ├── vi.json │ ├── zh-Hans.json │ └── zh.json ├── bin └── config-sync ├── codecov.yml ├── cypress.config.js ├── cypress └── support │ ├── commands.js │ └── e2e.js ├── dependabot.yml ├── docs ├── .github │ └── workflows │ │ └── deploy.yml ├── .gitignore ├── Dockerfile ├── README.md ├── babel.config.js ├── blog │ ├── 2019-05-28-first-blog-post.md │ ├── 2019-05-29-long-blog-post.md │ ├── 2021-08-01-mdx-blog-post.mdx │ ├── 2021-08-26-welcome │ │ ├── docusaurus-plushie-banner.jpeg │ │ └── index.md │ ├── authors.yml │ └── tags.yml ├── docs │ ├── api │ │ └── plugin-config-types.md │ ├── configuration │ │ ├── custom-types.md │ │ ├── excluded-config.md │ │ ├── excluded-types.md │ │ ├── import-on-bootstrap.md │ │ ├── introduction.md │ │ ├── minify.md │ │ ├── soft.md │ │ └── sync-dir.md │ ├── getting-started │ │ ├── admin-gui.md │ │ ├── cli.md │ │ ├── config-types.md │ │ ├── installation.md │ │ ├── motivation.md │ │ ├── naming-convention.md │ │ └── workflow.md │ └── upgrading │ │ └── generic-update.md ├── docusaurus.config.ts ├── package.json ├── sidebars.ts ├── src │ ├── components │ │ ├── ApiCall.js │ │ ├── Badge.js │ │ ├── Button │ │ │ ├── Button.jsx │ │ │ └── button.module.scss │ │ ├── Card │ │ │ ├── Card.jsx │ │ │ └── card.module.scss │ │ ├── Container │ │ │ ├── Container.jsx │ │ │ └── container.module.scss │ │ ├── CustomDocCard.js │ │ ├── CustomDocCardsWrapper.js │ │ ├── FeaturesList │ │ │ ├── FeaturesList.jsx │ │ │ └── features-list.module.scss │ │ ├── Hero │ │ │ ├── Hero.jsx │ │ │ └── hero.module.scss │ │ ├── HomepageFeatures │ │ │ ├── index.tsx │ │ │ └── styles.module.css │ │ ├── LinkWithArrow │ │ │ ├── LinkWithArrow.jsx │ │ │ └── link-with-arrow.module.scss │ │ ├── Request.js │ │ ├── Response.js │ │ ├── SubtleCallout.js │ │ └── index.js │ ├── scss │ │ ├── __index.scss │ │ ├── _base.scss │ │ ├── _fonts.scss │ │ ├── _mixins.scss │ │ ├── _tokens-overrides.scss │ │ ├── _tokens.scss │ │ ├── admonition.scss │ │ ├── api-call.scss │ │ ├── badge.scss │ │ ├── breadcrumbs.scss │ │ ├── card.scss │ │ ├── columns.scss │ │ ├── container.scss │ │ ├── custom-doc-cards.scss │ │ ├── details.scss │ │ ├── footer.scss │ │ ├── grid.scss │ │ ├── images.scss │ │ ├── markdown.scss │ │ ├── medium-zoom.scss │ │ ├── navbar.scss │ │ ├── pagination-nav.scss │ │ ├── scene.scss │ │ ├── search.scss │ │ ├── sidebar.scss │ │ ├── table-of-contents.scss │ │ ├── table.scss │ │ ├── tabs.scss │ │ └── typography.scss │ └── theme │ │ ├── Admonition │ │ └── index.js │ │ └── MDXComponents.js ├── static │ ├── .nojekyll │ └── img │ │ ├── assets │ │ ├── admin-diff-viewer.png │ │ └── icons │ │ │ ├── ArrowClockwise.svg │ │ │ ├── Browsers.svg │ │ │ ├── CheckCircle.svg │ │ │ ├── Clock.svg │ │ │ ├── CreditCard.svg │ │ │ ├── CrossCircle.svg │ │ │ ├── Duplicate copy.svg │ │ │ ├── Eye.svg │ │ │ ├── Faders.svg │ │ │ ├── Invoice.svg │ │ │ ├── MapTrifold.svg │ │ │ ├── ONHOLDCarretDown.svg │ │ │ ├── ONHOLDCarretUp.svg │ │ │ ├── Palette.svg │ │ │ ├── add.svg │ │ │ ├── add_circle.svg │ │ │ ├── add_icon.svg │ │ │ ├── after.svg │ │ │ ├── api_tokens.svg │ │ │ ├── application.svg │ │ │ ├── arrow-right.svg │ │ │ ├── back.svg │ │ │ ├── carret.svg │ │ │ ├── check_icon.svg │ │ │ ├── chevron-left.svg │ │ │ ├── chevron-right.svg │ │ │ ├── clear.svg │ │ │ ├── close-icon.svg │ │ │ ├── code.svg │ │ │ ├── code2.svg │ │ │ ├── cog.svg │ │ │ ├── content.svg │ │ │ ├── content_manager.svg │ │ │ ├── content_types_builder.svg │ │ │ ├── crop.svg │ │ │ ├── cross.svg │ │ │ ├── ctb_boolean.svg │ │ │ ├── ctb_component.svg │ │ │ ├── ctb_date.svg │ │ │ ├── ctb_dz.svg │ │ │ ├── ctb_email.svg │ │ │ ├── ctb_enum.svg │ │ │ ├── ctb_json.svg │ │ │ ├── ctb_media.svg │ │ │ ├── ctb_number.svg │ │ │ ├── ctb_password.svg │ │ │ ├── ctb_relation.svg │ │ │ ├── ctb_relation_1to1.svg │ │ │ ├── ctb_relation_1tomany.svg │ │ │ ├── ctb_relation_manyto1.svg │ │ │ ├── ctb_relation_manytomany.svg │ │ │ ├── ctb_relation_manyway.svg │ │ │ ├── ctb_relation_oneway.svg │ │ │ ├── ctb_richtext.svg │ │ │ ├── ctb_richtextblocks.svg │ │ │ ├── ctb_text.svg │ │ │ ├── ctb_uid.svg │ │ │ ├── delete.svg │ │ │ ├── documentation-plugin.svg │ │ │ ├── documentation.svg │ │ │ ├── down.svg │ │ │ ├── down2.svg │ │ │ ├── download.svg │ │ │ ├── drag.svg │ │ │ ├── duplicate.svg │ │ │ ├── edit.svg │ │ │ ├── email_template.svg │ │ │ ├── external_link.svg │ │ │ ├── feather.svg │ │ │ ├── globe.svg │ │ │ ├── grid_view.svg │ │ │ ├── image.svg │ │ │ ├── link.svg │ │ │ ├── list_view.svg │ │ │ ├── lock.svg │ │ │ ├── marketplace.svg │ │ │ ├── media_library.svg │ │ │ ├── minus.svg │ │ │ ├── more.svg │ │ │ ├── move.svg │ │ │ ├── notifications.svg │ │ │ ├── official-market.svg │ │ │ ├── plugins.svg │ │ │ ├── provider.svg │ │ │ ├── releases.svg │ │ │ ├── reorder.svg │ │ │ ├── reset_icon.svg │ │ │ ├── roles.svg │ │ │ ├── roles_permissions.svg │ │ │ ├── search.svg │ │ │ ├── settings.svg │ │ │ ├── up.svg │ │ │ ├── up2.svg │ │ │ ├── users.svg │ │ │ ├── verified-marketplace.svg │ │ │ ├── webhooks.svg │ │ │ ├── world.svg │ │ │ └── world_striked.svg │ │ ├── docusaurus-social-card.jpg │ │ ├── favicon.jpg │ │ ├── logo.png │ │ ├── undraw_docusaurus_mountain.svg │ │ ├── undraw_docusaurus_react.svg │ │ └── undraw_docusaurus_tree.svg ├── tsconfig.json └── yarn.lock ├── jest.config.js ├── package.json ├── packup.config.ts ├── playground ├── .editorconfig ├── .env ├── .env.example ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .strapi │ └── client │ │ ├── app.js │ │ └── index.html ├── README.md ├── __tests__ │ ├── cli.test.js │ ├── helpers.js │ └── import-on-boostrap.test.js ├── config │ ├── admin.js │ ├── api.js │ ├── database.js │ ├── middlewares.js │ ├── plugins.js │ └── server.js ├── database │ └── migrations │ │ └── .gitkeep ├── favicon.png ├── jest.config.js ├── jsconfig.json ├── package.json ├── public │ ├── robots.txt │ └── uploads │ │ └── .gitkeep ├── src │ ├── admin │ │ ├── app.example.js │ │ └── webpack.config.example.js │ ├── api │ │ ├── .gitkeep │ │ ├── home │ │ │ ├── content-types │ │ │ │ └── home │ │ │ │ │ └── schema.json │ │ │ ├── controllers │ │ │ │ └── home.js │ │ │ ├── routes │ │ │ │ └── home.js │ │ │ └── services │ │ │ │ └── home.js │ │ └── page │ │ │ ├── content-types │ │ │ └── page │ │ │ │ └── schema.json │ │ │ ├── controllers │ │ │ └── page.js │ │ │ ├── routes │ │ │ └── page.js │ │ │ └── services │ │ │ └── page.js │ ├── extensions │ │ └── .gitkeep │ └── index.js └── types │ └── generated │ ├── components.d.ts │ └── contentTypes.d.ts ├── server ├── bootstrap.js ├── cli.js ├── config.js ├── config │ ├── type.js │ └── types.js ├── controllers │ ├── config.js │ └── index.js ├── index.js ├── register.js ├── routes │ ├── admin.js │ └── index.js ├── services │ ├── index.js │ └── main.js ├── utils │ ├── getArrayDiff.js │ ├── getObjectDiff.js │ ├── index.js │ └── queryFallBack.js └── warnings.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = LF 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | **/public 3 | **/build 4 | **/dist 5 | **/config 6 | **/scripts 7 | **/docs 8 | **/playground 9 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # From https://github.com/Danimoth/gitattributes/blob/master/Web.gitattributes 2 | 3 | # Handle line endings automatically for files detected as text 4 | # and leave all files detected as binary untouched. 5 | * text=auto 6 | 7 | # 8 | # The above will handle all files NOT found below 9 | # 10 | 11 | # 12 | ## These files are text and should be normalized (Convert crlf => lf) 13 | # 14 | 15 | # source code 16 | *.php text 17 | *.css text 18 | *.sass text 19 | *.scss text 20 | *.less text 21 | *.styl text 22 | *.js text eol=lf 23 | *.coffee text 24 | *.json text 25 | *.htm text 26 | *.html text 27 | *.xml text 28 | *.svg text 29 | *.txt text 30 | *.ini text 31 | *.inc text 32 | *.pl text 33 | *.rb text 34 | *.py text 35 | *.scm text 36 | *.sql text 37 | *.sh text 38 | *.bat text 39 | 40 | # templates 41 | *.ejs text 42 | *.hbt text 43 | *.jade text 44 | *.haml text 45 | *.hbs text 46 | *.dot text 47 | *.tmpl text 48 | *.phtml text 49 | 50 | # git config 51 | .gitattributes text 52 | .gitignore text 53 | .gitconfig text 54 | 55 | # code analysis config 56 | .jshintrc text 57 | .jscsrc text 58 | .jshintignore text 59 | .csslintrc text 60 | 61 | # misc config 62 | *.yaml text 63 | *.yml text 64 | .editorconfig text 65 | 66 | # build config 67 | *.npmignore text 68 | *.bowerrc text 69 | 70 | # Heroku 71 | Procfile text 72 | .slugignore text 73 | 74 | # Documentation 75 | *.md text 76 | LICENSE text 77 | AUTHORS text 78 | 79 | 80 | # 81 | ## These files are binary and should be left untouched 82 | # 83 | 84 | # (binary is a macro for -text -diff) 85 | *.png binary 86 | *.jpg binary 87 | *.jpeg binary 88 | *.gif binary 89 | *.ico binary 90 | *.mov binary 91 | *.mp4 binary 92 | *.mp3 binary 93 | *.flv binary 94 | *.fla binary 95 | *.swf binary 96 | *.gz binary 97 | *.zip binary 98 | *.7z binary 99 | *.ttf binary 100 | *.eot binary 101 | *.woff binary 102 | *.pyc binary 103 | *.pdf binary 104 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🐛 Bug Report 3 | about: Create a report to help improve this plugin 4 | --- 5 | 6 | 9 | 10 | ## Bug report 11 | 12 | ### Describe the bug 13 | 14 | A clear and concise description of what the bug is. 15 | 16 | ### Steps to reproduce the behavior 17 | 18 | 1. Go to '...' 19 | 2. Click on '....' 20 | 3. Scroll down to '....' 21 | 4. See error 22 | 23 | ### Expected behavior 24 | 25 | A clear and concise description of what you expected to happen. 26 | 27 | ### Screenshots 28 | 29 | If applicable, add screenshots to help explain your problem. 30 | 31 | ### Code snippets 32 | 33 | If applicable, add code samples to help explain your problem. 34 | 35 | ### System 36 | 37 | - Node.js version: 38 | - NPM version: 39 | - Strapi version: 40 | - Plugin version: 41 | - Database: 42 | - Operating system: 43 | 44 | ### Additional context 45 | 46 | Add any other context about the problem here. 47 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🚀 Feature Request 3 | about: Suggest an idea to help make this plugin even better! 4 | --- 5 | 6 | 9 | 10 | ## Feature request 11 | 12 | ### Summary 13 | 14 | Quick summary what's this feature request about. 15 | 16 | ### Why is it needed? 17 | 18 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 19 | 20 | ### Suggested solution(s) 21 | 22 | A clear and concise description of what you want to happen. 23 | 24 | ### Related issue(s)/PR(s) 25 | 26 | Let us know if this is related to any issue/pull request. 27 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 11 | 12 | ### What does it do? 13 | 14 | Describe the technical changes you did. 15 | 16 | ### Why is it needed? 17 | 18 | Describe the issue you are solving. 19 | 20 | ### How to test it? 21 | 22 | Provide information about the environment and the path to verify the behaviour. 23 | 24 | ### Related issue(s)/PR(s) 25 | 26 | Let us know if this is related to any issue/pull request 27 | -------------------------------------------------------------------------------- /.github/config-diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pluginpal/strapi-plugin-config-sync/8a7d4baca80c6895b7f46acb4fe70d04a3fd6de7/.github/config-diff.png -------------------------------------------------------------------------------- /.github/workflows/deploy-docs.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Docs 2 | 3 | on: 4 | workflow_dispatch: 5 | release: 6 | types: [published] 7 | 8 | jobs: 9 | deploy: 10 | name: Deploy 11 | runs-on: ubuntu-latest 12 | environment: 13 | name: docs.pluginpal.io 14 | url: https://docs.pluginpal.io/config-sync 15 | steps: 16 | - name: Checkout repository 17 | uses: actions/checkout@v4 18 | 19 | - name: Set up Docker 20 | uses: actions/setup-node@v4 21 | with: 22 | node-version: '14' 23 | 24 | - name: Build a Docker image 25 | run: | 26 | cd docs 27 | docker build \ 28 | -t docs-config-sync:latest . 29 | docker save -o ../docs-config-sync-latest.tar docs-config-sync:latest 30 | 31 | - name: Transfer the Docker image to the Dokku server 32 | uses: appleboy/scp-action@v0.1.3 33 | with: 34 | host: ${{ secrets.SSH_HOST }} 35 | username: ${{ secrets.SSH_CI_USERNAME }} 36 | password: ${{ secrets.SSH_CI_PASSWORD }} 37 | source: docs-config-sync-latest.tar 38 | target: /var/lib/dokku/data/storage/docs/docker-images 39 | 40 | - name: Deploy the Dokku app based on the Docker image 41 | uses: appleboy/ssh-action@v0.1.10 42 | with: 43 | host: ${{ secrets.SSH_HOST }} 44 | username: ${{ secrets.SSH_CI_USERNAME }} 45 | password: ${{ secrets.SSH_CI_PASSWORD }} 46 | script_stop: true 47 | script: | 48 | sudo docker load -i /var/lib/dokku/data/storage/docs/docker-images/docs-config-sync-latest.tar 49 | DOCS_CONFIG_SYNC_LATEST_IMAGE=$(sudo docker images --format "{{.ID}}" docs-config-sync:latest) 50 | sudo docker tag docs-config-sync:latest docs-config-sync:$DOCS_CONFIG_SYNC_LATEST_IMAGE 51 | dokku git:from-image docs-config-sync docs-config-sync:$DOCS_CONFIG_SYNC_LATEST_IMAGE 52 | sudo docker system prune --all --force 53 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to NPM 2 | on: 3 | release: 4 | types: [published] 5 | jobs: 6 | publish: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@v4 11 | - name: Setup Node.js 12 | uses: actions/setup-node@v4 13 | with: 14 | always-auth: true 15 | node-version: 18 16 | cache: 'yarn' 17 | registry-url: 'https://registry.npmjs.org/' 18 | - name: Install dependencies 19 | run: yarn install --frozen-lockfile 20 | - name: Build the plugin 21 | run: yarn build 22 | - name: Get the release tag version 23 | id: get_version 24 | run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//} 25 | - name: Set package version 26 | run: yarn version --new-version "${{ steps.get_version.outputs.VERSION }}" --no-git-tag-version 27 | - name: Publish package 28 | run: yarn publish --access public 29 | env: 30 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 31 | - name: Push version bump 32 | uses: stefanzweifel/git-auto-commit-action@v4 33 | with: 34 | commit_message: 'chore: Bump version to ${{ steps.get_version.outputs.VERSION }}' 35 | file_pattern: 'package.json' 36 | branch: master 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Don't check auto-generated stuff into git 2 | coverage 3 | node_modules 4 | stats.json 5 | package-lock.json 6 | files 7 | 8 | # Cruft 9 | .DS_Store 10 | npm-debug.log 11 | .idea 12 | 13 | # Production build 14 | build 15 | dist 16 | bundle 17 | 18 | # Cypress 19 | cypress/screenshots/ 20 | cypress/videos/ 21 | cypress/downloads/ 22 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Copyright (c) 2021 Boaz Poolman. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /admin/src/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pluginpal/strapi-plugin-config-sync/8a7d4baca80c6895b7f46acb4fe70d04a3fd6de7/admin/src/components/.gitkeep -------------------------------------------------------------------------------- /admin/src/components/ConfigList/ConfigListRow/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Tr, Td, Checkbox, Typography } from '@strapi/design-system'; 3 | 4 | const CustomRow = ({ row, checked, updateValue, ...props }) => { 5 | const { configName, configType, state, onClick } = row; 6 | 7 | const stateStyle = (stateStr) => { 8 | const style = { 9 | display: 'inline-flex', 10 | padding: '0 10px', 11 | borderRadius: '12px', 12 | height: '24px', 13 | alignItems: 'center', 14 | fontWeight: '500', 15 | }; 16 | 17 | if (stateStr === 'Only in DB') { 18 | style.backgroundColor = '#cbf2d7'; 19 | style.color = '#1b522b'; 20 | } 21 | 22 | if (stateStr === 'Only in sync dir') { 23 | style.backgroundColor = '#f0cac7'; 24 | style.color = '#3d302f'; 25 | } 26 | 27 | if (stateStr === 'Different') { 28 | style.backgroundColor = '#e8e6b7'; 29 | style.color = '#4a4934'; 30 | } 31 | 32 | return style; 33 | }; 34 | 35 | return ( 36 | { 39 | if (e.target.type !== 'checkbox') { 40 | onClick(configType, configName); 41 | } 42 | }} 43 | style={{ cursor: 'pointer' }} 44 | > 45 | 46 | 51 | 52 | props.onClick(e)}> 53 | {configName} 54 | 55 | props.onClick(e)}> 56 | {configType} 57 | 58 | props.onClick(e)}> 59 | {state} 60 | 61 | 62 | ); 63 | }; 64 | 65 | export default CustomRow; 66 | -------------------------------------------------------------------------------- /admin/src/components/FirstExport/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useIntl } from 'react-intl'; 3 | import { useDispatch } from 'react-redux'; 4 | import { getFetchClient, useNotification } from '@strapi/strapi/admin'; 5 | import { Button, EmptyStateLayout } from '@strapi/design-system'; 6 | import { EmptyDocuments } from '@strapi/icons/symbols'; 7 | 8 | 9 | import { exportAllConfig } from '../../state/actions/Config'; 10 | import ConfirmModal from '../ConfirmModal'; 11 | 12 | const FirstExport = () => { 13 | const { post, get } = getFetchClient(); 14 | const { toggleNotification } = useNotification(); 15 | const dispatch = useDispatch(); 16 | const { formatMessage } = useIntl(); 17 | 18 | return ( 19 |
20 | dispatch(exportAllConfig([], toggleNotification, formatMessage, post, get))} 26 | trigger={( 27 | 28 | )} 29 | /> 30 | )} 31 | icon={} 32 | /> 33 |
34 | ); 35 | }; 36 | 37 | export default FirstExport; 38 | -------------------------------------------------------------------------------- /admin/src/components/Header/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * HeaderComponent 4 | * 5 | */ 6 | 7 | import React, { memo } from 'react'; 8 | import { useIntl } from 'react-intl'; 9 | 10 | import { Layouts } from '@strapi/admin/strapi-admin'; 11 | import { Box } from '@strapi/design-system'; 12 | 13 | const HeaderComponent = () => { 14 | const { formatMessage } = useIntl(); 15 | 16 | return ( 17 | 18 | 22 | 23 | ); 24 | }; 25 | 26 | export default memo(HeaderComponent); 27 | -------------------------------------------------------------------------------- /admin/src/components/NoChanges/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { EmptyStateLayout } from '@strapi/design-system'; 3 | import { useIntl } from 'react-intl'; 4 | import { EmptyDocuments } from '@strapi/icons/symbols'; 5 | 6 | const NoChanges = () => { 7 | const { formatMessage } = useIntl(); 8 | return ( 9 | } 12 | /> 13 | ); 14 | }; 15 | 16 | export default NoChanges; 17 | -------------------------------------------------------------------------------- /admin/src/config/constants.js: -------------------------------------------------------------------------------- 1 | export const __DEBUG__ = true; // TODO: set actual env. 2 | -------------------------------------------------------------------------------- /admin/src/config/logger.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | blacklist: [ 3 | 'REDUX_STORAGE_SAVE', 4 | 'REDUX_STORAGE_LOAD', 5 | ], 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /admin/src/containers/App/index.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * This component is the skeleton around the actual pages, and should only 4 | * contain code that should be seen on all pages. (e.g. navigation bar) 5 | * 6 | */ 7 | 8 | import React from 'react'; 9 | import { Provider } from 'react-redux'; 10 | import { Page } from '@strapi/strapi/admin'; 11 | 12 | import pluginPermissions from '../../permissions'; 13 | import Header from '../../components/Header'; 14 | import { store } from "../../helpers/configureStore"; 15 | import ConfigPage from '../ConfigPage'; 16 | 17 | const App = () => { 18 | return ( 19 | 20 | 21 |
22 | 23 | 24 | 25 | ); 26 | }; 27 | 28 | export default App; 29 | -------------------------------------------------------------------------------- /admin/src/containers/ConfigPage/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { useDispatch, useSelector } from 'react-redux'; 3 | import { Map } from 'immutable'; 4 | import { 5 | Box, 6 | Alert, 7 | Typography, 8 | } from '@strapi/design-system'; 9 | import { useNotification } from '@strapi/strapi/admin'; 10 | import { getFetchClient, Layouts } from '@strapi/admin/strapi-admin'; 11 | import { useIntl } from 'react-intl'; 12 | 13 | import { getAllConfigDiff, getAppEnv } from '../../state/actions/Config'; 14 | import ConfigList from '../../components/ConfigList'; 15 | import ActionButtons from '../../components/ActionButtons'; 16 | 17 | const ConfigPage = () => { 18 | const { toggleNotification } = useNotification(); 19 | const { get } = getFetchClient(); 20 | const { formatMessage } = useIntl(); 21 | 22 | const dispatch = useDispatch(); 23 | const isLoading = useSelector((state) => state.getIn(['config', 'isLoading'], Map({}))); 24 | const configDiff = useSelector((state) => state.getIn(['config', 'configDiff'], Map({}))); 25 | const appEnv = useSelector((state) => state.getIn(['config', 'appEnv', 'env'])); 26 | 27 | useEffect(() => { 28 | dispatch(getAllConfigDiff(toggleNotification, formatMessage, get)); 29 | dispatch(getAppEnv(toggleNotification, formatMessage, get)); 30 | }, []); 31 | 32 | return ( 33 | 34 | {appEnv === 'production' && ( 35 | 36 | 37 | You're in the production environment
38 | Please be careful when syncing your config in production.
39 | Make sure you are not overriding critical config changes on import. 40 |
41 |
42 | )} 43 | 44 | 45 |
46 | ); 47 | }; 48 | 49 | export default ConfigPage; 50 | -------------------------------------------------------------------------------- /admin/src/containers/Initializer/index.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Initializer 4 | * 5 | */ 6 | 7 | import { useEffect, useRef } from 'react'; 8 | import PropTypes from 'prop-types'; 9 | import pluginId from '../../helpers/pluginId'; 10 | 11 | const Initializer = ({ updatePlugin }) => { 12 | const ref = useRef(); 13 | ref.current = updatePlugin; 14 | 15 | useEffect(() => { 16 | ref.current(pluginId, 'isReady', true); 17 | }, []); 18 | 19 | return null; 20 | }; 21 | 22 | Initializer.propTypes = { 23 | updatePlugin: PropTypes.func.isRequired, 24 | }; 25 | 26 | export default Initializer; 27 | -------------------------------------------------------------------------------- /admin/src/helpers/blob.js: -------------------------------------------------------------------------------- 1 | export function b64toBlob(dataURI, type) { 2 | const byteString = atob(dataURI); 3 | const ab = new ArrayBuffer(byteString.length); 4 | const ia = new Uint8Array(ab); 5 | for (let i = 0; i < byteString.length; i++) { 6 | ia[i] = byteString.charCodeAt(i); 7 | } 8 | return new Blob([ab], { type }); 9 | } 10 | -------------------------------------------------------------------------------- /admin/src/helpers/configureStore.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from 'redux'; 2 | import thunkMiddleware from 'redux-thunk'; 3 | import { Map } from 'immutable'; 4 | 5 | import rootReducer from '../state/reducers'; 6 | import { __DEBUG__ } from '../config/constants'; 7 | 8 | const configureStore = () => { 9 | const initialStoreState = Map(); 10 | 11 | const enhancers = []; 12 | const middlewares = [ 13 | thunkMiddleware, 14 | ]; 15 | 16 | let devtools; 17 | 18 | if (__DEBUG__) { 19 | devtools = ( 20 | typeof window !== 'undefined' 21 | && typeof window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ === 'function' 22 | && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ actionsBlacklist: [] }) 23 | ); 24 | 25 | if (devtools) { 26 | console.info('[setup] ✓ Enabling Redux DevTools Extension'); 27 | } 28 | } 29 | 30 | const composedEnhancers = devtools || compose; 31 | const storeEnhancers = composedEnhancers( 32 | applyMiddleware(...middlewares), 33 | ...enhancers, 34 | ); 35 | 36 | const store = createStore( 37 | rootReducer, 38 | initialStoreState, 39 | storeEnhancers, 40 | ); 41 | 42 | return store; 43 | }; 44 | 45 | export default configureStore; 46 | 47 | export const store = configureStore(); 48 | -------------------------------------------------------------------------------- /admin/src/helpers/getTrad.js: -------------------------------------------------------------------------------- 1 | import pluginId from './pluginId'; 2 | 3 | const getTrad = (id) => `${pluginId}.${id}`; 4 | 5 | export default getTrad; 6 | -------------------------------------------------------------------------------- /admin/src/helpers/pluginId.js: -------------------------------------------------------------------------------- 1 | import pluginPkg from '../../../package.json'; 2 | 3 | const pluginId = pluginPkg.name.replace( 4 | /^strapi-plugin-/i, 5 | '', 6 | ); 7 | 8 | export default pluginId; 9 | -------------------------------------------------------------------------------- /admin/src/helpers/prefixPluginTranslations.js: -------------------------------------------------------------------------------- 1 | const prefixPluginTranslations = (trad, pluginId) => { 2 | if (!pluginId) { 3 | throw new TypeError("pluginId can't be empty"); 4 | } 5 | return Object.keys(trad).reduce((acc, current) => { 6 | acc[`${pluginId}.${current}`] = trad[current]; 7 | return acc; 8 | }, {}); 9 | }; 10 | 11 | export { prefixPluginTranslations }; 12 | -------------------------------------------------------------------------------- /admin/src/index.js: -------------------------------------------------------------------------------- 1 | import pluginPkg from '../../package.json'; 2 | import pluginId from './helpers/pluginId'; 3 | import { prefixPluginTranslations } from './helpers/prefixPluginTranslations'; 4 | import pluginPermissions from './permissions'; 5 | // import pluginIcon from './components/PluginIcon'; 6 | // import getTrad from './helpers/getTrad'; 7 | 8 | const pluginDescription = pluginPkg.strapi.description || pluginPkg.description; 9 | const { name } = pluginPkg.strapi; 10 | 11 | export default { 12 | register(app) { 13 | app.registerPlugin({ 14 | description: pluginDescription, 15 | id: pluginId, 16 | isReady: true, 17 | isRequired: pluginPkg.strapi.required || false, 18 | name, 19 | }); 20 | 21 | app.createSettingSection( 22 | { 23 | id: pluginId, 24 | intlLabel: { 25 | id: `${pluginId}.plugin.name`, 26 | defaultMessage: 'Config Sync', 27 | }, 28 | }, 29 | [ 30 | { 31 | intlLabel: { 32 | id: `${pluginId}.Settings.Tool.Title`, 33 | defaultMessage: 'Interface', 34 | }, 35 | id: 'config-sync-page', 36 | to: `${pluginId}`, 37 | Component: () => import('./containers/App'), 38 | permissions: pluginPermissions['settings'], 39 | }, 40 | ], 41 | ); 42 | }, 43 | bootstrap(app) {}, 44 | async registerTrads({ locales }) { 45 | const importedTrads = await Promise.all( 46 | locales.map((locale) => { 47 | return import(`./translations/${locale}.json`) 48 | .then(({ default: data }) => { 49 | return { 50 | data: prefixPluginTranslations(data, pluginId), 51 | locale, 52 | }; 53 | }) 54 | .catch(() => { 55 | return { 56 | data: {}, 57 | locale, 58 | }; 59 | }); 60 | }), 61 | ); 62 | 63 | return Promise.resolve(importedTrads); 64 | }, 65 | }; 66 | -------------------------------------------------------------------------------- /admin/src/permissions.js: -------------------------------------------------------------------------------- 1 | const pluginPermissions = { 2 | // This permission regards the main component (App) and is used to tell 3 | // If the plugin link should be displayed in the menu 4 | // And also if the plugin is accessible. This use case is found when a user types the url of the 5 | // plugin directly in the browser 6 | 'menu-link': [{ action: 'plugin::config-sync.menu-link', subject: null }], 7 | settings: [{ action: 'plugin::config-sync.settings.read', subject: null }], 8 | }; 9 | 10 | export default pluginPermissions; 11 | -------------------------------------------------------------------------------- /admin/src/state/reducers/Config/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Main reducer 4 | * 5 | */ 6 | 7 | import { fromJS, Map, List } from 'immutable'; 8 | import { 9 | SET_CONFIG_DIFF_IN_STATE, 10 | SET_CONFIG_PARTIAL_DIFF_IN_STATE, 11 | SET_LOADING_STATE, 12 | SET_APP_ENV_IN_STATE, 13 | } from '../../actions/Config'; 14 | 15 | const initialState = fromJS({ 16 | configDiff: Map({}), 17 | partialDiff: List([]), 18 | isLoading: false, 19 | appEnv: Map({}), 20 | }); 21 | 22 | export default function configReducer(state = initialState, action) { 23 | switch (action.type) { 24 | case SET_CONFIG_DIFF_IN_STATE: 25 | return state 26 | .update('configDiff', () => fromJS(action.config)); 27 | case SET_CONFIG_PARTIAL_DIFF_IN_STATE: 28 | return state 29 | .update('partialDiff', () => fromJS(action.config)); 30 | case SET_LOADING_STATE: 31 | return state 32 | .update('isLoading', () => fromJS(action.value)); 33 | case SET_APP_ENV_IN_STATE: 34 | return state 35 | .update('appEnv', () => fromJS(action.value)); 36 | default: 37 | return state; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /admin/src/state/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux-immutable'; 2 | import configReducer from './Config'; 3 | 4 | const rootReducer = combineReducers({ 5 | config: configReducer, 6 | }); 7 | 8 | export default rootReducer; 9 | -------------------------------------------------------------------------------- /admin/src/translations/ar.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /admin/src/translations/cs.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /admin/src/translations/de.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /admin/src/translations/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "popUpWarning.warning.import_1": "If you continue all your local config files", 3 | "popUpWarning.warning.import_2": "will be imported into the database.", 4 | "popUpWarning.warning.export_1": "If you continue all your database config", 5 | "popUpWarning.warning.export_2": "will be written into config files.", 6 | "popUpWarning.button.import": "Yes, import", 7 | "popUpWarning.button.export": "Yes, export", 8 | "popUpWarning.button.cancel": "Cancel", 9 | "popUpWarning.force": "Force", 10 | "popUpWarning.Confirmation": "Confirmation", 11 | 12 | "Header.Title": "Config Sync", 13 | "Header.Description": "Manage your database config across environments.", 14 | 15 | "ConfigList.Loading": "Loading content...", 16 | "ConfigList.SelectAll": "Select all entries", 17 | "ConfigList.ConfigName": "Config name", 18 | "ConfigList.ConfigType": "Config type", 19 | "ConfigList.State": "State", 20 | "ConfigList.Different": "Different", 21 | "ConfigList.OnlyDir": "Only in sync dir", 22 | "ConfigList.OnlyDB": "Only in DB", 23 | 24 | "NoChanges.Message": "No differences between DB and sync directory. You are up-to-date!", 25 | 26 | "ConfigDiff.Title": "Config changes for", 27 | "ConfigDiff.SyncDirectory": "Sync directory", 28 | "ConfigDiff.Database": "Database", 29 | 30 | "Buttons.Export": "Export", 31 | "Buttons.DownloadConfig": "Download Config", 32 | "Buttons.Import": "Import", 33 | 34 | "FirstExport.Message": "Looks like this is your first time using config-sync for this project.", 35 | "FirstExport.Button": "Make the initial export", 36 | 37 | "Settings.Tool.Title": "Interface", 38 | 39 | "plugin.name": "Config Sync" 40 | } 41 | -------------------------------------------------------------------------------- /admin/src/translations/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "popUpWarning.warning.import_1": "Si continuas todos tus ficheros de configuración locales", 3 | "popUpWarning.warning.import_2": "se importarán a la base de datos.", 4 | "popUpWarning.warning.export_1": "Si continuas las configuraciones de tu base de datos", 5 | "popUpWarning.warning.export_2": "se escribirán en ficheros de configuración locales.", 6 | "popUpWarning.button.import": "Sí, importar", 7 | "popUpWarning.button.export": "Sí, exportar", 8 | "popUpWarning.button.cancel": "Cancelar", 9 | "popUpWarning.force": "Forzar", 10 | "popUpWarning.Confirmation": "Confirmación", 11 | 12 | "Header.Title": "Config Sync", 13 | "Header.Description": "Gestiona las configuraciones de tu base de datos entre diferentes entornos o instancias.", 14 | 15 | "ConfigList.Loading": "Cargando contenido...", 16 | "ConfigList.SelectAll": "Seleccionar todas las entradas", 17 | "ConfigList.ConfigName": "Nombre", 18 | "ConfigList.ConfigType": "Tipo", 19 | "ConfigList.State": "Estado", 20 | "ConfigList.Different": "Diferentes", 21 | "ConfigList.OnlyDir": "Sólo en directorio de sincronización", 22 | "ConfigList.OnlyDB": "Sólo en la base de datos", 23 | 24 | "NoChanges.Message": "No hay diferencia entre la base de datos y el directorio de sincronización. ¡Estás actualizado!", 25 | 26 | "ConfigDiff.Title": "Cambios en la configuración para", 27 | "ConfigDiff.SyncDirectory": "Directorio de sincronización", 28 | "ConfigDiff.Database": "Base de datos", 29 | 30 | "Buttons.Import": "Importar", 31 | "Buttons.Export": "Exportar", 32 | 33 | "FirstExport.Message": "Parece ser la primera vez que se usa config-sync en este proyecto.", 34 | "FirstExport.Button": "Hacer la exportación inicial", 35 | 36 | "Settings.Tool.Title": "Interfaz", 37 | 38 | "plugin.name": "Config Sync" 39 | } -------------------------------------------------------------------------------- /admin/src/translations/fr.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /admin/src/translations/id.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /admin/src/translations/index.js: -------------------------------------------------------------------------------- 1 | import ar from './ar.json'; 2 | import cs from './cs.json'; 3 | import de from './de.json'; 4 | import en from './en.json'; 5 | import es from './es.json'; 6 | import fr from './fr.json'; 7 | import id from './id.json'; 8 | import it from './it.json'; 9 | import ko from './ko.json'; 10 | import ms from './ms.json'; 11 | import nl from './nl.json'; 12 | import pl from './pl.json'; 13 | import ptBR from './pt-BR.json'; 14 | import pt from './pt.json'; 15 | import ru from './ru.json'; 16 | import th from './th.json'; 17 | import tr from './tr.json'; 18 | import uk from './uk.json'; 19 | import vi from './vi.json'; 20 | import zhHans from './zh-Hans.json'; 21 | import zh from './zh.json'; 22 | import sk from './sk.json'; 23 | 24 | const trads = { 25 | ar, 26 | cs, 27 | de, 28 | en, 29 | es, 30 | fr, 31 | id, 32 | it, 33 | ko, 34 | ms, 35 | nl, 36 | pl, 37 | 'pt-BR': ptBR, 38 | pt, 39 | ru, 40 | th, 41 | tr, 42 | uk, 43 | vi, 44 | 'zh-Hans': zhHans, 45 | zh, 46 | sk, 47 | }; 48 | 49 | export default trads; 50 | -------------------------------------------------------------------------------- /admin/src/translations/it.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /admin/src/translations/ko.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /admin/src/translations/ms.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /admin/src/translations/nl.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /admin/src/translations/pl.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /admin/src/translations/pt-BR.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /admin/src/translations/pt.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /admin/src/translations/ru.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /admin/src/translations/sk.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /admin/src/translations/th.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /admin/src/translations/tr.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /admin/src/translations/uk.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /admin/src/translations/vi.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /admin/src/translations/zh-Hans.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /admin/src/translations/zh.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /bin/config-sync: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | require('../dist/cli'); 6 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: 2 | branches: 3 | - master 4 | - develop 5 | -------------------------------------------------------------------------------- /cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('cypress'); 2 | const fs = require('fs-extra'); 3 | 4 | module.exports = defineConfig({ 5 | e2e: { 6 | baseUrl: 'http://localhost:1337', 7 | specPattern: '**/*.cy.{js,ts,jsx,tsx}', 8 | video: true, 9 | defaultCommandTimeout: 30000, 10 | requestTimeout: 30000, 11 | setupNodeEvents(on, config) { 12 | // implement node event listeners here. 13 | // eslint-disable-next-line global-require 14 | require('cypress-terminal-report/src/installLogsPrinter')(on); 15 | 16 | on('task', { 17 | deleteFolder(folderName) { 18 | console.log(`deleting folder ${folderName}`); 19 | 20 | return fs.remove(folderName) 21 | .then(() => { 22 | console.log(`folder ${folderName} deleted`); 23 | return null; 24 | }) 25 | .catch((err) => { 26 | console.error(`error deleting folder ${folderName}`, err); 27 | throw err; 28 | }); 29 | }, 30 | }); 31 | }, 32 | }, 33 | }); 34 | -------------------------------------------------------------------------------- /cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/e2e.ts is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands'; 18 | 19 | require('cypress-terminal-report/src/installLogsCollector')(); 20 | 21 | // Alternatively you can use CommonJS syntax: 22 | // require('./commands') 23 | -------------------------------------------------------------------------------- /dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: / 5 | schedule: 6 | interval: daily 7 | ignore: 8 | - dependency-name: '\*' 9 | update-types: ["version-update:semver-patch"] 10 | groups: 11 | strapi: 12 | patterns: 13 | - "@strapi/*" 14 | -------------------------------------------------------------------------------- /docs/.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - main 8 | 9 | jobs: 10 | deploy: 11 | name: Deploy 12 | runs-on: ubuntu-latest 13 | environment: 14 | name: docs.pluginpal.io 15 | url: https://docs.pluginpal.io 16 | steps: 17 | - name: Checkout repository 18 | uses: actions/checkout@v3 19 | 20 | - name: Set up Docker 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: '14' 24 | 25 | - name: Build a Docker image 26 | run: | 27 | docker build \ 28 | -t pluginpal-docs:latest . 29 | docker save -o pluginpal-docs-latest.tar pluginpal-docs:latest 30 | 31 | - name: Transfer the Docker image to the Dokku server 32 | uses: appleboy/scp-action@v0.1.3 33 | with: 34 | host: ${{ secrets.SSH_HOST }} 35 | username: ${{ secrets.SSH_CI_USERNAME }} 36 | password: ${{ secrets.SSH_CI_PASSWORD }} 37 | source: pluginpal-docs-latest.tar 38 | target: /var/lib/dokku/data/storage/docs/docker-images 39 | 40 | - name: Deploy the Dokku app based on the Docker image 41 | uses: appleboy/ssh-action@v0.1.10 42 | with: 43 | host: ${{ secrets.SSH_HOST }} 44 | username: ${{ secrets.SSH_CI_USERNAME }} 45 | password: ${{ secrets.SSH_CI_PASSWORD }} 46 | script_stop: true 47 | script: | 48 | sudo docker load -i /var/lib/dokku/data/storage/docs/docker-images/pluginpal-docs-latest.tar 49 | DOCS_LATEST_IMAGE=$(sudo docker images --format "{{.ID}}" pluginpal-docs:latest) 50 | sudo docker tag pluginpal-docs:latest pluginpal-docs:$DOCS_LATEST_IMAGE 51 | dokku git:from-image docs pluginpal-docs:$DOCS_LATEST_IMAGE 52 | sudo docker system prune --all --force 53 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /docs/Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | 3 | # Stage 1: Base image. 4 | ## Start with a base image containing NodeJS so we can build Docusaurus. 5 | FROM node:18-alpine3.18 as base 6 | ## Disable colour output from yarn to make logs easier to read. 7 | ENV FORCE_COLOR=0 8 | ## Enable corepack. 9 | RUN corepack enable 10 | ## Set the working directory to `/opt/docusaurus`. 11 | WORKDIR /opt/docusaurus 12 | 13 | # Stage 2b: Production build mode. 14 | FROM base as prod 15 | ## Set the working directory to `/opt/docusaurus`. 16 | WORKDIR /opt/docusaurus 17 | ## Copy over the source code. 18 | COPY . /opt/docusaurus/ 19 | ## Install dependencies with `--immutable` to ensure reproducibility. 20 | RUN yarn install 21 | ## Build the static site. 22 | RUN yarn build 23 | 24 | # Stage 3a: Serve with `docusaurus serve`. 25 | FROM prod as serve 26 | ## Expose the port that Docusaurus will run on. 27 | EXPOSE 3000 28 | ## Run the production server. 29 | CMD ["yarn", "serve", "--host", "0.0.0.0", "--no-open"] 30 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator. 4 | 5 | ### Installation 6 | 7 | ``` 8 | $ yarn 9 | ``` 10 | 11 | ### Local Development 12 | 13 | ``` 14 | $ yarn start 15 | ``` 16 | 17 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. 18 | 19 | ### Build 20 | 21 | ``` 22 | $ yarn build 23 | ``` 24 | 25 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 26 | 27 | ### Deployment 28 | 29 | Using SSH: 30 | 31 | ``` 32 | $ USE_SSH=true yarn deploy 33 | ``` 34 | 35 | Not using SSH: 36 | 37 | ``` 38 | $ GIT_USER= yarn deploy 39 | ``` 40 | 41 | If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. 42 | -------------------------------------------------------------------------------- /docs/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /docs/blog/2019-05-28-first-blog-post.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: first-blog-post 3 | title: First Blog Post 4 | authors: [slorber, yangshun] 5 | tags: [hola, docusaurus] 6 | --- 7 | 8 | Lorem ipsum dolor sit amet... 9 | 10 | 11 | 12 | ...consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 13 | -------------------------------------------------------------------------------- /docs/blog/2021-08-01-mdx-blog-post.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | slug: mdx-blog-post 3 | title: MDX Blog Post 4 | authors: [slorber] 5 | tags: [docusaurus] 6 | --- 7 | 8 | Blog posts support [Docusaurus Markdown features](https://docusaurus.io/docs/markdown-features), such as [MDX](https://mdxjs.com/). 9 | 10 | :::tip 11 | 12 | Use the power of React to create interactive blog posts. 13 | 14 | ::: 15 | 16 | {/* truncate */} 17 | 18 | For example, use JSX to create an interactive button: 19 | 20 | ```js 21 | 22 | ``` 23 | 24 | 25 | -------------------------------------------------------------------------------- /docs/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pluginpal/strapi-plugin-config-sync/8a7d4baca80c6895b7f46acb4fe70d04a3fd6de7/docs/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg -------------------------------------------------------------------------------- /docs/blog/2021-08-26-welcome/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: welcome 3 | title: Welcome 4 | authors: [slorber, yangshun] 5 | tags: [facebook, hello, docusaurus] 6 | --- 7 | 8 | [Docusaurus blogging features](https://docusaurus.io/docs/blog) are powered by the [blog plugin](https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-content-blog). 9 | 10 | Here are a few tips you might find useful. 11 | 12 | 13 | 14 | Simply add Markdown files (or folders) to the `blog` directory. 15 | 16 | Regular blog authors can be added to `authors.yml`. 17 | 18 | The blog post date can be extracted from filenames, such as: 19 | 20 | - `2019-05-30-welcome.md` 21 | - `2019-05-30-welcome/index.md` 22 | 23 | A blog post folder can be convenient to co-locate blog post images: 24 | 25 | ![Docusaurus Plushie](./docusaurus-plushie-banner.jpeg) 26 | 27 | The blog supports tags as well! 28 | 29 | **And if you don't want a blog**: just delete this directory, and use `blog: false` in your Docusaurus config. 30 | -------------------------------------------------------------------------------- /docs/blog/authors.yml: -------------------------------------------------------------------------------- 1 | yangshun: 2 | name: Yangshun Tay 3 | title: Front End Engineer @ Facebook 4 | url: https://github.com/yangshun 5 | image_url: https://github.com/yangshun.png 6 | page: true 7 | socials: 8 | x: yangshunz 9 | github: yangshun 10 | 11 | slorber: 12 | name: Sébastien Lorber 13 | title: Docusaurus maintainer 14 | url: https://sebastienlorber.com 15 | image_url: https://github.com/slorber.png 16 | page: 17 | # customize the url of the author page at /blog/authors/ 18 | permalink: '/all-sebastien-lorber-articles' 19 | socials: 20 | x: sebastienlorber 21 | linkedin: sebastienlorber 22 | github: slorber 23 | newsletter: https://thisweekinreact.com 24 | -------------------------------------------------------------------------------- /docs/blog/tags.yml: -------------------------------------------------------------------------------- 1 | facebook: 2 | label: Facebook 3 | permalink: /facebook 4 | description: Facebook tag description 5 | 6 | hello: 7 | label: Hello 8 | permalink: /hello 9 | description: Hello tag description 10 | 11 | docusaurus: 12 | label: Docusaurus 13 | permalink: /docusaurus 14 | description: Docusaurus tag description 15 | 16 | hola: 17 | label: Hola 18 | permalink: /hola 19 | description: Hola tag description 20 | -------------------------------------------------------------------------------- /docs/docs/api/plugin-config-types.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_label: 'Plugin config types' 3 | displayed_sidebar: configSyncSidebar 4 | slug: /api/plugin-config-types 5 | --- 6 | 7 | # Plugin config types 8 | 9 | When you're writing a plugin, which registers a content type, you might want to consider that content type as a config type as defined in the Config Sync specification. 10 | 11 | ## Register a config type programatically 12 | 13 | You can register a config type by adding some code to the register function of your plugin. 14 | 15 | ```md title="register.js" 16 | // Register the config type when using the config-sync plugin. 17 | if (strapi.plugin('config-sync')) { 18 | if (!strapi.plugin('config-sync').pluginTypes) { 19 | strapi.plugin('config-sync').pluginTypes = []; 20 | } 21 | 22 | strapi.plugin('config-sync').pluginTypes.push({ 23 | configName: 'url-pattern', 24 | queryString: 'plugin::webtools.url-pattern', 25 | uid: 'code', 26 | }); 27 | } 28 | ``` 29 | 30 | If you want to read more about what the different values of a config type actually mean please read the in depth [custom types](/config-types#custom-types) docs 31 | -------------------------------------------------------------------------------- /docs/docs/configuration/custom-types.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_label: 'Custom types' 3 | displayed_sidebar: configSyncSidebar 4 | slug: /configuration/custom-types 5 | --- 6 | 7 | # Custom types 8 | 9 | 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](/config-types#custom-types) documentation. 10 | 11 | | Name | Details | 12 | | ---- | ------- | 13 | | Key | `customTypes` | 14 | | Required | false | 15 | | Type | array | 16 | | Default | `[]` | 17 | -------------------------------------------------------------------------------- /docs/docs/configuration/excluded-config.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_label: 'Excluded config' 3 | displayed_sidebar: configSyncSidebar 4 | slug: /configuration/excluded-config 5 | --- 6 | 7 | # Excluded config 8 | 9 | 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. 10 | 11 | | Name | Details | 12 | | ---- | ------- | 13 | | Key | `excludedConfig` | 14 | | Required | false | 15 | | Type | array | 16 | | Default | `['core-store.plugin_users-permissions_grant', 'core-store.plugin_upload_metrics', 'core-store.strapi_content_types_schema', 'core-store.ee_information',]` | 17 | -------------------------------------------------------------------------------- /docs/docs/configuration/excluded-types.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_label: 'Excluded types' 3 | displayed_sidebar: configSyncSidebar 4 | slug: /configuration/excluded-types 5 | --- 6 | 7 | # Excluded types 8 | 9 | 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. 10 | 11 | For example: 12 | 13 | ``` 14 | excludedTypes: ['admin-role'] 15 | ``` 16 | 17 | | Name | Details | 18 | | ---- | ------- | 19 | | Key | `excludedTypes` | 20 | | Required | false | 21 | | Type | array | 22 | | Default | `[]` | 23 | -------------------------------------------------------------------------------- /docs/docs/configuration/import-on-bootstrap.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_label: 'Import on bootstrap' 3 | displayed_sidebar: configSyncSidebar 4 | slug: /configuration/import-on-bootstrap 5 | --- 6 | 7 | # Import on bootstrap 8 | 9 | Allows you to let the config be imported automaticly when strapi is bootstrapping (on `strapi start`). 10 | 11 | :::danger 12 | 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**. 13 | ::: 14 | 15 | | Name | Details | 16 | | ---- | ------- | 17 | | Key | `importOnBootstrap` | 18 | | Required | false | 19 | | Type | bool | 20 | | Default | `false` | 21 | -------------------------------------------------------------------------------- /docs/docs/configuration/introduction.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_label: 'Introduction' 3 | displayed_sidebar: configSyncSidebar 4 | slug: /configuration 5 | --- 6 | 7 | # 🔧 Configuration 8 | The settings of the plugin can be overridden in the `config/plugins.js` file. 9 | In the example below you can see how, and also what the default settings are. 10 | 11 | ```md title="config/plugins.js" 12 | module.exports = ({ env }) => ({ 13 | // ... 14 | 'config-sync': { 15 | enabled: true, 16 | config: { 17 | syncDir: "config/sync/", 18 | minify: false, 19 | soft: false, 20 | importOnBootstrap: false, 21 | customTypes: [], 22 | excludedTypes: [], 23 | excludedConfig: [ 24 | "core-store.plugin_users-permissions_grant", 25 | "core-store.plugin_upload_metrics", 26 | "core-store.strapi_content_types_schema", 27 | "core-store.ee_information", 28 | ], 29 | }, 30 | }, 31 | }); 32 | ``` 33 | -------------------------------------------------------------------------------- /docs/docs/configuration/minify.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_label: 'Minify' 3 | displayed_sidebar: configSyncSidebar 4 | slug: /configuration/minify 5 | --- 6 | 7 | # Minify 8 | 9 | When enabled all the exported JSON files will be minified. 10 | 11 | | Name | Details | 12 | | ---- | ------- | 13 | | Key | `minify` | 14 | | Required | false | 15 | | Type | bool | 16 | | Default | `false` | 17 | -------------------------------------------------------------------------------- /docs/docs/configuration/soft.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_label: 'Soft' 3 | displayed_sidebar: configSyncSidebar 4 | slug: /configuration/soft 5 | --- 6 | 7 | # Soft 8 | 9 | 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. 10 | 11 | | Name | Details | 12 | | ---- | ------- | 13 | | Key | `soft` | 14 | | Required | false | 15 | | Type | bool | 16 | | Default | `false` | 17 | -------------------------------------------------------------------------------- /docs/docs/configuration/sync-dir.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_label: 'Sync dir' 3 | displayed_sidebar: configSyncSidebar 4 | slug: /configuration/sync-dir 5 | --- 6 | 7 | # Sync dir 8 | 9 | The path for reading and writing the sync files. 10 | 11 | | Name | Details | 12 | | ---- | ------- | 13 | | Key | `syncDir` | 14 | | Required | true | 15 | | Type | string | 16 | | Default | `config/sync/` | 17 | -------------------------------------------------------------------------------- /docs/docs/getting-started/admin-gui.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_label: 'Admin GUI' 3 | displayed_sidebar: configSyncSidebar 4 | slug: /admin-gui 5 | --- 6 | 7 | # 🖥️ Admin panel (GUI) 8 | 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. 9 | 10 | **Pro tip:** 11 | 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. 12 | 13 | ![Config diff in admin](/img/assets/admin-diff-viewer.png) 14 | -------------------------------------------------------------------------------- /docs/docs/getting-started/installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_label: 'Installation' 3 | displayed_sidebar: configSyncSidebar 4 | slug: / 5 | --- 6 | 7 | # ⏳ Installation 8 | 9 | :::prerequisites 10 | Complete installation requirements are the exact same as for Strapi itself and can be found in the Strapi documentation. 11 | 12 | **Supported Strapi versions:** 13 | 14 | Strapi v5 use `strapi-plugin-config-sync@^3` 15 | 16 | Strapi v4 use `strapi-plugin-config-sync@^1` 17 | 18 | ::: 19 | 20 | Install the plugin in your Strapi project. 21 | 22 | 23 | 24 | ``` 25 | yarn add strapi-plugin-config-sync 26 | ``` 27 | 28 | 29 | ``` 30 | npm install strapi-plugin-config-sync --save 31 | ``` 32 | 33 | 34 | 35 | Add the export path to the `watchIgnoreFiles` list in the `config/admin.js` file. 36 | This way your app won't reload when you export the config in development. 37 | 38 | ```md title="config/admin.js" 39 | module.exports = ({ env }) => ({ 40 | // ... 41 | watchIgnoreFiles: [ 42 | '**/config/sync/**', 43 | ], 44 | }); 45 | ``` 46 | 47 | After successful installation you have to rebuild the admin UI so it'll include this plugin. To rebuild and restart Strapi run: 48 | 49 | 50 | 51 | ``` 52 | yarn build 53 | yarn develop 54 | ``` 55 | 56 | 57 | ``` 58 | npm run build 59 | npm run develop 60 | ``` 61 | 62 | 63 | 64 | The **Config Sync** plugin should now appear in the **Settings** section of your Strapi app. 65 | 66 | 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](/cli) or [Strapi admin panel](/admin-gui) 67 | 68 | Enjoy 🎉 69 | -------------------------------------------------------------------------------- /docs/docs/getting-started/motivation.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_label: 'Motivation' 3 | displayed_sidebar: configSyncSidebar 4 | slug: /motivation 5 | --- 6 | 7 | # 💡 Motivation 8 | 9 | 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 often relies on the database records of these types. 10 | 11 | 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. 12 | 13 | Examples of these types are: 14 | 15 | - Admin roles _(admin::role)_ 16 | - User roles _(plugin::users-permissions.role)_ 17 | - Admin settings _(strapi::core-store)_ 18 | - I18n locale _(plugin::i18n.locale)_ 19 | 20 | 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). 21 | 22 | _With great power comes great responsibility - Uncle Ben_ 23 | -------------------------------------------------------------------------------- /docs/docs/getting-started/naming-convention.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_label: 'Naming convention' 3 | displayed_sidebar: configSyncSidebar 4 | slug: /naming-convention 5 | --- 6 | 7 | # 🔍 Naming convention 8 | All the config files written in the sync directory have the same naming convention. It goes as follows: 9 | 10 | [config-type].[identifier].json 11 | 12 | - `config-type` - Corresponds to the `configName` of the config type. 13 | - `identifier` - Corresponds to the value of the `uid` field of the config type. 14 | -------------------------------------------------------------------------------- /docs/docs/getting-started/workflow.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_label: 'Workflow' 3 | displayed_sidebar: configSyncSidebar 4 | slug: /workflow 5 | --- 6 | 7 | # ⌨️ Usage / Workflow 8 | This plugin works best when you use `git` for the version control of your Strapi project. 9 | 10 | _The following workflows are assuming you're using `git`._ 11 | 12 | ### Intro 13 | 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: 14 | 15 | - Change the file(s), and run an import. You have now imported from filesystem -> database. 16 | - Change the record(s), and run an export. You have now exported from database -> filesystem. 17 | 18 | ### Local development 19 | When building a new feature locally for your Strapi project you'd use the following workflow: 20 | 21 | - Build the feature. 22 | - Export the config. 23 | - Commit and push the files to git. 24 | 25 | ### Deployment 26 | When deploying the newly created feature - to either a server, or a co-worker's machine - you'd use the following workflow: 27 | 28 | - Pull the latest file changes to the environment. 29 | - (Re)start your Strapi instance. 30 | - Import the config. 31 | 32 | ## Production deployment 33 | 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. 34 | -------------------------------------------------------------------------------- /docs/docs/upgrading/generic-update.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_label: 'Generic update' 3 | displayed_sidebar: configSyncSidebar 4 | slug: /upgrading/generic-update 5 | --- 6 | 7 | # Updating Config Sync 8 | 9 | We are always working to make Config Sync better by fixing bugs and introducing new features. These changes will be released as minor or patch versions as defined in the Semantic Versioning specification. 10 | 11 | ## Bump a minor/patch version 12 | 13 | When you're updating Config Sync you'll have to follow these steps: 14 | 15 | 1. Make sure there are no config changes before starting. Either export or import all staged changes. 16 | 2. Update the version of the `strapi-plugin-config-sync` package in your `package.json` using your package manager of choice (yarn/npm/pnpm) 17 | 3. After you've bumped the version make sure to export any new changes that are now shown. It is possible that new configs are introduced, or old ones are updated/removed. 18 | 4. You're now ready to push these changes an commit them to your source control! 19 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pluginpal-docs", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build", 9 | "swizzle": "docusaurus swizzle", 10 | "deploy": "docusaurus deploy", 11 | "clear": "docusaurus clear", 12 | "serve": "docusaurus serve", 13 | "write-translations": "docusaurus write-translations", 14 | "write-heading-ids": "docusaurus write-heading-ids", 15 | "typecheck": "tsc" 16 | }, 17 | "dependencies": { 18 | "@docusaurus/core": "3.5.2", 19 | "@docusaurus/plugin-sitemap": "^3.5.2", 20 | "@docusaurus/preset-classic": "3.5.2", 21 | "@docusaurus/theme-live-codeblock": "^3.5.2", 22 | "@docusaurus/theme-mermaid": "^3.5.2", 23 | "@docusaurus/theme-search-algolia": "^3.5.2", 24 | "@mdx-js/react": "^3.0.0", 25 | "classnames": "^2.5.1", 26 | "clsx": "^2.0.0", 27 | "docusaurus-plugin-sass": "^0.2.5", 28 | "prism-react-renderer": "^2.3.0", 29 | "react": "^18.0.0", 30 | "react-dom": "^18.0.0", 31 | "sass": "^1.78.0" 32 | }, 33 | "devDependencies": { 34 | "@docusaurus/module-type-aliases": "3.5.2", 35 | "@docusaurus/tsconfig": "3.5.2", 36 | "@docusaurus/types": "3.5.2", 37 | "typescript": "~5.5.2" 38 | }, 39 | "browserslist": { 40 | "production": [ 41 | ">0.5%", 42 | "not dead", 43 | "not op_mini all" 44 | ], 45 | "development": [ 46 | "last 3 chrome version", 47 | "last 3 firefox version", 48 | "last 5 safari version" 49 | ] 50 | }, 51 | "engines": { 52 | "node": ">=18.0" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /docs/sidebars.ts: -------------------------------------------------------------------------------- 1 | /** 2 | - create an ordered group of docs 3 | - render a sidebar for each doc of that group 4 | - provide next/previous navigation 5 | 6 | The sidebars can be generated from the filesystem, or explicitly defined here. 7 | 8 | Create as many sidebars as you want. 9 | */ 10 | 11 | // @ts-check 12 | 13 | /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ 14 | const sidebars = { 15 | // By default, Docusaurus generates a sidebar from the docs folder structure 16 | // tutorialSidebar: [{type: 'autogenerated', dirName: '.'}], 17 | 18 | // But you can create a sidebar manually 19 | configSyncSidebar: [ 20 | { 21 | type: "category", 22 | collapsed: false, 23 | label: "🚀 Getting Started", 24 | items: [ 25 | "getting-started/installation", 26 | "getting-started/motivation", 27 | "getting-started/cli", 28 | "getting-started/admin-gui", 29 | "getting-started/config-types", 30 | "getting-started/workflow", 31 | "getting-started/naming-convention", 32 | // "dev-docs/usage-information", 33 | ], 34 | }, 35 | { 36 | type: "category", 37 | collapsed: false, 38 | label: "⚙️ Configuration", 39 | items: [ 40 | "configuration/introduction", 41 | "configuration/sync-dir", 42 | "configuration/minify", 43 | "configuration/import-on-bootstrap", 44 | "configuration/custom-types", 45 | "configuration/soft", 46 | "configuration/excluded-types", 47 | "configuration/excluded-config", 48 | ], 49 | }, 50 | { 51 | type: "category", 52 | collapsed: false, 53 | label: "📦 API", 54 | items: [ 55 | "api/plugin-config-types", 56 | ], 57 | }, 58 | { 59 | type: "category", 60 | collapsed: false, 61 | label: "♻️ Upgrading", 62 | items: [ 63 | "upgrading/generic-update", 64 | ], 65 | }, 66 | ], 67 | }; 68 | 69 | module.exports = sidebars; 70 | -------------------------------------------------------------------------------- /docs/src/components/ApiCall.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import clsx from 'clsx' 3 | 4 | export default function ApiCall({ 5 | children, 6 | noSideBySide = false, 7 | }) { 8 | return ( 9 |
15 | {children} 16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /docs/src/components/Button/Button.jsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx'; 2 | import React from 'react'; 3 | import Link from '@docusaurus/Link'; 4 | import styles from './button.module.scss'; 5 | 6 | export function Button({ 7 | href, 8 | to, 9 | children, 10 | className, 11 | decorative, 12 | size = '', 13 | variant = 'primary', 14 | ...rest 15 | }) { 16 | const ButtonElement = (to ? Link : (href ? 'a' : 'button')); 17 | 18 | return ( 19 | 32 | {children} 33 | {decorative && ( 34 | 35 | {decorative} 36 | 37 | )} 38 | 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /docs/src/components/Container/Container.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import clsx from 'clsx'; 3 | import styles from './container.module.scss'; 4 | 5 | export function Container({ className, ...rest }) { 6 | return ( 7 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /docs/src/components/Container/container.module.scss: -------------------------------------------------------------------------------- 1 | /** Component: Container */ 2 | 3 | :root { 4 | --strapi-container-px: var(--ifm-spacing-horizontal); 5 | --strapi-container-mw: calc(863px + calc(var(--strapi-container-px) * 2)); 6 | } 7 | 8 | .container { 9 | display: flex; 10 | flex-direction: column; 11 | align-items: stretch; 12 | margin-right: auto; 13 | margin-left: auto; 14 | padding-right: var(--strapi-container-px); 15 | padding-left: var(--strapi-container-px); 16 | max-width: var(--strapi-container-mw); 17 | width: 100%; 18 | } 19 | -------------------------------------------------------------------------------- /docs/src/components/CustomDocCard.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import classNames from 'classnames'; 3 | 4 | export default function CustomDocCard(props) { 5 | const { title, description, link, emoji, small = false } = props; 6 | const linkClasses = classNames({ 7 | card: true, 8 | cardContainer: true, 9 | 'padding--lg': !small, 10 | 'padding--md': small, 11 | }); 12 | const cardClasses = classNames({ 13 | 'custom-doc-card': true, 14 | 'margin-bottom--lg': !small, 15 | 'margin-bottom--sm': small, 16 | 'custom-doc-card--small': small, 17 | }); 18 | return ( 19 | 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /docs/src/components/CustomDocCardsWrapper.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default function CustomDocCardsWrapper({ children }) { 4 | return ( 5 |
6 | {children} 7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /docs/src/components/FeaturesList/FeaturesList.jsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx'; 2 | import React from 'react'; 3 | import styles from './features-list.module.scss'; 4 | import { LinkWithArrow } from '../LinkWithArrow/LinkWithArrow'; 5 | 6 | export function FeatureListItem({ 7 | children, 8 | className, 9 | href, 10 | icon: Icon, 11 | iconColor, 12 | label, 13 | to, 14 | ...rest 15 | }) { 16 | const ContentElement = ((href || to) ? LinkWithArrow : 'span'); 17 | const IconElement = ((href || to) ? 'a' : 'span'); 18 | 19 | return ( 20 |
  • 26 | {Icon && ( 27 | 36 | 37 | 38 | )} 39 | 45 | {children || label} 46 | 47 |
  • 48 | ); 49 | } 50 | 51 | export function FeaturesList({ 52 | className, 53 | id, 54 | icon, 55 | iconColor, 56 | items, 57 | ...rest 58 | }) { 59 | const defaultId = `featureListItem${Math.random()}`; 60 | 61 | return ( 62 |
      69 | {items?.map((featureListItem, featureListItemIndex) => { 70 | return ( 71 | 77 | ); 78 | })} 79 |
    80 | ); 81 | } 82 | -------------------------------------------------------------------------------- /docs/src/components/Hero/Hero.jsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx'; 2 | import React from 'react'; 3 | import styles from './hero.module.scss'; 4 | 5 | export function HeroTitle({ 6 | className, 7 | ...rest 8 | }) { 9 | return ( 10 |

    17 | ); 18 | } 19 | 20 | export function HeroDescription({ 21 | className, 22 | ...rest 23 | }) { 24 | return ( 25 |
    32 | ); 33 | } 34 | 35 | export function Hero({ 36 | className, 37 | ...rest 38 | }) { 39 | return ( 40 |
    47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /docs/src/components/Hero/hero.module.scss: -------------------------------------------------------------------------------- 1 | /** Component: Hero */ 2 | 3 | @import '../../scss/_mixins.scss'; 4 | 5 | :root { 6 | --strapi-hero-py: var(--strapi-spacing-6); 7 | --strapi-hero-gap: var(--strapi-spacing-4); 8 | 9 | --strapi-hero-title-color: #1D1B84; 10 | --strapi-hero-title-font-size: var(--strapi-font-size-xl); 11 | --strapi-hero-title-line-height: 1.25; 12 | 13 | --strapi-hero-description-color: #4E6294; 14 | --strapi-hero-description-font-size: var(--strapi-font-size-lg); 15 | --strapi-hero-description-line-height: 1.25; 16 | } 17 | 18 | .hero { 19 | padding-top: var(--strapi-hero-py); 20 | padding-bottom: var(--strapi-hero-py); 21 | text-align: center; 22 | 23 | &__title { 24 | color: var(--strapi-hero-title-color); 25 | font-weight: 700; 26 | font-size: var(--strapi-hero-title-font-size); 27 | line-height: var(--strapi-hero-title-line-height); 28 | letter-spacing: 0.4px; 29 | margin: 0 0 var(--strapi-hero-gap); 30 | } 31 | 32 | &__description { 33 | color: var(--strapi-hero-description-color); 34 | font-size: var(--strapi-hero-description-font-size); 35 | line-height: var(--strapi-hero-description-line-height); 36 | margin: 0; 37 | } 38 | } 39 | 40 | /** Responsive */ 41 | @include medium-up { 42 | :root { 43 | --strapi-hero-py: 40px; 44 | --strapi-hero-title-font-size: 43px; 45 | --strapi-hero-title-line-height: 56px; 46 | --strapi-hero-description-font-size: 21px; 47 | --strapi-hero-description-line-height: 28px; 48 | } 49 | } 50 | 51 | /** Dark mode */ 52 | @include dark { 53 | --strapi-hero-title-color: white; 54 | --strapi-hero-description-color: white; 55 | } 56 | -------------------------------------------------------------------------------- /docs/src/components/HomepageFeatures/index.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx'; 2 | import Heading from '@theme/Heading'; 3 | import styles from './styles.module.css'; 4 | 5 | type FeatureItem = { 6 | title: string; 7 | Svg: React.ComponentType>; 8 | description: JSX.Element; 9 | }; 10 | 11 | const FeatureList: FeatureItem[] = [ 12 | { 13 | title: 'Easy to Use', 14 | Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default, 15 | description: ( 16 | <> 17 | Docusaurus was designed from the ground up to be easily installed and 18 | used to get your website up and running quickly. 19 | 20 | ), 21 | }, 22 | { 23 | title: 'Focus on What Matters', 24 | Svg: require('@site/static/img/undraw_docusaurus_tree.svg').default, 25 | description: ( 26 | <> 27 | Docusaurus lets you focus on your docs, and we'll do the chores. Go 28 | ahead and move your docs into the docs directory. 29 | 30 | ), 31 | }, 32 | { 33 | title: 'Powered by React', 34 | Svg: require('@site/static/img/undraw_docusaurus_react.svg').default, 35 | description: ( 36 | <> 37 | Extend or customize your website layout by reusing React. Docusaurus can 38 | be extended while reusing the same header and footer. 39 | 40 | ), 41 | }, 42 | ]; 43 | 44 | function Feature({title, Svg, description}: FeatureItem) { 45 | return ( 46 |
    47 |
    48 | 49 |
    50 |
    51 | {title} 52 |

    {description}

    53 |
    54 |
    55 | ); 56 | } 57 | 58 | export default function HomepageFeatures(): JSX.Element { 59 | return ( 60 |
    61 |
    62 |
    63 | {FeatureList.map((props, idx) => ( 64 | 65 | ))} 66 |
    67 |
    68 |
    69 | ); 70 | } 71 | -------------------------------------------------------------------------------- /docs/src/components/HomepageFeatures/styles.module.css: -------------------------------------------------------------------------------- 1 | .features { 2 | display: flex; 3 | align-items: center; 4 | padding: 2rem 0; 5 | width: 100%; 6 | } 7 | 8 | .featureSvg { 9 | height: 200px; 10 | width: 200px; 11 | } 12 | -------------------------------------------------------------------------------- /docs/src/components/LinkWithArrow/LinkWithArrow.jsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx'; 2 | import React from 'react'; 3 | import Link from '@docusaurus/Link'; 4 | import IconArrow from '@site/static/img/assets/icons/arrow-right.svg'; 5 | import styles from './link-with-arrow.module.scss'; 6 | 7 | export function LinkWithArrow({ 8 | apart = false, 9 | children, 10 | className, 11 | href, 12 | to, 13 | ...rest 14 | }) { 15 | const LinkElement = (to ? Link : 'a'); 16 | 17 | return ( 18 | 28 | 29 | {children} 30 | 31 | 34 | 35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /docs/src/components/LinkWithArrow/link-with-arrow.module.scss: -------------------------------------------------------------------------------- 1 | /** Component: Link with Arrow */ 2 | 3 | @import '../../scss/_mixins.scss'; 4 | 5 | :root { 6 | --strapi-lwa-icon-ml: var(--strapi-spacing-2); 7 | --strapi-lwa-font-size: 14px; 8 | --strapi-lwa-font-weight: 500; 9 | --strapi-lwa-line-height: 20px; 10 | --strapi-lwa-hover-icon-ml: var(--strapi-spacing-3); 11 | } 12 | 13 | .lwa { 14 | --ifm-link-color: #1D1B84; 15 | --ifm-link-decoration: none; 16 | --ifm-link-hover-color: #2825B8; 17 | --ifm-link-hover-decoration: none; 18 | 19 | font-size: var(--strapi-lwa-font-size); 20 | font-weight: var(--strapi-lwa-font-weight); 21 | line-height: var(--strapi-lwa-line-height); 22 | transition: all 0.2s ease; 23 | 24 | &:focus, &:hover { 25 | --strapi-lwa-icon-ml: var(--strapi-lwa-hover-icon-ml); 26 | } 27 | 28 | &__icon { 29 | display: inline-block; 30 | line-height: 0; 31 | margin-left: var(--strapi-lwa-icon-ml); 32 | transition: margin-left 0.1s ease; 33 | } 34 | 35 | &--apart { 36 | display: flex; 37 | align-items: center; 38 | 39 | .lwa { 40 | &__content { 41 | flex-grow: 1; 42 | } 43 | } 44 | } 45 | } 46 | 47 | /** Dark mode */ 48 | @include dark { 49 | .lwa { 50 | --ifm-link-color: var(--strapi-neutral-1000); 51 | --ifm-link-hover-color: var(--strapi-neutral-500); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /docs/src/components/Request.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default function Request({ 4 | children, 5 | title = 'Example request', 6 | }) { 7 | return ( 8 |
    9 |
    {title}
    10 |
    {children}
    11 |
    12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /docs/src/components/Response.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default function Response({ 4 | children, 5 | title = 'Example response', 6 | }) { 7 | return ( 8 |
    9 |
    {title}
    10 |
    {children}
    11 |
    12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /docs/src/components/SubtleCallout.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default function SubtleCallout({ 4 | children, 5 | title, 6 | emoji = '🤓', 7 | }) { 8 | return ( 9 |
    10 |
    11 | { emoji } { title } 12 |
    13 | { children } 14 |
    15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /docs/src/components/index.js: -------------------------------------------------------------------------------- 1 | export * from './Button/Button'; 2 | export * from './Card/Card'; 3 | export * from './Container/Container'; 4 | export * from './HomepageFeatures'; 5 | export * from './FeaturesList/FeaturesList'; 6 | export * from './Hero/Hero'; 7 | export * from './LinkWithArrow/LinkWithArrow'; 8 | -------------------------------------------------------------------------------- /docs/src/scss/__index.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Any CSS included here will be global. 3 | * The classic template bundles Infima by default. 4 | * Infima is a CSS framework designed to work well for content-centric websites. 5 | */ 6 | 7 | /** Core */ 8 | @import '_mixins.scss'; 9 | @import '_tokens.scss'; 10 | @import '_tokens-overrides.scss'; 11 | 12 | /** Base */ 13 | @import '_fonts.scss'; 14 | @import '_base.scss'; 15 | 16 | /** Components */ 17 | @import 'admonition.scss'; 18 | @import 'api-call.scss'; 19 | @import 'badge.scss'; 20 | @import 'breadcrumbs.scss'; 21 | @import 'card.scss'; 22 | @import 'columns.scss'; 23 | @import 'container.scss'; 24 | @import 'custom-doc-cards.scss'; 25 | @import 'details.scss'; 26 | @import 'footer.scss'; 27 | @import 'grid.scss'; 28 | @import 'images.scss'; 29 | @import 'markdown.scss'; 30 | @import 'medium-zoom.scss'; 31 | @import 'navbar.scss'; 32 | @import 'pagination-nav.scss'; 33 | @import 'search.scss'; 34 | @import 'scene.scss'; 35 | @import 'sidebar.scss'; 36 | @import 'table.scss'; 37 | @import 'tabs.scss'; 38 | @import 'table-of-contents.scss'; 39 | @import 'typography.scss'; 40 | -------------------------------------------------------------------------------- /docs/src/scss/_base.scss: -------------------------------------------------------------------------------- 1 | /** Base: General Styles */ 2 | 3 | :root { 4 | --custom-selection-background-color: var(--strapi-primary-600); 5 | } 6 | 7 | ::selection { 8 | background-color: var(--custom-selection-background-color); 9 | color: var(--custom-selection-color, var(--strapi-neutral-0)); 10 | } 11 | 12 | main { 13 | article:first-child:not(.col):not(.custom-doc-card), 14 | article:first-child:not(.col) + nav { 15 | --custom-main-px: var(--strapi-spacing-0); 16 | --custom-main-width: 683px; 17 | 18 | max-width: calc(var(--custom-main-width) + calc(var(--strapi-spacing-4) * 2)); 19 | padding-left: var(--custom-main-px) !important; 20 | padding-right: var(--custom-main-px) !important; 21 | margin-left: auto; 22 | margin-right: auto; 23 | } 24 | } 25 | 26 | /** Responsive */ 27 | @include medium-up { 28 | main { 29 | article:first-child:not(.col), 30 | article:first-child:not(.col) + nav { 31 | --custom-main-px: var(--strapi-spacing-4); 32 | } 33 | } 34 | } 35 | 36 | /** Dark mode */ 37 | @include dark { 38 | .container img[width="16"] { 39 | /* 'Temporary' fix while we figure a way to display white icons in dark mode 😅 */ 40 | background-color: white; 41 | border-radius: 2px; 42 | padding: 1px; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /docs/src/scss/_fonts.scss: -------------------------------------------------------------------------------- 1 | /** Fonts */ 2 | 3 | @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;700&display=swap'); 4 | 5 | .font-poppins, 6 | .font-poppins button, 7 | .font-poppins input { 8 | --custom-font-family: "Poppins", sans-serif; 9 | --ifm-heading-font-family: "Poppins", sans-serif; 10 | 11 | font-family: var(--custom-font-family); 12 | } 13 | -------------------------------------------------------------------------------- /docs/src/scss/_mixins.scss: -------------------------------------------------------------------------------- 1 | /** Core: Sass Mixins */ 2 | 3 | /** Mixin: Responsive */ 4 | @mixin small-up { 5 | @media (min-width: 769px) { 6 | @content; 7 | } 8 | } 9 | 10 | /** Mixin: Responsive */ 11 | @mixin medium-up { 12 | @media (min-width: 997px) { 13 | @content; 14 | } 15 | } 16 | 17 | /** Mixin: Responsive */ 18 | @mixin large-up { 19 | @media (min-width: 1536px) { 20 | @content; 21 | } 22 | } 23 | 24 | /** Mixin: Dark mode */ 25 | @mixin dark { 26 | html[data-theme='dark'] { 27 | @content; 28 | } 29 | } 30 | 31 | /** Mixin: Light mode */ 32 | @mixin light { 33 | html[data-theme='light'] { 34 | @content; 35 | } 36 | } 37 | 38 | /** Mixin: Transition */ 39 | @mixin transition { 40 | transition: all 0.2s ease; 41 | } 42 | 43 | /** Mixin: Flex */ 44 | @mixin flex-row( 45 | $gap: var(--strapi-spacing-2), 46 | $align-items: center, 47 | $justify-content: null 48 | ) { 49 | display: flex; 50 | flex-direction: row; 51 | gap: $gap; 52 | align-items: $align-items; 53 | justify-content: $justify-content; 54 | } 55 | -------------------------------------------------------------------------------- /docs/src/scss/breadcrumbs.scss: -------------------------------------------------------------------------------- 1 | /** Component: Breadcrumbs */ 2 | 3 | .breadcrumbs { 4 | --ifm-breadcrumb-item-background-active: transparent; 5 | --ifm-breadcrumb-border-radius: var(--strapi-spacing-1); 6 | --ifm-breadcrumb-color-active: var(--strapi-neutral-800); 7 | --ifm-link-hover-color: var(--strapi-neutral-700); 8 | 9 | --custom-breadcrumbs-font-size: var(--strapi-font-size-xs); 10 | --custom-breadcrumbs-pt: var(--strapi-spacing-4); 11 | --custom-breadcrumbs-item-caret-mx: var(--strapi-spacing-1); 12 | 13 | padding: var(--custom-breadcrumbs-pt) 0 0; 14 | 15 | &__item { 16 | &:after { 17 | --ifm-breadcrumb-spacing: var(--custom-breadcrumbs-item-caret-mx); 18 | } 19 | 20 | &--active { 21 | font-weight: 700; 22 | } 23 | } 24 | 25 | &__link { 26 | background-color: transparent; 27 | font-size: var(--custom-breadcrumbs-font-size); 28 | 29 | @include transition; 30 | 31 | &:any-link:hover { 32 | --ifm-breadcrumb-item-background-active: var(--strapi-neutral-100); 33 | } 34 | } 35 | } 36 | 37 | @include medium-up { 38 | .breadcrumbs { 39 | --custom-breadcrumbs-pt: var(--strapi-spacing-6); 40 | --custom-breadcrumbs-item-caret-mx: var(--strapi-spacing-3); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /docs/src/scss/card.scss: -------------------------------------------------------------------------------- 1 | /** Component: Card */ 2 | 3 | :root body { 4 | --custom-card-border-color: var(--strapi-neutral-200); 5 | 6 | --ifm-card-background-color: var(--strapi-neutral-0); 7 | --ifm-card-border-radius: var(--strapi-spacing-1); 8 | } 9 | 10 | .card { 11 | --ifm-color-emphasis-200: var(--custom-card-border-color); 12 | 13 | border: 1px solid var(--custom-card-border-color); 14 | box-shadow: 0px 1px 4px rgba(33, 33, 52, 0.1) !important; 15 | @include transition; 16 | 17 | &[href] { 18 | color: var(--strapi-neutral-600); 19 | 20 | &:hover { 21 | --ifm-color-primary: var(--strapi-neutral-300); 22 | 23 | box-shadow: 0px 2px 15px rgba(33, 33, 52, 0.1) !important; 24 | } 25 | } 26 | 27 | h2 { 28 | --ifm-h2-font-size: var(--strapi-font-size-md); 29 | margin-bottom: var(--strapi-spacing-2); 30 | } 31 | 32 | p { 33 | font-size: var(--strapi-font-size-sm); 34 | 35 | &:last-of-type { 36 | margin-bottom: 0; 37 | } 38 | } 39 | } 40 | 41 | /** Dark mode */ 42 | @include dark { 43 | .card { 44 | &[href] { 45 | color: var(--strapi-neutral-700); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /docs/src/scss/columns.scss: -------------------------------------------------------------------------------- 1 | /** Components: Columns */ 2 | 3 | .column { 4 | &__title { 5 | font-weight: 700; 6 | margin-bottom: var(--strapi-spacing-1); 7 | } 8 | } 9 | 10 | /** Responsive */ 11 | @include medium-up { 12 | .columns { 13 | display: flex; 14 | justify-content: space-between; 15 | gap: var(--strapi-spacing-4); 16 | 17 | > .column { 18 | flex: 1; 19 | max-width: 50%; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /docs/src/scss/container.scss: -------------------------------------------------------------------------------- 1 | /** Component: Container */ 2 | 3 | .container { 4 | padding-top: 0 !important; 5 | } 6 | -------------------------------------------------------------------------------- /docs/src/scss/custom-doc-cards.scss: -------------------------------------------------------------------------------- 1 | /** Component: CustomDocCardWrapper */ 2 | 3 | .custom-cards-wrapper { 4 | display: flex; 5 | flex-flow: row wrap; 6 | max-width: 715px; 7 | justify-content: flex-start; 8 | 9 | .custom-doc-card { 10 | max-width: 320px; 11 | width: 320px; 12 | margin-bottom: 10px; 13 | margin-right: 20px; 14 | 15 | a { 16 | height: 170px; 17 | } 18 | 19 | } 20 | } 21 | 22 | /** Component: CustomDocCard */ 23 | .custom-doc-card { 24 | 25 | a { 26 | text-decoration: none; 27 | } 28 | 29 | a:hover { 30 | .cardTitle { 31 | color: var(--strapi-primary-600); 32 | } 33 | } 34 | 35 | .text--truncate.cardDescription { 36 | // cancel --truncate styles since we can't remove the' class 37 | overflow: auto; 38 | white-space: normal; 39 | } 40 | } 41 | 42 | .custom-doc-card--small { 43 | .cardTitle { 44 | margin-bottom: 0; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /docs/src/scss/details.scss: -------------------------------------------------------------------------------- 1 | /** Component: Details/Accordion */ 2 | 3 | details.alert { 4 | --docusaurus-details-decoration-color: var(--strapi-neutral-800); 5 | 6 | --ifm-alert-background-color: var(--strapi-neutral-150); 7 | --ifm-alert-background-color-highlight: var(--strapi-neutral-500); 8 | --ifm-alert-border-radius: var(--strapi-spacing-1); 9 | --ifm-alert-foreground-color: var( --ifm-color-info-contrast-foreground ); 10 | --ifm-alert-border-color: transparent; 11 | 12 | --ifm-tabs-color-active: var(--ifm-color-primary); 13 | --ifm-tabs-color-active-border: var(--ifm-color-primary); 14 | 15 | summary { 16 | p { 17 | margin: 0; 18 | } 19 | } 20 | 21 | /** Content element */ 22 | > div > div { 23 | --docusaurus-details-decoration-color: transparent; 24 | 25 | margin-top: 0; 26 | } 27 | 28 | a { 29 | color: var(--custom-code-color); 30 | } 31 | 32 | a:hover { 33 | text-decoration: var(--ifm-link-decoration); 34 | } 35 | } 36 | 37 | @include dark { 38 | details a { 39 | color: var(--strapi-primary-500); 40 | font-weight: 700; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /docs/src/scss/footer.scss: -------------------------------------------------------------------------------- 1 | /** Component: Footer */ 2 | 3 | .footer { 4 | a { 5 | @include transition; 6 | font-weight: 400; 7 | 8 | &:hover { 9 | font-weight: 700; 10 | } 11 | } 12 | 13 | &.footer--dark { 14 | --ifm-footer-background-color: var(--strapi-neutral-700); 15 | --ifm-footer-link-hover-color: var(--strapi-neutral-0); 16 | } 17 | } 18 | 19 | /** Dark mode */ 20 | @include dark { 21 | .footer { 22 | &.footer--dark { 23 | --ifm-footer-background-color: var(--strapi-neutral-150); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /docs/src/scss/grid.scss: -------------------------------------------------------------------------------- 1 | /** Component: Grid */ 2 | 3 | .row { 4 | --custom-grid-spacing: var(--strapi-spacing-2); 5 | --ifm-spacing-horizontal: var(--custom-grid-spacing); 6 | 7 | .col { 8 | &.margin-bottom--lg { 9 | margin-bottom: calc(var(--custom-grid-spacing) * 2) !important; 10 | } 11 | } 12 | } 13 | 14 | /** Responsive */ 15 | @include medium-up { 16 | .row { 17 | &--huge { 18 | --custom-grid-spacing: var(--strapi-spacing-4); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /docs/src/scss/images.scss: -------------------------------------------------------------------------------- 1 | /** General: Images */ 2 | 3 | /** Dark Mode images border */ 4 | .theme-doc-markdown.markdown [class*="themedImage--dark"] { 5 | border: 0.125rem solid var(--strapi-neutral-200); 6 | border-radius: 0.25rem; 7 | } 8 | -------------------------------------------------------------------------------- /docs/src/scss/markdown.scss: -------------------------------------------------------------------------------- 1 | /** Component: Markdown */ 2 | 3 | $selector-markdown: ".theme-doc-markdown.markdown"; 4 | 5 | #{$selector-markdown} { 6 | --custom-markdown-pt: var(--strapi-spacing-0); 7 | --custom-markdown-pb: var(--strapi-spacing-2); 8 | 9 | --markdown-link-color: var(--strapi-primary-600); 10 | 11 | font-weight: 400; 12 | padding: var(--custom-markdown-pt) 0 var(--custom-markdown-pb); 13 | } 14 | 15 | /** Dark mode */ 16 | @include dark { 17 | #{$selector-markdown} { 18 | --markdown-link-color: var(--strapi-primary-600); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /docs/src/scss/medium-zoom.scss: -------------------------------------------------------------------------------- 1 | /** Component: Medium Zoom */ 2 | 3 | .medium-zoom { 4 | &-overlay { 5 | background: var(--strapi-neutral-150) !important; 6 | } 7 | } 8 | 9 | /** Responsive */ 10 | @include medium-up { 11 | .medium-zoom { 12 | &-image { 13 | &--opened { 14 | margin-top: var(--ifm-navbar-height) !important; 15 | margin-bottom: var(--ifm-navbar-height) !important; 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /docs/src/scss/pagination-nav.scss: -------------------------------------------------------------------------------- 1 | /** Component: Pagination Nav */ 2 | 3 | .pagination-nav { 4 | --ifm-spacing-horizontal: var(--strapi-spacing-4); 5 | 6 | &__link { 7 | &:hover { 8 | --ifm-pagination-nav-color-hover: var(--custom-card-border-color); 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /docs/src/scss/scene.scss: -------------------------------------------------------------------------------- 1 | /** Component: Scene */ 2 | 3 | #scene { 4 | position: fixed; 5 | top: 0px; 6 | width: 100%; 7 | height: 100%; 8 | z-index: 1000; 9 | } 10 | -------------------------------------------------------------------------------- /docs/src/scss/search.scss: -------------------------------------------------------------------------------- 1 | /** Component: Search */ 2 | 3 | :root body { 4 | --docsearch-hit-height: 56px; 5 | --docsearch-searchbox-height: 40px; 6 | --docsearch-spacing: var(--strapi-spacing-4); 7 | } 8 | 9 | body .DocSearch { 10 | --custom-search-hit-pb: var(--strapi-spacing-2); 11 | 12 | &-SearchBar { 13 | padding-bottom: var(--strapi-spacing-1); 14 | } 15 | 16 | &-Input { 17 | --docsearch-text-color: var(--strapi-neutral-800); 18 | } 19 | 20 | &-Input, 21 | &-Cancel { 22 | font-size: var(--strapi-font-size-md); 23 | } 24 | 25 | &-Hit { 26 | padding-bottom: var(--custom-search-hit-pb); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /docs/src/scss/table-of-contents.scss: -------------------------------------------------------------------------------- 1 | /** Component: Table of Contents */ 2 | 3 | .table-of-contents { 4 | --custom-toc-py: var(--strapi-spacing-1); 5 | --custom-toc-items-py: var(--strapi-spacing-0); 6 | 7 | --ifm-toc-padding-vertical: var(--custom-toc-py); 8 | 9 | font-size: var(--strapi-font-size-xs); 10 | 11 | > li { 12 | margin: 0 var(--ifm-toc-padding-horizontal); 13 | padding-top: var(--custom-toc-items-py); 14 | padding-bottom: var(--custom-toc-items-py); 15 | } 16 | 17 | &__link { 18 | --custom-table-of-contents-link-active-before-left: -16px; 19 | 20 | position: relative; 21 | font-weight: 400; 22 | 23 | @include transition; 24 | 25 | &:hover { 26 | font-weight: 500; 27 | 28 | &:not(.table-of-contents__link--active) { 29 | --ifm-color-primary: var(--strapi-neutral-900); 30 | } 31 | } 32 | 33 | &--active { 34 | font-weight: 500; 35 | 36 | &:before { 37 | content: " "; 38 | position: absolute; 39 | top: 0; 40 | bottom: 0; 41 | left: var(--custom-table-of-contents-link-active-before-left); 42 | width: 3px; 43 | border-radius: 0 2px 2px 0; 44 | background-color: var(--strapi-primary-600); 45 | } 46 | } 47 | 48 | + ul li .table-of-contents { 49 | &__link { 50 | --custom-table-of-contents-link-active-before-left: -32px; 51 | } 52 | } 53 | 54 | img { 55 | display: inline-block; 56 | vertical-align: bottom; 57 | max-width: 22px; 58 | margin-right: 2px; 59 | } 60 | } 61 | } 62 | 63 | /** Responsive */ 64 | @include medium-up { 65 | .table-of-contents { 66 | --custom-toc-items-py: var(--strapi-spacing-2); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /docs/src/scss/table.scss: -------------------------------------------------------------------------------- 1 | /** Component: Table */ 2 | table { 3 | min-width: 100%; 4 | overflow: auto; 5 | 6 | thead { 7 | --ifm-table-background: transparent; 8 | --ifm-table-stripe-background: transparent; 9 | 10 | tr { 11 | border-bottom-width: 1px; 12 | 13 | th { 14 | --ifm-table-head-background: transparent; 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /docs/src/scss/tabs.scss: -------------------------------------------------------------------------------- 1 | /** Component: Tab */ 2 | 3 | :root body { 4 | --custom-tabs-px: var(--strapi-spacing-5); 5 | --custom-tabs-py: var(--strapi-spacing-2); 6 | 7 | --ifm-tabs-padding-horizontal: var(--custom-tabs-px); 8 | --ifm-tabs-padding-vertical: var(--custom-tabs-py); 9 | } 10 | 11 | .tabs { 12 | &__item { 13 | border-top: 2px solid transparent; 14 | } 15 | 16 | /** Tabs inside Tabs */ 17 | + div { 18 | [role="tabpanel"] { 19 | .tabs { 20 | font-size: var(--strapi-font-size-ssm); 21 | 22 | &__item { 23 | &--active { 24 | --ifm-tabs-color-active-border: transparent; 25 | 26 | background-color: var(--ifm-hover-overlay); 27 | } 28 | } 29 | 30 | + [class*="margin-top"] { 31 | margin-top: 0 !important; 32 | } 33 | } 34 | } 35 | } 36 | } 37 | 38 | /** Tabs inside Details component */ 39 | details { 40 | .tabs { 41 | --ifm-tabs-color-active-border: var(--strapi-) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /docs/src/scss/typography.scss: -------------------------------------------------------------------------------- 1 | /** General: Typography */ 2 | 3 | :root { 4 | --custom-heading-decorative-line-color: var(--strapi-neutral-150); 5 | } 6 | 7 | h1, h2, h3, h4, h5, h6 { 8 | --ifm-heading-color: var(--strapi-neutral-900); 9 | --ifm-heading-font-weight: 600; 10 | --ifm-code-font-size: 70%; 11 | } 12 | 13 | h1, .markdown h1:first-child { 14 | --ifm-h1-font-size: 35px; 15 | // --ifm-heading-line-height: 24px; // not good 16 | } 17 | 18 | h2, .markdown > h2 { 19 | --ifm-h2-font-size: 26px; 20 | // --ifm-heading-line-height: 24px; // not good 21 | } 22 | 23 | h3, .markdown > h3 { 24 | --ifm-h3-font-size: var(--strapi-font-size-lg); 25 | @include flex-row; 26 | } 27 | 28 | h2 { 29 | position: relative; 30 | 31 | &:after { 32 | content: " "; 33 | position: absolute; 34 | left: 0; 35 | right: 0; 36 | bottom: -10px; 37 | height: 1px; 38 | background-color: var(--custom-heading-decorative-line-color); 39 | } 40 | } 41 | 42 | p, ul { 43 | img { 44 | display: inline-block; 45 | vertical-align: text-bottom; 46 | } 47 | } 48 | 49 | /** Dark mode */ 50 | @include dark { 51 | h1, h2, h3, h4, h5, h6 { 52 | --ifm-heading-color: var(--strapi-neutral-900); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /docs/src/theme/Admonition/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import clsx from 'clsx'; 3 | import { ThemeClassNames } from '@docusaurus/theme-common'; 4 | 5 | const defaultClassName = ThemeClassNames.common.admonition; 6 | const customDefaultProps = { 7 | note: { 8 | icon: '✏️', 9 | title: 'Note', 10 | }, 11 | tip: { 12 | icon: '💡', 13 | title: 'Tip', 14 | }, 15 | info: { 16 | icon: '👀', 17 | title: 'Info', 18 | }, 19 | caution: { 20 | icon: '✋', 21 | title: 'Caution', 22 | }, 23 | warning: { 24 | icon: '⚠️', 25 | title: 'Warning', 26 | }, 27 | danger: { 28 | icon: '❗️', 29 | title: 'Warning', 30 | }, 31 | strapi: { 32 | icon: '🤓', 33 | }, 34 | prerequisites: { 35 | icon: '☑️', 36 | title: 'Prerequisites', 37 | }, 38 | }; 39 | 40 | export default function CustomAdmonition({ 41 | children, 42 | className, 43 | icon: propIcon, 44 | title: propTitle, 45 | type, 46 | ...rest 47 | }) { 48 | const { icon: defaultIcon, title: defaultTitle } = (customDefaultProps[type] || {}); 49 | const icon = (propIcon || defaultIcon); 50 | const title = (propTitle || defaultTitle); 51 | const shouldRenderHeading = !!(icon || title); 52 | 53 | return ( 54 |
    62 | {shouldRenderHeading && ( 63 |
    64 | {icon && ( 65 | 66 | {icon}{' '} 67 | 68 | )} 69 | {title} 70 |
    71 | )} 72 | {children} 73 |
    74 | ); 75 | } 76 | -------------------------------------------------------------------------------- /docs/src/theme/MDXComponents.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | // Import the original mapper 3 | import MDXComponents from '@theme-original/MDXComponents'; 4 | /** Import built-in Docusaurus components at the global level 5 | * so we don't have to re-import them in every file 6 | */ 7 | 8 | import Tabs from '@theme/Tabs'; 9 | import TabItem from '@theme/TabItem'; 10 | 11 | // Import custom components, globally as well 12 | import Request from '../components/Request'; 13 | import Response from '../components/Response'; 14 | import ApiCall from '../components/ApiCall'; 15 | import SubtleCallout from '../components/SubtleCallout'; 16 | import CustomDocCard from '../components/CustomDocCard'; 17 | import CustomDocCardsWrapper from '../components/CustomDocCardsWrapper'; 18 | 19 | export default { 20 | // Re-use the default mapping 21 | ...MDXComponents, 22 | 23 | /** 24 | * Components below are imported within the global scope, 25 | * meaning you don't have to insert the typical 'import SomeStuff from '/path/to/stuff' line 26 | * at the top of a Markdown file before being able to use these components 27 | * — see https://docusaurus.io/docs/next/markdown-features/react#mdx-component-scope 28 | */ 29 | Request, 30 | Response, 31 | ApiCall, 32 | Tabs, 33 | TabItem, 34 | SubtleCallout, 35 | CustomDocCard, 36 | CustomDocCardsWrapper, 37 | }; 38 | -------------------------------------------------------------------------------- /docs/static/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pluginpal/strapi-plugin-config-sync/8a7d4baca80c6895b7f46acb4fe70d04a3fd6de7/docs/static/.nojekyll -------------------------------------------------------------------------------- /docs/static/img/assets/admin-diff-viewer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pluginpal/strapi-plugin-config-sync/8a7d4baca80c6895b7f46acb4fe70d04a3fd6de7/docs/static/img/assets/admin-diff-viewer.png -------------------------------------------------------------------------------- /docs/static/img/assets/icons/ArrowClockwise.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/Browsers.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/CheckCircle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/Clock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/CreditCard.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/CrossCircle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/Duplicate copy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/Eye.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/Invoice.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/MapTrifold.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/ONHOLDCarretDown.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/ONHOLDCarretUp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/add.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/add_circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/add_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/after.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/api_tokens.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/application.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/arrow-right.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/back.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/carret.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/check_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/chevron-left.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/chevron-right.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/clear.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/close-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/code.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/cog.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/content.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/content_manager.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/content_types_builder.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/crop.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/cross.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/ctb_boolean.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/ctb_date.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/ctb_dz.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/ctb_email.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/ctb_enum.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/ctb_json.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/ctb_media.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/ctb_number.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/ctb_password.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/ctb_relation_1to1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/ctb_relation_1tomany.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/ctb_relation_manyto1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/ctb_relation_manyway.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/ctb_relation_oneway.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/ctb_richtext.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/ctb_text.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/ctb_uid.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/delete.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/documentation-plugin.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/documentation.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/down2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/download.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/duplicate.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/edit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/email_template.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/external_link.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/feather.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/globe.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/grid_view.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/image.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/list_view.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/lock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/marketplace.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/media_library.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/minus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/more.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/move.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/notifications.svg: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 10 | 11 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/official-market.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/plugins.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/provider.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/releases.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/reorder.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/reset_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/roles.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/roles_permissions.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/up.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/up2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/users.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/verified-marketplace.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/assets/icons/webhooks.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/static/img/docusaurus-social-card.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pluginpal/strapi-plugin-config-sync/8a7d4baca80c6895b7f46acb4fe70d04a3fd6de7/docs/static/img/docusaurus-social-card.jpg -------------------------------------------------------------------------------- /docs/static/img/favicon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pluginpal/strapi-plugin-config-sync/8a7d4baca80c6895b7f46acb4fe70d04a3fd6de7/docs/static/img/favicon.jpg -------------------------------------------------------------------------------- /docs/static/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pluginpal/strapi-plugin-config-sync/8a7d4baca80c6895b7f46acb4fe70d04a3fd6de7/docs/static/img/logo.png -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // This file is not used in compilation. It is here just for a nice editor experience. 3 | "extends": "@docusaurus/tsconfig", 4 | "compilerOptions": { 5 | "baseUrl": "." 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'Unit test', 3 | testMatch: ['**/__tests__/?(*.)+(spec|test).js'], 4 | transform: {}, 5 | coverageDirectory: "./coverage/", 6 | collectCoverage: true, 7 | }; 8 | -------------------------------------------------------------------------------- /packup.config.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-extraneous-dependencies 2 | import { defineConfig } from '@strapi/pack-up'; 3 | 4 | export default defineConfig({ 5 | bundles: [ 6 | { 7 | source: './admin/src/index.js', 8 | import: './dist/admin/index.mjs', 9 | require: './dist/admin/index.js', 10 | runtime: 'web', 11 | }, 12 | { 13 | source: './server/index.js', 14 | import: './dist/server/index.mjs', 15 | require: './dist/server/index.js', 16 | runtime: 'node', 17 | }, 18 | { 19 | source: './server/cli.js', 20 | import: './dist/cli/index.mjs', 21 | require: './dist/cli/index.js', 22 | runtime: 'node', 23 | }, 24 | ], 25 | dist: './dist', 26 | exports: {}, 27 | }); 28 | -------------------------------------------------------------------------------- /playground/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [{package.json,*.yml}] 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /playground/.env: -------------------------------------------------------------------------------- 1 | HOST=0.0.0.0 2 | PORT=1337 3 | APP_KEYS=ujfpKPEst1tv0WDxJEhjJw==,MOnFjWYKbWYmtrBZ3cQTFQ==,zQpX70tJw/Mw+Y656kXfVA==,xJT1vbsiz3cgabfgpLu72w== 4 | API_TOKEN_SALT=5FoJkYoZV8IA6+NnZJDzng== 5 | ADMIN_JWT_SECRET=tkeg3+HqE+QmTd2ITEivtA== 6 | TRANSFER_TOKEN_SALT=UUMCRQ2cx9qvKw/RkB815Q== 7 | # Database 8 | DATABASE_CLIENT=sqlite 9 | DATABASE_FILENAME=.tmp/data.db 10 | JWT_SECRET=Dn/nUGQsREUw4/lfQYOScw== 11 | -------------------------------------------------------------------------------- /playground/.env.example: -------------------------------------------------------------------------------- 1 | HOST=0.0.0.0 2 | PORT=1337 3 | APP_KEYS="toBeModified1,toBeModified2" 4 | API_TOKEN_SALT=tobemodified 5 | ADMIN_JWT_SECRET=tobemodified 6 | TRANSFER_TOKEN_SALT=tobemodified 7 | JWT_SECRET=tobemodified 8 | -------------------------------------------------------------------------------- /playground/.eslintignore: -------------------------------------------------------------------------------- 1 | .cache 2 | build 3 | **/node_modules/** 4 | -------------------------------------------------------------------------------- /playground/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": "eslint:recommended", 4 | "env": { 5 | "commonjs": true, 6 | "es6": true, 7 | "node": true, 8 | "browser": false 9 | }, 10 | "parserOptions": { 11 | "ecmaFeatures": { 12 | "experimentalObjectRestSpread": true, 13 | "jsx": false 14 | }, 15 | "sourceType": "module" 16 | }, 17 | "globals": { 18 | "strapi": true 19 | }, 20 | "rules": { 21 | "indent": ["error", 2, { "SwitchCase": 1 }], 22 | "linebreak-style": ["error", "unix"], 23 | "no-console": 0, 24 | "quotes": ["error", "single"], 25 | "semi": ["error", "always"] 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /playground/.gitignore: -------------------------------------------------------------------------------- 1 | ############################ 2 | # OS X 3 | ############################ 4 | 5 | .DS_Store 6 | .AppleDouble 7 | .LSOverride 8 | Icon 9 | .Spotlight-V100 10 | .Trashes 11 | ._* 12 | 13 | 14 | ############################ 15 | # Linux 16 | ############################ 17 | 18 | *~ 19 | 20 | 21 | ############################ 22 | # Windows 23 | ############################ 24 | 25 | Thumbs.db 26 | ehthumbs.db 27 | Desktop.ini 28 | $RECYCLE.BIN/ 29 | *.cab 30 | *.msi 31 | *.msm 32 | *.msp 33 | 34 | 35 | ############################ 36 | # Packages 37 | ############################ 38 | 39 | *.7z 40 | *.csv 41 | *.dat 42 | *.dmg 43 | *.gz 44 | *.iso 45 | *.jar 46 | *.rar 47 | *.tar 48 | *.zip 49 | *.com 50 | *.class 51 | *.dll 52 | *.exe 53 | *.o 54 | *.seed 55 | *.so 56 | *.swo 57 | *.swp 58 | *.swn 59 | *.swm 60 | *.out 61 | *.pid 62 | 63 | 64 | ############################ 65 | # Logs and databases 66 | ############################ 67 | 68 | .tmp 69 | *.log 70 | *.sql 71 | *.sqlite 72 | *.sqlite3 73 | 74 | 75 | ############################ 76 | # Misc. 77 | ############################ 78 | 79 | *# 80 | ssl 81 | .idea 82 | nbproject 83 | public/uploads/* 84 | !public/uploads/.gitkeep 85 | 86 | ############################ 87 | # Node.js 88 | ############################ 89 | 90 | lib-cov 91 | lcov.info 92 | pids 93 | logs 94 | results 95 | node_modules 96 | .node_history 97 | yarn.lock 98 | 99 | ############################ 100 | # Tests 101 | ############################ 102 | 103 | testApp 104 | coverage 105 | /config/sync 106 | 107 | ############################ 108 | # Strapi 109 | ############################ 110 | 111 | # .env 112 | license.txt 113 | exports 114 | *.cache 115 | build 116 | .strapi-updater.json 117 | 118 | # yalc 119 | .yalc 120 | yalc.lock 121 | -------------------------------------------------------------------------------- /playground/.strapi/client/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was automatically generated by Strapi. 3 | * Any modifications made will be discarded. 4 | */ 5 | import strapiCloud from "@strapi/plugin-cloud/strapi-admin"; 6 | import usersPermissions from "@strapi/plugin-users-permissions/strapi-admin"; 7 | import configSync from "strapi-plugin-config-sync/strapi-admin"; 8 | import { renderAdmin } from "@strapi/strapi/admin"; 9 | 10 | renderAdmin(document.getElementById("strapi"), { 11 | plugins: { 12 | "strapi-cloud": strapiCloud, 13 | "users-permissions": usersPermissions, 14 | "config-sync": configSync, 15 | }, 16 | }); 17 | -------------------------------------------------------------------------------- /playground/.strapi/client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 13 | 14 | 15 | Strapi Admin 16 | 27 | 28 | 29 |
    30 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /playground/__tests__/helpers.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const { createStrapi, compileStrapi } = require('@strapi/strapi'); 3 | 4 | let instance; 5 | 6 | async function setupStrapi() { 7 | if (!instance) { 8 | const appContext = await compileStrapi(); 9 | await createStrapi(appContext).load(); 10 | instance = strapi; 11 | 12 | await instance.server.mount(); 13 | } 14 | return instance; 15 | } 16 | 17 | async function cleanupStrapi() { 18 | const dbSettings = strapi.config.get('database.connection'); 19 | 20 | // close server to release the db-file. 21 | await strapi.server.httpServer.close(); 22 | 23 | // close the connection to the database before deletion. 24 | await strapi.db.connection.destroy(); 25 | 26 | // delete test database after all tests have completed. 27 | if (dbSettings && dbSettings.connection && dbSettings.connection.filename) { 28 | const tmpDbFile = dbSettings.connection.filename; 29 | if (fs.existsSync(tmpDbFile)) { 30 | fs.unlinkSync(tmpDbFile); 31 | } 32 | } 33 | } 34 | 35 | module.exports = { setupStrapi, cleanupStrapi }; 36 | -------------------------------------------------------------------------------- /playground/__tests__/import-on-boostrap.test.js: -------------------------------------------------------------------------------- 1 | const util = require('util'); 2 | const exec = util.promisify(require('child_process').exec); 3 | 4 | const { setupStrapi, cleanupStrapi } = require('./helpers'); 5 | 6 | jest.setTimeout(20000); 7 | 8 | afterEach(async () => { 9 | // Disable importOnBootstrap 10 | await exec('sed -i "s/importOnBootstrap: true/importOnBootstrap: false/g" config/plugins.js'); 11 | 12 | await cleanupStrapi(); 13 | await exec('rm -rf config/sync'); 14 | }); 15 | 16 | describe('Test the importOnBootstrap feature', () => { 17 | test('Without a database', async () => { 18 | // Do the initial export and remove the database. 19 | await exec('yarn cs export -y'); 20 | await exec('rm -rf .tmp'); 21 | 22 | // Manually change the plugins.js to enable importOnBoostrap. 23 | await exec('sed -i "s/importOnBootstrap: false/importOnBootstrap: true/g" config/plugins.js'); 24 | 25 | // Start up Strapi to initiate the importOnBootstrap function. 26 | await setupStrapi(); 27 | 28 | expect(strapi).toBeDefined(); 29 | }); 30 | 31 | test('With a database', async () => { 32 | // Delete any existing database and do an export. 33 | await exec('rm -rf .tmp'); 34 | await exec('yarn cs export -y'); 35 | 36 | // Manually change the plugins.js to enable importOnBoostrap. 37 | await exec('sed -i "s/importOnBootstrap: false/importOnBootstrap: true/g" config/plugins.js'); 38 | 39 | // Remove a config file to make sure the importOnBoostrap 40 | // function actually attempts to import. 41 | await exec('rm -rf config/sync/admin-role.strapi-editor.json'); 42 | 43 | // Start up Strapi to initiate the importOnBootstrap function. 44 | await setupStrapi(); 45 | 46 | expect(strapi).toBeDefined(); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /playground/config/admin.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ env }) => ({ 2 | auth: { 3 | secret: env('ADMIN_JWT_SECRET'), 4 | }, 5 | apiToken: { 6 | salt: env('API_TOKEN_SALT'), 7 | }, 8 | rateLimit: { 9 | enabled: false, 10 | }, 11 | transfer: { 12 | token: { 13 | salt: env('TRANSFER_TOKEN_SALT'), 14 | }, 15 | }, 16 | flags: { 17 | nps: env.bool('FLAG_NPS', true), 18 | promoteEE: env.bool('FLAG_PROMOTE_EE', true), 19 | }, 20 | watchIgnoreFiles: [ 21 | '!**/.yalc/**/server/**', 22 | '**/config/sync/**', 23 | ] 24 | }); 25 | -------------------------------------------------------------------------------- /playground/config/api.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rest: { 3 | defaultLimit: 25, 4 | maxLimit: 100, 5 | withCount: true, 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /playground/config/database.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = ({ env }) => ({ 4 | connection: { 5 | client: 'sqlite', 6 | connection: { 7 | filename: path.join(__dirname, '..', env('DATABASE_FILENAME', '.tmp/data.db')), 8 | }, 9 | useNullAsDefault: true, 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /playground/config/middlewares.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | 'strapi::logger', 3 | 'strapi::errors', 4 | 'strapi::security', 5 | 'strapi::cors', 6 | 'strapi::poweredBy', 7 | 'strapi::query', 8 | 'strapi::body', 9 | 'strapi::session', 10 | 'strapi::favicon', 11 | 'strapi::public', 12 | ]; 13 | -------------------------------------------------------------------------------- /playground/config/plugins.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'config-sync': { 3 | enabled: true, 4 | config: { 5 | importOnBootstrap: false, 6 | minify: true, 7 | }, 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /playground/config/server.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ env }) => ({ 2 | host: env('HOST', '0.0.0.0'), 3 | port: env.int('PORT', 1337), 4 | app: { 5 | keys: env.array('APP_KEYS'), 6 | }, 7 | webhooks: { 8 | populateRelations: env.bool('WEBHOOKS_POPULATE_RELATIONS', false), 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /playground/database/migrations/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pluginpal/strapi-plugin-config-sync/8a7d4baca80c6895b7f46acb4fe70d04a3fd6de7/playground/database/migrations/.gitkeep -------------------------------------------------------------------------------- /playground/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pluginpal/strapi-plugin-config-sync/8a7d4baca80c6895b7f46acb4fe70d04a3fd6de7/playground/favicon.png -------------------------------------------------------------------------------- /playground/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'Integration test', 3 | testMatch: ['**/__tests__/?(*.)+(spec|test).js'], 4 | transform: {}, 5 | coverageDirectory: '../coverage/', 6 | collectCoverage: true, 7 | }; 8 | -------------------------------------------------------------------------------- /playground/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "nodenext", 4 | "target": "ES2021", 5 | "checkJs": true, 6 | "allowJs": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "strapi-5-beta", 3 | "private": true, 4 | "version": "0.1.0", 5 | "description": "A Strapi application", 6 | "scripts": { 7 | "develop": "strapi develop", 8 | "start": "strapi start", 9 | "build": "strapi build", 10 | "strapi": "strapi", 11 | "cs": "config-sync" 12 | }, 13 | "devDependencies": { 14 | "jest": "^29.7.0", 15 | "jest-cli": "^29.7.0", 16 | "supertest": "^6.3.3", 17 | "yalc": "^1.0.0-pre.53" 18 | }, 19 | "dependencies": { 20 | "@strapi/plugin-cloud": "5.0.4", 21 | "@strapi/plugin-users-permissions": "5.0.4", 22 | "@strapi/strapi": "5.0.4", 23 | "better-sqlite3": "9.4.3", 24 | "react": "^18.0.0", 25 | "react-dom": "^18.0.0", 26 | "react-router-dom": "^6.0.0", 27 | "strapi-plugin-config-sync": "link:.yalc/strapi-plugin-config-sync", 28 | "styled-components": "^6.0.0" 29 | }, 30 | "author": { 31 | "name": "A Strapi developer" 32 | }, 33 | "strapi": { 34 | "uuid": "edadddbd-0f25-4da7-833b-d4cd7dcae2fc" 35 | }, 36 | "engines": { 37 | "node": ">=18.0.0 <=20.x.x", 38 | "npm": ">=6.0.0" 39 | }, 40 | "license": "MIT" 41 | } 42 | -------------------------------------------------------------------------------- /playground/public/robots.txt: -------------------------------------------------------------------------------- 1 | # To prevent search engines from seeing the site altogether, uncomment the next two lines: 2 | # User-Agent: * 3 | # Disallow: / 4 | -------------------------------------------------------------------------------- /playground/public/uploads/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pluginpal/strapi-plugin-config-sync/8a7d4baca80c6895b7f46acb4fe70d04a3fd6de7/playground/public/uploads/.gitkeep -------------------------------------------------------------------------------- /playground/src/admin/app.example.js: -------------------------------------------------------------------------------- 1 | export default { 2 | config: { 3 | locales: [ 4 | // 'ar', 5 | // 'fr', 6 | // 'cs', 7 | // 'de', 8 | // 'dk', 9 | // 'es', 10 | // 'he', 11 | // 'id', 12 | // 'it', 13 | // 'ja', 14 | // 'ko', 15 | // 'ms', 16 | // 'nl', 17 | // 'no', 18 | // 'pl', 19 | // 'pt-BR', 20 | // 'pt', 21 | // 'ru', 22 | // 'sk', 23 | // 'sv', 24 | // 'th', 25 | // 'tr', 26 | // 'uk', 27 | // 'vi', 28 | // 'zh-Hans', 29 | // 'zh', 30 | ], 31 | }, 32 | bootstrap(app) { 33 | console.log(app); 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /playground/src/admin/webpack.config.example.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint-disable no-unused-vars */ 4 | module.exports = (config, webpack) => { 5 | // Note: we provide webpack above so you should not `require` it 6 | // Perform customizations to webpack config 7 | // Important: return the modified config 8 | return config; 9 | }; 10 | -------------------------------------------------------------------------------- /playground/src/api/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pluginpal/strapi-plugin-config-sync/8a7d4baca80c6895b7f46acb4fe70d04a3fd6de7/playground/src/api/.gitkeep -------------------------------------------------------------------------------- /playground/src/api/home/content-types/home/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "singleType", 3 | "collectionName": "homes", 4 | "info": { 5 | "singularName": "home", 6 | "pluralName": "homes", 7 | "displayName": "Home", 8 | "description": "" 9 | }, 10 | "options": { 11 | "draftAndPublish": false 12 | }, 13 | "pluginOptions": {}, 14 | "attributes": { 15 | "title": { 16 | "type": "string" 17 | }, 18 | "slug": { 19 | "type": "uid", 20 | "targetField": "title" 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /playground/src/api/home/controllers/home.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * home controller 5 | */ 6 | 7 | const { createCoreController } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreController('api::home.home'); 10 | -------------------------------------------------------------------------------- /playground/src/api/home/routes/home.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * home router 5 | */ 6 | 7 | const { createCoreRouter } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreRouter('api::home.home'); 10 | -------------------------------------------------------------------------------- /playground/src/api/home/services/home.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * home service 5 | */ 6 | 7 | const { createCoreService } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreService('api::home.home'); 10 | -------------------------------------------------------------------------------- /playground/src/api/page/content-types/page/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "kind": "collectionType", 3 | "collectionName": "pages", 4 | "info": { 5 | "singularName": "page", 6 | "pluralName": "pages", 7 | "displayName": "Page" 8 | }, 9 | "options": { 10 | "draftAndPublish": false 11 | }, 12 | "pluginOptions": {}, 13 | "attributes": { 14 | "title": { 15 | "type": "string", 16 | "required": true 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /playground/src/api/page/controllers/page.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * page controller 5 | */ 6 | 7 | const { createCoreController } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreController('api::page.page'); 10 | -------------------------------------------------------------------------------- /playground/src/api/page/routes/page.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * page router 5 | */ 6 | 7 | const { createCoreRouter } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreRouter('api::page.page'); 10 | -------------------------------------------------------------------------------- /playground/src/api/page/services/page.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * page service 5 | */ 6 | 7 | const { createCoreService } = require('@strapi/strapi').factories; 8 | 9 | module.exports = createCoreService('api::page.page'); 10 | -------------------------------------------------------------------------------- /playground/src/extensions/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pluginpal/strapi-plugin-config-sync/8a7d4baca80c6895b7f46acb4fe70d04a3fd6de7/playground/src/extensions/.gitkeep -------------------------------------------------------------------------------- /playground/src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | /** 5 | * An asynchronous register function that runs before 6 | * your application is initialized. 7 | * 8 | * This gives you an opportunity to extend code. 9 | */ 10 | register(/*{ strapi }*/) {}, 11 | 12 | /** 13 | * An asynchronous bootstrap function that runs before 14 | * your application gets started. 15 | * 16 | * This gives you an opportunity to set up your data model, 17 | * run jobs, or perform some special logic. 18 | */ 19 | bootstrap(/*{ strapi }*/) {}, 20 | }; 21 | -------------------------------------------------------------------------------- /playground/types/generated/components.d.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * The app doesn't have any components yet. 3 | */ 4 | -------------------------------------------------------------------------------- /server/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export default { 4 | default: { 5 | syncDir: "config/sync/", 6 | minify: false, 7 | soft: false, 8 | importOnBootstrap: false, 9 | customTypes: [], 10 | excludedTypes: [], 11 | excludedConfig: [ 12 | "core-store.plugin_users-permissions_grant", 13 | "core-store.plugin_upload_metrics", 14 | "core-store.plugin_upload_api-folder", 15 | "core-store.strapi_content_types_schema", 16 | "core-store.ee_information", 17 | ], 18 | }, 19 | validator() {}, 20 | }; 21 | -------------------------------------------------------------------------------- /server/config/types.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const types = (strapi) => { 4 | // Initiate Strapi 'core-store' and 'admin-role' types. 5 | const typesArray = [ 6 | { 7 | configName: 'core-store', 8 | queryString: 'strapi::core-store', 9 | uid: 'key', 10 | jsonFields: ['value'], 11 | }, 12 | { 13 | configName: 'admin-role', 14 | queryString: 'admin::role', 15 | uid: 'code', 16 | relations: [{ 17 | queryString: 'admin::permission', 18 | relationName: 'permissions', 19 | parentName: 'role', 20 | relationSortFields: ['action', 'subject'], 21 | }], 22 | }, 23 | ]; 24 | 25 | // Register plugin users-permissions 'role' type. 26 | if (strapi.plugin('users-permissions')) { 27 | typesArray.push({ 28 | configName: 'user-role', 29 | queryString: 'plugin::users-permissions.role', 30 | uid: 'type', 31 | relations: [{ 32 | queryString: 'plugin::users-permissions.permission', 33 | relationName: 'permissions', 34 | parentName: 'role', 35 | relationSortFields: ['action'], 36 | }], 37 | }); 38 | } 39 | 40 | // Register plugin i18n 'locale' type. 41 | if (strapi.plugin('i18n')) { 42 | typesArray.push({ 43 | configName: 'i18n-locale', 44 | queryString: 'plugin::i18n.locale', 45 | uid: 'code', 46 | }); 47 | } 48 | 49 | return typesArray; 50 | }; 51 | 52 | export default types; 53 | -------------------------------------------------------------------------------- /server/controllers/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import config from './config'; 4 | 5 | export default { 6 | config: config, 7 | }; 8 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import register from './register'; 4 | import bootstrap from './bootstrap'; 5 | import services from './services'; 6 | import routes from './routes'; 7 | import config from './config'; 8 | import controllers from './controllers'; 9 | 10 | export default { 11 | register, 12 | bootstrap, 13 | routes, 14 | config, 15 | controllers, 16 | services, 17 | }; 18 | -------------------------------------------------------------------------------- /server/register.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export default async ({ strapi }) => { 4 | // Instantiate the pluginTypes array. 5 | if (!strapi.plugin('config-sync').pluginTypes) { 6 | strapi.plugin('config-sync').pluginTypes = []; 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /server/routes/admin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export default { 4 | type: 'admin', 5 | routes: [ 6 | { 7 | method: "POST", 8 | path: "/export", 9 | handler: "config.exportAll", 10 | config: { 11 | policies: [], 12 | }, 13 | }, 14 | { 15 | method: "POST", 16 | path: "/import", 17 | handler: "config.importAll", 18 | config: { 19 | policies: [], 20 | }, 21 | }, 22 | { 23 | method: "GET", 24 | path: "/diff", 25 | handler: "config.getDiff", 26 | config: { 27 | policies: [], 28 | }, 29 | }, 30 | { 31 | method: "GET", 32 | path: "/zip", 33 | handler: "config.zipConfig", 34 | config: { 35 | policies: [], 36 | }, 37 | }, 38 | { 39 | method: "GET", 40 | path: "/app-env", 41 | handler: "config.getAppEnv", 42 | config: { 43 | policies: [], 44 | }, 45 | }, 46 | ], 47 | }; 48 | -------------------------------------------------------------------------------- /server/routes/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import adminRoutes from './admin'; 4 | 5 | export default { 6 | admin: adminRoutes, 7 | }; 8 | -------------------------------------------------------------------------------- /server/services/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import main from './main'; 4 | 5 | export default { 6 | main, 7 | }; 8 | -------------------------------------------------------------------------------- /server/utils/getArrayDiff.js: -------------------------------------------------------------------------------- 1 | export const difference = (arrayOne, arrayTwo, compareKeys) => { 2 | return arrayOne.filter(({ 3 | [compareKeys[0]]: id1, 4 | [compareKeys[1]]: id2, 5 | }) => { 6 | return !arrayTwo.some(({ 7 | [compareKeys[0]]: id3, 8 | [compareKeys[1]]: id4, 9 | }) => id1 === id3 && id2 === id4); 10 | }); 11 | }; 12 | 13 | export const same = (arrayOne, arrayTwo, compareKeys) => { 14 | return arrayOne.filter(({ 15 | [compareKeys[0]]: id1, 16 | [compareKeys[1]]: id2, 17 | ...restOne 18 | }) => { 19 | return !arrayTwo.some(({ 20 | [compareKeys[0]]: id3, 21 | [compareKeys[1]]: id4, 22 | ...restTwo 23 | }) => id1 === id3 24 | && id2 === id4 25 | && JSON.stringify(restOne) === JSON.stringify(restTwo)); 26 | }); 27 | }; 28 | -------------------------------------------------------------------------------- /server/utils/getObjectDiff.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import transform from 'lodash/transform'; 4 | import isEqual from 'lodash/isEqual'; 5 | import isArray from 'lodash/isArray'; 6 | import isObject from 'lodash/isObject'; 7 | 8 | /** 9 | * Find difference between two objects 10 | * @param {object} origObj - Source object to compare newObj against 11 | * @param {object} newObj - New object with potential changes 12 | * @return {object} differences 13 | */ 14 | const difference = (origObj, newObj) => { 15 | let arrayIndexCounter = 0; 16 | 17 | const newObjChange = transform(newObj, (result, value, key) => { 18 | if (!isEqual(value, origObj[key])) { 19 | const resultKey = isArray(origObj) ? arrayIndexCounter++ : key; 20 | result[resultKey] = (isObject(value) && isObject(origObj[key])) ? difference(value, origObj[key]) : value; 21 | } 22 | }); 23 | const origObjChange = transform(origObj, (result, value, key) => { 24 | if (!isEqual(value, newObj[key])) { 25 | const resultKey = isArray(newObj) ? arrayIndexCounter++ : key; 26 | result[resultKey] = (isObject(value) && isObject(newObj[key])) ? difference(value, newObj[key]) : value; 27 | } 28 | }); 29 | 30 | return Object.assign(newObjChange, origObjChange); 31 | }; 32 | 33 | export default difference; 34 | -------------------------------------------------------------------------------- /server/utils/queryFallBack.js: -------------------------------------------------------------------------------- 1 | const queryFallBack = { 2 | create: async (queryString, options) => { 3 | try { 4 | const newEntity = await strapi.documents(queryString).create(options); 5 | 6 | return newEntity; 7 | } catch (e) { 8 | return strapi.query(queryString).create(options); 9 | } 10 | }, 11 | update: async (queryString, options) => { 12 | try { 13 | const entity = await strapi.query(queryString).findOne(options); 14 | const updatedEntity = await strapi.documents(queryString).update({ 15 | documentId: entity.documentId, 16 | ...options, 17 | }); 18 | 19 | return updatedEntity; 20 | } catch (e) { 21 | return strapi.query(queryString).update(options); 22 | } 23 | }, 24 | delete: async (queryString, options) => { 25 | try { 26 | const entity = await strapi.query(queryString).findOne(options); 27 | await strapi.documents(queryString).delete({ 28 | documentId: entity.documentId, 29 | }); 30 | } catch (e) { 31 | await strapi.query(queryString).delete(options); 32 | } 33 | }, 34 | }; 35 | 36 | export default queryFallBack; 37 | -------------------------------------------------------------------------------- /server/warnings.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export default { 4 | delete: { 5 | 'user-role.public': "You've just deleted a default role from the users-permissions plugin.", 6 | 'user-role.authenticated': "You've just deleted a default role from the users-permissions plugin.", 7 | 'admin-role.strapi-super-admin': "You've just deleted one of the default admin roles from Strapi.", 8 | 'admin-role.strapi-author': "You've just deleted one of the default admin roles from Strapi.", 9 | 'admin-role.strapi-editor': "You've just deleted one of the default admin roles from Strapi.", 10 | }, 11 | }; 12 | --------------------------------------------------------------------------------