[
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = LF\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n\n[*.md]\ntrim_trailing_whitespace = false"
  },
  {
    "path": ".eslintignore",
    "content": "**/node_modules\n**/public\n**/build\n**/dist\n**/config\n**/scripts\n**/docs\n**/playground\n"
  },
  {
    "path": ".eslintrc",
    "content": "{\n  \"root\": true,\n  \"extends\": [\"react-app\", \"airbnb\"],\n  \"parser\": \"babel-eslint\",\n  \"plugins\": [\n    \"babel\",\n    \"react\",\n    \"jsx-a11y\",\n    \"import\",\n    \"react-hooks\"\n  ],\n  \"env\": {\n    \"browser\": true,\n    \"es6\": true,\n    \"commonjs\": true,\n    \"node\": true\n  },\n  \"parserOptions\": {\n    \"ecmaFeatures\": {\n      \"jsx\": true\n    },\n    \"ecmaVersion\": 12,\n    \"sourceType\": \"module\"\n  },\n  \"globals\": {\n    \"strapi\": true\n  },\n  \"overrides\": [\n    {\n      \"files\": [\n        \"**/*.cy.*\",\n        \"./cypress/**/*.*\"\n      ],\n      \"extends\": [\n        \"plugin:cypress/recommended\"\n      ],\n      \"parserOptions\": {\n        \"project\": \"./tsconfig.cypress.json\"\n      }\n    }\n  ],\n  \"rules\": {\n    \"import/no-unresolved\": [2, {\n      \"ignore\": [\n        \"@strapi/strapi/admin\",\n        \"@strapi/icons/symbols\",\n        \"@strapi/admin/strapi-admin\"\n      ]\n    }],\n\n    \"template-curly-spacing\" : \"off\",\n\n    \"indent\" : \"off\",\n\n    \"react/jsx-fragments\": \"off\",\n\n    \"react/jsx-props-no-spreading\": \"off\",\n\n    \"react-hooks/rules-of-hooks\": \"error\",\n\n    \"react-hooks/exhaustive-deps\": \"off\",\n\n    \"react/no-unused-prop-types\": \"warn\",\n\n    \"react/jsx-no-target-blank\": \"error\",\n\n    \"no-invalid-this\": \"off\",\n\n    \"babel/no-invalid-this\": \"error\",\n\n    \"arrow-spacing\": \"warn\",\n\n    \"implicit-arrow-linebreak\": \"warn\",\n\n    \"react/no-unused-state\": \"warn\",\n\n    \"react/boolean-prop-naming\": \"off\",\n\n    \"react/destructuring-assignment\": [\"warn\", \"always\", { \"ignoreClassFields\": true }],\n\n    \"react/no-access-state-in-setstate\": \"warn\",\n\n    \"operator-linebreak\": \"warn\",\n\n    \"no-useless-constructor\": \"warn\",\n\n    \"react/no-danger\": \"off\",\n\n    \"react/jsx-indent-props\": \"warn\",\n\n    \"react/jsx-curly-brace-presence\": \"warn\",\n\n    \"react/jsx-key\": \"error\",\n\n    \"react/jsx-boolean-value\": \"warn\",\n\n    \"react/jsx-closing-tag-location\": \"warn\",\n\n    \"import/extensions\": \"error\",\n\n    \"newline-per-chained-call\": \"warn\",\n\n    \"prefer-arrow-callback\": \"warn\",\n\n    \"block-spacing\": \"warn\",\n\n    \"one-var-declaration-per-line\": \"warn\",\n\n    \"prefer-const\": \"warn\",\n\n    \"import/first\": \"off\",\n\n    \"react/jsx-max-props-per-line\": 1,\n\n    \"react/jsx-first-prop-new-line\": \"warn\",\n\n    \"react/jsx-equals-spacing\": \"warn\",\n\n    \"react/jsx-indent\": \"warn\",\n\n    \"react/jsx-closing-bracket-location\": \"off\",\n\n    \"import/no-mutable-exports\": \"error\",\n\n    \"import/no-extraneous-dependencies\": \"off\",\n\n    \"object-shorthand\": [\"off\", \"never\"],\n\n    \"object-curly-newline\": \"off\",\n\n    \"arrow-body-style\": \"off\",\n\n    \"comma-dangle\": [\"warn\", \"always-multiline\"],\n\n    \"import/prefer-default-export\": \"off\",\n\n    \"no-cond-assign\": \"warn\",\n\n    \"no-confusing-arrow\": \"off\",\n\n    \"no-console\": \"off\",\n\n    \"no-constant-condition\": \"warn\",\n\n    \"no-control-regex\": \"warn\",\n\n    \"no-continue\": \"warn\",\n\n    \"react/forbid-prop-types\": \"warn\",\n\n    \"no-debugger\": \"warn\",\n\n    \"no-dupe-args\": \"error\",\n\n    \"no-dupe-keys\": \"error\",\n\n    \"no-duplicate-case\": \"error\",\n\n    \"no-empty\": \"warn\",\n\n    \"no-empty-character-class\": \"error\",\n\n    \"no-ex-assign\": \"error\",\n\n    \"no-extra-boolean-cast\": \"warn\",\n\n    \"no-extra-semi\": \"warn\",\n\n    \"no-func-assign\": \"error\",\n\n    \"no-inner-declarations\": \"error\",\n\n    \"no-invalid-regexp\": \"error\",\n\n    \"no-mixed-operators\": \"off\",\n\n    \"no-irregular-whitespace\": \"error\",\n\n    \"no-negated-in-lhs\": \"error\",\n\n    \"no-obj-calls\": \"error\",\n\n    \"no-regex-spaces\": \"warn\",\n\n    \"no-sparse-arrays\": \"error\",\n\n    \"no-unreachable\": \"warn\",\n\n    \"use-isnan\": \"error\",\n\n    \"valid-jsdoc\": \"warn\",\n\n    \"valid-typeof\": \"error\",\n\n    \"array-callback-return\": \"off\",\n\n    \"block-scoped-var\": \"off\",\n\n    \"prefer-destructuring\": \"warn\",\n\n    \"complexity\": \"off\",\n\n    \"consistent-return\": \"off\",\n\n    \"curly\": \"warn\",\n\n    \"default-case\": \"warn\",\n\n    \"dot-notation\": \"off\",\n\n    \"eqeqeq\": \"warn\",\n\n    \"guard-for-in\": \"off\",\n\n    \"no-alert\": \"warn\",\n\n    \"no-caller\": \"error\",\n\n    \"no-div-regex\": \"off\",\n\n    \"no-else-return\": \"off\",\n\n    \"no-eq-null\": \"error\",\n\n    \"no-eval\": \"error\",\n\n    \"no-extend-native\": \"error\",\n\n    \"no-extra-bind\": \"error\",\n\n    \"no-fallthrough\": \"error\",\n\n    \"no-floating-decimal\": \"error\",\n\n    \"no-implied-eval\": \"error\",\n\n    \"no-iterator\": \"error\",\n\n    \"no-labels\": \"off\",\n\n    \"no-lone-blocks\": \"warn\",\n\n    \"no-loop-func\": \"error\",\n\n    \"no-multi-spaces\": \"warn\",\n\n    \"no-multi-str\": \"error\",\n\n    \"no-native-reassign\": \"error\",\n\n    \"no-new\": \"warn\",\n\n    \"no-new-func\": \"error\",\n\n    \"no-new-wrappers\": \"error\",\n\n    \"no-octal\": \"error\",\n\n    \"no-octal-escape\": \"error\",\n\n    \"no-param-reassign\": \"off\",\n\n    \"no-process-env\": \"off\",\n\n    \"no-proto\": \"error\",\n\n    \"no-redeclare\": \"error\",\n\n    \"no-return-assign\": \"off\",\n\n    \"arrow-parens\": [\"warn\", \"always\", { \"requireForBlockBody\": false }],\n\n    \"no-script-url\": \"error\",\n\n    \"no-self-compare\": \"error\",\n\n    \"no-sequences\": \"error\",\n\n    \"no-throw-literal\": \"error\",\n\n    \"no-unused-expressions\": \"warn\",\n\n    \"no-void\": \"error\",\n\n    \"no-with\": \"error\",\n\n    \"radix\": \"off\",\n\n    \"vars-on-top\": \"off\",\n\n    \"wrap-iife\": \"error\",\n\n    \"yoda\": \"warn\",\n\n    \"strict\": \"off\",\n\n    \"no-catch-shadow\": \"error\",\n\n    \"no-delete-var\": \"error\",\n\n    \"no-label-var\": \"error\",\n\n    \"no-shadow\": \"warn\",\n\n    \"no-shadow-restricted-names\": \"error\",\n\n    \"no-undef\": \"error\",\n\n    \"no-undef-init\": \"error\",\n\n    \"no-multi-assign\": \"warn\",\n\n    \"no-undefined\": \"error\",\n\n    \"no-unused-vars\": [\"warn\", { \"args\": \"none\", \"ignoreRestSiblings\": true }],\n\n    \"no-use-before-define\": [\n      \"error\",\n      { \"functions\": false, \"classes\": true, \"variables\": true }\n    ],\n\n    \"no-restricted-properties\": \"warn\",\n\n    \"no-restricted-syntax\": \"warn\",\n\n    \"brace-style\": \"off\",\n\n    \"camelcase\": \"warn\",\n\n    \"comma-spacing\": [\"warn\", { \"before\": false, \"after\": true }],\n\n    \"comma-style\": [\"warn\", \"last\"],\n\n    \"consistent-this\": [\"off\", \"_this\"],\n\n    \"eol-last\": \"warn\",\n\n    \"func-names\": \"off\",\n\n    \"func-style\": [\"warn\", \"declaration\", { \"allowArrowFunctions\": true }],\n\n    \"key-spacing\": [\"warn\", { \"beforeColon\": false, \"afterColon\": true }],\n\n    \"max-nested-callbacks\": [\"warn\", 5],\n\n    \"new-cap\": [\"warn\", { \"newIsCap\": true, \"capIsNew\": false }],\n\n    \"new-parens\": \"warn\",\n\n    \"newline-after-var\": \"off\",\n\n    \"no-array-constructor\": \"off\",\n\n    \"no-inline-comments\": \"off\",\n\n    \"no-lonely-if\": \"warn\",\n\n    \"no-mixed-spaces-and-tabs\": \"warn\",\n\n    \"no-multiple-empty-lines\": [\"warn\", { \"max\": 2 }],\n\n    \"no-nested-ternary\": \"warn\",\n\n    \"no-new-object\": \"off\",\n\n    \"no-spaced-func\": \"warn\",\n\n    \"no-ternary\": \"off\",\n\n    \"no-trailing-spaces\": \"warn\",\n\n    \"no-underscore-dangle\": \"off\",\n\n    \"no-extra-parens\": \"off\",\n\n    \"padding-line-between-statements\": \"off\",\n\n    \"one-var\": [\"warn\", \"never\"],\n\n    \"operator-assignment\": [\"off\", \"never\"],\n\n    \"class-methods-use-this\": \"off\",\n\n    \"padded-blocks\": [\"off\", \"never\"],\n\n    \"lines-between-class-members\": [\"warn\", \"always\"],\n\n    \"quote-props\": [\"warn\", \"as-needed\"],\n\n    \"quotes\": [\"off\", \"single\"],\n\n    \"semi\": [\"warn\", \"always\"],\n\n    \"semi-spacing\": [\"warn\", { \"before\": false, \"after\": true }],\n\n    \"sort-vars\": \"off\",\n\n    \"keyword-spacing\": [\"warn\", { \"before\": true, \"after\": true }],\n\n    \"space-before-blocks\": [\"warn\", \"always\"],\n\n    \"function-paren-newline\": \"off\",\n\n    \"space-before-function-paren\": [\"warn\", { \"anonymous\": \"never\", \"named\": \"never\" }],\n\n    \"object-curly-spacing\": [\"warn\", \"always\"],\n\n    \"array-bracket-spacing\": [\"warn\", \"never\"],\n\n    \"computed-property-spacing\": [\"warn\", \"never\"],\n\n    \"space-in-parens\": [\"warn\", \"never\"],\n\n    \"space-infix-ops\": \"warn\",\n\n    \"space-unary-ops\": [\"warn\", { \"words\": true, \"nonwords\": false }],\n\n    \"spaced-comment\": [\"warn\", \"always\"],\n\n    \"wrap-regex\": \"off\",\n\n    \"no-var\": \"error\",\n\n    \"generator-star-spacing\": [\"error\", \"before\"],\n\n    \"max-depth\": [\"warn\", 4],\n\n    \"max-len\": [\"off\", 80, 2],\n\n    \"max-params\": [\"off\", 99],\n\n    \"max-statements\": \"off\",\n\n    \"no-bitwise\": \"off\",\n\n    \"no-plusplus\": \"off\",\n\n    \"react/display-name\": \"off\",\n\n    \"react/jsx-tag-spacing\": \"warn\",\n\n    \"jsx-quotes\": [\"warn\", \"prefer-double\"],\n\n    \"react/jsx-no-undef\": \"error\",\n\n    \"react/jsx-sort-props\": \"off\",\n\n    \"react/jsx-uses-react\": \"error\",\n\n    \"react/prefer-stateless-function\": \"warn\",\n\n    \"react/jsx-uses-vars\": \"error\",\n\n    \"react/jsx-no-bind\": \"error\",\n\n    \"react/no-did-mount-set-state\": \"warn\",\n\n    \"react/no-will-update-set-state\": \"warn\",\n\n    \"react/no-did-update-set-state\": \"warn\",\n\n    \"react/no-multi-comp\": \"off\",\n\n    \"react/no-unknown-property\": \"warn\",\n\n    \"react/prop-types\": \"off\",\n\n    \"react/react-in-jsx-scope\": \"error\",\n\n    \"react/self-closing-comp\": \"warn\",\n\n    \"react/jsx-wrap-multilines\": \"warn\",\n\n    \"react/no-array-index-key\": \"warn\",\n\n    \"react/no-unescaped-entities\": \"warn\",\n\n    \"react/sort-comp\": \"off\",\n\n    \"jsx-a11y/no-static-element-interactions\": \"off\",\n\n    \"jsx-a11y/click-events-have-key-events\": \"off\",\n\n    \"jsx-a11y/no-noninteractive-element-interactions\": \"off\",\n\n    \"react/jsx-one-expression-per-line\": \"off\",\n\n    \"jsx-a11y/anchor-is-valid\": \"off\",\n\n    \"jsx-a11y/alt-text\": \"warn\",\n\n    \"jsx-a11y/label-has-for\": [\n      \"warn\",\n      {\n        \"required\": {\n          \"some\": [\"nesting\", \"id\"]\n        }\n      }\n    ],\n\n    \"jsx-a11y/img-redundant-alt\": \"warn\",\n\n    \"jsx-a11y/no-autofocus\": \"off\",\n\n    \"jsx-a11y/iframe-has-title\": \"warn\",\n\n    \"jsx-a11y/anchor-has-content\": \"off\",\n\n    \"jsx-a11y/label-has-associated-control\": \"warn\",\n\n    \"jsx-a11y/mouse-events-have-key-events\": \"off\",\n\n    \"jsx-a11y/interactive-supports-focus\": \"off\",\n\n    \"jsx-a11y/no-distracting-elements\": \"warn\",\n\n    \"jsx-a11y/heading-has-content\": \"warn\",\n\n    \"jsx-a11y/html-has-lang\": \"warn\",\n\n    \"jsx-a11y/href-no-hash\": \"off\",\n\n    \"react/jsx-filename-extension\": \"off\",\n\n    \"jsx-a11y/no-noninteractive-tabindex\": \"warn\",\n\n    \"jsx-a11y/media-has-caption\": \"off\"\n  }\n}\n"
  },
  {
    "path": ".gitattributes",
    "content": "# From https://github.com/Danimoth/gitattributes/blob/master/Web.gitattributes\n\n# Handle line endings automatically for files detected as text\n# and leave all files detected as binary untouched.\n* text=auto\n\n#\n# The above will handle all files NOT found below\n#\n\n#\n## These files are text and should be normalized (Convert crlf => lf)\n#\n\n# source code\n*.php text\n*.css text\n*.sass text\n*.scss text\n*.less text\n*.styl text\n*.js text eol=lf\n*.coffee text\n*.json text\n*.htm text\n*.html text\n*.xml text\n*.svg text\n*.txt text\n*.ini text\n*.inc text\n*.pl text\n*.rb text\n*.py text\n*.scm text\n*.sql text\n*.sh text\n*.bat text\n\n# templates\n*.ejs text\n*.hbt text\n*.jade text\n*.haml text\n*.hbs text\n*.dot text\n*.tmpl text\n*.phtml text\n\n# git config\n.gitattributes text\n.gitignore text\n.gitconfig text\n\n# code analysis config\n.jshintrc text\n.jscsrc text\n.jshintignore text\n.csslintrc text\n\n# misc config\n*.yaml text\n*.yml text\n.editorconfig text\n\n# build config\n*.npmignore text\n*.bowerrc text\n\n# Heroku\nProcfile text\n.slugignore text\n\n# Documentation\n*.md text\nLICENSE text\nAUTHORS text\n\n\n#\n## These files are binary and should be left untouched\n#\n\n# (binary is a macro for -text -diff)\n*.png binary\n*.jpg binary\n*.jpeg binary\n*.gif binary\n*.ico binary\n*.mov binary\n*.mp4 binary\n*.mp3 binary\n*.flv binary\n*.fla binary\n*.swf binary\n*.gz binary\n*.zip binary\n*.7z binary\n*.ttf binary\n*.eot binary\n*.woff binary\n*.pyc binary\n*.pdf binary\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: 🐛 Bug Report\nabout: Create a report to help improve this plugin\n---\n\n<!--\nHello 👋 Thank you for submitting an issue.\n-->\n\n## Bug report\n\n### Describe the bug\n\nA clear and concise description of what the bug is.\n\n### Steps to reproduce the behavior\n\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\n### Expected behavior\n\nA clear and concise description of what you expected to happen.\n\n### Screenshots\n\nIf applicable, add screenshots to help explain your problem.\n\n### Code snippets\n\nIf applicable, add code samples to help explain your problem.\n\n### System\n\n- Node.js version: <!-- Please ensure you are using the Node LTS version (v12 / v14) -->\n- NPM version:\n- Strapi version:\n- Plugin version:\n- Database:\n- Operating system:\n\n### Additional context\n\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: 🚀 Feature Request\nabout: Suggest an idea to help make this plugin even better!\n---\n\n<!--\nHello 👋 Thank you for submitting a feature request.\n-->\n\n## Feature request\n\n### Summary\n\nQuick summary what's this feature request about.\n\n### Why is it needed?\n\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n### Suggested solution(s)\n\nA clear and concise description of what you want to happen.\n\n### Related issue(s)/PR(s)\n\nLet us know if this is related to any issue/pull request.\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "<!--\nHello 👋 Thank you for submitting a pull request.\n\nTo help us merge your PR, make sure to follow the instructions below:\n\n- Create or update the documentation.\n- Create or update the tests.\n- Refer to the issue you are closing in the PR description - fix #issue\n- Specify if the PR is in WIP (work in progress) state or ready to be merged\n-->\n\n### What does it do?\n\nDescribe the technical changes you did.\n\n### Why is it needed?\n\nDescribe the issue you are solving.\n\n### How to test it?\n\nProvide information about the environment and the path to verify the behaviour.\n\n### Related issue(s)/PR(s)\n\nLet us know if this is related to any issue/pull request\n"
  },
  {
    "path": ".github/workflows/deploy-docs.yml",
    "content": "name: Deploy Docs\n\non:\n  workflow_dispatch:\n  release:\n    types: [published]\n\njobs:\n  deploy:\n    name: Deploy\n    runs-on: ubuntu-latest\n    environment:\n      name: docs.pluginpal.io\n      url: https://docs.pluginpal.io/config-sync\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n\n      - name: Set up Docker\n        uses: actions/setup-node@v4\n        with:\n          node-version: '14'\n\n      - name: Build a Docker image\n        run: |\n          cd docs\n          docker build \\\n            -t docs-config-sync:latest .\n          docker save -o ../docs-config-sync-latest.tar docs-config-sync:latest\n\n      - name: Transfer the Docker image to the Dokku server\n        uses: appleboy/scp-action@v0.1.3\n        with:\n          host: ${{ secrets.SSH_HOST }}\n          username: ${{ secrets.SSH_CI_USERNAME }}\n          password: ${{ secrets.SSH_CI_PASSWORD }}\n          source: docs-config-sync-latest.tar\n          target: /var/lib/dokku/data/storage/docs/docker-images\n\n      - name: Deploy the Dokku app based on the Docker image\n        uses: appleboy/ssh-action@v0.1.10\n        with:\n          host: ${{ secrets.SSH_HOST }}\n          username: ${{ secrets.SSH_CI_USERNAME }}\n          password: ${{ secrets.SSH_CI_PASSWORD }}\n          script_stop: true\n          script: |\n            sudo docker load -i /var/lib/dokku/data/storage/docs/docker-images/docs-config-sync-latest.tar\n            DOCS_CONFIG_SYNC_LATEST_IMAGE=$(sudo docker images --format \"{{.ID}}\" docs-config-sync:latest)\n            sudo docker tag docs-config-sync:latest docs-config-sync:$DOCS_CONFIG_SYNC_LATEST_IMAGE\n            dokku git:from-image docs-config-sync docs-config-sync:$DOCS_CONFIG_SYNC_LATEST_IMAGE\n            sudo docker system prune --all --force\n"
  },
  {
    "path": ".github/workflows/publish.yml",
    "content": "name: Publish to NPM\npermissions:\n  id-token: write\n  contents: write\non:\n  release:\n    types: [published]\njobs:\n  publish:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n      - name: Setup Node.js\n        uses: actions/setup-node@v4\n        with:\n          always-auth: true\n          node-version: 24\n          cache: 'yarn'\n          registry-url: 'https://registry.npmjs.org/'\n      - name: Install dependencies\n        run: yarn install --frozen-lockfile --ignore-engines\n      - name: Build the plugin\n        run: yarn build\n      - name: Get the release tag version\n        id: get_version\n        run: echo \"VERSION=${GITHUB_REF/refs\\/tags\\//}\" >> $GITHUB_OUTPUT\n      - name: Extract pre-release tag if any\n        id: extract_tag\n        run: |\n          VERSION=\"${{ steps.get_version.outputs.VERSION }}\"\n          if [[ $VERSION == *-* ]]; then\n            # Extract everything between hyphen and last period (or end of string)\n            PRETAG=$(echo $VERSION | sed -E 's/.*-([^.]+).*/\\1/')\n            echo \"IS_PRERELEASE=true\" >> $GITHUB_OUTPUT\n            echo \"NPM_TAG=$PRETAG\" >> $GITHUB_OUTPUT\n          else\n            echo \"IS_PRERELEASE=false\" >> $GITHUB_OUTPUT\n            echo \"NPM_TAG=latest\" >> $GITHUB_OUTPUT\n          fi\n      - name: Get source branch\n        id: get_branch\n        run: |\n          RELEASE_COMMIT=$(git rev-list -n 1 ${{ steps.get_version.outputs.VERSION }})\n          SOURCE_BRANCH=$(git branch -r --contains $RELEASE_COMMIT | grep -v HEAD | head -n 1 | sed 's/.*origin\\///')\n          echo \"SOURCE_BRANCH=$SOURCE_BRANCH\" >> $GITHUB_OUTPUT\n      - name: Set package version\n        run: yarn version --new-version \"${{ steps.get_version.outputs.VERSION }}\" --no-git-tag-version\n      - name: Publish package\n        run: npm publish --provenance --access public --tag ${{ steps.extract_tag.outputs.NPM_TAG }}\n      - name: Push version bump\n        uses: stefanzweifel/git-auto-commit-action@v4\n        with:\n          commit_message: 'chore: Bump version to ${{ steps.get_version.outputs.VERSION }}'\n          file_pattern: 'package.json'\n          branch: master\n"
  },
  {
    "path": ".github/workflows/tests.yml",
    "content": "name: Tests\n\non:\n  push:\n    branches:\n      - master\n  pull_request:\n    branches:\n      - master\n      - develop\n      - beta\n\njobs:\n  lint:\n    name: 'lint'\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        node: [20, 22]\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-node@v4\n        with:\n          node-version: ${{ matrix.node }}\n          cache: 'yarn'\n      - name: Install dependencies\n        run: yarn --frozen-lockfile\n      - name: Run eslint\n        run: yarn run eslint\n  test:\n    name: 'test'\n    needs: [lint]\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        node: [20, 22]\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-node@v4\n        with:\n          node-version: ${{ matrix.node }}\n          cache: 'yarn'\n      - name: Install dependencies plugin\n        run: yarn --no-lockfile --unsafe-perm\n      - name: Push the package to yalc\n        run: yarn build\n      - name: Add yalc package to the playground\n        run: yarn playground:yalc-add\n      - name: Install dependencies playground\n        run: cd playground && yarn install --unsafe-perm\n      - name: Build playground\n        run: yarn playground:build\n      # - name: Run unit tests\n      #   run: yarn test:unit\n      - name: Run integration tests\n        run: yarn run -s test:integration\n      - name: Run end-to-end tests\n        uses: cypress-io/github-action@v6\n        with:\n          start: yarn playground:start\n      - uses: actions/upload-artifact@v4\n        if: failure()\n        with:\n          name: cypress-screenshots\n          path: cypress/screenshots\n          if-no-files-found: ignore # 'warn' or 'error' are also available, defaults to `warn`\n      - uses: actions/upload-artifact@v4\n        if: failure()\n        with:\n          name: cypress-videos\n          path: cypress/videos\n          if-no-files-found: ignore # 'warn' or 'error' are also available, defaults to `warn`\n      - name: Upload coverage to Codecov\n        uses: codecov/codecov-action@v4\n        with:\n          token: ${{ secrets.CODECOV }}\n          flags: unit\n          verbose: true\n          fail_ci_if_error: true\n"
  },
  {
    "path": ".gitignore",
    "content": "# Don't check auto-generated stuff into git\ncoverage\nnode_modules\nstats.json\npackage-lock.json\nfiles\n\n# Cruft\n.DS_Store\nnpm-debug.log\n.idea\n\n# Production build\nbuild\ndist\nbundle\n\n# Cypress\ncypress/screenshots/\ncypress/videos/\ncypress/downloads/\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participation in our\ncommunity a harassment-free experience for everyone, regardless of age, body\nsize, visible or invisible disability, ethnicity, sex characteristics, gender\nidentity and expression, level of experience, education, socio-economic status,\nnationality, personal appearance, race, religion, or sexual identity\nand orientation.\n\nWe pledge to act and interact in ways that contribute to an open, welcoming,\ndiverse, inclusive, and healthy community.\n\n## Our Standards\n\nExamples of behavior that contributes to a positive environment for our\ncommunity include:\n\n* Demonstrating empathy and kindness toward other people\n* Being respectful of differing opinions, viewpoints, and experiences\n* Giving and gracefully accepting constructive feedback\n* Accepting responsibility and apologizing to those affected by our mistakes,\n  and learning from the experience\n* Focusing on what is best not just for us as individuals, but for the\n  overall community\n\nExamples of unacceptable behavior include:\n\n* The use of sexualized language or imagery, and sexual attention or\n  advances of any kind\n* Trolling, insulting or derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or email\n  address, without their explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Enforcement Responsibilities\n\nCommunity leaders are responsible for clarifying and enforcing our standards of\nacceptable behavior and will take appropriate and fair corrective action in\nresponse to any behavior that they deem inappropriate, threatening, offensive,\nor harmful.\n\nCommunity leaders have the right and responsibility to remove, edit, or reject\ncomments, commits, code, wiki edits, issues, and other contributions that are\nnot aligned to this Code of Conduct, and will communicate reasons for moderation\ndecisions when appropriate.\n\n## Scope\n\nThis Code of Conduct applies within all community spaces, and also applies when\nan individual is officially representing the community in public spaces.\nExamples of representing our community include using an official e-mail address,\nposting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported to the community leaders responsible for enforcement at\n[@boazpoolman](https://twitter.com/boazpoolman) on twitter.\nAll complaints will be reviewed and investigated promptly and fairly.\n\nAll community leaders are obligated to respect the privacy and security of the\nreporter of any incident.\n\n## Enforcement Guidelines\n\nCommunity leaders will follow these Community Impact Guidelines in determining\nthe consequences for any action they deem in violation of this Code of Conduct:\n\n### 1. Correction\n\n**Community Impact**: Use of inappropriate language or other behavior deemed\nunprofessional or unwelcome in the community.\n\n**Consequence**: A private, written warning from community leaders, providing\nclarity around the nature of the violation and an explanation of why the\nbehavior was inappropriate. A public apology may be requested.\n\n### 2. Warning\n\n**Community Impact**: A violation through a single incident or series\nof actions.\n\n**Consequence**: A warning with consequences for continued behavior. No\ninteraction with the people involved, including unsolicited interaction with\nthose enforcing the Code of Conduct, for a specified period of time. This\nincludes avoiding interactions in community spaces as well as external channels\nlike social media. Violating these terms may lead to a temporary or\npermanent ban.\n\n### 3. Temporary Ban\n\n**Community Impact**: A serious violation of community standards, including\nsustained inappropriate behavior.\n\n**Consequence**: A temporary ban from any sort of interaction or public\ncommunication with the community for a specified period of time. No public or\nprivate interaction with the people involved, including unsolicited interaction\nwith those enforcing the Code of Conduct, is allowed during this period.\nViolating these terms may lead to a permanent ban.\n\n### 4. Permanent Ban\n\n**Community Impact**: Demonstrating a pattern of violation of community\nstandards, including sustained inappropriate behavior,  harassment of an\nindividual, or aggression toward or disparagement of classes of individuals.\n\n**Consequence**: A permanent ban from any sort of public interaction within\nthe community.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage],\nversion 2.0, available at\nhttps://www.contributor-covenant.org/version/2/0/code_of_conduct.html.\n\nCommunity Impact Guidelines were inspired by [Mozilla's code of conduct\nenforcement ladder](https://github.com/mozilla/diversity).\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see the FAQ at\nhttps://www.contributor-covenant.org/faq. Translations are available at\nhttps://www.contributor-covenant.org/translations.\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n\nWe want this community to be friendly and respectful to each other. Please follow it in all your interactions with the project.\n\n## Development Workflow\n\nThis plugin provides a local development instance of Strapi to develop it's features. We call this instance `playground` and it can be found in the playground folder in the root of the project. For that reason it is not needed to have your own Strapi instance running to work on this plugin. Just clone the repo and you're ready to go!\n\n#### 1. Fork the [repository](https://github.com/pluginpal/strapi-plugin-config-sync)\n\n[Go to the repository](https://github.com/pluginpal/strapi-plugin-config-sync) and fork it to your own GitHub account.\n\n#### 2. Clone the forked repository\n\n```bash\ngit clone git@github.com:YOUR_USERNAME/strapi-plugin-config-sync.git\n```\n\n#### 3. Install the dependencies\n\nGo to the folder and install the dependencies\n\n```bash\ncd strapi-plugin-config-sync && yarn install\n```\n\n#### 4. Install the playground dependencies\n\nRun this in the root of the repository\n\n```bash\nyarn playground:install\n```\n\n#### 5. Run the compiler of the plugin \n\nWe use `yalc` to publish the package to a local registry. Run the following command o watch for changes and push to `yalc` every time a change is made:\n\n```bash\nyarn develop\n```\n\n#### 6. Start the playground instance\n\nLeave the watcher running, open up a new terminal window and browse back to the root of the plugin repo. Run the following command:\n\n```bash\nyarn playground:develop\n```\n\nThis will start the playground instance that will have the plugin installed from the `yalc` registry. Browse to http://localhost:1337 and create a test admin user to log in to the playground.\n\n#### 7. Start your contribution!\n\nYou can now start working on your contribution. If you had trouble setting up this testing environment please feel free to report an issue on Github.\n\n### Commit message convention\n\nWe follow the [conventional commits specification](https://www.conventionalcommits.org/en) for our commit messages:\n\n- `fix`: bug fixes, e.g. fix crash due to deprecated method.\n- `feat`: new features, e.g. add new method to the module.\n- `refactor`: code refactor, e.g. migrate from class components to hooks.\n- `docs`: changes into documentation, e.g. add usage example for the module..\n- `test`: adding or updating tests, eg add integration tests using detox.\n- `chore`: tooling changes, e.g. change CI config.\n\n### Linting and tests\n\n[ESLint](https://eslint.org/)\n\nWe use [ESLint](https://eslint.org/) for linting and formatting the code, and [Jest](https://jestjs.io/) for testing.\n\n### Scripts\n\nThe `package.json` file contains various scripts for common tasks:\n\n- `yarn eslint`: lint files with ESLint.\n- `yarn eslint:fix`: auto-fix ESLint issues.\n- `yarn test:integration`: run integration tests with Jest.\n\n### Sending a pull request\n\nWhen you're sending a pull request:\n\n- Prefer small pull requests focused on one change.\n- Verify that linters and tests are passing.\n- Review the documentation to make sure it looks good.\n- Follow the pull request template when opening a pull request.\n- For pull requests that change the API or implementation, discuss with maintainers first by opening an issue.\n"
  },
  {
    "path": "LICENSE.md",
    "content": "\n\nCopyright (c) 2021 Boaz Poolman.\n\nPermission 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:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE 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.\n"
  },
  {
    "path": "README.md",
    "content": "<div align=\"center\">\n<h1>Strapi config-sync plugin</h1>\n\t\n<p style=\"margin-top: 0;\">This plugin is a multi-purpose tool to manage your Strapi database records through JSON files. Mostly used to version controlconfig data for automated deployment, automated tests and data sharing for collaboration purposes.</p>\n\n<a href=\"https://docs.pluginpal.io/config-sync\">Read the documentation</a>\n\t\n<p>\n  <a href=\"https://www.npmjs.org/package/strapi-plugin-config-sync\">\n    <img src=\"https://img.shields.io/npm/v/strapi-plugin-config-sync/latest.svg\" alt=\"NPM Version\" />\n  </a>\n  <a href=\"https://www.npmjs.org/package/strapi-plugin-config-sync\">\n    <img src=\"https://img.shields.io/npm/dm/strapi-plugin-config-sync\" alt=\"Monthly download on NPM\" />\n  </a>\n  <a href=\"https://codecov.io/gh/boazpoolman/strapi-plugin-config-sync\">\n    <img src=\"https://img.shields.io/github/actions/workflow/status/boazpoolman/strapi-plugin-config-sync/tests.yml?branch=master\" alt=\"CI build status\" />\n  </a>\n  <a href=\"https://codecov.io/gh/boazpoolman/strapi-plugin-config-sync\">\n    <img src=\"https://codecov.io/gh/boazpoolman/strapi-plugin-config-sync/coverage.svg?branch=master\" alt=\"codecov.io\" />\n  </a>\n</p>\n</div>\n\n## ✨ Features\n\n- **CLI** - `config-sync` CLI for syncing the config from the command line\n- **GUI** - Settings page for syncing the config in Strapi admin\n- **Partial sync** - Import or export only specific portions of config\n- **Custom types** - Include your custom collection types in the sync process\n- **Import on bootstrap** - Easy automated deployment with `importOnBootstrap`\n- **Exclusion** - Exclude single config entries or all entries of a given type\n- **Diff viewer** - A git-style diff viewer to inspect the config changes\n\n## ⏳ Getting started\n\n[Read the Getting Started tutorial](https://docs.pluginpal.io/config-sync) or follow the steps below:\n\n```bash\n# using yarn\nyarn add strapi-plugin-config-sync\n\n# using npm\nnpm install strapi-plugin-config-sync --save\n```\n \nAdd the export path to the `watchIgnoreFiles` list in the `config/admin.js` file.\nThis way your app won't reload when you export the config in development.\n\n##### `config/admin.js`:\n```\nmodule.exports = ({ env }) => ({\n  // ...\n  watchIgnoreFiles: [\n    '**/config/sync/**',\n  ],\n});\n```\n\nAfter successful installation you have to rebuild the admin UI so it'll include this plugin. To rebuild and restart Strapi run:\n\n```bash\n# using yarn\nyarn build\nyarn develop\n\n# using npm\nnpm run build\nnpm run develop\n```\n\nThe **Config Sync** plugin should now appear in the **Settings** section of your Strapi app.\n\nTo start tracking your config changes you have to make the first export. This will dump all your configuration data to the `/config/sync` directory. You can export either through [the CLI](https://docs.pluginpal.io/config-sync/cli) or [Strapi admin panel](https://docs.pluginpal.io/config-sync/admin-gui)\n\nEnjoy 🎉\n\n## 📓 Documentation\n\nSee our dedicated [repository](https://github.com/pluginpal/docs) for all of PluginPal's documentation, or view the Config Sync documentation live:\n\n- [Config Sync documentation](https://docs.pluginpal.io/config-sync)\n\n## 🤝 Contributing\n\nFeel free to fork and make a pull request of this plugin. All the input is welcome!\n\n## ⭐️ Show your support\n\nGive a star if this project helped you.\n\n## 🔗 Links\n\n- [PluginPal marketplace](https://www.pluginpal.io/plugin/config-sync)\n- [NPM package](https://www.npmjs.com/package/strapi-plugin-config-sync)\n- [GitHub repository](https://github.com/boazpoolman/strapi-plugin-config-sync)\n- [Strapi marketplace](https://market.strapi.io/plugins/strapi-plugin-config-sync)\n\n## 🌎 Community support\n\n- For general help using Strapi, please refer to [the official Strapi documentation](https://strapi.io/documentation/).\n- For support with this plugin you can DM me in the Strapi Discord [channel](https://discord.strapi.io/).\n\n## 📝 Resources\n\n- [MIT License](https://github.com/pluginpal/strapi-plugin-config-sync/blob/master/LICENSE.md)\n"
  },
  {
    "path": "admin/src/components/.gitkeep",
    "content": ""
  },
  {
    "path": "admin/src/components/ActionButtons/index.jsx",
    "content": "import React from 'react';\nimport styled from 'styled-components';\nimport { useDispatch, useSelector } from 'react-redux';\nimport isEmpty from 'lodash/isEmpty';\nimport { Button, Typography } from '@strapi/design-system';\nimport { Map } from 'immutable';\nimport { getFetchClient, useNotification } from '@strapi/strapi/admin';\nimport { useIntl } from 'react-intl';\n\nimport ConfirmModal from '../ConfirmModal';\nimport { exportAllConfig, importAllConfig, downloadZip } from '../../state/actions/Config';\n\nconst ActionButtons = () => {\n  const { post, get } = getFetchClient();\n  const dispatch = useDispatch();\n  const { toggleNotification } = useNotification();\n  const partialDiff = useSelector((state) => state.getIn(['config', 'partialDiff'], Map({}))).toJS();\n  const { formatMessage } = useIntl();\n\n  return (\n    <ActionButtonsStyling>\n      <ConfirmModal\n        type=\"import\"\n        trigger={(\n          <Button disabled={isEmpty(partialDiff)}>\n            {formatMessage({ id: 'config-sync.Buttons.Import' })}\n          </Button>\n        )}\n        onSubmit={(force) => dispatch(importAllConfig(partialDiff, force, toggleNotification, formatMessage, post, get))}\n      />\n      <ConfirmModal\n        type=\"export\"\n        trigger={(\n          <Button disabled={isEmpty(partialDiff)}>\n            {formatMessage({ id: 'config-sync.Buttons.Export' })}\n          </Button>\n        )}\n        onSubmit={(force) => dispatch(exportAllConfig(partialDiff, toggleNotification, formatMessage, post, get))}\n      />\n      {!isEmpty(partialDiff) && (\n        <Typography variant=\"epsilon\">{Object.keys(partialDiff).length} {Object.keys(partialDiff).length === 1 ? \"config change\" : \"config changes\"}</Typography>\n      )}\n      <Button onClick={() => dispatch(downloadZip(toggleNotification, formatMessage, post, get))}>{formatMessage({ id: 'config-sync.Buttons.DownloadConfig' })}</Button>\n    </ActionButtonsStyling>\n  );\n};\n\nconst ActionButtonsStyling = styled.div`\n  padding: 10px 0 20px 0;\n  display: flex;\n  align-items: center;\n\n  > button {\n    margin-right: 10px;\n  }\n  > button:last-of-type {\n    margin-left: auto;\n  }\n`;\n\nexport default ActionButtons;\n"
  },
  {
    "path": "admin/src/components/ConfigDiff/index.jsx",
    "content": "import React from 'react';\nimport RDV, { DiffMethod } from 'react-diff-viewer-continued';\nimport { useIntl } from 'react-intl';\n\n/**\n * An issue with the diff-viewer library causes a difference in the way the library is exported.\n * Depending on whether the library is loaded through the browser or through the server, the default export may or may not be present.\n * This causes issues with SSR and the way the library is imported.\n *\n * Below a workaround to fix this issue.\n *\n * @see https://github.com/Aeolun/react-diff-viewer-continued/issues/43\n */\nlet ReactDiffViewer;\nif (typeof RDV.default !== 'undefined') {\n  ReactDiffViewer = RDV.default;\n} else {\n  ReactDiffViewer = RDV;\n}\n\nimport {\n  Modal,\n  Grid,\n  Typography,\n} from '@strapi/design-system';\n\nconst ConfigDiff = ({ oldValue, newValue, configName, trigger }) => {\n  const { formatMessage } = useIntl();\n\n  return (\n    <Modal.Root>\n      <Modal.Trigger>\n        {trigger}\n      </Modal.Trigger>\n      <Modal.Content>\n        <Modal.Header>\n          <Typography variant=\"omega\" fontWeight=\"bold\" textColor=\"neutral800\">\n            {formatMessage({ id: 'config-sync.ConfigDiff.Title' })} {configName}\n          </Typography>\n        </Modal.Header>\n        <Modal.Body>\n          <Grid.Root paddingBottom={4} style={{ textAlign: 'center' }}>\n            <Grid.Item col={6}>\n              <Typography variant=\"delta\" style={{ width: '100%' }}>{formatMessage({ id: 'config-sync.ConfigDiff.SyncDirectory' })}</Typography>\n            </Grid.Item>\n            <Grid.Item col={6}>\n              <Typography variant=\"delta\" style={{ width: '100%' }}>{formatMessage({ id: 'config-sync.ConfigDiff.Database' })}</Typography>\n            </Grid.Item>\n          </Grid.Root>\n          <Typography variant=\"pi\">\n            <ReactDiffViewer\n              oldValue={JSON.stringify(oldValue, null, 2)}\n              newValue={JSON.stringify(newValue, null, 2)}\n              splitView\n              compareMethod={DiffMethod.WORDS}\n            />\n          </Typography>\n        </Modal.Body>\n      </Modal.Content>\n    </Modal.Root>\n  );\n};\n\nexport default ConfigDiff;\n"
  },
  {
    "path": "admin/src/components/ConfigList/ConfigListRow/index.jsx",
    "content": "import React from 'react';\nimport { Tr, Td, Checkbox, Typography } from '@strapi/design-system';\n\nconst CustomRow = ({ row, checked, updateValue, ...props }) => {\n  const { configName, configType, state, onClick } = row;\n\n  const stateStyle = (stateStr) => {\n    const style = {\n      display: 'inline-flex',\n      padding: '0 10px',\n      borderRadius: '12px',\n      height: '24px',\n      alignItems: 'center',\n      fontWeight: '500',\n    };\n\n    if (stateStr === 'Only in DB') {\n      style.backgroundColor = '#cbf2d7';\n      style.color = '#1b522b';\n    }\n\n    if (stateStr === 'Only in sync dir') {\n      style.backgroundColor = '#f0cac7';\n      style.color = '#3d302f';\n    }\n\n    if (stateStr === 'Different') {\n      style.backgroundColor = '#e8e6b7';\n      style.color = '#4a4934';\n    }\n\n    return style;\n  };\n\n  return (\n    <Tr\n      {...props}\n      onClick={(e) => {\n        if (e.target.type !== 'checkbox') {\n          onClick(configType, configName);\n        }\n      }}\n      style={{ cursor: 'pointer' }}\n    >\n      <Td>\n        <Checkbox\n          aria-label={`Select ${configName}`}\n          checked={checked}\n          onCheckedChange={updateValue}\n        />\n      </Td>\n      <Td onClick={(e) => props.onClick(e)}>\n        <Typography variant=\"omega\">{configName}</Typography>\n      </Td>\n      <Td onClick={(e) => props.onClick(e)}>\n        <Typography variant=\"omega\">{configType}</Typography>\n      </Td>\n      <Td onClick={(e) => props.onClick(e)}>\n        <Typography variant=\"omega\" style={stateStyle(state)}>{state}</Typography>\n      </Td>\n    </Tr>\n  );\n};\n\nexport default CustomRow;\n"
  },
  {
    "path": "admin/src/components/ConfigList/index.jsx",
    "content": "import React, { useState, useEffect } from 'react';\nimport { useIntl } from 'react-intl';\nimport { isEmpty } from 'lodash';\nimport { useDispatch } from 'react-redux';\n\nimport {\n  Table,\n  Thead,\n  Tbody,\n  Tr,\n  Th,\n  Typography,\n  Checkbox,\n  Loader,\n} from '@strapi/design-system';\n\nimport ConfigDiff from '../ConfigDiff';\nimport FirstExport from '../FirstExport';\nimport NoChanges from '../NoChanges';\nimport ConfigListRow from './ConfigListRow';\nimport { setConfigPartialDiffInState } from '../../state/actions/Config';\n\n\nconst ConfigList = ({ diff, isLoading }) => {\n  const [originalConfig, setOriginalConfig] = useState({});\n  const [newConfig, setNewConfig] = useState({});\n  const [cName, setCname] = useState('');\n  const [rows, setRows] = useState([]);\n  const [checkedItems, setCheckedItems] = useState([]);\n  const dispatch = useDispatch();\n  const { formatMessage } = useIntl();\n\n  const getConfigState = (configName) => {\n    if (\n      diff.fileConfig[configName]\n      && diff.databaseConfig[configName]\n    ) {\n      return formatMessage({ id: 'config-sync.ConfigList.Different' });\n    } else if (\n      diff.fileConfig[configName]\n      && !diff.databaseConfig[configName]\n    ) {\n      return formatMessage({ id: 'config-sync.ConfigList.OnlyDir' });\n    } else if (\n      !diff.fileConfig[configName]\n      && diff.databaseConfig[configName]\n    ) {\n      return formatMessage({ id: 'config-sync.ConfigList.OnlyDB' });\n    }\n  };\n\n  useEffect(() => {\n    if (isEmpty(diff.diff)) {\n      setRows([]);\n      return;\n    }\n\n    const formattedRows = [];\n    const newCheckedItems = [];\n    Object.keys(diff.diff).map((name) => {\n      const type = name.split('.')[0]; // Grab the first part of the filename.\n      const formattedName = name.split(/\\.(.+)/)[1]; // Grab the rest of the filename minus the file extension.\n\n      newCheckedItems.push(true);\n\n      formattedRows.push({\n        configName: formattedName,\n        configType: type,\n        state: getConfigState(name),\n        onClick: (configType, configName) => {\n          setOriginalConfig(diff.fileConfig[`${configType}.${configName}`]);\n          setNewConfig(diff.databaseConfig[`${configType}.${configName}`]);\n          setCname(`${configType}.${configName}`);\n        },\n      });\n    });\n    setCheckedItems(newCheckedItems);\n\n    setRows(formattedRows);\n  }, [diff]);\n\n  useEffect(() => {\n    const newPartialDiff = [];\n    checkedItems.map((item, index) => {\n      if (item && rows[index]) newPartialDiff.push(`${rows[index].configType}.${rows[index].configName}`);\n    });\n    dispatch(setConfigPartialDiffInState(newPartialDiff));\n  }, [checkedItems]);\n\n  if (isLoading) {\n    return (\n      <div style={{ textAlign: 'center', marginTop: 40 }}>\n        <Loader>{formatMessage({ id: 'config-sync.ConfigList.Loading' })}</Loader>\n      </div>\n    );\n  }\n\n  if (!isLoading && !isEmpty(diff.message)) {\n    return <FirstExport />;\n  }\n\n  if (!isLoading && isEmpty(diff.diff)) {\n    return <NoChanges />;\n  }\n\n  const allChecked = checkedItems && checkedItems.every(Boolean);\n  const isIndeterminate = checkedItems.some(Boolean) && !allChecked;\n\n  return (\n    <div>\n      <Table colCount={4} rowCount={rows.length + 1}>\n        <Thead>\n          <Tr>\n            <Th>\n              <Checkbox\n                aria-label={formatMessage({ id: 'config-sync.ConfigList.SelectAll' })}\n                checked={isIndeterminate ? \"indeterminate\" : allChecked}\n                onCheckedChange={(value) => setCheckedItems(checkedItems.map(() => value))}\n              />\n            </Th>\n            <Th>\n              <Typography variant=\"sigma\">{formatMessage({ id: 'config-sync.ConfigList.ConfigName' })}</Typography>\n            </Th>\n            <Th>\n              <Typography variant=\"sigma\">{formatMessage({ id: 'config-sync.ConfigList.ConfigType' })}</Typography>\n            </Th>\n            <Th>\n              <Typography variant=\"sigma\">{formatMessage({ id: 'config-sync.ConfigList.State' })}</Typography>\n            </Th>\n          </Tr>\n        </Thead>\n        <Tbody>\n          {rows.map((row, index) => (\n            <ConfigDiff\n              key={row.configName}\n              oldValue={originalConfig}\n              newValue={newConfig}\n              configName={cName}\n              trigger={(\n                <ConfigListRow\n                  row={row}\n                  checked={checkedItems[index]}\n                  updateValue={() => {\n                    checkedItems[index] = !checkedItems[index];\n                    setCheckedItems([...checkedItems]);\n                  }}\n                />\n              )}\n            />\n          ))}\n        </Tbody>\n      </Table>\n    </div>\n  );\n};\n\nexport default ConfigList;\n"
  },
  {
    "path": "admin/src/components/ConfirmModal/index.jsx",
    "content": "import React, { useState } from 'react';\nimport { useIntl } from 'react-intl';\nimport { useSelector } from 'react-redux';\n\nimport {\n  Dialog,\n  Flex,\n  Typography,\n  Button,\n  Checkbox,\n  Divider,\n  Box,\n  Field,\n} from '@strapi/design-system';\nimport { WarningCircle } from '@strapi/icons';\n\nconst ConfirmModal = ({ onClose, onSubmit, type, trigger }) => {\n  const soft = useSelector((state) => state.getIn(['config', 'appEnv', 'config', 'soft'], false));\n  const [force, setForce] = useState(false);\n  const { formatMessage } = useIntl();\n\n  return (\n    <Dialog.Root>\n      <Dialog.Trigger>\n        {trigger}\n      </Dialog.Trigger>\n      <Dialog.Content>\n        <Dialog.Header>{formatMessage({ id: \"config-sync.popUpWarning.Confirmation\" })}</Dialog.Header>\n        <Dialog.Body>\n          <WarningCircle fill=\"danger600\" width=\"32px\" height=\"32px\" />\n          <Flex size={2}>\n            <Flex justifyContent=\"center\">\n              <Typography variant=\"omega\" id=\"confirm-description\" style={{ textAlign: 'center' }}>\n                {formatMessage({ id: `config-sync.popUpWarning.warning.${type}_1` })}<br />\n                {formatMessage({ id: `config-sync.popUpWarning.warning.${type}_2` })}\n              </Typography>\n            </Flex>\n          </Flex>\n          {(soft && type === 'import') && (\n            <Box width=\"100%\">\n              <Divider marginTop={4} />\n              <Box paddingTop={6}>\n                <Field.Root hint=\"Check this to ignore the soft setting.\">\n                  <Checkbox\n                    onValueChange={(value) => setForce(value)}\n                    value={force}\n                    name=\"force\"\n                  >\n                    {formatMessage({ id: 'config-sync.popUpWarning.force' })}\n                  </Checkbox>\n                  <Field.Hint />\n                </Field.Root>\n              </Box>\n            </Box>\n          )}\n        </Dialog.Body>\n        <Dialog.Footer>\n          <Dialog.Cancel>\n            <Button fullWidth variant=\"tertiary\">\n              {formatMessage({ id: 'config-sync.popUpWarning.button.cancel' })}\n            </Button>\n          </Dialog.Cancel>\n          <Dialog.Action>\n            <Button\n              fullWidth\n              variant=\"secondary\"\n              onClick={() => {\n                onSubmit(force);\n              }}\n            >\n              {formatMessage({ id: `config-sync.popUpWarning.button.${type}` })}\n            </Button>\n          </Dialog.Action>\n        </Dialog.Footer>\n      </Dialog.Content>\n    </Dialog.Root>\n  );\n};\n\nexport default ConfirmModal;\n"
  },
  {
    "path": "admin/src/components/FirstExport/index.jsx",
    "content": "import React from 'react';\nimport { useIntl } from 'react-intl';\nimport { useDispatch } from 'react-redux';\nimport { getFetchClient, useNotification } from '@strapi/strapi/admin';\nimport { Button, EmptyStateLayout } from '@strapi/design-system';\nimport { EmptyDocuments } from '@strapi/icons/symbols';\n\n\nimport { exportAllConfig } from '../../state/actions/Config';\nimport ConfirmModal from '../ConfirmModal';\n\nconst FirstExport = () => {\n  const { post, get } = getFetchClient();\n  const { toggleNotification } = useNotification();\n  const dispatch = useDispatch();\n  const { formatMessage } = useIntl();\n\n  return (\n    <div>\n      <EmptyStateLayout\n        content={formatMessage({ id: 'config-sync.FirstExport.Message' })}\n        action={(\n          <ConfirmModal\n            type=\"export\"\n            onSubmit={() => dispatch(exportAllConfig([], toggleNotification, formatMessage, post, get))}\n            trigger={(\n              <Button>{formatMessage({ id: 'config-sync.FirstExport.Button' })}</Button>\n            )}\n          />\n        )}\n        icon={<EmptyDocuments width={160} />}\n      />\n    </div>\n  );\n};\n\nexport default FirstExport;\n"
  },
  {
    "path": "admin/src/components/Header/index.jsx",
    "content": "/*\n *\n * HeaderComponent\n *\n */\n\nimport React, { memo } from 'react';\nimport { useIntl } from 'react-intl';\n\nimport { Layouts } from '@strapi/admin/strapi-admin';\nimport { Box } from '@strapi/design-system';\n\nconst HeaderComponent = () => {\n  const { formatMessage } = useIntl();\n\n  return (\n    <Box background=\"neutral100\">\n      <Layouts.Header\n        title={formatMessage({ id: 'config-sync.Header.Title' })}\n        subtitle={formatMessage({ id: 'config-sync.Header.Description' })}\n      />\n    </Box>\n  );\n};\n\nexport default memo(HeaderComponent);\n"
  },
  {
    "path": "admin/src/components/NoChanges/index.jsx",
    "content": "import React from 'react';\nimport { EmptyStateLayout } from '@strapi/design-system';\nimport { useIntl } from 'react-intl';\nimport { EmptyDocuments } from '@strapi/icons/symbols';\n\nconst NoChanges = () => {\n  const { formatMessage } = useIntl();\n  return (\n    <EmptyStateLayout\n      content={formatMessage({ id: 'config-sync.NoChanges.Message', defaultMessage: 'No differences between DB and sync directory. You are up-to-date!' })}\n      icon={<EmptyDocuments width={160} />}\n    />\n  );\n};\n\nexport default NoChanges;\n"
  },
  {
    "path": "admin/src/config/constants.js",
    "content": "export const __DEBUG__ = true; // TODO: set actual env.\n"
  },
  {
    "path": "admin/src/config/logger.js",
    "content": "const config = {\n  blacklist: [\n    'REDUX_STORAGE_SAVE',\n    'REDUX_STORAGE_LOAD',\n  ],\n};\n\nexport default config;\n"
  },
  {
    "path": "admin/src/containers/App/index.jsx",
    "content": "/**\n *\n * This component is the skeleton around the actual pages, and should only\n * contain code that should be seen on all pages. (e.g. navigation bar)\n *\n */\n\nimport React from 'react';\nimport { Provider } from 'react-redux';\nimport { Page } from '@strapi/strapi/admin';\n\nimport pluginPermissions from '../../permissions';\nimport Header from '../../components/Header';\nimport { store } from \"../../helpers/configureStore\";\nimport ConfigPage from '../ConfigPage';\n\nconst App = () => {\n  return (\n    <Page.Protect permissions={pluginPermissions.settings}>\n      <Provider store={store}>\n        <Header />\n        <ConfigPage />\n      </Provider>\n    </Page.Protect>\n  );\n};\n\nexport default App;\n"
  },
  {
    "path": "admin/src/containers/ConfigPage/index.jsx",
    "content": "import React, { useEffect } from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { Map } from 'immutable';\nimport {\n  Box,\n  Alert,\n  Typography,\n} from '@strapi/design-system';\nimport { useNotification } from '@strapi/strapi/admin';\nimport { getFetchClient, Layouts } from '@strapi/admin/strapi-admin';\nimport { useIntl } from 'react-intl';\n\nimport { getAllConfigDiff, getAppEnv } from '../../state/actions/Config';\nimport ConfigList from '../../components/ConfigList';\nimport ActionButtons from '../../components/ActionButtons';\n\nconst ConfigPage = () => {\n  const { toggleNotification } = useNotification();\n  const { get } = getFetchClient();\n  const { formatMessage } = useIntl();\n\n  const dispatch = useDispatch();\n  const isLoading = useSelector((state) => state.getIn(['config', 'isLoading'], Map({})));\n  const configDiff = useSelector((state) => state.getIn(['config', 'configDiff'], Map({})));\n  const appEnv = useSelector((state) => state.getIn(['config', 'appEnv', 'env']));\n\n  useEffect(() => {\n    dispatch(getAllConfigDiff(toggleNotification, formatMessage, get));\n    dispatch(getAppEnv(toggleNotification, formatMessage, get));\n  }, []);\n\n  return (\n    <Layouts.Content paddingBottom={8}>\n      {appEnv === 'production' && (\n        <Box paddingBottom={4}>\n          <Alert variant=\"danger\">\n            <Typography variant=\"omega\" fontWeight=\"bold\">{formatMessage({ id: 'config-sync.envWarning.production.heading' })}</Typography><br />\n            {formatMessage({ id: 'config-sync.envWarning.production.message_1' })}<br />\n            {formatMessage({ id: 'config-sync.envWarning.production.message_2' })}\n          </Alert>\n        </Box>\n      )}\n      <ActionButtons />\n      <ConfigList isLoading={isLoading} diff={configDiff.toJS()} />\n    </Layouts.Content>\n  );\n};\n\nexport default ConfigPage;\n"
  },
  {
    "path": "admin/src/containers/Initializer/index.jsx",
    "content": "/**\n *\n * Initializer\n *\n */\n\nimport { useEffect, useRef } from 'react';\nimport PropTypes from 'prop-types';\nimport pluginId from '../../helpers/pluginId';\n\nconst Initializer = ({ updatePlugin }) => {\n  const ref = useRef();\n  ref.current = updatePlugin;\n\n  useEffect(() => {\n    ref.current(pluginId, 'isReady', true);\n  }, []);\n\n  return null;\n};\n\nInitializer.propTypes = {\n  updatePlugin: PropTypes.func.isRequired,\n};\n\nexport default Initializer;\n"
  },
  {
    "path": "admin/src/helpers/blob.js",
    "content": "export function b64toBlob(dataURI, type) {\n    const byteString = atob(dataURI);\n    const ab = new ArrayBuffer(byteString.length);\n    const ia = new Uint8Array(ab);\n    for (let i = 0; i < byteString.length; i++) {\n        ia[i] = byteString.charCodeAt(i);\n    }\n    return new Blob([ab], { type });\n}\n"
  },
  {
    "path": "admin/src/helpers/configureStore.js",
    "content": "import { createStore, applyMiddleware, compose } from 'redux';\nimport { thunk } from 'redux-thunk';\nimport { Map } from 'immutable';\n\nimport rootReducer from '../state/reducers';\nimport { __DEBUG__ } from '../config/constants';\n\nconst configureStore = () => {\n  const initialStoreState = Map();\n\n  const enhancers = [];\n  const middlewares = [\n    thunk,\n  ];\n\n  let devtools;\n\n  if (__DEBUG__) {\n    devtools = (\n      typeof window !== 'undefined'\n      && typeof window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ === 'function'\n      && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ actionsBlacklist: [] })\n    );\n\n    if (devtools) {\n      console.info('[setup] ✓ Enabling Redux DevTools Extension');\n    }\n  }\n\n  const composedEnhancers = devtools || compose;\n  const storeEnhancers = composedEnhancers(\n    applyMiddleware(...middlewares),\n    ...enhancers,\n  );\n\n  const store = createStore(\n    rootReducer,\n    initialStoreState,\n    storeEnhancers,\n  );\n\n  return store;\n};\n\nexport default configureStore;\n\nexport const store = configureStore();\n"
  },
  {
    "path": "admin/src/helpers/getTrad.js",
    "content": "import pluginId from './pluginId';\n\nconst getTrad = (id) => `${pluginId}.${id}`;\n\nexport default getTrad;\n"
  },
  {
    "path": "admin/src/helpers/pluginId.js",
    "content": "import pluginPkg from '../../../package.json';\n\nconst pluginId = pluginPkg.name.replace(\n  /^strapi-plugin-/i,\n  '',\n);\n\nexport default pluginId;\n"
  },
  {
    "path": "admin/src/helpers/prefixPluginTranslations.js",
    "content": "const prefixPluginTranslations = (trad, pluginId) => {\n  if (!pluginId) {\n    throw new TypeError(\"pluginId can't be empty\");\n  }\n  return Object.keys(trad).reduce((acc, current) => {\n    acc[`${pluginId}.${current}`] = trad[current];\n    return acc;\n  }, {});\n};\n\nexport { prefixPluginTranslations };\n"
  },
  {
    "path": "admin/src/index.cy.jsx",
    "content": "// <reference types=\"cypress\" />\n\ndescribe('Config Sync', () => {\n  beforeEach(() => {\n    cy.task('deleteFolder', 'playground/config/sync');\n  });\n\n  it('Check the config diff', () => {\n    cy.login();\n    cy.navigateToInterface();\n    cy.initialExport();\n\n    cy.makeConfigChanges();\n\n    cy.navigateToInterface();\n\n    cy.get('tbody tr').contains('plugin_users-permissions_advanced').click();\n\n    cy.contains('\"unique_email\": true,');\n    cy.contains('\"unique_email\": false,');\n  });\n\n  it('Download the config as zip', () => {\n    cy.login();\n    cy.navigateToInterface();\n    cy.initialExport();\n\n    cy.intercept({\n      method: 'GET',\n      url: '/config-sync/zip',\n    }).as('getConfigZip');\n\n    cy.get('button').contains('Download Config').click();\n\n    cy.wait('@getConfigZip').then((interception) => {\n      const configZipResponse = interception.response.body;\n      const downloadsFolder = Cypress.config('downloadsFolder');\n      cy.readFile(`${downloadsFolder}/${configZipResponse.name.replaceAll(':', '_')}`).should('exist');\n    });\n  });\n\n  it('Partial import & export', () => {\n    cy.login();\n    cy.navigateToInterface();\n    cy.initialExport();\n\n    cy.makeConfigChanges();\n\n    cy.navigateToInterface();\n\n    cy.get('button[aria-label=\"Select all entries\"]').click();\n\n    cy.intercept({\n      method: 'POST',\n      url: '/config-sync/import',\n    }).as('importConfig');\n    cy.get('button[aria-label=\"Select plugin_upload_settings\"]').click();\n    cy.get('button').contains('Import').click();\n    cy.get('button').contains('Yes, import').click();\n    cy.wait('@importConfig').its('response.statusCode').should('equal', 200);\n    cy.contains('plugin_users-permissions_advanced');\n    cy.contains('plugin_users-permissions_email');\n\n    cy.intercept({\n      method: 'POST',\n      url: '/config-sync/export',\n    }).as('exportConfig');\n    cy.get('button[aria-label=\"Select plugin_users-permissions_advanced\"]').click();\n    cy.get('button').contains('Export').click();\n    cy.get('button').contains('Yes, export').click();\n    cy.wait('@exportConfig').its('response.statusCode').should('equal', 200);\n    cy.contains('plugin_users-permissions_email');\n  });\n});\n"
  },
  {
    "path": "admin/src/index.js",
    "content": "import pluginPkg from '../../package.json';\nimport pluginId from './helpers/pluginId';\nimport { prefixPluginTranslations } from './helpers/prefixPluginTranslations';\nimport pluginPermissions from './permissions';\n// import pluginIcon from './components/PluginIcon';\n// import getTrad from './helpers/getTrad';\n\nconst pluginDescription = pluginPkg.strapi.description || pluginPkg.description;\nconst { name } = pluginPkg.strapi;\n\nexport default {\n  register(app) {\n    app.registerPlugin({\n      description: pluginDescription,\n      id: pluginId,\n      isReady: true,\n      isRequired: pluginPkg.strapi.required || false,\n      name,\n    });\n\n    app.createSettingSection(\n      {\n        id: pluginId,\n        intlLabel: {\n          id: `${pluginId}.plugin.name`,\n          defaultMessage: 'Config Sync',\n        },\n      },\n      [\n        {\n          intlLabel: {\n            id: `${pluginId}.Settings.Tool.Title`,\n            defaultMessage: 'Interface',\n          },\n          id: 'config-sync-page',\n          to: `${pluginId}`,\n          Component: () => import('./containers/App'),\n          permissions: pluginPermissions['settings'],\n        },\n      ],\n    );\n  },\n  bootstrap(app) {},\n  async registerTrads({ locales }) {\n    const importedTrads = await Promise.all(\n      locales.map((locale) => {\n        return import(`./translations/${locale}.json`)\n          .then(({ default: data }) => {\n            return {\n              data: prefixPluginTranslations(data, pluginId),\n              locale,\n            };\n          })\n          .catch(() => {\n            return {\n              data: {},\n              locale,\n            };\n          });\n      }),\n    );\n\n    return Promise.resolve(importedTrads);\n  },\n};\n"
  },
  {
    "path": "admin/src/permissions.js",
    "content": "const pluginPermissions = {\n  // This permission regards the main component (App) and is used to tell\n  // If the plugin link should be displayed in the menu\n  // And also if the plugin is accessible. This use case is found when a user types the url of the\n  // plugin directly in the browser\n  'menu-link': [{ action: 'plugin::config-sync.menu-link', subject: null }],\n  settings: [{ action: 'plugin::config-sync.settings.read', subject: null }],\n};\n\nexport default pluginPermissions;\n"
  },
  {
    "path": "admin/src/state/actions/Config.js",
    "content": "/**\n *\n * Main actions\n *\n */\nimport { saveAs } from 'file-saver';\nimport { b64toBlob } from '../../helpers/blob';\n\nexport function getAllConfigDiff(toggleNotification, formatMessage, get) {\n  return async function(dispatch) {\n    dispatch(setLoadingState(true));\n    try {\n      const configDiff = await get('/config-sync/diff');\n      dispatch(setConfigPartialDiffInState([]));\n      dispatch(setConfigDiffInState(configDiff.data));\n      dispatch(setLoadingState(false));\n    } catch (err) {\n      toggleNotification({ type: 'warning', message: formatMessage({ id: 'notification.error' }) });\n      dispatch(setLoadingState(false));\n    }\n  };\n}\n\nexport const SET_CONFIG_DIFF_IN_STATE = 'SET_CONFIG_DIFF_IN_STATE';\nexport function setConfigDiffInState(config) {\n  return {\n    type: SET_CONFIG_DIFF_IN_STATE,\n    config,\n  };\n}\n\nexport const SET_CONFIG_PARTIAL_DIFF_IN_STATE = 'SET_CONFIG_PARTIAL_DIFF_IN_STATE';\nexport function setConfigPartialDiffInState(config) {\n  return {\n    type: SET_CONFIG_PARTIAL_DIFF_IN_STATE,\n    config,\n  };\n}\n\nexport function exportAllConfig(partialDiff, toggleNotification, formatMessage, post, get) {\n  return async function(dispatch) {\n    dispatch(setLoadingState(true));\n    try {\n      const response = await post('/config-sync/export', partialDiff);\n      toggleNotification({ type: 'success', message: response.data.message });\n      dispatch(getAllConfigDiff(toggleNotification, formatMessage, get));\n      dispatch(setLoadingState(false));\n    } catch (err) {\n      toggleNotification({ type: 'warning', message: formatMessage({ id: 'notification.error' }) });\n      dispatch(setLoadingState(false));\n    }\n  };\n}\n\nexport function downloadZip(toggleNotification, formatMessage, post, get) {\n  return async function(dispatch) {\n    dispatch(setLoadingState(true));\n    try {\n      const { message, base64Data, name } = (await get('/config-sync/zip')).data;\n      toggleNotification({ type: 'success', message });\n      if (base64Data) {\n        saveAs(b64toBlob(base64Data, 'application/zip'), name, { type: 'application/zip' });\n      }\n      dispatch(setLoadingState(false));\n    } catch (err) {\n      toggleNotification({ type: 'warning', message: formatMessage({ id: 'notification.error' }) });\n      dispatch(setLoadingState(false));\n    }\n  };\n}\n\nexport function importAllConfig(partialDiff, force, toggleNotification, formatMessage, post, get) {\n  return async function(dispatch) {\n    dispatch(setLoadingState(true));\n    try {\n      const response = await post('/config-sync/import', {\n        force,\n        config: partialDiff,\n      });\n      toggleNotification({ type: 'success', message: response.data.message });\n      dispatch(getAllConfigDiff(toggleNotification, formatMessage, get));\n      dispatch(setLoadingState(false));\n    } catch (err) {\n      toggleNotification({ type: 'warning', message: formatMessage({ id: 'notification.error' }) });\n      dispatch(setLoadingState(false));\n    }\n  };\n}\n\nexport const SET_LOADING_STATE = 'SET_LOADING_STATE';\nexport function setLoadingState(value) {\n  return {\n    type: SET_LOADING_STATE,\n    value,\n  };\n}\n\nexport function getAppEnv(toggleNotification, formatMessage, get) {\n  return async function(dispatch) {\n    try {\n      const envVars = await get('/config-sync/app-env');\n      dispatch(setAppEnvInState(envVars.data));\n    } catch (err) {\n      toggleNotification({ type: 'warning', message: formatMessage({ id: 'notification.error' }) });\n    }\n  };\n}\n\nexport const SET_APP_ENV_IN_STATE = 'SET_APP_ENV_IN_STATE';\nexport function setAppEnvInState(value) {\n  return {\n    type: SET_APP_ENV_IN_STATE,\n    value,\n  };\n}\n"
  },
  {
    "path": "admin/src/state/reducers/Config/index.js",
    "content": "/**\n *\n * Main reducer\n *\n */\n\nimport { fromJS, Map, List } from 'immutable';\nimport {\n  SET_CONFIG_DIFF_IN_STATE,\n  SET_CONFIG_PARTIAL_DIFF_IN_STATE,\n  SET_LOADING_STATE,\n  SET_APP_ENV_IN_STATE,\n} from '../../actions/Config';\n\nconst initialState = fromJS({\n  configDiff: Map({}),\n  partialDiff: List([]),\n  isLoading: false,\n  appEnv: Map({}),\n});\n\nexport default function configReducer(state = initialState, action) {\n  switch (action.type) {\n    case SET_CONFIG_DIFF_IN_STATE:\n      return state\n        .update('configDiff', () => fromJS(action.config));\n    case SET_CONFIG_PARTIAL_DIFF_IN_STATE:\n      return state\n        .update('partialDiff', () => fromJS(action.config));\n    case SET_LOADING_STATE:\n      return state\n        .update('isLoading', () => fromJS(action.value));\n    case SET_APP_ENV_IN_STATE:\n      return state\n        .update('appEnv', () => fromJS(action.value));\n    default:\n      return state;\n  }\n}\n"
  },
  {
    "path": "admin/src/state/reducers/index.js",
    "content": "import { combineReducers } from 'redux-immutable';\nimport configReducer from './Config';\n\nconst rootReducer = combineReducers({\n  config: configReducer,\n});\n\nexport default rootReducer;\n"
  },
  {
    "path": "admin/src/translations/ar.json",
    "content": "{}"
  },
  {
    "path": "admin/src/translations/cs.json",
    "content": "{}"
  },
  {
    "path": "admin/src/translations/de.json",
    "content": "{}\n"
  },
  {
    "path": "admin/src/translations/en.json",
    "content": "{\n  \"popUpWarning.warning.import_1\": \"If you continue all your local config files\",\n  \"popUpWarning.warning.import_2\": \"will be imported into the database.\",\n  \"popUpWarning.warning.export_1\": \"If you continue all your database config\",\n  \"popUpWarning.warning.export_2\": \"will be written into config files.\",\n  \"popUpWarning.button.import\": \"Yes, import\",\n  \"popUpWarning.button.export\": \"Yes, export\",\n  \"popUpWarning.button.cancel\": \"Cancel\",\n  \"popUpWarning.force\": \"Force\",\n  \"popUpWarning.Confirmation\": \"Confirmation\",\n\n  \"envWarning.production.heading\": \"You're in the production environment\",\n  \"envWarning.production.message_1\": \"Please be careful when syncing your config in production.\",\n  \"envWarning.production.message_2\": \"Make sure you are not overriding critical config changes on import.\",\n\n  \"Header.Title\": \"Config Sync\",\n  \"Header.Description\": \"Manage your database config across environments.\",\n\n  \"ConfigList.Loading\": \"Loading content...\",\n  \"ConfigList.SelectAll\": \"Select all entries\",\n  \"ConfigList.ConfigName\": \"Config name\",\n  \"ConfigList.ConfigType\": \"Config type\",\n  \"ConfigList.State\": \"State\",\n  \"ConfigList.Different\": \"Different\",\n  \"ConfigList.OnlyDir\": \"Only in sync dir\",\n  \"ConfigList.OnlyDB\": \"Only in DB\",\n\n  \"NoChanges.Message\": \"No differences between DB and sync directory. You are up-to-date!\",\n\n  \"ConfigDiff.Title\": \"Config changes for\",\n  \"ConfigDiff.SyncDirectory\": \"Sync directory\",\n  \"ConfigDiff.Database\": \"Database\",\n\n  \"Buttons.Export\": \"Export\",\n  \"Buttons.DownloadConfig\": \"Download Config\",\n  \"Buttons.Import\": \"Import\",\n\n  \"FirstExport.Message\": \"Looks like this is your first time using config-sync for this project.\",\n  \"FirstExport.Button\": \"Make the initial export\",\n\n  \"Settings.Tool.Title\": \"Interface\",\n\n  \"plugin.name\": \"Config Sync\"\n}\n"
  },
  {
    "path": "admin/src/translations/es.json",
    "content": "{ \n  \"popUpWarning.warning.import_1\": \"Si continuas todos tus ficheros de configuración locales\",\n  \"popUpWarning.warning.import_2\": \"se importarán a la base de datos.\",\n  \"popUpWarning.warning.export_1\": \"Si continuas las configuraciones de tu base de datos\",\n  \"popUpWarning.warning.export_2\": \"se escribirán en ficheros de configuración locales.\",\n  \"popUpWarning.button.import\": \"Sí, importar\",\n  \"popUpWarning.button.export\": \"Sí, exportar\",\n  \"popUpWarning.button.cancel\": \"Cancelar\",\n  \"popUpWarning.force\": \"Forzar\",\n  \"popUpWarning.Confirmation\": \"Confirmación\",\n\n  \"Header.Title\": \"Config Sync\",\n  \"Header.Description\": \"Gestiona las configuraciones de tu base de datos entre diferentes entornos o instancias.\",\n\n  \"ConfigList.Loading\": \"Cargando contenido...\",\n  \"ConfigList.SelectAll\": \"Seleccionar todas las entradas\",\n  \"ConfigList.ConfigName\": \"Nombre\",\n  \"ConfigList.ConfigType\": \"Tipo\",\n  \"ConfigList.State\": \"Estado\",\n  \"ConfigList.Different\": \"Diferentes\",\n  \"ConfigList.OnlyDir\": \"Sólo en directorio de sincronización\",\n  \"ConfigList.OnlyDB\": \"Sólo en la base de datos\",\n\n  \"NoChanges.Message\": \"No hay diferencia entre la base de datos y el directorio de sincronización. ¡Estás actualizado!\",\n\n  \"ConfigDiff.Title\": \"Cambios en la configuración para\",\n  \"ConfigDiff.SyncDirectory\": \"Directorio de sincronización\",\n  \"ConfigDiff.Database\": \"Base de datos\",\n\n  \"Buttons.Import\": \"Importar\",\n  \"Buttons.Export\": \"Exportar\",\n\n  \"FirstExport.Message\": \"Parece ser la primera vez que se usa config-sync en este proyecto.\",\n  \"FirstExport.Button\": \"Hacer la exportación inicial\",\n\n  \"Settings.Tool.Title\": \"Interfaz\",\n\n  \"plugin.name\": \"Config Sync\"\n}"
  },
  {
    "path": "admin/src/translations/fr.json",
    "content": "{}"
  },
  {
    "path": "admin/src/translations/id.json",
    "content": "{}"
  },
  {
    "path": "admin/src/translations/index.js",
    "content": "import ar from './ar.json';\nimport cs from './cs.json';\nimport de from './de.json';\nimport en from './en.json';\nimport es from './es.json';\nimport fr from './fr.json';\nimport id from './id.json';\nimport it from './it.json';\nimport ko from './ko.json';\nimport ms from './ms.json';\nimport nl from './nl.json';\nimport pl from './pl.json';\nimport ptBR from './pt-BR.json';\nimport pt from './pt.json';\nimport ru from './ru.json';\nimport th from './th.json';\nimport tr from './tr.json';\nimport uk from './uk.json';\nimport vi from './vi.json';\nimport zhHans from './zh-Hans.json';\nimport zh from './zh.json';\nimport sk from './sk.json';\n\nconst trads = {\n  ar,\n  cs,\n  de,\n  en,\n  es,\n  fr,\n  id,\n  it,\n  ko,\n  ms,\n  nl,\n  pl,\n  'pt-BR': ptBR,\n  pt,\n  ru,\n  th,\n  tr,\n  uk,\n  vi,\n  'zh-Hans': zhHans,\n  zh,\n  sk,\n};\n\nexport default trads;\n"
  },
  {
    "path": "admin/src/translations/it.json",
    "content": "{}"
  },
  {
    "path": "admin/src/translations/ko.json",
    "content": "{}"
  },
  {
    "path": "admin/src/translations/ms.json",
    "content": "{}"
  },
  {
    "path": "admin/src/translations/nl.json",
    "content": "{}"
  },
  {
    "path": "admin/src/translations/pl.json",
    "content": "{}\n"
  },
  {
    "path": "admin/src/translations/pt-BR.json",
    "content": "{}"
  },
  {
    "path": "admin/src/translations/pt.json",
    "content": "{}"
  },
  {
    "path": "admin/src/translations/ru.json",
    "content": "{\n  \"popUpWarning.warning.import_1\": \"Если вы продолжите, все ваши локальные конфигурационные файлы\",\n  \"popUpWarning.warning.import_2\": \"будут импортированы в базу данных.\",\n  \"popUpWarning.warning.export_1\": \"Если вы продолжите, вся конфигурация вашей базы данных\",\n  \"popUpWarning.warning.export_2\": \"будет записана в конфигурационные файлы.\",\n  \"popUpWarning.button.import\": \"Да, импортировать\",\n  \"popUpWarning.button.export\": \"Да, экспортитровать\",\n  \"popUpWarning.button.cancel\": \"Отмена\",\n  \"popUpWarning.force\": \"Принудительно\",\n  \"popUpWarning.Confirmation\": \"Подтверждение\",\n\n  \"envWarning.production.heading\": \"Вы находитесь в prodoction-среде\",\n  \"envWarning.production.message_1\": \"Пожалуйста, будьте осторожны при синхронизации конфигурации в prodoction-среде.\",\n  \"envWarning.production.message_2\": \"Убедитесь, что вы не переопределяете важные изменения конфигурации при импорте.\",\n\n  \"Header.Title\": \"Config Sync\",\n  \"Header.Description\": \"Управляйте конфигурацией своей базы данных в разных средах.\",\n\n  \"ConfigList.Loading\": \"Загрузка содержимого...\",\n  \"ConfigList.SelectAll\": \"Выбрать все записи\",\n  \"ConfigList.ConfigName\": \"Имя конфигурации\",\n  \"ConfigList.ConfigType\": \"Тип конфигурации\",\n  \"ConfigList.State\": \"Состояние\",\n  \"ConfigList.Different\": \"Различия\",\n  \"ConfigList.OnlyDir\": \"Только в каталоге синхронизации\",\n  \"ConfigList.OnlyDB\": \"Только в базе данных\",\n\n  \"NoChanges.Message\": \"Нет различий между базой данных и каталогом синхронизации. Данные синхронизированы!\",\n\n  \"ConfigDiff.Title\": \"Изменения конфигурации для\",\n  \"ConfigDiff.SyncDirectory\": \"Каталог синхронизации\",\n  \"ConfigDiff.Database\": \"База данных\",\n\n  \"Buttons.Export\": \"Экспортировать\",\n  \"Buttons.DownloadConfig\": \"Скачать конфиг\",\n  \"Buttons.Import\": \"Импортировать\",\n\n  \"FirstExport.Message\": \"Похоже, вы впервые используете config-sync для этого проекта.\",\n  \"FirstExport.Button\": \"Выполнить первоначальный экспорт\",\n\n  \"Settings.Tool.Title\": \"Управление\",\n\n  \"plugin.name\": \"Config Sync\"\n}\n"
  },
  {
    "path": "admin/src/translations/sk.json",
    "content": "{}"
  },
  {
    "path": "admin/src/translations/th.json",
    "content": "{}"
  },
  {
    "path": "admin/src/translations/tr.json",
    "content": "{}\n"
  },
  {
    "path": "admin/src/translations/uk.json",
    "content": "{}\n"
  },
  {
    "path": "admin/src/translations/vi.json",
    "content": "{}"
  },
  {
    "path": "admin/src/translations/zh-Hans.json",
    "content": "{}\n"
  },
  {
    "path": "admin/src/translations/zh.json",
    "content": "{}\n"
  },
  {
    "path": "bin/config-sync",
    "content": "#!/usr/bin/env node\n\n'use strict';\n\nrequire('../dist/cli');\n"
  },
  {
    "path": "codecov.yml",
    "content": "comment:\n  branches:\n    - master\n    - develop\n"
  },
  {
    "path": "cypress/support/commands.js",
    "content": "// <reference types=\"cypress\" />\n// ***********************************************\n// This example commands.ts shows you how to\n// create various custom commands and overwrite\n// existing commands.\n//\n// For more comprehensive examples of custom\n// commands please read more here:\n// https://on.cypress.io/custom-commands\n// ***********************************************\n//\n//\n// -- This is a parent command --\n// Cypress.Commands.add('login', (email, password) => { ... })\n//\n//\n// -- This is a child command --\n// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })\n//\n//\n// -- This is a dual command --\n// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })\n//\n//\n// -- This will overwrite an existing command --\n// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })\n//\n\nCypress.Commands.add('login', (path) => {\n  cy.visit('/');\n\n  cy.intercept({\n    method: 'GET',\n    url: '/admin/users/me',\n  }).as('sessionCheck');\n\n  cy.intercept({\n    method: 'GET',\n    url: '/admin/init',\n  }).as('adminInit');\n\n  // Wait for the initial request to complete.\n  cy.wait('@adminInit').its('response.statusCode').should('equal', 200);\n\n  // Wait for the form to render.\n  // eslint-disable-next-line cypress/no-unnecessary-waiting\n  cy.wait(1000);\n\n  cy.get('body').then(($body) => {\n    // Login\n    if ($body.text().includes('Log in to your Strapi account')) {\n      cy.get('input[name=\"email\"]').type('johndoe@example.com');\n      cy.get('input[name=\"password\"]').type('Abc12345678');\n      cy.get('button[type=\"submit\"]').click();\n      cy.wait('@sessionCheck').its('response.statusCode').should('equal', 200);\n    }\n\n    // Register\n    if ($body.text().includes('Credentials are only used to authenticate in Strapi')) {\n      cy.get('input[name=\"firstname\"]').type('John');\n      cy.get('input[name=\"email\"]').type('johndoe@example.com');\n      cy.get('input[name=\"password\"]').type('Abc12345678');\n      cy.get('input[name=\"confirmPassword\"]').type('Abc12345678');\n      cy.get('button[type=\"submit\"]').click();\n      cy.wait('@sessionCheck').its('response.statusCode').should('equal', 200);\n    }\n  });\n});\n\nCypress.Commands.add('navigateToInterface', (path) => {\n  cy.intercept({\n    method: 'GET',\n    url: '/config-sync/diff',\n  }).as('getConfigDiff');\n\n  cy.get('button[aria-controls=\"burger-menu\"]').click();\n  cy.get('a[href=\"/admin/settings\"]').click();\n  cy.get('a[href=\"/admin/settings/config-sync\"]').click();\n\n  cy.wait('@getConfigDiff').its('response.statusCode').should('equal', 200);\n});\n\n\nCypress.Commands.add('initialExport', (path) => {\n  cy.intercept({\n    method: 'POST',\n    url: '/config-sync/export',\n  }).as('exportConfig');\n\n  cy.get('button').contains('Make the initial export').click();\n  cy.get('button').contains('Yes, export').click();\n\n  cy.wait('@exportConfig').its('response.statusCode').should('equal', 200);\n\n  cy.contains('Config was successfully exported to config/sync/.');\n});\n\nCypress.Commands.add('makeConfigChanges', (path) => {\n  // Change a setting in the UP advanced settings\n  cy.intercept({\n    method: 'PUT',\n    url: '/users-permissions/advanced',\n  }).as('saveUpAdvanced');\n  cy.get('a[href=\"/admin/settings/users-permissions/advanced-settings\"]').click();\n  cy.get('input[name=\"unique_email\"').click();\n  cy.get('button[type=\"submit\"]').click();\n  cy.wait('@saveUpAdvanced').its('response.statusCode').should('equal', 200);\n\n  // Change a setting in the media library settings\n  cy.intercept({\n    method: 'PUT',\n    url: '/upload/settings',\n  }).as('saveMediaLibrarySettings');\n  cy.get('a[href=\"/admin/settings/media-library\"]').click();\n  cy.get('input[name=\"responsiveDimensions\"').click();\n  cy.get('button[type=\"submit\"]').click();\n  cy.wait('@saveMediaLibrarySettings').its('response.statusCode').should('equal', 200);\n\n  // Change a setting in the email templates\n  cy.intercept({\n    method: 'PUT',\n    url: '/users-permissions/email-templates',\n  }).as('saveUpEmailTemplates');\n  cy.get('a[href=\"/admin/settings/users-permissions/email-templates\"]').click();\n  cy.get('tbody tr').contains('Reset password').click();\n  cy.get('input[name=\"options.response_email\"]').clear();\n  cy.get('input[name=\"options.response_email\"]').type(`${Math.random().toString(36).substring(2, 15)}@example.com`);\n  cy.get('button[type=\"submit\"]').click();\n  cy.wait('@saveUpEmailTemplates').its('response.statusCode').should('equal', 200);\n});\n"
  },
  {
    "path": "cypress/support/e2e.js",
    "content": "// ***********************************************************\n// This example support/e2e.ts is processed and\n// loaded automatically before your test files.\n//\n// This is a great place to put global configuration and\n// behavior that modifies Cypress.\n//\n// You can change the location of this file or turn off\n// automatically serving support files with the\n// 'supportFile' configuration option.\n//\n// You can read more here:\n// https://on.cypress.io/configuration\n// ***********************************************************\n\n// Import commands.js using ES2015 syntax:\nimport './commands';\n\nrequire('cypress-terminal-report/src/installLogsCollector')();\n\n// Alternatively you can use CommonJS syntax:\n// require('./commands')\n"
  },
  {
    "path": "cypress.config.js",
    "content": "const { defineConfig } = require('cypress');\nconst fs = require('fs-extra');\n\nmodule.exports = defineConfig({\n  e2e: {\n    baseUrl: 'http://localhost:1337',\n    specPattern: '**/*.cy.{js,ts,jsx,tsx}',\n    video: true,\n    defaultCommandTimeout: 30000,\n    requestTimeout: 30000,\n    setupNodeEvents(on, config) {\n      // implement node event listeners here.\n      // eslint-disable-next-line global-require\n      require('cypress-terminal-report/src/installLogsPrinter')(on);\n\n      on('task', {\n        deleteFolder(folderName) {\n          console.log(`deleting folder ${folderName}`);\n\n          return fs.remove(folderName)\n          .then(() => {\n            console.log(`folder ${folderName} deleted`);\n            return null;\n          })\n          .catch((err) => {\n            console.error(`error deleting folder ${folderName}`, err);\n            throw err;\n          });\n        },\n      });\n    },\n  },\n});\n"
  },
  {
    "path": "dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: npm\n    directory: /\n    schedule:\n      interval: daily\n    ignore:\n      - dependency-name: '\\*'\n        update-types: [\"version-update:semver-patch\"]\n    groups:\n      strapi:\n        patterns:\n          - \"@strapi/*\"\n"
  },
  {
    "path": "docs/.github/workflows/deploy.yml",
    "content": "name: Deploy\n\non:\n  workflow_dispatch:\n  push:\n    branches:\n    - main\n\njobs:\n  deploy:\n    name: Deploy\n    runs-on: ubuntu-latest\n    environment:\n      name: docs.pluginpal.io\n      url: https://docs.pluginpal.io\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v3\n\n      - name: Set up Docker\n        uses: actions/setup-node@v3\n        with:\n          node-version: '14'\n\n      - name: Build a Docker image\n        run: |\n          docker build \\\n            -t pluginpal-docs:latest .\n          docker save -o pluginpal-docs-latest.tar pluginpal-docs:latest\n\n      - name: Transfer the Docker image to the Dokku server\n        uses: appleboy/scp-action@v0.1.3\n        with:\n          host: ${{ secrets.SSH_HOST }}\n          username: ${{ secrets.SSH_CI_USERNAME }}\n          password: ${{ secrets.SSH_CI_PASSWORD }}\n          source: pluginpal-docs-latest.tar\n          target: /var/lib/dokku/data/storage/docs/docker-images\n\n      - name: Deploy the Dokku app based on the Docker image\n        uses: appleboy/ssh-action@v0.1.10\n        with:\n          host: ${{ secrets.SSH_HOST }}\n          username: ${{ secrets.SSH_CI_USERNAME }}\n          password: ${{ secrets.SSH_CI_PASSWORD }}\n          script_stop: true\n          script: |\n            sudo docker load -i /var/lib/dokku/data/storage/docs/docker-images/pluginpal-docs-latest.tar\n            DOCS_LATEST_IMAGE=$(sudo docker images --format \"{{.ID}}\" pluginpal-docs:latest)\n            sudo docker tag pluginpal-docs:latest pluginpal-docs:$DOCS_LATEST_IMAGE\n            dokku git:from-image docs pluginpal-docs:$DOCS_LATEST_IMAGE\n            sudo docker system prune --all --force\n"
  },
  {
    "path": "docs/.gitignore",
    "content": "# Dependencies\n/node_modules\n\n# Production\n/build\n\n# Generated files\n.docusaurus\n.cache-loader\n\n# Misc\n.DS_Store\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n"
  },
  {
    "path": "docs/Dockerfile",
    "content": "# syntax=docker/dockerfile:1\n\n# Stage 1: Base image.\n## Start with a base image containing NodeJS so we can build Docusaurus.\nFROM node:20-alpine as base\n## Disable colour output from yarn to make logs easier to read.\nENV FORCE_COLOR=0\n## Enable corepack.\nRUN corepack enable\n## Set the working directory to `/opt/docusaurus`.\nWORKDIR /opt/docusaurus\n\n# Stage 2b: Production build mode.\nFROM base as prod\n## Set the working directory to `/opt/docusaurus`.\nWORKDIR /opt/docusaurus\n## Copy over the source code.\nCOPY . /opt/docusaurus/\n## Install dependencies with `--immutable` to ensure reproducibility.\nRUN yarn install\n## Build the static site.\nRUN yarn build\n\n# Stage 3a: Serve with `docusaurus serve`.\nFROM prod as serve\n## Expose the port that Docusaurus will run on.\nEXPOSE 3000\n## Run the production server.\nCMD [\"yarn\", \"serve\", \"--host\", \"0.0.0.0\", \"--no-open\"]\n"
  },
  {
    "path": "docs/README.md",
    "content": "# Website\n\nThis website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator.\n\n### Installation\n\n```\n$ yarn\n```\n\n### Local Development\n\n```\n$ yarn start\n```\n\nThis command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.\n\n### Build\n\n```\n$ yarn build\n```\n\nThis command generates static content into the `build` directory and can be served using any static contents hosting service.\n\n### Deployment\n\nUsing SSH:\n\n```\n$ USE_SSH=true yarn deploy\n```\n\nNot using SSH:\n\n```\n$ GIT_USER=<Your GitHub username> yarn deploy\n```\n\nIf you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.\n"
  },
  {
    "path": "docs/babel.config.js",
    "content": "module.exports = {\n  presets: [require.resolve('@docusaurus/core/lib/babel/preset')],\n};\n"
  },
  {
    "path": "docs/blog/2019-05-28-first-blog-post.md",
    "content": "---\nslug: first-blog-post\ntitle: First Blog Post\nauthors: [slorber, yangshun]\ntags: [hola, docusaurus]\n---\n\nLorem ipsum dolor sit amet...\n\n<!-- truncate -->\n\n...consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet\n"
  },
  {
    "path": "docs/blog/2019-05-29-long-blog-post.md",
    "content": "---\nslug: long-blog-post\ntitle: Long Blog Post\nauthors: yangshun\ntags: [hello, docusaurus]\n---\n\nThis is the summary of a very long blog post,\n\nUse a `<!--` `truncate` `-->` comment to limit blog post size in the list view.\n\n<!-- truncate -->\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet\n"
  },
  {
    "path": "docs/blog/2021-08-01-mdx-blog-post.mdx",
    "content": "---\nslug: mdx-blog-post\ntitle: MDX Blog Post\nauthors: [slorber]\ntags: [docusaurus]\n---\n\nBlog posts support [Docusaurus Markdown features](https://docusaurus.io/docs/markdown-features), such as [MDX](https://mdxjs.com/).\n\n:::tip\n\nUse the power of React to create interactive blog posts.\n\n:::\n\n{/* truncate */}\n\nFor example, use JSX to create an interactive button:\n\n```js\n<button onClick={() => alert('button clicked!')}>Click me!</button>\n```\n\n<button onClick={() => alert('button clicked!')}>Click me!</button>\n"
  },
  {
    "path": "docs/blog/2021-08-26-welcome/index.md",
    "content": "---\nslug: welcome\ntitle: Welcome\nauthors: [slorber, yangshun]\ntags: [facebook, hello, docusaurus]\n---\n\n[Docusaurus blogging features](https://docusaurus.io/docs/blog) are powered by the [blog plugin](https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-content-blog).\n\nHere are a few tips you might find useful.\n\n<!-- truncate -->\n\nSimply add Markdown files (or folders) to the `blog` directory.\n\nRegular blog authors can be added to `authors.yml`.\n\nThe blog post date can be extracted from filenames, such as:\n\n- `2019-05-30-welcome.md`\n- `2019-05-30-welcome/index.md`\n\nA blog post folder can be convenient to co-locate blog post images:\n\n![Docusaurus Plushie](./docusaurus-plushie-banner.jpeg)\n\nThe blog supports tags as well!\n\n**And if you don't want a blog**: just delete this directory, and use `blog: false` in your Docusaurus config.\n"
  },
  {
    "path": "docs/blog/authors.yml",
    "content": "yangshun:\n  name: Yangshun Tay\n  title: Front End Engineer @ Facebook\n  url: https://github.com/yangshun\n  image_url: https://github.com/yangshun.png\n  page: true\n  socials:\n    x: yangshunz\n    github: yangshun\n\nslorber:\n  name: Sébastien Lorber\n  title: Docusaurus maintainer\n  url: https://sebastienlorber.com\n  image_url: https://github.com/slorber.png\n  page:\n    # customize the url of the author page at /blog/authors/<permalink>\n    permalink: '/all-sebastien-lorber-articles'\n  socials:\n    x: sebastienlorber\n    linkedin: sebastienlorber\n    github: slorber\n    newsletter: https://thisweekinreact.com\n"
  },
  {
    "path": "docs/blog/tags.yml",
    "content": "facebook:\n  label: Facebook\n  permalink: /facebook\n  description: Facebook tag description\n\nhello:\n  label: Hello\n  permalink: /hello\n  description: Hello tag description\n\ndocusaurus:\n  label: Docusaurus\n  permalink: /docusaurus\n  description: Docusaurus tag description\n\nhola:\n  label: Hola\n  permalink: /hola\n  description: Hola tag description\n"
  },
  {
    "path": "docs/docs/api/plugin-config-types.md",
    "content": "---\nsidebar_label: 'Plugin config types'\ndisplayed_sidebar: configSyncSidebar\nslug: /api/plugin-config-types\n---\n\n# Plugin config types\n\nWhen 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.\n\n## Register a config type programatically\n\nYou can register a config type by adding some code to the register function of your plugin.\n\n```md title=\"register.js\"\n// Register the config type when using the config-sync plugin.\nif (strapi.plugin('config-sync')) {\n  if (!strapi.plugin('config-sync').pluginTypes) {\n    strapi.plugin('config-sync').pluginTypes = [];\n  }\n\n  strapi.plugin('config-sync').pluginTypes.push({\n    configName: 'url-pattern',\n    queryString: 'plugin::webtools.url-pattern',\n    uid: 'code',\n  });\n}\n```\n\nIf 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\n"
  },
  {
    "path": "docs/docs/configuration/custom-types.md",
    "content": "---\nsidebar_label: 'Custom types'\ndisplayed_sidebar: configSyncSidebar\nslug: /configuration/custom-types\n---\n\n# Custom types\n\nWith 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.\n\n| Name | Details |\n| ---- | ------- |\n| Key | `customTypes` |\n| Required | false |\n| Type | array |\n| Default | `[]` |\n"
  },
  {
    "path": "docs/docs/configuration/excluded-config.md",
    "content": "---\nsidebar_label: 'Excluded config'\ndisplayed_sidebar: configSyncSidebar\nslug: /configuration/excluded-config\n---\n\n# Excluded config\n\nSpecify 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.\n\n| Name | Details |\n| ---- | ------- |\n| Key | `excludedConfig` |\n| Required | false |\n| Type | array |\n| Default | `['core-store.plugin_users-permissions_grant', 'core-store.plugin_upload_metrics', 'core-store.strapi_content_types_schema', 'core-store.ee_information',]` |\n"
  },
  {
    "path": "docs/docs/configuration/excluded-types.md",
    "content": "---\nsidebar_label: 'Excluded types'\ndisplayed_sidebar: configSyncSidebar\nslug: /configuration/excluded-types\n---\n\n# Excluded types\n\nThis 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.\n\nFor example:\n\n```\nexcludedTypes: ['admin-role']\n```\n\n| Name | Details |\n| ---- | ------- |\n| Key | `excludedTypes` |\n| Required | false |\n| Type | array |\n| Default | `[]` |\n"
  },
  {
    "path": "docs/docs/configuration/import-on-bootstrap.md",
    "content": "---\nsidebar_label: 'Import on bootstrap'\ndisplayed_sidebar: configSyncSidebar\nslug: /configuration/import-on-bootstrap\n---\n\n# Import on bootstrap\n\nAllows you to let the config be imported automaticly when strapi is bootstrapping (on `strapi start`). \n\n:::danger\nThis 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**.\n:::\n\n| Name | Details |\n| ---- | ------- |\n| Key | `importOnBootstrap` |\n| Required | false |\n| Type | bool |\n| Default | `false` |\n"
  },
  {
    "path": "docs/docs/configuration/introduction.md",
    "content": "---\nsidebar_label: 'Introduction'\ndisplayed_sidebar: configSyncSidebar\nslug: /configuration\n---\n\n# 🔧 Configuration\nThe settings of the plugin can be overridden in the `config/plugins.js` file. \nIn the example below you can see how, and also what the default settings are.\n\n```md title=\"config/plugins.js\"\nmodule.exports = ({ env }) => ({\n  // ...\n  'config-sync': {\n    enabled: true,\n    config: {\n      syncDir: \"config/sync/\",\n      minify: false,\n      soft: false,\n      importOnBootstrap: false,\n      customTypes: [],\n      excludedTypes: [],\n      excludedConfig: [\n        \"core-store.plugin_users-permissions_grant\",\n        \"core-store.plugin_upload_metrics\",\n        \"core-store.strapi_content_types_schema\",\n        \"core-store.ee_information\",\n      ],\n    },\n  },\n});\n```\n"
  },
  {
    "path": "docs/docs/configuration/minify.md",
    "content": "---\nsidebar_label: 'Minify'\ndisplayed_sidebar: configSyncSidebar\nslug: /configuration/minify\n---\n\n# Minify\n\nWhen enabled all the exported JSON files will be minified.\n\n| Name | Details |\n| ---- | ------- |\n| Key | `minify` |\n| Required | false |\n| Type | bool |\n| Default | `false` |\n"
  },
  {
    "path": "docs/docs/configuration/soft.md",
    "content": "---\nsidebar_label: 'Soft'\ndisplayed_sidebar: configSyncSidebar\nslug: /configuration/soft\n---\n\n# Soft\n\nWhen 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.\n\n| Name | Details |\n| ---- | ------- |\n| Key | `soft` |\n| Required | false |\n| Type | bool |\n| Default | `false` |\n"
  },
  {
    "path": "docs/docs/configuration/sync-dir.md",
    "content": "---\nsidebar_label: 'Sync dir'\ndisplayed_sidebar: configSyncSidebar\nslug: /configuration/sync-dir\n---\n\n# Sync dir\n\nThe path for reading and writing the sync files.\n\n| Name | Details |\n| ---- | ------- |\n| Key | `syncDir` |\n| Required | true |\n| Type | string |\n| Default | `config/sync/` |\n"
  },
  {
    "path": "docs/docs/getting-started/admin-gui.md",
    "content": "---\nsidebar_label: 'Admin GUI'\ndisplayed_sidebar: configSyncSidebar\nslug: /admin-gui\n---\n\n# 🖥️ Admin panel (GUI)\nThis 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.\n\n**Pro tip:**\nBy 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.\n\n![Config diff in admin](/img/assets/admin-diff-viewer.png)\n"
  },
  {
    "path": "docs/docs/getting-started/cli.md",
    "content": "---\nsidebar_label: 'CLI'\ndisplayed_sidebar: configSyncSidebar\nslug: /cli\n---\n\n# 🔌 Command line interface (CLI)\n\nAdd the `config-sync` command as a script to the `package.json` of your Strapi project:\n\n```\n\"scripts\": {\n  // ...\n  \"cs\": \"config-sync\"\n},\n```\n\nYou can now run all the `config-sync` commands like this:\n\n<Tabs groupId=\"yarn-npm\">\n  <TabItem value=\"yarn\" label=\"Yarn\">\n    ```\n    yarn cs --help\n    ```\n  </TabItem>\n  <TabItem value=\"npm\" label=\"NPM\">\n    ```\n    npm run cs -- --help\n    ```\n  </TabItem>\n</Tabs>\n\n## ⬆️ Import ⬇️ Export\n\n> _Command:_ `import` _Alias:_ `i`\n> \n> _Command:_ `export` _Alias:_ `e`\n\nThese commands are used to sync the config in your Strapi project. \n\n_Example:_\n\n<Tabs groupId=\"yarn-npm\">\n  <TabItem value=\"yarn\" label=\"Yarn\">\n    ```\n    yarn cs import\n    yarn cs export\n    ```\n  </TabItem>\n  <TabItem value=\"npm\" label=\"NPM\">\n    ```\n    npm run cs import\n    npm run cs export\n    ```\n  </TabItem>\n</Tabs>\n\n:::info\nWhen you're using `npm` to run these commands, please note that you need an extra `--` to forward the flags to the script.\nMore information about this topic can be found on the <a href=\"https://docs.npmjs.com/cli/commands/npm-run-script\">NPM documentation</a>.\n\nExample:\n```\nnpm run cs import -- --yes\n```\n:::\n\n### Flag: `-y`, `--yes`\n\nUse this flag to skip the confirm prompt and go straight to syncing the config.\n\n```bash\n[command] --yes\n```\n\n### Flag: `-t`, `--type`\n\nUse this flag to specify the type of config you want to sync.\n\n```bash\n[command] --type user-role\n```\n\n### Flag: `-p`, `--partial`\n\nUse this flag to sync a specific set of configs by giving the CLI a comma-separated string of config names.\n\n```bash\n[command] --partial user-role.public,i18n-locale.en\n```\n\n### Flag: `-f`, `--force`\n\nIf you're using the soft setting to gracefully import config, you can use this flag to ignore the setting for the current command and forcefully import all changes anyway.\n\n```bash\n[command] --force\n```\n\n## ↔️ Diff\n\n> _Command:_ `diff` | _Alias:_ `d`\n\nThis command is used to see the difference between the config as found in the sync directory, and the config as found in the database.\n\n_Example:_\n\n<Tabs groupId=\"yarn-npm\">\n  <TabItem value=\"yarn\" label=\"Yarn\">\n    ```\n    yarn cs diff\n    ```\n  </TabItem>\n  <TabItem value=\"npm\" label=\"NPM\">\n    ```\n    npm run cs diff\n    ```\n  </TabItem>\n</Tabs>\n\n### Argument: `<single>`\n\nAdd a single config name as the argument of the `diff` command to see the difference of that single file in a git-style diff viewer.\n\n_Example:_\n\n<Tabs groupId=\"yarn-npm\">\n  <TabItem value=\"yarn\" label=\"Yarn\">\n    ```\n    yarn cs diff user-role.public\n    ```\n  </TabItem>\n  <TabItem value=\"npm\" label=\"NPM\">\n    ```\n    npm run cs diff user-role.public\n    ```\n  </TabItem>\n</Tabs>\n"
  },
  {
    "path": "docs/docs/getting-started/config-types.md",
    "content": "---\nsidebar_label: 'Config Types'\ndisplayed_sidebar: configSyncSidebar\nslug: /config-types\n---\n\n# 🚀 Config types\n\nBy default the plugin will track 4 (official) types. \n\nTo track your own custom types you can register them by setting some plugin config.\n\n## Default types\n\nThese 4 types are by default registered in the sync process. \n\n### Admin role\n\n> Config name: `admin-role` | UID: `code` | Query string: `admin::role`\n\n### User role\n\n> Config name: `user-role` | UID: `type` | Query string: `plugin::users-permissions.role`\n\n### Core store\n\n> Config name: `core-store` | UID: `key` | Query string: `strapi::core-store`\n\n### I18n locale\n\n> Config name: `i18n-locale` | UID: `code` | Query string: `plugin::i18n.locale`\n\n## Custom types\n\nYour custom types can be registered through the `customTypes` plugin config. This is a setting that can be set in the `config/plugins.js` file in your project.\n\n_Read more about the `config/plugins.js` file [here](/configuration)._\n\nYou can register a type by giving the `customTypes` array an object which contains at least the following 3 properties:\n\n```\ncustomTypes: [{\n  configName: 'webhook',\n  queryString: 'webhook',\n  uid: 'name',\n}],\n```\n\n_The example above will register the Strapi webhook type._\n\n### Config name\n\nThe name of the config type. This value will be used as the first part of the filename for all config of this type. It should be unique from the other types and is preferably written in kebab-case.\n\n##### Key: `configName`\n\n> `required:` YES | `type:` string\n\n### Query string\n\nThis is the query string of the type. Each type in Strapi has its own query string you can use to programatically preform CRUD actions on the entries of the type. Often for custom types in Strapi the format is something like `api::custom-api.custom-type`.\n\n##### Key: `queryString`\n\n> `required:` YES | `type:` string\n\n### UID\n\nThe UID represents a field on the registered type. The value of this field will act as a unique identifier to identify the entries across environments. Therefore it should be unique and preferably un-editable after initial creation.\n\nMind that you can not use an auto-incremental value like the `id` as auto-increment does not play nice when you try to match entries across different databases.\n\nIf you do not have a single unique value, you can also pass in an array of keys for a combined uid key. This is for example the case for all content types which use i18n features (An example config would be `uid: ['productId', 'locale']`).\n\n##### Key: `uid`\n\n> `required:` YES | `type:` string | string[]\n\n### Relations\n\nThe relations array specifies the relations you want to include in the sync process.\nThis feature is used to sync the relations between `roles` and `permissions`. See https://github.com/boazpoolman/strapi-plugin-config-sync/blob/master/server/config/types.js#L16.\n\nExample:\n```\n{\n  configName: 'admin-role',\n  queryString: 'admin::role',\n  uid: 'code',\n  relations: [{\n    queryString: 'admin::permission',\n    relationName: 'permissions',\n    parentName: 'role',\n    relationSortFields: ['action', 'subject'],\n  }],\n},\n```\n\n##### Key: `relations`\n\n> `required:` NO | `type:` array\n\n### Components\n\nThis property can accept an array of component names from the type. Strapi Components can be included in the export/import process. With \".\" nested components can also be included in the process.\n```\ncustomTypes: [{\n  configName: 'webhook',\n  queryString: 'webhook',\n  uid: 'name',\n  components: ['ParentComponentA', 'ParentComponentA.ChildComponent', 'ParentComponentB']\n}],\n```\n\n##### Key: `components`\n\n> `required:` NO | `type:` array\n\n### JSON fields\n\nThis property can accept an array of field names from the type. It is meant to specify the JSON fields on the type so the plugin can better format the field values when calculating the config difference.\n\n##### Key: `jsonFields`\n\n> `required:` NO | `type:` array\n"
  },
  {
    "path": "docs/docs/getting-started/installation.md",
    "content": "---\nsidebar_label: 'Installation'\ndisplayed_sidebar: configSyncSidebar\nslug: /\n---\n\n# ⏳ Installation\n\n:::prerequisites\nComplete installation requirements are the exact same as for Strapi itself and can be found in the Strapi documentation.\n\n**Supported Strapi versions:**\n\nStrapi v5 use `strapi-plugin-config-sync@^3`\n\nStrapi v4 use `strapi-plugin-config-sync@^1`\n\n:::\n\nInstall the plugin in your Strapi project.\n\n<Tabs groupId=\"yarn-npm\">\n  <TabItem value=\"yarn\" label=\"Yarn\">\n    ```\n    yarn add strapi-plugin-config-sync\n    ```\n  </TabItem>\n  <TabItem value=\"npm\" label=\"NPM\">\n    ```\n    npm install strapi-plugin-config-sync --save\n    ```\n  </TabItem>\n</Tabs>\n \nAdd the export path to the `watchIgnoreFiles` list in the `config/admin.js` file.\nThis way your app won't reload when you export the config in development.\n\n```md title=\"config/admin.js\"\nmodule.exports = ({ env }) => ({\n  // ...\n  watchIgnoreFiles: [\n    '**/config/sync/**',\n  ],\n});\n```\n\nAfter successful installation you have to rebuild the admin UI so it'll include this plugin. To rebuild and restart Strapi run:\n\n<Tabs groupId=\"yarn-npm\">\n  <TabItem value=\"yarn\" label=\"Yarn\">\n    ```\n    yarn build\n    yarn develop\n    ```\n  </TabItem>\n  <TabItem value=\"npm\" label=\"NPM\">\n    ```\n    npm run build\n    npm run develop\n    ```\n  </TabItem>\n</Tabs>\n\nThe **Config Sync** plugin should now appear in the **Settings** section of your Strapi app.\n\nTo 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)\n\nEnjoy 🎉\n"
  },
  {
    "path": "docs/docs/getting-started/motivation.md",
    "content": "---\nsidebar_label: 'Motivation'\ndisplayed_sidebar: configSyncSidebar\nslug: /motivation\n---\n\n# 💡 Motivation\n\nIn 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. \n\nHaving 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.\n\nExamples of these types are:\n\n- Admin roles _(admin::role)_\n- User roles _(plugin::users-permissions.role)_\n- Admin settings _(strapi::core-store)_\n- I18n locale _(plugin::i18n.locale)_\n\nThis 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).\n\n_With great power comes great responsibility - Uncle Ben_\n"
  },
  {
    "path": "docs/docs/getting-started/naming-convention.md",
    "content": "---\nsidebar_label: 'Naming convention'\ndisplayed_sidebar: configSyncSidebar\nslug: /naming-convention\n---\n\n# 🔍 Naming convention\nAll the config files written in the sync directory have the same naming convention. It goes as follows:\n\n\t[config-type].[identifier].json\n\n- `config-type` - Corresponds to the `configName` of the config type.\n- `identifier` - Corresponds to the value of the `uid` field of the config type.\n"
  },
  {
    "path": "docs/docs/getting-started/workflow.md",
    "content": "---\nsidebar_label: 'Workflow'\ndisplayed_sidebar: configSyncSidebar\nslug: /workflow\n---\n\n# ⌨️ Usage / Workflow\nThis plugin works best when you use `git` for the version control of your Strapi project.\n\n_The following workflows are assuming you're using `git`._\n\n### Intro\nAll 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:\n\n- Change the file(s), and run an import. You have now imported from filesystem -> database.\n- Change the record(s), and run an export. You have now exported from database -> filesystem. \n\n### Local development\nWhen building a new feature locally for your Strapi project you'd use the following workflow:\n\n- Build the feature.\n- Export the config.\n- Commit and push the files to git.\n\n### Deployment\nWhen deploying the newly created feature - to either a server, or a co-worker's machine - you'd use the following workflow:\n\n- Pull the latest file changes to the environment.\n- (Re)start your Strapi instance.\n- Import the config.\n\n## Production deployment\nThe 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.\n"
  },
  {
    "path": "docs/docs/upgrading/generic-update.md",
    "content": "---\nsidebar_label: 'Generic update'\ndisplayed_sidebar: configSyncSidebar\nslug: /upgrading/generic-update\n---\n\n# Updating Config Sync\n\nWe 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.\n\n## Bump a minor/patch version\n\nWhen you're updating Config Sync you'll have to follow these steps:\n\n1. Make sure there are no config changes before starting. Either export or import all staged changes.\n2. Update the version of the `strapi-plugin-config-sync` package in your `package.json` using your package manager of choice (yarn/npm/pnpm)\n3. 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.\n4. You're now ready to push these changes an commit them to your source control!\n"
  },
  {
    "path": "docs/docusaurus.config.ts",
    "content": "import {themes as prismThemes} from 'prism-react-renderer';\nimport type {Config} from '@docusaurus/types';\nimport type * as Preset from '@docusaurus/preset-classic';\n\nconst config: Config = {\n  title: 'Strapi Config Sync',\n  tagline: \"Documentation for the config-sync plugin for Strapi\",\n  favicon: 'img/favicon.jpg',\n\n  plugins: [\n    'docusaurus-plugin-sass',\n  ],\n\n  // Set the production url of your site here\n  url: 'https://docs.pluginpal.io',\n  // Set the /<baseUrl>/ pathname under which your site is served\n  // For GitHub pages deployment, it is often '/<projectName>/'\n  baseUrl: '/config-sync/',\n\n  // GitHub pages deployment config.\n  // If you aren't using GitHub pages, you don't need these.\n  organizationName: 'pluginpal', // Usually your GitHub org/user name.\n\n  onBrokenLinks: 'throw',\n  onBrokenMarkdownLinks: 'warn',\n\n  // Even if you don't use internationalization, you can use this field to set\n  // useful metadata like html lang. For example, if your site is Chinese, you\n  // may want to replace \"en\" with \"zh-Hans\".\n  i18n: {\n    defaultLocale: 'en',\n    locales: ['en'],\n  },\n\n  // themes: ['@docusaurus/theme-live-codeblock', '@docusaurus/theme-mermaid'],\n\n  presets: [\n    [\n      'classic',\n      {\n        docs: {\n          routeBasePath: '/',\n          sidebarPath: './sidebars.ts',\n          // Please change this to your repo.\n          // Remove this to remove the \"edit this page\" links.\n          editUrl:\n            'https://github.com/pluginpal/strapi-plugin-config-sync/tree/master/docs',\n          admonitions: {\n            keywords: [\n              // Admonitions defaults\n              'note',\n              'tip',\n              'info',\n              'caution',\n              'danger',\n\n              // Admonitions custom\n              'callout',\n              'prerequisites',\n              'strapi',\n              'warning',\n            ],\n          },\n        },\n        blog: false,\n        sitemap:  {\n          lastmod: 'date',\n          changefreq: 'weekly',\n          priority: 0.6,\n          // ignorePatterns: ['/tags/**'],\n          filename: 'sitemap.xml',\n          createSitemapItems: async (params) => {\n            const {defaultCreateSitemapItems, ...rest} = params;\n            const items = await defaultCreateSitemapItems(rest);\n            return items;\n          },\n        },\n        theme: {\n          customCss: './src/scss/__index.scss',\n        },\n      } satisfies Preset.Options,\n    ],\n  ],\n\n  themeConfig: {\n    // Replace with your project's social card\n    // image: 'img/docusaurus-social-card.jpg',\n    navbar: {\n      title: 'Strapi Config Sync',\n      logo: {\n        alt: 'Config Sync logo',\n        src: 'img/logo.png',\n      },\n      items: [\n        {\n          href: 'https://github.com/pluginpal/strapi-plugin-config-sync',\n          label: 'GitHub',\n          position: 'right',\n        },\n      ],\n    },\n    footer: {\n      style: 'dark',\n      links: [\n        {\n          title: 'Community',\n          items: [\n            {\n              label: 'Discord',\n              href: 'https://discord.com/invite/strapi',\n            },\n            {\n              label: 'Forum',\n              href: 'https://forum.strapi.io/',\n            },\n          ],\n        },\n        {\n          title: 'More',\n          items: [\n            {\n              label: 'Website',\n              href: 'https://www.pluginpal.io',\n            },\n            {\n              label: 'GitHub',\n              href: 'https://github.com/pluginpal',\n            },\n          ],\n        },\n      ],\n    },\n\n    algolia: {\n      appId: 'ADLP623G89',\n      apiKey: '8f91ceaf54e8e8db14479fd79a420a8c',\n      indexName: 'pluginpal',\n    },\n\n    prism: {\n      theme: prismThemes.github,\n      darkTheme: prismThemes.dracula,\n    },\n  } satisfies Preset.ThemeConfig,\n};\n\nexport default config;\n"
  },
  {
    "path": "docs/package.json",
    "content": "{\n  \"name\": \"pluginpal-docs\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"docusaurus\": \"docusaurus\",\n    \"start\": \"docusaurus start\",\n    \"build\": \"docusaurus build\",\n    \"swizzle\": \"docusaurus swizzle\",\n    \"deploy\": \"docusaurus deploy\",\n    \"clear\": \"docusaurus clear\",\n    \"serve\": \"docusaurus serve\",\n    \"write-translations\": \"docusaurus write-translations\",\n    \"write-heading-ids\": \"docusaurus write-heading-ids\",\n    \"typecheck\": \"tsc\"\n  },\n  \"dependencies\": {\n    \"@docusaurus/core\": \"^3.9.2\",\n    \"@docusaurus/plugin-sitemap\": \"^3.9.2\",\n    \"@docusaurus/preset-classic\": \"^3.9.2\",\n    \"@docusaurus/theme-live-codeblock\": \"^3.9.2\",\n    \"@docusaurus/theme-mermaid\": \"^3.9.2\",\n    \"@docusaurus/theme-search-algolia\": \"^3.9.2\",\n    \"@mdx-js/react\": \"^3.0.0\",\n    \"classnames\": \"^2.5.1\",\n    \"clsx\": \"^2.0.0\",\n    \"docusaurus-plugin-sass\": \"^0.2.5\",\n    \"prism-react-renderer\": \"^2.3.0\",\n    \"react\": \"^18.0.0\",\n    \"react-dom\": \"^18.0.0\",\n    \"sass\": \"^1.78.0\"\n  },\n  \"devDependencies\": {\n    \"@docusaurus/module-type-aliases\": \"^3.9.2\",\n    \"@docusaurus/tsconfig\": \"^3.9.2\",\n    \"@docusaurus/types\": \"^3.9.2\",\n    \"typescript\": \"~5.5.2\"\n  },\n  \"browserslist\": {\n    \"production\": [\n      \">0.5%\",\n      \"not dead\",\n      \"not op_mini all\"\n    ],\n    \"development\": [\n      \"last 3 chrome version\",\n      \"last 3 firefox version\",\n      \"last 5 safari version\"\n    ]\n  },\n  \"engines\": {\n    \"node\": \">=18.0\"\n  }\n}\n"
  },
  {
    "path": "docs/sidebars.ts",
    "content": "/**\n - create an ordered group of docs\n - render a sidebar for each doc of that group\n - provide next/previous navigation\n\n The sidebars can be generated from the filesystem, or explicitly defined here.\n\n Create as many sidebars as you want.\n */\n\n// @ts-check\n\n/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */\nconst sidebars = {\n  // By default, Docusaurus generates a sidebar from the docs folder structure\n  // tutorialSidebar: [{type: 'autogenerated', dirName: '.'}],\n\n  // But you can create a sidebar manually\n  configSyncSidebar: [\n    {\n      type: \"category\",\n      collapsed: false,\n      label: \"🚀 Getting Started\",\n      items: [\n        \"getting-started/installation\",\n        \"getting-started/motivation\",\n        \"getting-started/cli\",\n        \"getting-started/admin-gui\",\n        \"getting-started/config-types\",\n        \"getting-started/workflow\",\n        \"getting-started/naming-convention\",\n        // \"dev-docs/usage-information\",\n      ],\n    },\n    {\n      type: \"category\",\n      collapsed: false,\n      label: \"⚙️ Configuration\",\n      items: [\n        \"configuration/introduction\",\n        \"configuration/sync-dir\",\n        \"configuration/minify\",\n        \"configuration/import-on-bootstrap\",\n        \"configuration/custom-types\",\n        \"configuration/soft\",\n        \"configuration/excluded-types\",\n        \"configuration/excluded-config\",\n      ],\n    },\n    {\n      type: \"category\",\n      collapsed: false,\n      label: \"📦 API\",\n      items: [\n        \"api/plugin-config-types\",\n      ],\n    },\n    {\n      type: \"category\",\n      collapsed: false,\n      label: \"♻️ Upgrading\",\n      items: [\n        \"upgrading/generic-update\",\n      ],\n    },\n  ],\n};\n\nmodule.exports = sidebars;\n"
  },
  {
    "path": "docs/src/components/ApiCall.js",
    "content": "import React from 'react'\nimport clsx from 'clsx'\n\nexport default function ApiCall({\n  children,\n  noSideBySide = false,\n}) {\n  return (\n    <div\n      className={clsx(\n        'api-call',\n        (noSideBySide && 'api-call--no-side-by-side'),\n      )}\n    >\n      {children}\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/components/Badge.js",
    "content": "import React from 'react';\nimport clsx from 'clsx';\n\nexport default function Badge({\n  children,\n  className,\n  link = '',\n  noLink = false,\n  variant = '',\n  ...rest\n}) {\n  const variantNormalized = variant.toLowerCase().replace(/\\W/g, '');\n\n  return (\n    <span\n      className={clsx(\n        'badge',\n        'badge--feature',\n        (variantNormalized && `badge--${variantNormalized.toLowerCase()}`),\n      )}\n      {...rest}\n    >\n      {(noLink || !link) ? (\n        <>\n          {variant}\n        </>\n      ) : (\n        <a className=\"badge__link\" href={link}>\n          {variant}\n        </a>\n      )}\n      {children}\n    </span>\n  );\n}\n\nexport function AlphaBadge(props) {\n  return (\n    <Badge\n      variant=\"Alpha\"\n      {...props}\n    />\n  );\n}\n\nexport function BetaBadge(props) {\n  return (\n    <Badge\n      variant=\"Beta\"\n      {...props}\n    />\n  );\n}\n\nexport function FutureBadge(props) {\n  return (\n    <Badge\n      variant=\"Future\"\n      link=\"/dev-docs/configurations/features\"\n      {...props}\n    />\n  );\n}\n\nexport function EnterpriseBadge(props) {\n  return (\n    <Badge\n      variant=\"Enterprise\"\n      link=\"https://strapi.io/pricing-self-hosted\"\n      {...props}\n    />\n  );\n}\n\nexport function CloudProBadge(props) {\n  return (\n    <Badge\n      variant=\"Strapi Cloud Pro\"\n      link=\"https://strapi.io/pricing-cloud\"\n      {...props}\n    />\n  );\n}\n\nexport function CloudTeamBadge(props) {\n  return (\n    <Badge\n      variant=\"Strapi Cloud Team\"\n      link=\"https://strapi.io/pricing-cloud\"\n      {...props}\n    />\n  );\n}\nexport function CloudDevBadge(props) {\n  return (\n    <Badge\n      variant=\"Strapi Cloud Dev\"\n      link=\"https://strapi.io/pricing-cloud\"\n      {...props}\n    />\n  );\n}\n\nexport function NewBadge(props) {\n  return (\n    <Badge\n      variant=\"New ✨\"\n      {...props}\n    />\n  );\n}\n\n\nexport function UpdatedBadge(props) {\n  return (\n    <Badge\n      variant=\"Updated ️🖌\"\n      {...props}\n    />\n  );\n}\n"
  },
  {
    "path": "docs/src/components/Button/Button.jsx",
    "content": "import clsx from 'clsx';\nimport React from 'react';\nimport Link from '@docusaurus/Link';\nimport styles from './button.module.scss';\n\nexport function Button({\n  href,\n  to,\n  children,\n  className,\n  decorative,\n  size = '',\n  variant = 'primary',\n  ...rest\n}) {\n  const ButtonElement = (to ? Link : (href ? 'a' : 'button'));\n\n  return (\n    <ButtonElement\n      {...rest}\n      {...(!href ? {} : { href, target: '_blank' })}\n      {...(!to ? {} : { to })}\n      className={clsx(\n        'button',\n        (variant && styles[`button--${variant}`]),\n        (size && styles[`button--${size}`]),\n        styles.button,\n        styles[variant],\n        className,\n      )}\n    >\n      {children}\n      {decorative && (\n        <span className={styles.button__decorative}>\n          {decorative}\n        </span>\n      )}\n    </ButtonElement>\n  );\n}\n"
  },
  {
    "path": "docs/src/components/Button/button.module.scss",
    "content": "/** Component: Button */\n\n@import '../../scss/_mixins.scss';\n\n.button {\n  --strapi-button-background-color: var(--strapi-primary-600);\n  --strapi-button-border-color: var(--strapi-primary-600);\n  --strapi-button-border-radius: 4px;\n  --strapi-button-box-shadow: 0 0 0 transparent;\n  --strapi-button-color: #fff;\n  --strapi-button-font-size: 12px;\n  --strapi-button-font-weight: 600;\n  --strapi-button-line-height: 16px;\n  --strapi-button-position: relative;\n  --strapi-button-py: 7px;\n  --strapi-button-px: 15px;\n  --strapi-button-transition-property: color, background, border-color, box-shadow;\n\n  --strapi-button-hover-background-color: var(--strapi-primary-700);\n  --strapi-button-hover-border-color: var(--strapi-primary-700);\n  --strapi-button-hover-box-shadow: 0px 9px 10px rgba(44, 56, 148, 0.2475);\n  --strapi-button-hover-color: #fff;\n\n  --ifm-button-color: var(--strapi-button-color);\n  --ifm-button-background-color: var(--strapi-button-background-color);\n  --ifm-button-border-color: var(--strapi-button-border-color);\n  --ifm-button-border-radius: var(--strapi-button-border-radius);\n  --ifm-button-font-weight: var(--strapi-button-font-weight);\n  --ifm-button-padding-horizontal: var(--strapi-button-px);\n  --ifm-button-padding-vertical: var(--strapi-button-py);\n  --ifm-button-size-multiplier: 1;\n\n  --ifm-color-primary-darker: var(--strapi-primary-200);\n\n  --ifm-link-hover-color: var(--strapi-button-color);\n  --ifm-link-hover-decoration: none;\n\n  position: var(--strapi-button-position);\n  font-size: var(--strapi-button-font-size);\n  line-height: var(--strapi-button-line-height);\n  box-shadow: var(--strapi-button-box-shadow);\n  transition-property: var(--strapi-button-transition-property);\n\n  &__decorative {\n    position: absolute;\n    font-size: 32px;\n    line-height: 32px;\n    bottom: -16px;\n    right: -8px;\n  }\n\n  &:not(:disabled),\n  &:not([aria-disabled=\"true\"]) {\n    &:focus, &:hover {\n      --strapi-button-box-shadow: var(--strapi-button-hover-box-shadow);\n      --strapi-button-background-color: var(--strapi-button-hover-background-color);\n      --strapi-button-border-color: var(--strapi-button-hover-border-color);\n      --strapi-button-color: var(--strapi-button-hover-color);\n    }\n  }\n\n  /** Sizes */\n  &--huge {\n    --strapi-button-border-radius: 6px;\n    --strapi-button-font-size: 15px;\n    --strapi-button-line-height: 23px;\n    --strapi-button-py: 11px;\n    --strapi-button-px: 71px;\n  }\n\n  /** Variants */\n  &--secondary {\n    --strapi-button-background-color: #f0f0ff;\n    --strapi-button-border-color: #d9d8ff;\n    --strapi-button-color: var(--strapi-primary-600);\n\n    --strapi-button-hover-background-color: var(--strapi-neutral-0);\n    --strapi-button-hover-border-color: #d9d8ff;\n    --strapi-button-hover-box-shadow: none;\n    --strapi-button-hover-color: var(--strapi-primary-600);\n  }\n}\n\n/** Dark mode */\n@include dark {\n  .button {\n    /** Dark mode Variants */\n    &--secondary {\n      --strapi-button-background-color: var(--strapi-neutral-100);\n      --strapi-button-border-color: var(--strapi-neutral-200);\n\n      --strapi-button-hover-background-color: var(--strapi-neutral-0);\n      --strapi-button-hover-border-color: var(--strapi-neutral-200);\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/components/Card/Card.jsx",
    "content": "import React from 'react';\nimport clsx from 'clsx';\nimport Link from '@docusaurus/Link';\nimport styles from './card.module.scss';\nimport IconArrow from '@site/static/img/assets/icons/arrow-right.svg';\n\nexport function CardTitle({\n  as,\n  children,\n  className,\n  withArrow,\n  ...rest\n}) {\n  const TitleElement = (as || 'h3');\n\n  return (\n    <TitleElement\n      className={clsx(\n        styles.card__title,\n        className,\n      )}\n      {...rest}\n    >\n      {children}\n      {withArrow && (\n        <span className={styles.card__title__arrow}>\n          <IconArrow />\n        </span>\n      )}\n    </TitleElement>\n  );\n}\n\nexport function CardDescription({\n  as,\n  className,\n  ...rest\n}) {\n  const DescriptionElement = (as || 'div');\n\n  return (\n    <DescriptionElement\n      className={clsx(\n        styles.card__description,\n        className,\n      )}\n      {...rest}\n    />\n  );\n}\n\nexport function CardImgBg({\n  className,\n  ...rest\n}) {\n  return (\n    <img\n      className={clsx(\n        styles['card__img-bg'],\n        className,\n      )}\n      {...rest}\n    />\n  );\n}\n\nexport function CardImg({\n  className,\n  ...rest\n}) {\n  return (\n    <img\n      className={clsx(\n        styles['card__img'],\n        className,\n      )}\n      {...rest}\n    />\n  );\n}\n\nexport function Card({\n  className,\n  href,\n  isContentDelimited,\n  to,\n  variant,\n  ...rest\n}) {\n  const asCallToAction = !!(href || to);\n  const CardElement = (to ? Link : (href ? 'a' : 'div'));\n\n  return (\n    <CardElement\n      {...(!href ? {} : { href, target: '_blank' })}\n      {...(!to ? {} : { to })}\n      className={clsx(\n        styles.card,\n        (asCallToAction && styles['card--cta']),\n        (isContentDelimited && styles['card--content-delimited']),\n        (variant && styles[`card--${variant}`]),\n        className,\n      )}\n      {...rest}\n    />\n  );\n}\n"
  },
  {
    "path": "docs/src/components/Card/card.module.scss",
    "content": "/** Component: Card */\n\n@import '../../scss/_mixins.scss';\n\n:root {\n  --strapi-card-background: var(--strapi-neutral-0);\n  --strapi-card-border-color: #EDEDFF;\n  --strapi-card-border-radius: 10px;\n  --strapi-card-box-shadow: 0 0 0 transparent;\n  --strapi-card-content-delimited: 395px;\n  --strapi-card-img-border-width: 5px;\n  --strapi-card-img-border-radius: 5px 5px 0 0;\n  --strapi-card-img-bg-scale: 1;\n  --strapi-card-justify-content: center;\n  --strapi-card-position: relative;\n  --strapi-card-overflow: hidden;\n  --strapi-card-text-align: center;\n  --strapi-card-gap: var(--strapi-spacing-2);\n  --strapi-card-px: var(--strapi-spacing-6);\n  --strapi-card-py: var(--strapi-spacing-6);\n  --strapi-card-title-arrow-left: var(--strapi-card-gap);\n  --strapi-card-title-color: #1D1B84;\n  --strapi-card-title-font-size: 17px;\n  --strapi-card-title-font-weight: 700;\n  --strapi-card-title-line-height: 26px;\n  --strapi-card-description-color: #4E6294;\n  --strapi-card-description-font-size: 15px;\n  --strapi-card-description-line-height: 24px;\n  --strapi-card-hover-border-color: #D6D6FF;\n  --strapi-card-hover-img-bg-scale: 1.15;\n}\n\n.card {\n  position: var(--strapi-card-position);\n  overflow: var(--strapi-card-overflow);\n  background: var(--strapi-card-background);\n  border-radius: var(--strapi-card-border-radius);\n  border: 1px solid var(--strapi-card-border-color);\n  box-shadow: var(--strapi-card-box-shadow);\n  display: flex;\n  flex-direction: column;\n  gap: var(--strapi-card-gap);\n  align-items: stretch;\n  justify-content: var(--strapi-card-justify-content);\n  text-align: var(--strapi-card-text-align);\n  padding: var(--strapi-card-py) var(--strapi-card-px);\n  transition: all 0.2s ease;\n\n  &:focus, &:hover {\n    --strapi-card-border-color: var(--strapi-card-hover-border-color);\n    --strapi-card-title-arrow-left: var(--strapi-spacing-3);\n    --strapi-card-img-bg-scale: var(--strapi-card-hover-img-bg-scale);\n  }\n\n  &__title {\n    display: block;\n    color: var(--strapi-card-title-color);\n    font-size: var(--strapi-card-title-font-size);\n    font-weight: var(--strapi-card-title-font-weight);\n    line-height: var(--strapi-card-title-line-height);\n    margin: 0;\n\n    &:after {\n      content: none;\n    }\n\n    &__arrow {\n      display: inline-block;\n      line-height: 0;\n      margin-left: var(--strapi-card-title-arrow-left);\n      transition: margin-left 0.1s ease;\n    }\n  }\n\n  &__description {\n    --ifm-link-color: var(--strapi-card-description-color);\n    --ifm-link-decoration: underline;\n\n    color: var(--strapi-card-description-color);\n    opacity: 0.8;\n    font-size: var(--strapi-card-description-font-size);\n    line-height: var(--strapi-card-description-line-height);\n  }\n\n  &__img {\n    border-bottom: none;\n    box-shadow: 0 1px 10px 0 #7A78B61A;\n  }\n\n  &--cta {\n    --ifm-link-color: currentColor;\n    --strapi-card-background:\n      linear-gradient(\n        310deg,\n        rgba(168, 166, 255, 0.15) 1.16%,\n        rgba(226, 225, 255, 0.15) 69.23%\n      ),\n      #FFFFFF\n    ;\n    --strapi-card-text-align: left;\n    --strapi-card-gap: var(--strapi-spacing-2);\n    --strapi-card-title-font-size: 21px;\n    --strapi-card-title-font-weight: 600;\n    --strapi-card-title-line-height: 28px;\n    --ifm-link-decoration: none;\n    --ifm-link-hover-decoration: none;\n  }\n\n  &--content-delimited {\n    .card {\n      &__title,\n      &__description {\n        width: 100%;\n        max-width: var(--strapi-card-content-delimited);\n        margin-right: auto;\n        margin-left: auto;\n      }\n    }\n  }\n}\n\n/** Responsive */\n@include medium-up {\n  :root {\n    --strapi-card-px: var(--strapi-spacing-8);\n    --strapi-card-py: var(--strapi-spacing-9);\n  }\n\n  .card {\n    &__title {\n      &__arrow {\n        transition: margin-left 0.2s ease;\n        will-change: margin-left;\n      }\n    }\n\n    &:focus, &:hover {\n      &.card--cta {\n        --strapi-card-border-color: #D6D6FF;\n        --strapi-card-box-shadow: 0px 1px 4px rgba(33, 33, 52, 0.1);\n      }\n    }\n\n    &--cta {\n      transition: all 0.2s ease;\n      will-change: border-color, box-shadow, color;\n\n      .card {\n        &__img {\n          transition: all 0.2s ease;\n          will-change: border-radius, transform;\n          transform:\n            scale(var(--strapi-card-img-scale, 1))\n            translate(var(--strapi-card-img-translate, '0, 0'))\n          ;\n        }\n      }\n    }\n  }\n}\n\n/** Dark mode */\n@include dark {\n  --strapi-card-border-color: var(--strapi-neutral-150);\n  --strapi-card-title-color: var(--strapi-netral-1000);\n  --strapi-card-description-color: var(--strapi-netral-1000);\n  --strapi-card-img-border-color: rgba(255, 255, 255, 0.5);\n  --strapi-card-hover-border-color: #49494D;\n\n  .card {\n    &--cta {\n      --strapi-card-background: var(--strapi-neutral-0);\n\n      &:focus, &:hover {\n        --strapi-card-border-color: #49494D;\n        --strapi-card-color: var(--strapi-neutral-1000);\n\n        --ifm-link-hover-color: var(--strapi-neutral-1000);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/components/Container/Container.jsx",
    "content": "import React from 'react';\nimport clsx from 'clsx';\nimport styles from './container.module.scss';\n\nexport function Container({ className, ...rest }) {\n  return (\n    <div\n      className={clsx(\n        styles.container,\n        className,\n      )}\n      {...rest}\n    />\n  );\n}\n"
  },
  {
    "path": "docs/src/components/Container/container.module.scss",
    "content": "/** Component: Container */\n\n:root {\n  --strapi-container-px: var(--ifm-spacing-horizontal);\n  --strapi-container-mw: calc(863px + calc(var(--strapi-container-px) * 2));\n}\n\n.container {\n  display: flex;\n  flex-direction: column;\n  align-items: stretch;\n  margin-right: auto;\n  margin-left: auto;\n  padding-right: var(--strapi-container-px);\n  padding-left: var(--strapi-container-px);\n  max-width: var(--strapi-container-mw);\n  width: 100%;\n}\n"
  },
  {
    "path": "docs/src/components/CustomDocCard.js",
    "content": "import React from 'react'\nimport classNames from 'classnames';\n\nexport default function CustomDocCard(props) {\n  const { title, description, link, emoji, small = false } = props;\n  const linkClasses = classNames({\n    card: true,\n    cardContainer: true,\n    'padding--lg': !small,\n    'padding--md': small,\n  });\n  const cardClasses = classNames({\n    'custom-doc-card': true,\n    'margin-bottom--lg': !small,\n    'margin-bottom--sm': small,\n    'custom-doc-card--small': small,\n  });\n  return (\n      <article className={ cardClasses }>\n        <a className={ linkClasses }\n          href={ link }\n        >\n          <h2 className=\"text--truncate cardTitle\" title={title}>\n            {emoji ? emoji : '📄️'} {title}\n          </h2>\n          <p className=\"text--truncate cardDescription\" title={ description }>\n            {description}\n          </p>\n        </a>\n      </article>\n  );\n}\n"
  },
  {
    "path": "docs/src/components/CustomDocCardsWrapper.js",
    "content": "import React from 'react';\n\nexport default function CustomDocCardsWrapper({ children }) {\n  return (\n    <div className=\"custom-cards-wrapper\">\n      {children}\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/components/FeaturesList/FeaturesList.jsx",
    "content": "import clsx from 'clsx';\nimport React from 'react';\nimport styles from './features-list.module.scss';\nimport { LinkWithArrow } from '../LinkWithArrow/LinkWithArrow';\n\nexport function FeatureListItem({\n  children,\n  className,\n  href,\n  icon: Icon,\n  iconColor,\n  label,\n  to,\n  ...rest\n}) {\n  const ContentElement = ((href || to) ? LinkWithArrow : 'span');\n  const IconElement = ((href || to) ? 'a' : 'span');\n\n  return (\n    <li\n      className={clsx(\n        styles['features-list__item'],\n        className,\n      )}\n    >\n      {Icon && (\n        <IconElement\n          className={clsx(\n            styles['features-list__item__icon'],\n            (iconColor && styles[`features-list__item__icon--${iconColor}`]),\n          )}\n          href={href}\n          to={to}\n          {...(IconElement === 'a' ? { href: to || href } : {})}\n        >\n          <Icon />\n        </IconElement>\n      )}\n      <ContentElement\n        className={styles['features-list__item__content']}\n        href={href}\n        to={to}\n        {...rest}\n      >\n        {children || label}\n      </ContentElement>\n    </li>\n  );\n}\n\nexport function FeaturesList({\n  className,\n  id,\n  icon,\n  iconColor,\n  items,\n  ...rest\n}) {\n  const defaultId = `featureListItem${Math.random()}`;\n\n  return (\n    <ul\n      className={clsx(\n        styles['features-list'],\n        className,\n      )}\n      {...rest}\n    >\n      {items?.map((featureListItem, featureListItemIndex) => {\n        return (\n          <FeatureListItem\n            key={`${id || defaultId}${featureListItemIndex}`}\n            icon={icon}\n            iconColor={iconColor}\n            {...featureListItem}\n          />\n        );\n      })}\n    </ul>\n  );\n}\n"
  },
  {
    "path": "docs/src/components/FeaturesList/features-list.module.scss",
    "content": "/** Component: Features List */\n\n@import '../../scss/_mixins.scss';\n\n:root {\n  --strapi-features-list-gap: var(--strapi-spacing-2);\n  --strapi-features-list-margin: 0;\n  --strapi-features-list-py: var(--ifm-spacing-horizontal);\n  --strapi-features-list-px: 0;\n\n  --strapi-features-list-item-inner-gap: 8px;\n  --strapi-features-list-item-icon-background-color: var(--strapi-secondary-100);\n  --strapi-features-list-item-icon-border-color: #D4EDFF;\n  --strapi-features-list-item-icon-color: var(--strapi-secondary-500);\n  --strapi-features-list-item-icon-border-radius: 7px;\n  --strapi-features-list-item-icon-area: 32px;\n  --strapi-features-list-item-icon-size: 16px;\n}\n\n.features-list {\n  margin: var(--strapi-features-list-margin);\n  padding: var(--strapi-features-list-py) var(--strapi-features-list-px);\n  list-style: none;\n  display: flex;\n  flex-direction: column;\n  align-items: stretch;\n  justify-content: flex-start;\n  gap: var(--strapi-features-list-gap);\n\n  &__item {\n    display: flex;\n    align-items: center;\n    gap: var(--strapi-features-list-item-inner-gap);\n\n    &__icon {\n      background-color: var(--strapi-features-list-item-icon-background-color);\n      border: 1px solid var(--strapi-features-list-item-icon-border-color);\n      border-radius: var(--strapi-features-list-item-icon-border-radius);\n      color: var(--strapi-features-list-item-icon-color);\n      display: inline-flex;\n      align-items: center;\n      justify-content: center;\n      height: var(--strapi-features-list-item-icon-area);\n      width: var(--strapi-features-list-item-icon-area);\n      transition: all 0.2s ease;\n\n      &--green {\n        --strapi-features-list-item-icon-background-color: var(--strapi-success-100);\n        --strapi-features-list-item-icon-border-color: #DAF0D8;\n        --strapi-features-list-item-icon-color: var(--strapi-success-500);\n      }\n\n      svg {\n        height: var(--strapi-features-list-item-icon-size);\n        width: var(--strapi-features-list-item-icon-size);\n      }\n    }\n  }\n}\n\n/** Responsive */\n@include medium-up {\n  :root {\n    --strapi-features-list-py: calc(var(--ifm-spacing-horizontal) * 2);\n    --strapi-features-list-px: var(--ifm-spacing-horizontal);\n\n    --strapi-features-list-item-inner-gap: 20px;\n    --strapi-features-list-item-icon-area: 40px;\n  }\n}\n\n/** Dark mode */\n@include dark {\n  --strapi-features-list-item-icon-background-color: var(--strapi-secondary-500);\n  --strapi-features-list-item-icon-border-color: var(--strapi-secondary-600);\n\n  .features-list {\n    &__item {\n      &__icon {\n        --strapi-features-list-item-icon-color: #fff;\n\n        &--green {\n          --strapi-features-list-item-icon-background-color: var(--strapi-success-500);\n          --strapi-features-list-item-icon-border-color: var(--strapi-success-600);\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/components/Hero/Hero.jsx",
    "content": "import clsx from 'clsx';\nimport React from 'react';\nimport styles from './hero.module.scss';\n\nexport function HeroTitle({\n  className,\n  ...rest\n}) {\n  return (\n    <h1\n      className={clsx(\n        styles.hero__title,\n        className,\n      )}\n      {...rest}\n    />\n  );\n}\n\nexport function HeroDescription({\n  className,\n  ...rest\n}) {\n  return (\n    <div\n      className={clsx(\n        styles.hero__description,\n        className,\n      )}\n      {...rest}\n    />\n  );\n}\n\nexport function Hero({\n  className,\n  ...rest\n}) {\n  return (\n    <header\n      className={clsx(\n        styles.hero,\n        className,\n      )}\n      {...rest}\n    />\n  );\n}\n"
  },
  {
    "path": "docs/src/components/Hero/hero.module.scss",
    "content": "/** Component: Hero */\n\n@import '../../scss/_mixins.scss';\n\n:root {\n  --strapi-hero-py: var(--strapi-spacing-6);\n  --strapi-hero-gap: var(--strapi-spacing-4);\n\n  --strapi-hero-title-color: #1D1B84;\n  --strapi-hero-title-font-size: var(--strapi-font-size-xl);\n  --strapi-hero-title-line-height: 1.25;\n\n  --strapi-hero-description-color: #4E6294;\n  --strapi-hero-description-font-size: var(--strapi-font-size-lg);\n  --strapi-hero-description-line-height: 1.25;\n}\n\n.hero {\n  padding-top: var(--strapi-hero-py);\n  padding-bottom: var(--strapi-hero-py);\n  text-align: center;\n\n  &__title {\n    color: var(--strapi-hero-title-color);\n    font-weight: 700;\n    font-size: var(--strapi-hero-title-font-size);\n    line-height: var(--strapi-hero-title-line-height);\n    letter-spacing: 0.4px;\n    margin: 0 0 var(--strapi-hero-gap);\n  }\n\n  &__description {\n    color: var(--strapi-hero-description-color);\n    font-size: var(--strapi-hero-description-font-size);\n    line-height: var(--strapi-hero-description-line-height);\n    margin: 0;\n  }\n}\n\n/** Responsive */\n@include medium-up {\n  :root {\n    --strapi-hero-py: 40px;\n    --strapi-hero-title-font-size: 43px;\n    --strapi-hero-title-line-height: 56px;\n    --strapi-hero-description-font-size: 21px;\n    --strapi-hero-description-line-height: 28px;\n  }\n}\n\n/** Dark mode */\n@include dark {\n  --strapi-hero-title-color: white;\n  --strapi-hero-description-color: white;\n}\n"
  },
  {
    "path": "docs/src/components/HomepageFeatures/index.tsx",
    "content": "import clsx from 'clsx';\nimport Heading from '@theme/Heading';\nimport styles from './styles.module.css';\n\ntype FeatureItem = {\n  title: string;\n  Svg: React.ComponentType<React.ComponentProps<'svg'>>;\n  description: JSX.Element;\n};\n\nconst FeatureList: FeatureItem[] = [\n  {\n    title: 'Easy to Use',\n    Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default,\n    description: (\n      <>\n        Docusaurus was designed from the ground up to be easily installed and\n        used to get your website up and running quickly.\n      </>\n    ),\n  },\n  {\n    title: 'Focus on What Matters',\n    Svg: require('@site/static/img/undraw_docusaurus_tree.svg').default,\n    description: (\n      <>\n        Docusaurus lets you focus on your docs, and we&apos;ll do the chores. Go\n        ahead and move your docs into the <code>docs</code> directory.\n      </>\n    ),\n  },\n  {\n    title: 'Powered by React',\n    Svg: require('@site/static/img/undraw_docusaurus_react.svg').default,\n    description: (\n      <>\n        Extend or customize your website layout by reusing React. Docusaurus can\n        be extended while reusing the same header and footer.\n      </>\n    ),\n  },\n];\n\nfunction Feature({title, Svg, description}: FeatureItem) {\n  return (\n    <div className={clsx('col col--4')}>\n      <div className=\"text--center\">\n        <Svg className={styles.featureSvg} role=\"img\" />\n      </div>\n      <div className=\"text--center padding-horiz--md\">\n        <Heading as=\"h3\">{title}</Heading>\n        <p>{description}</p>\n      </div>\n    </div>\n  );\n}\n\nexport default function HomepageFeatures(): JSX.Element {\n  return (\n    <section className={styles.features}>\n      <div className=\"container\">\n        <div className=\"row\">\n          {FeatureList.map((props, idx) => (\n            <Feature key={idx} {...props} />\n          ))}\n        </div>\n      </div>\n    </section>\n  );\n}\n"
  },
  {
    "path": "docs/src/components/HomepageFeatures/styles.module.css",
    "content": ".features {\n  display: flex;\n  align-items: center;\n  padding: 2rem 0;\n  width: 100%;\n}\n\n.featureSvg {\n  height: 200px;\n  width: 200px;\n}\n"
  },
  {
    "path": "docs/src/components/LinkWithArrow/LinkWithArrow.jsx",
    "content": "import clsx from 'clsx';\nimport React from 'react';\nimport Link from '@docusaurus/Link';\nimport IconArrow from '@site/static/img/assets/icons/arrow-right.svg';\nimport styles from './link-with-arrow.module.scss';\n\nexport function LinkWithArrow({\n  apart = false,\n  children,\n  className,\n  href,\n  to,\n  ...rest\n}) {\n  const LinkElement = (to ? Link : 'a');\n\n  return (\n    <LinkElement\n      className={clsx(\n        styles.lwa,\n        (apart && styles['lwa--apart']),\n        className,\n      )}\n      {...(href && { href, target: '_blank' })}\n      {...(to && { to })}\n      {...rest}\n    >\n      <span className={styles.lwa__content}>\n        {children}\n      </span>\n      <IconArrow\n        className={styles.lwa__icon}\n      />\n    </LinkElement>\n  )\n}\n"
  },
  {
    "path": "docs/src/components/LinkWithArrow/link-with-arrow.module.scss",
    "content": "/** Component: Link with Arrow */\n\n@import '../../scss/_mixins.scss';\n\n:root {\n  --strapi-lwa-icon-ml: var(--strapi-spacing-2);\n  --strapi-lwa-font-size: 14px;\n  --strapi-lwa-font-weight: 500;\n  --strapi-lwa-line-height: 20px;\n  --strapi-lwa-hover-icon-ml: var(--strapi-spacing-3);\n}\n\n.lwa {\n  --ifm-link-color: #1D1B84;\n  --ifm-link-decoration: none;\n  --ifm-link-hover-color: #2825B8;\n  --ifm-link-hover-decoration: none;\n\n  font-size: var(--strapi-lwa-font-size);\n  font-weight: var(--strapi-lwa-font-weight);\n  line-height: var(--strapi-lwa-line-height);\n  transition: all 0.2s ease;\n\n  &:focus, &:hover {\n    --strapi-lwa-icon-ml: var(--strapi-lwa-hover-icon-ml);\n  }\n\n  &__icon {\n    display: inline-block;\n    line-height: 0;\n    margin-left: var(--strapi-lwa-icon-ml);\n    transition: margin-left 0.1s ease;\n  }\n\n  &--apart {\n    display: flex;\n    align-items: center;\n\n    .lwa {\n      &__content {\n        flex-grow: 1;\n      }\n    }\n  }\n}\n\n/** Dark mode */\n@include dark {\n  .lwa {\n    --ifm-link-color: var(--strapi-neutral-1000);\n    --ifm-link-hover-color: var(--strapi-neutral-500);\n  }\n}\n"
  },
  {
    "path": "docs/src/components/Request.js",
    "content": "import React from 'react'\n\nexport default function Request({\n  children,\n  title = 'Example request',\n}) {\n  return (\n    <div className=\"api-call__request\">\n      <div className=\"api-call__request__heading\">{title}</div>\n      <div className=\"api-call__request__content\">{children}</div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/components/Response.js",
    "content": "import React from 'react'\n\nexport default function Response({\n  children,\n  title = 'Example response',\n}) {\n  return (\n    <div className=\"api-call__response\">\n      <div className=\"api-call__response__heading\">{title}</div>\n      <div className=\"api-call__response__content\">{children}</div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/components/SubtleCallout.js",
    "content": "import React from 'react';\n\nexport default function SubtleCallout({\n  children,\n  title,\n  emoji = '🤓',\n}) {\n  return (\n    <div className=\"theme-admonition theme-admonition--callout theme-admonition--subtle\">\n      <div className=\"theme-admonition__heading\">\n        <span style={{fontWeight: 300}} className=\"theme-admonition__heading__icon\">{ emoji } </span>{ title }\n      </div>\n      { children }\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/components/index.js",
    "content": "export * from './Button/Button';\nexport * from './Card/Card';\nexport * from './Container/Container';\nexport * from './HomepageFeatures';\nexport * from './FeaturesList/FeaturesList';\nexport * from './Hero/Hero';\nexport * from './LinkWithArrow/LinkWithArrow';\n"
  },
  {
    "path": "docs/src/scss/__index.scss",
    "content": "/**\n * Any CSS included here will be global.\n * The classic template bundles Infima by default.\n * Infima is a CSS framework designed to work well for content-centric websites.\n */\n\n/** Core */\n@import '_mixins.scss';\n@import '_tokens.scss';\n@import '_tokens-overrides.scss';\n\n/** Base */\n@import '_fonts.scss';\n@import '_base.scss';\n\n/** Components */\n@import 'admonition.scss';\n@import 'api-call.scss';\n@import 'badge.scss';\n@import 'breadcrumbs.scss';\n@import 'card.scss';\n@import 'columns.scss';\n@import 'container.scss';\n@import 'custom-doc-cards.scss';\n@import 'details.scss';\n@import 'footer.scss';\n@import 'grid.scss';\n@import 'images.scss';\n@import 'markdown.scss';\n@import 'medium-zoom.scss';\n@import 'navbar.scss';\n@import 'pagination-nav.scss';\n@import 'search.scss';\n@import 'scene.scss';\n@import 'sidebar.scss';\n@import 'table.scss';\n@import 'tabs.scss';\n@import 'table-of-contents.scss';\n@import 'typography.scss';\n"
  },
  {
    "path": "docs/src/scss/_base.scss",
    "content": "/** Base: General Styles */\n\n:root {\n  --custom-selection-background-color: var(--strapi-primary-600);\n}\n\n::selection {\n  background-color:  var(--custom-selection-background-color);\n  color: var(--custom-selection-color, var(--strapi-neutral-0));\n}\n\nmain {\n  article:first-child:not(.col):not(.custom-doc-card),\n  article:first-child:not(.col) + nav {\n    --custom-main-px: var(--strapi-spacing-0);\n    --custom-main-width: 683px;\n\n    max-width: calc(var(--custom-main-width) + calc(var(--strapi-spacing-4) * 2));\n    padding-left: var(--custom-main-px) !important;\n    padding-right: var(--custom-main-px) !important;\n    margin-left: auto;\n    margin-right: auto;\n  }\n}\n\n/** Responsive */\n@include medium-up {\n  main {\n    article:first-child:not(.col),\n    article:first-child:not(.col) + nav {\n      --custom-main-px: var(--strapi-spacing-4);\n    }\n  }\n}\n\n/** Dark mode */\n@include dark {\n  .container img[width=\"16\"] {\n    /* 'Temporary' fix while we figure a way to display white icons in dark mode 😅  */\n    background-color: white;\n    border-radius: 2px;\n    padding: 1px;\n  }\n}\n"
  },
  {
    "path": "docs/src/scss/_fonts.scss",
    "content": "/** Fonts */\n\n@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;700&display=swap');\n\n.font-poppins,\n.font-poppins button,\n.font-poppins input {\n  --custom-font-family: \"Poppins\", sans-serif;\n  --ifm-heading-font-family: \"Poppins\", sans-serif;\n\n  font-family: var(--custom-font-family);\n}\n"
  },
  {
    "path": "docs/src/scss/_mixins.scss",
    "content": "/** Core: Sass Mixins */\n\n/** Mixin: Responsive */\n@mixin small-up {\n  @media (min-width: 769px) {\n    @content;\n  }\n}\n\n/** Mixin: Responsive */\n@mixin medium-up {\n  @media (min-width: 997px) {\n    @content;\n  }\n}\n\n/** Mixin: Responsive */\n@mixin large-up {\n  @media (min-width: 1536px) {\n    @content;\n  }\n}\n\n/** Mixin: Dark mode */\n@mixin dark {\n  html[data-theme='dark'] {\n    @content;\n  }\n}\n\n/** Mixin: Light mode */\n@mixin light {\n  html[data-theme='light'] {\n    @content;\n  }\n}\n\n/** Mixin: Transition */\n@mixin transition {\n  transition: all 0.2s ease;\n}\n\n/** Mixin: Flex */\n@mixin flex-row(\n  $gap: var(--strapi-spacing-2),\n  $align-items: center,\n  $justify-content: null\n) {\n  display: flex;\n  flex-direction: row;\n  gap: $gap;\n  align-items: $align-items;\n  justify-content: $justify-content;\n}\n"
  },
  {
    "path": "docs/src/scss/_tokens-overrides.scss",
    "content": "/** Core: Docusaurus/Infima Style Tokens Overrides */\n\n:root {\n  --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);\n\n  --ifm-background-color: var(--strapi-neutral-0);\n  --ifm-color-content: var(--strapi-neutral-800);\n  --ifm-font-color-base: var(--strapi-neutral-800);\n\n  --ifm-color-content-secondary: var(--strapi-neutral-600);\n  --ifm-color-emphasis-100: var(--strapi-neutral-100);\n  --ifm-color-emphasis-200: var(--strapi-neutral-150);\n  --ifm-color-emphasis-300: var(--strapi-neutral-200);\n  --ifm-color-emphasis-400: var(--strapi-neutral-300);\n  --ifm-color-emphasis-500: var(--strapi-neutral-400);\n  --ifm-color-emphasis-600: var(--strapi-neutral-500);\n  --ifm-color-emphasis-700: var(--strapi-neutral-600);\n  --ifm-color-emphasis-800: var(--strapi-neutral-700);\n  --ifm-color-emphasis-900: var(--strapi-neutral-800);\n  --ifm-color-emphasis-1000: var(--strapi-neutral-1000);\n\n  --ifm-color-primary:  var(--strapi-primary-600);\n  --ifm-color-primary-dark: var(--strapi-primary-700);\n  --ifm-color-primary-darker: var(--strapi-primary-800);\n  --ifm-color-primary-darkest: var(--strapi-primary-900);\n  --ifm-color-primary-light: var(--strapi-primary-500);\n  --ifm-color-primary-lighter: var(--strapi-primary-400);\n  --ifm-color-primary-lightest: var(--strapi-primary-300);\n\n  --ifm-link-color: var(--strapi-primary-600);\n  --ifm-link-decoration: none;\n  // --ifm-link-hover-decoration: none;\n\n  --ifm-paragraph-margin-bottom: var(--strapi-spacing-4);\n\n  --ifm-scrollbar-track-background-color: var(--strapi-neutral-100);\n  --ifm-scrollbar-thumb-background-color: var(--strapi-neutral-200);\n  --ifm-scrollbar-thumb-hover-background-color: var(--strapi-neutral-300);\n\n  --ifm-table-stripe-background: #DCDCE43E;\n  --ifm-toc-border-color: var(--strapi-neutral-150);\n}\n\n/* Dark mode */\n@include dark {\n  --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);\n\n  --ifm-background-color: var(--strapi-neutral-0);\n  --ifm-color-content: var(--strapi-neutral-800);\n  --ifm-font-color-base: var(--strapi-neutral-800);\n\n  --ifm-color-emphasis-100: var(--strapi-neutral-100);\n  --ifm-color-emphasis-200: var(--strapi-neutral-150);\n  --ifm-color-emphasis-300: var(--strapi-neutral-200);\n  --ifm-color-emphasis-400: var(--strapi-neutral-300);\n  --ifm-color-emphasis-500: var(--strapi-neutral-400);\n  --ifm-color-emphasis-600: var(--strapi-neutral-500);\n  --ifm-color-emphasis-700: var(--strapi-neutral-600);\n  --ifm-color-emphasis-800: var(--strapi-neutral-700);\n  --ifm-color-emphasis-900: var(--strapi-neutral-800);\n  --ifm-color-emphasis-1000: var(--strapi-neutral-1000);\n\n  --ifm-color-primary:  var(--strapi-primary-600);\n  --ifm-color-primary-dark: var(--strapi-primary-700);\n  --ifm-color-primary-darker: var(--strapi-primary-800);\n  --ifm-color-primary-darkest: var(--strapi-primary-900);\n  --ifm-color-primary-light: var(--strapi-primary-500);\n  --ifm-color-primary-lighter: var(--strapi-primary-400);\n  --ifm-color-primary-lightest: var(--strapi-primary-300);\n\n  --ifm-link-color: var(--strapi-primary-500);\n\n  --ifm-navbar-background-color: #212134;\n\n  --ifm-scrollbar-track-background-color: var(--strapi-neutral-100);\n  --ifm-scrollbar-thumb-background-color: var(--strapi-neutral-150);\n  --ifm-scrollbar-thumb-hover-background-color: var(--strapi-neutral-200);\n\n  --ifm-toc-border-color: var(--strapi-neutral-150);\n}\n"
  },
  {
    "path": "docs/src/scss/_tokens.scss",
    "content": "/** Core: Strapi Design System Tokens */\n\n:root {\n  /** Spacing */\n  --strapi-spacing-0: 0;          // 0px\n  --strapi-spacing-1: 0.25rem;    // 4px\n  --strapi-spacing-2: 0.5rem;     // 8px\n  --strapi-spacing-3: 0.75rem;    // 12px\n  --strapi-spacing-4: 1rem;       // 16px\n  --strapi-spacing-5: 1.25rem;    // 20px\n  --strapi-spacing-6: 1.5rem;     // 24px\n  --strapi-spacing-7: 2rem;       // 32px\n  --strapi-spacing-8: 2.5rem;     // 40px\n  --strapi-spacing-9: 3rem;       // 48px\n  --strapi-spacing-10: 3.5rem;    // 56px\n  --strapi-spacing-11: 3.75rem;   // 64px\n\n  /** Fonts */\n  --strapi-font-size-tiny: 0.625rem;  // 10px\n  --strapi-font-size-xxs: 0.6875rem;  // 11px\n  --strapi-font-size-xs: 0.75rem;     // 12px\n  --strapi-font-size-ssm: 0.8125rem;  // 13px\n  --strapi-font-size-sm: 0.875rem;    // 14px\n  --strapi-font-size-md: 1rem;        // 16px\n  --strapi-font-size-lg: 1.125rem;    // 18px\n  --strapi-font-size-xl: 2rem;        // 32px\n\n  /** Color: Neutral */\n  --strapi-neutral-1000: #181826;\n  --strapi-neutral-900: #212134;\n  --strapi-neutral-800: #32324D;\n  --strapi-neutral-700: #4A4A6A;\n  --strapi-neutral-600: #666687;\n  --strapi-neutral-500: #8E8EA9;\n  --strapi-neutral-400: #A5A5BA;\n  --strapi-neutral-300: #C0C0CF;\n  --strapi-neutral-200: #DCDCE4;\n  --strapi-neutral-150: #EAEAEF;\n  --strapi-neutral-100: #F6F6F9;\n  --strapi-neutral-0: #FFFFFF;\n\n  /** Color: Primary */\n  --strapi-primary-700: #271FE0;\n  --strapi-primary-600: #4945FF;\n  --strapi-primary-500: #7B79FF;\n  --strapi-primary-200: #D9D8FF;\n  --strapi-primary-100: #F0F0FF;\n\n  /** Color: Secondary */\n  --strapi-secondary-700: #006096;\n  --strapi-secondary-600: #0C75AF;\n  --strapi-secondary-500: #66B7F1;\n  --strapi-secondary-200: #B8E1FF;\n  --strapi-secondary-100: #EAF5FF;\n\n  /** Color: Success */\n  --strapi-success-700: #2F6846;\n  --strapi-success-600: #328048;\n  --strapi-success-500: #5CB176;\n  --strapi-success-300: #BBEFB5; /* not in Figma, used for code blocks */\n  --strapi-success-200: #C6F0C2;\n  --strapi-success-100: #EAFBE7;\n\n  /** Color: Danger */\n  --strapi-danger-800: #4B1113; /* not actually existing in Figma */\n  --strapi-danger-700: #B72B1A;\n  --strapi-danger-600: #D02B20;\n  --strapi-danger-500: #EE5E52;\n  --strapi-danger-200: #F5C0B8;\n  --strapi-danger-100: #FCECEA;\n\n  /** Color: Warning */\n  --strapi-warning-700: #BE5D01;\n  --strapi-warning-600: #D9822F;\n  --strapi-warning-500: #F29D41;\n  --strapi-warning-200: #FAE7B9;\n  --strapi-warning-100: #FDF4DC;\n\n  /** Color: Alternative */\n  --strapi-alternative-600: #9736E8;\n  --strapi-alternative-500: #AC73E6;\n  --strapi-alternative-200: #E0C1F4;\n  --strapi-alternative-100: #F6ECFC;\n\n  /** Color: Code */\n  --strapi-code-fluo-green: #50FA7B;\n  --strapi-code-green: #50FA7B;\n  --strapi-code-rose: #FF79C6;\n  --strapi-code-purple: #BD93F9;\n  --strapi-code-dark-blue: #6272A4;\n\n  /** Components */\n  --strapi-input-border-color: var(--strapi-neutral-200);\n  --strapi-input-border-width: 1px;\n  --strapi-input-border-style: solid;\n  --strapi-input-border:\n    var(--strapi-input-border-width)\n    var(--strapi-input-border-style)\n    var(--strapi-input-border-color)\n  ;\n}\n\n/** Dark mode */\n@include dark {\n  /** Dark Color: Neutral */\n  --strapi-neutral-1000: #FFFFFF; /* both 800 and 900 identical in Figma */\n  --strapi-neutral-900: #FFFFFF; /* both 800 and 900 identical in Figma */\n  --strapi-neutral-800: #FFFFFF;\n  --strapi-neutral-700: #EAEAEF;\n  --strapi-neutral-600: #666687;\n  --strapi-neutral-500: #c0c0cf;\n  --strapi-neutral-400: #A5A5BA; /* incorrecly named in Figma */\n  --strapi-neutral-300: #666687;\n  --strapi-neutral-200: #4A4A6A;\n  --strapi-neutral-150: #32324D;\n  --strapi-neutral-100: #181826;\n  --strapi-neutral-0: #212134;\n\n  /** Dark Color: Primary */\n  --strapi-primary-600: #7B79FF;\n}\n"
  },
  {
    "path": "docs/src/scss/admonition.scss",
    "content": "/** Component: Alert */\n\n.theme-admonition {\n  --custom-admonition-background-color: var(--strapi-neutral-100);\n  --custom-admonition-border-color: var(--strapi-neutral-500);\n  --custom-admonition-border-radius: var(--strapi-spacing-0);\n  --custom-admonition-color: var(--strapi-neutral-800);\n  --custom-admonition-heading-color: var(--strapi-neutral-700);\n  --custom-admonition-heading-mb: var(--strapi-spacing-2);\n  --custom-admonition-mb: var(--strapi-spacing-4);\n  --custom-admonition-px: var(--strapi-spacing-4);\n  --custom-admonition-py: var(--strapi-spacing-4);\n\n  --ifm-link-decoration: none;\n  --ifm-link-color: var(--custom-admonition-heading-color);\n  --ifm-link-hover-color: var(--custom-admonition-heading-color);\n\n  background-color: var(--custom-admonition-background-color);\n  border-left: 4px solid var(--custom-admonition-border-color);\n  border-radius: var(--custom-admonition-border-radius);\n  color: var(--custom-admonition-color);\n  margin: 0 0 var(--custom-admonition-mb);\n  padding: var(--custom-admonition-py) var(--custom-admonition-px);\n\n  /** Alert Title element */\n  &__heading {\n    color: var(--custom-admonition-heading-color);\n    font-weight: var(--custom-admonition-heading-font-weight, 600);\n    margin-bottom: var(--custom-admonition-heading-mb);\n  }\n\n  h1, h2, h3, li, p, table {\n    code,\n    a code {\n      --custom-code-border-color: transparent;\n      --custom-code-background-color: var(--custom-admonition-code-background-color);\n      --custom-code-color: var(--custom-admonition-heading-color);\n    }\n  }\n\n  a {\n    font-weight: 700;\n  }\n\n  *:last-child {\n    margin-bottom: 0;\n  }\n\n  &--info,\n  &--callout,\n  &--prerequisites {\n    --custom-admonition-background-color: var(--strapi-neutral-100);\n    --custom-admonition-border-color: var(--strapi-neutral-500);\n    --custom-admonition-code-background-color: var(--strapi-neutral-200);\n    --custom-admonition-code-color: var(--strapi-primary-600);\n    --custom-admonition-heading-color: var(--strapi-neutral-700);\n\n    --custom-selection-background-color: var(--strapi-neutral-700);\n    --custom-selection-color: var(--strapi-neutral-100);\n\n    --ifm-link-color: var(--strapi-primary-600);\n    --ifm-link-hover-color: var(--strapi-primary-600);\n  }\n\n  &--note {\n    --custom-admonition-background-color: var(--strapi-secondary-100);\n    --custom-admonition-border-color: var(--strapi-secondary-500);\n    --custom-admonition-code-background-color: var(--strapi-secondary-200);\n    --custom-admonition-code-color: var(--strapi-secondary-600);\n    --custom-admonition-heading-color: var(--strapi-secondary-700);\n\n    --custom-selection-background-color: var(--strapi-secondary-700);\n    --custom-selection-color: var(--strapi-secondary-100);\n  }\n\n  &--tip,\n  &--success {\n    --custom-admonition-background-color: var(--strapi-success-100);\n    --custom-admonition-border-color: var(--strapi-success-500);\n    --custom-admonition-code-background-color: var(--strapi-success-200);\n    --custom-admonition-code-color: var(--strapi-success-600);\n    --custom-admonition-heading-color: var(--strapi-success-700);\n\n    --custom-selection-background-color: var(--strapi-success-700);\n    --custom-selection-color: var(--strapi-success-100);\n  }\n\n  &--caution {\n    --custom-admonition-background-color: var(--strapi-warning-100);\n    --custom-admonition-border-color: var(--strapi-warning-500);\n    --custom-admonition-code-background-color: var(--strapi-warning-200);\n    --custom-admonition-code-color: var(--strapi-warning-600);\n    --custom-admonition-heading-color: var(--strapi-warning-700);\n\n    --custom-selection-background-color: var(--strapi-warning-700);\n    --custom-selection-color: var(--strapi-warning-100);\n  }\n\n  &--danger,\n  &--warning  {\n    --custom-admonition-background-color: var(--strapi-danger-100);\n    --custom-admonition-border-color: var(--strapi-danger-500);\n    --custom-admonition-code-background-color: var(--strapi-danger-200);\n    --custom-admonition-code-color: var(--strapi-danger-600);\n    --custom-admonition-heading-color: var(--strapi-danger-700);\n\n    --custom-selection-background-color: var(--strapi-danger-700);\n    --custom-selection-color: var(--strapi-danger-100);\n  }\n\n  &--strapi {\n    --custom-admonition-background-color: var(--strapi-primary-100);\n    --custom-admonition-border-color: var(--strapi-primary-500);\n    --custom-admonition-code-background-color: var(--strapi-primary-200);\n    --custom-admonition-code-color: var(--strapi-primary-600);\n    --custom-admonition-heading-color: var(--strapi-primary-700);\n\n    --custom-selection-background-color: var(--strapi-primary-700);\n    --custom-selection-color: var(--strapi-primary-100);\n  }\n\n  &--subtle {\n    border-radius: 8px;\n    border: none;\n    background-color: transparent;\n    border: solid 1px var(--strapi-neutral-500);\n    font-size: 90%;\n  }\n}\n\n@media screen and (min-width: 997px) {\n  [class*=\"sbs-column\"]:nth-of-type(2) .theme-admonition--subtle {\n    margin-left: 80px;\n  }\n}\n\n/** Dark mode */\n@include dark {\n  .theme-admonition {\n    // --custom-admonition-color: var(--strapi-neutral-150);\n    --custom-admonition-background-color: var(--strapi-neutral-100);\n    --custom-admonition-code-background-color: var(--strapi-neutral-200);\n\n    &--info,\n    &--callout,\n    &--prerequisites {\n      --custom-admonition-code-color: var(--strapi-primary-500);\n      --custom-admonition-heading-color: var(--strapi-neutral-500);\n    }\n\n    &--note {\n      --custom-admonition-code-color: var(--strapi-secondary-500);\n      --custom-admonition-heading-color: var(--strapi-secondary-500);\n    }\n\n    &--tip,\n    &--success {\n      --custom-admonition-code-color: var(--strapi-success-500);\n      --custom-admonition-heading-color: var(--strapi-success-500);\n    }\n\n    &--caution,\n    &--warning {\n      --custom-admonition-code-color: var(--strapi-warning-500);\n      --custom-admonition-heading-color: var(--strapi-warning-500);\n    }\n\n    &--danger {\n      --custom-admonition-code-color: var(--strapi-danger-500);\n      --custom-admonition-heading-color: var(--strapi-danger-500);\n    }\n\n    &--strapi {\n      --custom-admonition-code-color: var(--strapi-primary-500);\n      --custom-admonition-heading-color: var(--strapi-primary-500);\n    }\n\n    code,\n    a code {\n      --custom-code-border-color: transparent;\n      --custom-code-background-color: var(--custom-admonition-code-background-color);\n      --custom-code-color: var(--custom-admonition-code-color);\n    }\n\n    .strapi-iqb {\n      --strapi-iqb-background-color: var(--strapi-neutral-0);\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/scss/api-call.scss",
    "content": "/** Component: API Call / Request / Response */\n\n:root {\n  --custom-api-call-gap: var(--strapi-spacing-4);\n  --custom-api-call-heading-font-size: var(--strapi-font-size-sm);\n  --custom-api-call-heading-font-weight: 700;\n  --custom-api-call-heading-py: var(--strapi-spacing-2);\n  --custom-api-call-heading-px: var(--strapi-spacing-4);\n  --custom-api-call-radius: var(--strapi-spacing-2);\n  --custom-api-call-response-heading-background-color: var(--strapi-neutral-200);\n  --custom-api-call-response-content-background-color: var(--strapi-neutral-300);\n  --custom-api-call-request-heading-background-color: var(--strapi-neutral-100);\n  --custom-api-call-request-content-background-color: var(--strapi-neutral-150);\n}\n\n.api-call {\n  display: flex;\n  flex-direction: column;\n  align-items: stretch;\n  gap: var(--custom-api-call-gap);\n  margin-bottom: var(--custom-api-call-gap);\n\n  &__request__content,\n  &__request__heading,\n  &__response__content,\n  &__response__heading {\n    background-color: var(--custom-api-call-background-color);\n    color: var(--custom-api-call-color);\n  }\n\n  &__request,\n  &__response {\n    border-radius: var(--custom-api-call-radius);\n\n    &__heading,\n    &__content {\n      *:last-child {\n        margin-bottom: 0;\n      }\n    }\n\n    &__heading {\n      border-top-left-radius: var(--custom-api-call-radius);\n      border-top-right-radius: var(--custom-api-call-radius);\n      font-size: var(--custom-api-call-heading-font-size);\n      font-weight: var(--custom-api-call-heading-font-weight);\n      padding: var(--custom-api-call-heading-py) var(--custom-api-call-heading-px);\n    }\n\n    &__content {\n      border-bottom-left-radius: var(--custom-api-call-radius);\n      border-bottom-right-radius: var(--custom-api-call-radius);\n      padding: var(--custom-api-call-content-py) var(--custom-api-call-content-px);\n    }\n  }\n\n  &__request {\n    &__heading {\n      --custom-api-call-background-color: var(--custom-api-call-request-heading-background-color);\n    }\n\n    &__content {\n      --custom-api-call-background-color: var(--custom-api-call-request-content-background-color);\n      --custom-api-call-content-py: var(--custom-api-call-heading-px);\n      --custom-api-call-content-px: var(--custom-api-call-heading-px);\n\n      --custom-code-block-background-color: var(--custom-api-call-response-heading-background-color);\n\n      --custom-code-background-color: var(--custom-api-call-response-heading-background-color);\n      --custom-code-border-color: transparent;\n      --custom-code-color: currentColor;\n    }\n  }\n\n  &__response {\n    &__heading {\n      --custom-api-call-background-color: var(--custom-api-call-request-content-background-color);\n    }\n\n    &__content {\n      --custom-api-call-background-color: var(--custom-api-call-request-content-background-color);\n\n      --custom-code-block-background-color: var(--custom-api-call-response-content-background-color);\n    }\n  }\n\n  .theme-code-block {\n    border-radius: var(--custom-api-call-radius);\n  }\n}\n\n/** Dark mode */\n@include dark {\n  --custom-api-call-color: var(--strapi-neutral-1000);\n\n  --custom-api-call-request-heading-background-color: var(--strapi-neutral-300);\n  --custom-api-call-request-content-background-color: var(--strapi-neutral-200);\n\n  --custom-api-call-response-heading-background-color: var(--strapi-neutral-150);\n  --custom-api-call-response-content-background-color: var(--strapi-neutral-100);\n\n  .api-call {\n    &__request {\n      &__content {\n        pre {\n          --custom-code-block-background-color: var(--custom-api-call-response-heading-background-color);\n        }\n      }\n    }\n\n    &__response {\n      &__content {\n        pre {\n          --custom-code-block-background-color: var(--custom-api-call-response-content-background-color);\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/scss/badge.scss",
    "content": " /** Component: Feature Badges (alpha, beta) */\n\n.badge {\n  --custom-badge-background-color: var(--strapi-neutral-200);\n  --custom-badge-color: var(--strapi-neutral-800);\n  --custom-badge-font-size: var(--strapi-font-size-sm);\n\n  --custom-selection-background-color: var(--custom-badge-color);\n\n  --ifm-badge-background-color: var(--custom-badge-background-color);\n  --ifm-badge-color: var(--custom-badge-color);\n  --ifm-badge-border-radius: var(--strapi-spacing-1);\n  --ifm-badge-padding-horizontal: var(--strapi-spacing-2);\n  --ifm-badge-padding-vertical: var(--strapi-spacing-2);\n\n  --ifm-font-weight-bold: var(--strapi-font-size-sm);\n  --ifm-link-color: var(--ifm-badge-color);\n  --ifm-link-hover-color: var(--ifm-badge-color);\n\n  position: relative;\n  top: var(--custom-badge-inside-titles-top);\n  font-size: var(--custom-badge-font-size);\n  font-weight: 400;\n  letter-spacing: 0.3px;\n  text-align: center;\n\n  &-link {\n    font-weight: 400;\n  }\n\n  &--alpha {\n    --custom-badge-background-color: rgb(255,230,228);\n    --custom-badge-color: rgb(219, 0, 22);\n  }\n\n  &--beta {\n    --custom-badge-background-color: rgb(246, 228, 253);\n    --custom-badge-color: rgb(160,25,240);\n  }\n\n  &--future {\n    --custom-badge-background-color: var(--strapi-danger-200);\n    --custom-badge-color: rgb(219, 0, 22);\n  }\n\n  &--enterprise {\n    --custom-badge-background-color: rgb(255, 241, 209);\n    --custom-badge-color: rgb(229, 136, 41);\n  }\n\n  &--strapicloudpro {\n    --custom-badge-background-color: rgb(55, 34, 254);\n    --custom-badge-color: #fff;\n  }\n\n  &--strapicloudteam {\n    --custom-badge-background-color: rgb(154, 53, 242);\n    --custom-badge-color: #fff;\n  }\n  &--strapiclouddev {\n    --custom-badge-background-color: rgb(44,170,73);\n    --custom-badge-color: #fff;\n  }\n\n  &--new {\n    --custom-badge-background-color: var(--strapi-warning-100);\n    --custom-badge-border-color: var(--strapi-warning-200);\n    --custom-badge-color: var(--strapi-warning-700);\n\n    --ifm-badge-padding-horizontal: 2px;\n    --ifm-badge-padding-vertical: 2px;\n\n    font-weight: 500;\n    font-size: 12px;\n    line-height: 20px;\n    letter-spacing: -0.5px;\n    text-transform: uppercase;\n    min-width: 52px;\n    border: 1px solid var(--custom-badge-border-color);\n  }\n\n\n  &--updated {\n    --custom-badge-background-color: var(--strapi-secondary-100);\n    --custom-badge-border-color: var(--strapi-secondary-200);\n    --custom-badge-color: var(--strapi-secondary-600);\n\n    --ifm-badge-padding-horizontal: 2px;\n    --ifm-badge-padding-vertical: 2px;\n\n    font-weight: 500;\n    font-size: 12px;\n    line-height: 20px;\n    letter-spacing: -0.5px;\n    text-transform: uppercase;\n    min-width: 52px;\n    border: 1px solid var(--custom-badge-border-color);\n    flex-shrink: 0;\n  }\n}\n\nh1 .badge {\n  --custom-badge-inside-titles-top: -.4em;\n}\n\nh2 .badge {\n  --custom-badge-inside-titles-top: -.3em;\n}\n\n/** Dark mode */\n@include dark {\n  .badge {\n    &--new {\n      --custom-badge-background-color: var(--strapi-neutral-100);\n      --custom-badge-border-color: var(--strapi-warning-500);\n      --custom-badge-color: var(--strapi-warning-500);\n    }\n    &--updated {\n      --custom-badge-background-color: var(--strapi-neutral-100);\n      --custom-badge-border-color: var(--strapi-secondary-500);\n      --custom-badge-color: var(--strapi-secondary-500);\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/scss/breadcrumbs.scss",
    "content": "/** Component: Breadcrumbs */\n\n.breadcrumbs {\n  --ifm-breadcrumb-item-background-active: transparent;\n  --ifm-breadcrumb-border-radius: var(--strapi-spacing-1);\n  --ifm-breadcrumb-color-active: var(--strapi-neutral-800);\n  --ifm-link-hover-color: var(--strapi-neutral-700);\n\n  --custom-breadcrumbs-font-size: var(--strapi-font-size-xs);\n  --custom-breadcrumbs-pt: var(--strapi-spacing-4);\n  --custom-breadcrumbs-item-caret-mx: var(--strapi-spacing-1);\n\n  padding: var(--custom-breadcrumbs-pt) 0 0;\n\n  &__item {\n    &:after {\n      --ifm-breadcrumb-spacing: var(--custom-breadcrumbs-item-caret-mx);\n    }\n\n    &--active {\n      font-weight: 700;\n    }\n  }\n\n  &__link {\n    background-color: transparent;\n    font-size: var(--custom-breadcrumbs-font-size);\n\n    @include transition;\n\n    &:any-link:hover {\n      --ifm-breadcrumb-item-background-active: var(--strapi-neutral-100);\n    }\n  }\n}\n\n@include medium-up {\n  .breadcrumbs {\n    --custom-breadcrumbs-pt: var(--strapi-spacing-6);\n    --custom-breadcrumbs-item-caret-mx: var(--strapi-spacing-3);\n  }\n}\n"
  },
  {
    "path": "docs/src/scss/card.scss",
    "content": "/** Component: Card */\n\n:root body {\n  --custom-card-border-color: var(--strapi-neutral-200);\n\n  --ifm-card-background-color: var(--strapi-neutral-0);\n  --ifm-card-border-radius: var(--strapi-spacing-1);\n}\n\n.card {\n  --ifm-color-emphasis-200: var(--custom-card-border-color);\n\n  border: 1px solid var(--custom-card-border-color);\n  box-shadow: 0px 1px 4px rgba(33, 33, 52, 0.1) !important;\n  @include transition;\n\n  &[href] {\n    color: var(--strapi-neutral-600);\n\n    &:hover {\n      --ifm-color-primary: var(--strapi-neutral-300);\n\n      box-shadow: 0px 2px 15px rgba(33, 33, 52, 0.1) !important;\n    }\n  }\n\n  h2 {\n    --ifm-h2-font-size: var(--strapi-font-size-md);\n    margin-bottom: var(--strapi-spacing-2);\n  }\n\n  p {\n    font-size: var(--strapi-font-size-sm);\n\n    &:last-of-type {\n      margin-bottom: 0;\n    }\n  }\n}\n\n/** Dark mode */\n@include dark {\n  .card {\n    &[href] {\n      color: var(--strapi-neutral-700);\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/scss/columns.scss",
    "content": "/** Components: Columns */\n\n.column {\n  &__title {\n    font-weight: 700;\n    margin-bottom: var(--strapi-spacing-1);\n  }\n}\n\n/** Responsive */\n@include medium-up {\n  .columns {\n    display: flex;\n    justify-content: space-between;\n    gap: var(--strapi-spacing-4);\n\n    > .column {\n      flex: 1;\n      max-width: 50%;\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/scss/container.scss",
    "content": "/** Component: Container */\n\n.container {\n  padding-top: 0 !important;\n}\n"
  },
  {
    "path": "docs/src/scss/custom-doc-cards.scss",
    "content": "/** Component: CustomDocCardWrapper */\n\n.custom-cards-wrapper {\n  display: flex;\n  flex-flow: row wrap;\n  max-width: 715px;\n  justify-content: flex-start;\n\n  .custom-doc-card {\n    max-width: 320px;\n    width: 320px;\n    margin-bottom: 10px;\n    margin-right: 20px;\n\n    a {\n      height: 170px;\n    }\n\n  }\n}\n\n/** Component: CustomDocCard */\n.custom-doc-card {\n\n  a {\n    text-decoration: none;\n  }\n\n  a:hover {\n    .cardTitle {\n      color: var(--strapi-primary-600);\n    }\n  }\n\n  .text--truncate.cardDescription {\n    // cancel --truncate styles since we can't remove the' class\n    overflow: auto;\n    white-space: normal;\n  }\n}\n\n.custom-doc-card--small {\n  .cardTitle {\n    margin-bottom: 0;\n  }\n}\n"
  },
  {
    "path": "docs/src/scss/details.scss",
    "content": "/** Component: Details/Accordion */\n\ndetails.alert {\n  --docusaurus-details-decoration-color: var(--strapi-neutral-800);\n\n  --ifm-alert-background-color: var(--strapi-neutral-150);\n  --ifm-alert-background-color-highlight: var(--strapi-neutral-500);\n  --ifm-alert-border-radius: var(--strapi-spacing-1);\n  --ifm-alert-foreground-color: var( --ifm-color-info-contrast-foreground );\n  --ifm-alert-border-color: transparent;\n\n  --ifm-tabs-color-active: var(--ifm-color-primary);\n  --ifm-tabs-color-active-border: var(--ifm-color-primary);\n\n  summary {\n    p {\n      margin: 0;\n    }\n  }\n\n  /** Content element */\n  > div > div {\n    --docusaurus-details-decoration-color: transparent;\n\n    margin-top: 0;\n  }\n\n  a {\n    color: var(--custom-code-color);\n  }\n\n  a:hover {\n    text-decoration: var(--ifm-link-decoration);\n  }\n}\n\n@include dark {\n  details a {\n    color: var(--strapi-primary-500);\n    font-weight: 700;\n  }\n}\n"
  },
  {
    "path": "docs/src/scss/footer.scss",
    "content": "/** Component: Footer */\n\n.footer {\n  a {\n    @include transition;\n    font-weight: 400;\n\n    &:hover {\n      font-weight: 700;\n    }\n  }\n\n  &.footer--dark {\n    --ifm-footer-background-color: var(--strapi-neutral-700);\n    --ifm-footer-link-hover-color: var(--strapi-neutral-0);\n  }\n}\n\n/** Dark mode */\n@include dark {\n  .footer {\n    &.footer--dark {\n      --ifm-footer-background-color: var(--strapi-neutral-150);\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/scss/grid.scss",
    "content": "/** Component: Grid */\n\n.row {\n  --custom-grid-spacing: var(--strapi-spacing-2);\n  --ifm-spacing-horizontal: var(--custom-grid-spacing);\n\n  .col {\n    &.margin-bottom--lg {\n      margin-bottom: calc(var(--custom-grid-spacing) * 2) !important;\n    }\n  }\n}\n\n/** Responsive */\n@include medium-up {\n  .row {\n    &--huge {\n      --custom-grid-spacing: var(--strapi-spacing-4);\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/scss/images.scss",
    "content": "/** General: Images */\n\n/** Dark Mode images border */\n.theme-doc-markdown.markdown [class*=\"themedImage--dark\"] {\n  border: 0.125rem solid var(--strapi-neutral-200);\n  border-radius: 0.25rem;\n}\n"
  },
  {
    "path": "docs/src/scss/markdown.scss",
    "content": "/** Component: Markdown */\n\n$selector-markdown: \".theme-doc-markdown.markdown\";\n\n#{$selector-markdown} {\n  --custom-markdown-pt: var(--strapi-spacing-0);\n  --custom-markdown-pb: var(--strapi-spacing-2);\n\n  --markdown-link-color: var(--strapi-primary-600);\n\n  font-weight: 400;\n  padding: var(--custom-markdown-pt) 0 var(--custom-markdown-pb);\n}\n\n/** Dark mode */\n@include dark {\n  #{$selector-markdown} {\n    --markdown-link-color: var(--strapi-primary-600);\n  }\n}\n"
  },
  {
    "path": "docs/src/scss/medium-zoom.scss",
    "content": "/** Component: Medium Zoom */\n\n.medium-zoom {\n  &-overlay {\n    background: var(--strapi-neutral-150) !important;\n  }\n}\n\n/** Responsive */\n@include medium-up {\n  .medium-zoom {\n    &-image {\n      &--opened {\n        margin-top: var(--ifm-navbar-height) !important;\n        margin-bottom: var(--ifm-navbar-height) !important;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/scss/navbar.scss",
    "content": "/** Component: Navbar */\n\n$selector-color-mode-toggle-button: 'button[class*=\"ColorModeToggle\"]';\n$selector-color-mode-toggle-wrapper: 'div[class*=\"ColorModeToggle\"]';\n\n:root body {\n  --docsearch-searchbox-background: var(--strapi-neutral-150);\n  --docsearch-searchbox-shadow: 0 0 0 2px var(--strapi-primary-600);\n  --docsearch-muted-color: var(--strapi-neutral-400);\n  --docsearch-text-color: var(--strapi-neutral-400);\n\n  --ifm-navbar-height: 56px;\n  --ifm-navbar-padding-vertical: var(--strapi-spacing-1);\n  --ifm-navbar-padding-horizontal: var(--strapi-spacing-2);\n  --ifm-navbar-shadow: 0 1px 0 0 var(--strapi-neutral-150);\n}\n\n.navbar {\n  --custom-navbar-brand-mr: 0;\n  --custom-navbar-icon-color: var(--strapi-neutral-500);\n  --custom-navbar-icon-button-size: 36px;\n  --custom-navbar-icon-button-hover-background: var(--strapi-neutral-100);\n  --custom-navbar-items-font-size: var(--strapi-font-size-sm);\n  --custom-navbar-items-gap: var(--strapi-spacing-3);\n  --custom-navbar-logo-img-width: 40px;\n  --custom-navbar-toggle-mr: var(--strapi-spacing-1);\n  --custom-navbar-transition: all 0.2s ease;\n\n  --ifm-navbar-padding-horizontal: var(--custom-navbar-items-gap);\n\n  transition: var(--custom-navbar-transition);\n\n  &__items {\n    max-width: var(--custom-navbar-items-width);\n    gap: var(--strapi-spacing-2);\n  }\n\n  &__brand,\n  &__logo {\n    height: auto;\n    display: flex;\n  }\n\n  &__brand {\n    margin-right: var(--custom-navbar-brand-mr);\n    font-size: large;\n\n    @include small-up {\n      font-size: x-large;\n    }\n  }\n\n  &__toggle {\n    margin-right: var(--custom-navbar-toggle-mr);\n  }\n\n  &__logo {\n    margin-right: var(--custom-navbar-items-gap);\n\n    img {\n      height: var(--custom-navbar-logo-img-height);\n      width: var(--custom-navbar-logo-img-width);\n    }\n  }\n\n  &__link {\n    --ifm-font-weight-semibold: 500;\n    --ifm-navbar-link-hover-color: var(--strapi-neutral-800);\n\n    font-size: var(--custom-navbar-items-font-size);\n\n    &--active {\n      --ifm-font-weight-semibold: 700;\n    }\n  }\n\n  &__link svg,\n  .navbar-sidebar__close,\n  .DocSearch-Button,\n  #{$selector-color-mode-toggle-button} {\n    color: var(--custom-navbar-icon-color);\n  }\n\n  .navbar-sidebar__close,\n  .DocSearch-Button,\n  #{$selector-color-mode-toggle-button} {\n    --ifm-color-emphasis-600: currentColor;\n\n    background: transparent;\n    border-radius: 50%;\n\n    &:hover {\n      background: var(--custom-navbar-icon-button-hover-background);\n    }\n  }\n\n  .navbar__toggle,\n  .navbar-sidebar__close,\n  #{$selector-color-mode-toggle-wrapper} {\n    align-items: center;\n    justify-content: center;\n    height: var(--custom-navbar-icon-button-size);\n    width: var(--custom-navbar-icon-button-size);\n  }\n\n  .DocSearch-Button {\n    width: var(--custom-navbar-search-button-width);\n  }\n}\n\n/** Responsive */\n@include small-up {\n  .navbar {\n    .DocSearch-Button {\n      --custom-navbar-icon-button-hover-background: var(--strapi-neutral-0);\n\n      color: var(--strapi-neutral-400);\n      border: var(--strapi-input-border);\n      border-radius: 6px;\n      font-family: var(--ifm-font-family-base);\n      height: 40px;\n      padding: 0 var(--strapi-spacing-3);\n      background-color: var(--custom-navbar-icon-button-hover-background);\n\n      svg path {\n        stroke-width: 2px;\n      }\n\n      &-Container {\n        gap:  var(--strapi-spacing-1);\n      }\n\n      &-Placeholder,\n      &-Key {\n        font-size: var(--custom-navbar-items-font-size);\n      }\n\n      &-Key {\n        margin: 0;\n        padding: 0;\n        width: 14px;\n        height: 14px;\n        background: none;\n        border: none;\n        box-shadow: none;\n        font-weight: 600;\n        line-height: 0;\n        align-items: center;\n        justify-content: center;\n\n        &:first-of-type {\n          font-size: 150%;\n        }\n      }\n\n      &-Keys {\n        padding: 2px 0 0;\n        justify-content: end;\n      }\n    }\n  }\n}\n\n/** Responsive */\n@include medium-up {\n  .navbar {\n    --custom-navbar-brand-mr: 60px;\n    --custom-navbar-items-gap: var(--strapi-spacing-4);\n    --custom-navbar-search-button-width: 266px;\n\n    --ifm-navbar-padding-horizontal: var(--custom-navbar-items-gap);\n  }\n}\n"
  },
  {
    "path": "docs/src/scss/pagination-nav.scss",
    "content": "/** Component: Pagination Nav */\n\n.pagination-nav {\n  --ifm-spacing-horizontal: var(--strapi-spacing-4);\n\n  &__link {\n    &:hover {\n      --ifm-pagination-nav-color-hover: var(--custom-card-border-color);\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/scss/scene.scss",
    "content": "/** Component: Scene */\n\n#scene {\n  position: fixed;\n  top: 0px;\n  width: 100%;\n  height: 100%;\n  z-index: 1000;\n}\n"
  },
  {
    "path": "docs/src/scss/search.scss",
    "content": "/** Component: Search */\n\n:root body {\n  --docsearch-hit-height: 56px;\n  --docsearch-searchbox-height: 40px;\n  --docsearch-spacing: var(--strapi-spacing-4);\n}\n\nbody .DocSearch {\n  --custom-search-hit-pb: var(--strapi-spacing-2);\n\n  &-SearchBar {\n    padding-bottom: var(--strapi-spacing-1);\n  }\n\n  &-Input {\n    --docsearch-text-color: var(--strapi-neutral-800);\n  }\n\n  &-Input,\n  &-Cancel {\n    font-size: var(--strapi-font-size-md);\n  }\n\n  &-Hit {\n    padding-bottom: var(--custom-search-hit-pb);\n  }\n}\n"
  },
  {
    "path": "docs/src/scss/sidebar.scss",
    "content": "/** Component: Sidebar / Menu */\n\n$selector-color-mode-toggle-button: 'button[class*=\"ColorModeToggle\"]';\n$selector-color-mode-toggle-wrapper: 'div[class*=\"ColorModeToggle\"]';\n\n:root body {\n  --doc-sidebar-width: 290px;\n}\n\n.navbar-sidebar {\n  --ifm-navbar-background-color: var(--strapi-neutral-0);\n\n  &__brand {\n    --custom-navbar-sidebar-horizontal-padding: calc(var(--custom-navbar-items-gap) * 2);\n    --ifm-navbar-padding-horizontal:\n      var(--custom-navbar-items-gap)\n      var(--ifm-navbar-padding-vertical)\n      var(--custom-navbar-sidebar-horizontal-padding)\n    ;\n  }\n\n  &__back {\n    --ifm-menu-color-background-active: var(--strapi-neutral-100);\n    top: -0.95rem;\n    margin-bottom: -0.45rem;\n  }\n\n  .navbar__brand {\n    flex-grow: 1;\n  }\n\n  .navbar-sidebar__close,\n  #{$selector-color-mode-toggle-wrapper} {\n    display: flex;\n  }\n\n  .navbar-sidebar__close {\n    margin-left: initial;\n    padding: 9px;\n  }\n\n  #{$selector-color-mode-toggle-wrapper} {\n    margin-right: 0 !important;\n  }\n}\n\n.menu {\n  --custom-sidebar-caret-size: 1.25rem;\n  --custom-sidebar-menu-font-weight: 400;\n  --custom-sidebar-menu-padding-y: var(--strapi-spacing-4);\n\n  --ifm-menu-color-background-active: transparent;\n  --ifm-menu-color-background-hover: var(--strapi-neutral-100);\n  --ifm-menu-link-padding-vertical: var(--strapi-spacing-1);\n\n  font-weight: var(--custom-sidebar-menu-font-weight);\n  padding-top: var(--custom-sidebar-menu-padding-y) !important;\n\n  &__caret {\n    margin: 0 0 0 3px;\n    padding: 0;\n\n    &:before {\n      background-size: var(--custom-sidebar-caret-size);\n    }\n  }\n\n  &__caret,\n  &__caret:before {\n    height: 16px;\n    width: 16px;\n  }\n\n  &__list {\n    &-item {\n      font-size: var(--custom-sidebar-menu-list-item-font-size, --strapi-font-size-md);\n\n      &-collapsible {\n        &:hover {\n          background-color: var(--ifm-menu-color-background-hover);\n        }\n      }\n    }\n  }\n\n  &__link {\n    font-weight: 500;\n    min-height: var(--custom-menu-item-link-min-height, 24px);\n    @include transition;\n\n    &:hover {\n      --ifm-menu-color: var(--strapi-neutral-800);\n    }\n\n    &--active {\n      --ifm-menu-color-active: var(--strapi-neutral-700);\n\n      font-weight: 700;\n\n      &:not(.menu__link--sublist) {\n        --ifm-menu-color-active: var(--strapi-primary-600);\n\n        position: relative;\n\n        &:before {\n          position: absolute;\n          content: \" \";\n          width: 5px;\n          top: 0;\n          bottom: 0;\n          left: var(--custom-sidebar-menu-list-item-link-active-left, -8px);\n          background-color: var(--strapi-primary-600);\n          border-radius: 0 2px 2px 0;\n        }\n      }\n    }\n\n    &--sublist-caret {\n      --ifm-menu-color: var(--strapi-neutral-800);\n      --ifm-menu-color-active: var(--strapi-neutral-800);\n\n      &:after {\n        display: none;\n      }\n    }\n\n    &--sublist {\n      &.menu__link--with-badge {\n        --custom-menu-link-content-mw: calc(100% - 84px);\n      }\n    }\n\n    &--with-badge {\n      max-width: 100%;\n\n      .menu__link__content {\n        max-width: var(--custom-menu-link-content-mw, calc(100% - 56px));\n        overflow: hidden;\n        text-overflow: ellipsis;\n        white-space: nowrap;\n      }\n    }\n  }\n\n  &__link__content {\n    padding-right: 10px;\n  }\n\n  .badge {\n    margin: -1px 0 -1px auto;\n  }\n\n  .theme-doc-sidebar {\n    &-item {\n      &-category {\n        &-level-1 {\n          --custom-sidebar-menu-list-item-py: var(--strapi-spacing-1);\n\n          padding-top: var(--custom-sidebar-menu-list-item-py);\n          padding-bottom: var(--custom-sidebar-menu-list-item-py);\n\n          > .menu__list-item-collapsible {\n            font-weight: 700;\n            font-size: var(--strapi-font-size-md);\n          }\n        }\n      }\n\n      &-category, &-link {\n        &-level {\n          &-1 {\n            --custom-sidebar-menu-list-item-font-size: var(--strapi-font-size-md);\n          }\n\n          &-2 {\n            --custom-sidebar-menu-list-item-font-size: var(--strapi-font-size-sm); // next levels will have this same value\n            --custom-sidebar-menu-list-item-link-active-left: -30px;\n\n            padding-left: 10px;\n          }\n\n          &-3 {\n            --custom-sidebar-menu-list-item-link-active-left: -42px;\n          }\n\n          &-4 {\n            --custom-sidebar-menu-list-item-link-active-left: -54px;\n          }\n\n          &-5 {\n            --custom-sidebar-menu-list-item-link-active-left: -66px;\n          }\n        }\n      }\n    }\n  }\n}\n\n.theme-doc-sidebar-container {\n  --docusaurus-collapse-button-bg: var(--strapi-neutral-0);\n  --docusaurus-collapse-button-bg-hover: var(--strapi-neutral-100);\n}\n\n/** Dark mode */\n@include dark {\n  --ifm-menu-color: var(--strapi-neutral-1000);\n\n  .theme-doc-sidebar-container {\n    .menu {\n      --ifm-menu-color-background-hover: var(--strapi-neutral-100);\n\n      &__link--active {\n        --ifm-menu-color-active: var(--strapi-neutral-800);\n\n        &:not(.menu__link--sublist) {\n          --ifm-menu-color-active: var(--strapi-primary-500);\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/scss/table-of-contents.scss",
    "content": "/** Component: Table of Contents */\n\n.table-of-contents {\n  --custom-toc-py: var(--strapi-spacing-1);\n  --custom-toc-items-py: var(--strapi-spacing-0);\n\n  --ifm-toc-padding-vertical: var(--custom-toc-py);\n\n  font-size: var(--strapi-font-size-xs);\n\n  > li {\n    margin: 0 var(--ifm-toc-padding-horizontal);\n    padding-top: var(--custom-toc-items-py);\n    padding-bottom: var(--custom-toc-items-py);\n  }\n\n  &__link {\n    --custom-table-of-contents-link-active-before-left: -16px;\n\n    position: relative;\n    font-weight: 400;\n\n    @include transition;\n\n    &:hover {\n      font-weight: 500;\n\n      &:not(.table-of-contents__link--active) {\n        --ifm-color-primary: var(--strapi-neutral-900);\n      }\n    }\n\n    &--active {\n      font-weight: 500;\n\n      &:before {\n        content: \" \";\n        position: absolute;\n        top: 0;\n        bottom: 0;\n        left: var(--custom-table-of-contents-link-active-before-left);\n        width: 3px;\n        border-radius: 0 2px 2px 0;\n        background-color: var(--strapi-primary-600);\n      }\n    }\n\n    + ul li .table-of-contents {\n      &__link {\n        --custom-table-of-contents-link-active-before-left: -32px;\n      }\n    }\n\n    img {\n      display: inline-block;\n      vertical-align: bottom;\n      max-width: 22px;\n      margin-right: 2px;\n    }\n  }\n}\n\n/** Responsive */\n@include medium-up {\n  .table-of-contents {\n    --custom-toc-items-py: var(--strapi-spacing-2);\n  }\n}\n"
  },
  {
    "path": "docs/src/scss/table.scss",
    "content": "/** Component: Table */\ntable {\n  min-width: 100%;\n  overflow: auto;\n\n  thead {\n    --ifm-table-background: transparent;\n    --ifm-table-stripe-background: transparent;\n\n    tr {\n      border-bottom-width: 1px;\n\n      th {\n        --ifm-table-head-background: transparent;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "docs/src/scss/tabs.scss",
    "content": "/** Component: Tab */\n\n:root body {\n  --custom-tabs-px: var(--strapi-spacing-5);\n  --custom-tabs-py: var(--strapi-spacing-2);\n\n  --ifm-tabs-padding-horizontal: var(--custom-tabs-px);\n  --ifm-tabs-padding-vertical: var(--custom-tabs-py);\n}\n\n.tabs {\n  &__item {\n    border-top: 2px solid transparent;\n  }\n\n  /** Tabs inside Tabs */\n  + div {\n    [role=\"tabpanel\"] {\n      .tabs {\n        font-size: var(--strapi-font-size-ssm);\n\n        &__item {\n          &--active {\n            --ifm-tabs-color-active-border: transparent;\n\n            background-color: var(--ifm-hover-overlay);\n          }\n        }\n\n        + [class*=\"margin-top\"] {\n          margin-top: 0 !important;\n        }\n      }\n    }\n  }\n}\n\n/** Tabs inside Details component */\ndetails {\n  .tabs {\n    --ifm-tabs-color-active-border: var(--strapi-)\n  }\n}\n"
  },
  {
    "path": "docs/src/scss/typography.scss",
    "content": "/** General: Typography */\n\n:root {\n  --custom-heading-decorative-line-color: var(--strapi-neutral-150);\n}\n\nh1, h2, h3, h4, h5, h6 {\n  --ifm-heading-color: var(--strapi-neutral-900);\n  --ifm-heading-font-weight: 600;\n  --ifm-code-font-size: 70%;\n}\n\nh1, .markdown h1:first-child {\n  --ifm-h1-font-size: 35px;\n  // --ifm-heading-line-height: 24px; // not good\n}\n\nh2, .markdown > h2 {\n  --ifm-h2-font-size: 26px;\n  // --ifm-heading-line-height: 24px; // not good\n}\n\nh3, .markdown > h3 {\n  --ifm-h3-font-size: var(--strapi-font-size-lg);\n  @include flex-row;\n}\n\nh2 {\n  position: relative;\n\n  &:after {\n    content: \" \";\n    position: absolute;\n    left: 0;\n    right: 0;\n    bottom: -10px;\n    height: 1px;\n    background-color: var(--custom-heading-decorative-line-color);\n  }\n}\n\np, ul {\n  img {\n    display: inline-block;\n    vertical-align: text-bottom;\n  }\n}\n\n/** Dark mode */\n@include dark {\n  h1, h2, h3, h4, h5, h6 {\n    --ifm-heading-color: var(--strapi-neutral-900);\n  }\n}\n"
  },
  {
    "path": "docs/src/theme/Admonition/index.js",
    "content": "import React from 'react';\nimport clsx from 'clsx';\nimport { ThemeClassNames } from '@docusaurus/theme-common';\n\nconst defaultClassName = ThemeClassNames.common.admonition;\nconst customDefaultProps = {\n  note: {\n    icon: '✏️',\n    title: 'Note',\n  },\n  tip: {\n    icon: '💡',\n    title: 'Tip',\n  },\n  info: {\n    icon: '👀',\n    title: 'Info',\n  },\n  caution: {\n    icon: '✋',\n    title: 'Caution',\n  },\n  warning: {\n    icon: '⚠️',\n    title: 'Warning',\n  },\n  danger: {\n    icon: '❗️',\n    title: 'Warning',\n  },\n  strapi: {\n    icon: '🤓',\n  },\n  prerequisites: {\n    icon: '☑️',\n    title: 'Prerequisites',\n  },\n};\n\nexport default function CustomAdmonition({\n  children,\n  className,\n  icon: propIcon,\n  title: propTitle,\n  type,\n  ...rest\n}) {\n  const { icon: defaultIcon, title: defaultTitle } = (customDefaultProps[type] || {});\n  const icon = (propIcon || defaultIcon);\n  const title = (propTitle || defaultTitle);\n  const shouldRenderHeading = !!(icon || title);\n\n  return (\n    <div\n      {...rest}\n      className={clsx(\n        defaultClassName,\n        (type && `${defaultClassName}--${type}`),\n        className,\n      )}\n    >\n      {shouldRenderHeading && (\n        <div className={`${defaultClassName}__heading`}>\n          {icon && (\n            <span className={`${defaultClassName}__heading__icon`}>\n              {icon}{' '}\n            </span>\n          )}\n          {title}\n        </div>\n      )}\n      {children}\n    </div>\n  );\n}\n"
  },
  {
    "path": "docs/src/theme/MDXComponents.js",
    "content": "import React from 'react';\n// Import the original mapper\nimport MDXComponents from '@theme-original/MDXComponents';\n/** Import built-in Docusaurus components at the global level\n * so we don't have to re-import them in every file\n */\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n// Import custom components, globally as well\nimport Request from '../components/Request';\nimport Response from '../components/Response';\nimport ApiCall from '../components/ApiCall';\nimport SubtleCallout from '../components/SubtleCallout';\nimport CustomDocCard from '../components/CustomDocCard';\nimport CustomDocCardsWrapper from '../components/CustomDocCardsWrapper';\n\nexport default {\n  // Re-use the default mapping\n  ...MDXComponents,\n\n  /**\n   * Components below are imported within the global scope,\n   * meaning you don't have to insert the typical 'import SomeStuff from '/path/to/stuff' line\n   * at the top of a Markdown file before being able to use these components\n   *  — see https://docusaurus.io/docs/next/markdown-features/react#mdx-component-scope\n   */\n  Request,\n  Response,\n  ApiCall,\n  Tabs,\n  TabItem,\n  SubtleCallout,\n  CustomDocCard,\n  CustomDocCardsWrapper,\n};\n"
  },
  {
    "path": "docs/static/.nojekyll",
    "content": ""
  },
  {
    "path": "docs/tsconfig.json",
    "content": "{\n  // This file is not used in compilation. It is here just for a nice editor experience.\n  \"extends\": \"@docusaurus/tsconfig\",\n  \"compilerOptions\": {\n    \"baseUrl\": \".\"\n  }\n}\n"
  },
  {
    "path": "jest.config.js",
    "content": "module.exports = {\n  name: 'Unit test',\n  testMatch: ['**/__tests__/?(*.)+(spec|test).js'],\n  transform: {},\n  coverageDirectory: \"./coverage/\",\n  collectCoverage: true,\n};\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"strapi-plugin-config-sync\",\n  \"version\": \"3.2.0\",\n  \"description\": \"Migrate your config data across environments using the CLI or Strapi admin panel.\",\n  \"strapi\": {\n    \"displayName\": \"Config Sync\",\n    \"name\": \"config-sync\",\n    \"icon\": \"sync\",\n    \"description\": \"Migrate your config data across environments using the CLI or Strapi admin panel.\",\n    \"required\": false,\n    \"kind\": \"plugin\"\n  },\n  \"bin\": {\n    \"config-sync\": \"./bin/config-sync\"\n  },\n  \"exports\": {\n    \"./strapi-admin\": {\n      \"source\": \"./admin/src/index.js\",\n      \"import\": \"./dist/admin/index.mjs\",\n      \"require\": \"./dist/admin/index.js\",\n      \"default\": \"./dist/admin/index.js\"\n    },\n    \"./strapi-server\": {\n      \"source\": \"./server/index.js\",\n      \"import\": \"./dist/server/index.mjs\",\n      \"require\": \"./dist/server/index.js\",\n      \"default\": \"./dist/server/index.js\"\n    },\n    \"./cli\": {\n      \"source\": \"./server/cli.js\",\n      \"import\": \"./dist/cli/index.mjs\",\n      \"require\": \"./dist/cli/index.js\",\n      \"default\": \"./dist/cli/index.js\"\n    },\n    \"./package.json\": \"./package.json\"\n  },\n  \"scripts\": {\n    \"develop\": \"strapi-plugin watch:link\",\n    \"watch\": \"strapi-plugin watch\",\n    \"build\": \"strapi-plugin build && yalc push --publish\",\n    \"eslint\": \"eslint --max-warnings=0 './**/*.{js,jsx}'\",\n    \"eslint:fix\": \"eslint --fix './**/*.{js,jsx}'\",\n    \"test:unit\": \"jest --verbose\",\n    \"test:integration\": \"cd playground && node_modules/.bin/jest --verbose  --forceExit --detectOpenHandles\",\n    \"test:e2e\": \"cypress open\",\n    \"playground:install\": \"yarn playground:yalc-add-link && cd playground && yarn install\",\n    \"playground:yalc-add\": \"cd playground && yalc add strapi-plugin-config-sync\",\n    \"playground:yalc-add-link\": \"cd playground && yalc add --link strapi-plugin-config-sync\",\n    \"playground:build\": \"cd playground && yarn build\",\n    \"playground:develop\": \"cd playground && yarn develop\",\n    \"playground:start\": \"cd playground && yarn start\"\n  },\n  \"dependencies\": {\n    \"adm-zip\": \"^0.5.16\",\n    \"chalk\": \"^5.6.2\",\n    \"cli-table\": \"^0.3.6\",\n    \"commander\": \"^14.0.3\",\n    \"file-saver\": \"^2.0.5\",\n    \"git-diff\": \"^2.0.6\",\n    \"immutable\": \"^4.0.0\",\n    \"inquirer\": \"^13.3.0\",\n    \"lodash\": \"^4.17.23\",\n    \"react-diff-viewer-continued\": \"4.0.6\",\n    \"react-intl\": \"^6\",\n    \"react-query\": \"^3.39.3\",\n    \"react-redux\": \"^9.2.0\",\n    \"redux\": \"^5.0.1\",\n    \"redux-immutable\": \"^4.0.0\",\n    \"redux-thunk\": \"^3.1.0\"\n  },\n  \"author\": {\n    \"name\": \"Boaz Poolman\",\n    \"email\": \"boaz@pluginpal.io\",\n    \"url\": \"https://github.com/boazpoolman\"\n  },\n  \"maintainers\": [\n    {\n      \"name\": \"Boaz Poolman\",\n      \"email\": \"boaz@pluginpal.io\",\n      \"url\": \"https://github.com/boazpoolman\"\n    }\n  ],\n  \"files\": [\n    \"dist\",\n    \"bin\"\n  ],\n  \"peerDependencies\": {\n    \"@strapi/admin\": \"^5.0.0\",\n    \"@strapi/design-system\": \"^2.0.0\",\n    \"@strapi/icons\": \"^2.0.0\",\n    \"@strapi/strapi\": \"^5.0.0\",\n    \"@strapi/typescript-utils\": \"^5.0.0\",\n    \"@strapi/utils\": \"^5.0.0\",\n    \"react\": \"^17.0.0 || ^18.0.0\",\n    \"react-dom\": \"^17.0.0 || ^18.0.0\",\n    \"react-router-dom\": \"^6.0.0\",\n    \"styled-components\": \"^6.0.0\"\n  },\n  \"devDependencies\": {\n    \"@strapi/admin\": \"^5.0.0\",\n    \"@strapi/design-system\": \"^2.0.0\",\n    \"@strapi/icons\": \"^2.0.0\",\n    \"@strapi/sdk-plugin\": \"^6.0.0\",\n    \"@strapi/strapi\": \"^5.0.0\",\n    \"@strapi/typescript-utils\": \"^5.0.0\",\n    \"@strapi/utils\": \"^5.0.0\",\n    \"babel-eslint\": \"9.0.0\",\n    \"cypress\": \"^15\",\n    \"cypress-terminal-report\": \"^7\",\n    \"eslint\": \"^7.32.0\",\n    \"eslint-config-airbnb\": \"^18.2.1\",\n    \"eslint-config-react-app\": \"^3.0.7\",\n    \"eslint-import-resolver-webpack\": \"^0.11.0\",\n    \"eslint-loader\": \"^4.0.2\",\n    \"eslint-plugin-babel\": \"^5.3.0\",\n    \"eslint-plugin-cypress\": \"^3.2.0\",\n    \"eslint-plugin-flowtype\": \"2.50.1\",\n    \"eslint-plugin-import\": \"^2.22.1\",\n    \"eslint-plugin-jsx-a11y\": \"^6.4.1\",\n    \"eslint-plugin-react\": \"^7.21.5\",\n    \"eslint-plugin-react-hooks\": \"^2.3.0\",\n    \"jest\": \"^29.7.0\",\n    \"jest-cli\": \"^29.3.1\",\n    \"jest-styled-components\": \"^7.0.2\",\n    \"nodemon\": \"^3.1.7\",\n    \"react\": \"^17.0.0\",\n    \"styled-components\": \"^5.2.3\",\n    \"yalc\": \"^1.0.0-pre.53\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/pluginpal/strapi-plugin-config-sync/issues\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git://github.com/pluginpal/strapi-plugin-config-sync.git\"\n  },\n  \"homepage\": \"https://www.pluginpal.io/plugin/config-sync\",\n  \"engines\": {\n    \"node\": \">=20.0.0 <=24.x.x\",\n    \"npm\": \">=6.0.0\"\n  },\n  \"license\": \"MIT\",\n  \"publishConfig\": {\n    \"registry\": \"https://registry.npmjs.org/\",\n    \"access\": \"public\",\n    \"provenance\": true\n  }\n}\n"
  },
  {
    "path": "packup.config.ts",
    "content": "// eslint-disable-next-line import/no-extraneous-dependencies\nimport { defineConfig } from '@strapi/pack-up';\n\nexport default defineConfig({\n  bundles: [\n    {\n      source: './admin/src/index.js',\n      import: './dist/admin/index.mjs',\n      require: './dist/admin/index.js',\n      runtime: 'web',\n    },\n    {\n      source: './server/index.js',\n      import: './dist/server/index.mjs',\n      require: './dist/server/index.js',\n      runtime: 'node',\n    },\n    {\n      source: './server/cli.js',\n      import: './dist/cli/index.mjs',\n      require: './dist/cli/index.js',\n      runtime: 'node',\n    },\n  ],\n  dist: './dist',\n  exports: {},\n});\n"
  },
  {
    "path": "playground/.editorconfig",
    "content": "root = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n\n[{package.json,*.yml}]\nindent_style = space\nindent_size = 2\n\n[*.md]\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "playground/.eslintignore",
    "content": ".cache\nbuild\n**/node_modules/**\n"
  },
  {
    "path": "playground/.eslintrc",
    "content": "{\n  \"parser\": \"babel-eslint\",\n  \"extends\": \"eslint:recommended\",\n  \"env\": {\n    \"commonjs\": true,\n    \"es6\": true,\n    \"node\": true,\n    \"browser\": false\n  },\n  \"parserOptions\": {\n    \"ecmaFeatures\": {\n      \"experimentalObjectRestSpread\": true,\n      \"jsx\": false\n    },\n    \"sourceType\": \"module\"\n  },\n  \"globals\": {\n    \"strapi\": true\n  },\n  \"rules\": {\n    \"indent\": [\"error\", 2, { \"SwitchCase\": 1 }],\n    \"linebreak-style\": [\"error\", \"unix\"],\n    \"no-console\": 0,\n    \"quotes\": [\"error\", \"single\"],\n    \"semi\": [\"error\", \"always\"]\n  }\n}\n"
  },
  {
    "path": "playground/.gitignore",
    "content": "############################\n# OS X\n############################\n\n.DS_Store\n.AppleDouble\n.LSOverride\nIcon\n.Spotlight-V100\n.Trashes\n._*\n\n\n############################\n# Linux\n############################\n\n*~\n\n\n############################\n# Windows\n############################\n\nThumbs.db\nehthumbs.db\nDesktop.ini\n$RECYCLE.BIN/\n*.cab\n*.msi\n*.msm\n*.msp\n\n\n############################\n# Packages\n############################\n\n*.7z\n*.csv\n*.dat\n*.dmg\n*.gz\n*.iso\n*.jar\n*.rar\n*.tar\n*.zip\n*.com\n*.class\n*.dll\n*.exe\n*.o\n*.seed\n*.so\n*.swo\n*.swp\n*.swn\n*.swm\n*.out\n*.pid\n\n\n############################\n# Logs and databases\n############################\n\n.tmp\n*.log\n*.sql\n*.sqlite\n*.sqlite3\n\n\n############################\n# Misc.\n############################\n\n*#\nssl\n.idea\nnbproject\npublic/uploads/*\n!public/uploads/.gitkeep\n\n############################\n# Node.js\n############################\n\nlib-cov\nlcov.info\npids\nlogs\nresults\nnode_modules\n.node_history\nyarn.lock\n\n############################\n# Tests\n############################\n\ntestApp\ncoverage\n/config/sync\n\n############################\n# Strapi\n############################\n\nlicense.txt\nexports\n.strapi\ndist\nbuild\n.strapi-updater.json\n.strapi-cloud.json\n\n# yalc\n.yalc\nyalc.lock\n"
  },
  {
    "path": "playground/README.md",
    "content": "# 🚀 Getting started with Strapi\n\nStrapi comes with a full featured [Command Line Interface](https://docs.strapi.io/dev-docs/cli) (CLI) which lets you scaffold and manage your project in seconds.\n\n### `develop`\n\nStart your Strapi application with autoReload enabled. [Learn more](https://docs.strapi.io/dev-docs/cli#strapi-develop)\n\n```\nnpm run develop\n# or\nyarn develop\n```\n\n### `start`\n\nStart your Strapi application with autoReload disabled. [Learn more](https://docs.strapi.io/dev-docs/cli#strapi-start)\n\n```\nnpm run start\n# or\nyarn start\n```\n\n### `build`\n\nBuild your admin panel. [Learn more](https://docs.strapi.io/dev-docs/cli#strapi-build)\n\n```\nnpm run build\n# or\nyarn build\n```\n\n## ⚙️ Deployment\n\nStrapi gives you many possible deployment options for your project including [Strapi Cloud](https://cloud.strapi.io). Browse the [deployment section of the documentation](https://docs.strapi.io/dev-docs/deployment) to find the best solution for your use case.\n\n## 📚 Learn more\n\n- [Resource center](https://strapi.io/resource-center) - Strapi resource center.\n- [Strapi documentation](https://docs.strapi.io) - Official Strapi documentation.\n- [Strapi tutorials](https://strapi.io/tutorials) - List of tutorials made by the core team and the community.\n- [Strapi blog](https://strapi.io/blog) - Official Strapi blog containing articles made by the Strapi team and the community.\n- [Changelog](https://strapi.io/changelog) - Find out about the Strapi product updates, new features and general improvements.\n\nFeel free to check out the [Strapi GitHub repository](https://github.com/strapi/strapi). Your feedback and contributions are welcome!\n\n## ✨ Community\n\n- [Discord](https://discord.strapi.io) - Come chat with the Strapi community including the core team.\n- [Forum](https://forum.strapi.io/) - Place to discuss, ask questions and find answers, show your Strapi project and get feedback or just talk with other Community members.\n- [Awesome Strapi](https://github.com/strapi/awesome-strapi) - A curated list of awesome things related to Strapi.\n\n---\n\n<sub>🤫 Psst! [Strapi is hiring](https://strapi.io/careers).</sub>\n"
  },
  {
    "path": "playground/__tests__/cli.test.js",
    "content": "'use strict';\n\nconst util = require('util');\nconst exec = util.promisify(require('child_process').exec);\n\njest.setTimeout(20000);\n\ndescribe('Test the config-sync CLI', () => {\n  afterAll(async () => {\n    // Remove the generated files and the DB.\n    await exec('rm -rf config/sync');\n    await exec('rm -rf .tmp');\n  });\n\n  test('Export', async () => {\n    const { stdout: exportOutput } = await exec('yarn cs export -y');\n    expect(exportOutput).toContain('Finished export');\n    const { stdout: diffOutput } = await exec('yarn cs diff');\n    expect(diffOutput).toContain('No differences between DB and sync directory');\n  });\n\n  test('Import (delete)', async () => {\n    // Remove a file to trigger a delete.\n    await exec('mv config/sync/admin-role.strapi-editor.json .tmp');\n    const { stdout: importOutput } = await exec('yarn cs import -y');\n    expect(importOutput).toContain('Finished import');\n    const { stdout: diffOutput } = await exec('yarn cs diff');\n    expect(diffOutput).toContain('No differences between DB and sync directory');\n  });\n  test('Import (update)', async () => {\n    // Update a core-store file.\n    await exec('sed -i \\'s/\"description\":\"\",/\"description\":\"test\",/g\\' config/sync/core-store.plugin_content_manager_configuration_content_types##plugin##users-permissions.user.json');\n    // Update a file that has relations.\n    await exec('sed -i \\'s/{\"action\":\"plugin::users-permissions.auth.register\"},//g\\' config/sync/user-role.public.json');\n    const { stdout: importOutput } = await exec('yarn cs import -y');\n    expect(importOutput).toContain('Finished import');\n    const { stdout: diffOutput } = await exec('yarn cs diff');\n    expect(diffOutput).toContain('No differences between DB and sync directory');\n  });\n  test('Import (create)', async () => {\n    // Add a file to trigger a creation.\n    await exec('mv .tmp/admin-role.strapi-editor.json config/sync/');\n    const { stdout: importOutput } = await exec('yarn cs import -y');\n    expect(importOutput).toContain('Finished import');\n    const { stdout: diffOutput } = await exec('yarn cs diff');\n    expect(diffOutput).toContain('No differences between DB and sync directory');\n  });\n\n  test('Non-empty diff returns 1', async () => {\n    await exec('rm -rf config/sync/admin-role.strapi-editor.json');\n    // Work around Jest not supporting custom error matching.\n    // https://github.com/facebook/jest/issues/8140\n    let error;\n    try {\n      await exec('yarn cs diff');\n    } catch (e) {\n      error = e;\n    }\n    expect(error).toHaveProperty('code', 1);\n  });\n\n  test('Import build project', async () => {\n    // First we make sure the dist folder is deleted.\n    await exec('mv dist .tmp');\n\n    await exec('yarn cs import -y');\n\n    const { stdout: buildOutput } = await exec('ls dist');\n    expect(buildOutput).toContain('config');\n    expect(buildOutput).toContain('src');\n    expect(buildOutput).toContain('tsconfig.tsbuildinfo');\n\n    // We restore the dist folder.\n    await exec('rm -rf dist');\n    await exec('mv .tmp/dist ./dist');\n\n  });\n  test('Import project already built', async () => {\n\n\n    // First we make sure the dist folder exists by doing an import.\n    await exec('yarn cs import -y');\n\n    const { stdout: lsDist } = await exec('ls dist');\n    expect(lsDist).toContain('config');\n    expect(lsDist).toContain('src');\n    expect(lsDist).toContain('tsconfig.tsbuildinfo');\n\n    // We remove on file from the dist folder\n    await exec('mv dist/tsconfig.tsbuildinfo .tmp');\n\n    // The new import should not rebuild the project.\n    await exec('yarn cs import -y');\n\n    const { stdout: buildOutput } = await exec('ls dist');\n    expect(buildOutput).toContain('config');\n    expect(buildOutput).toContain('src');\n    expect(buildOutput).not.toContain('tsconfig.tsbuildinfo');\n\n    // We restore the tsconfig.tsbuildinfo file.\n    await exec('mv .tmp/tsconfig.tsbuildinfo dist/tsconfig.tsbuildinfo');\n\n  });\n});\n"
  },
  {
    "path": "playground/__tests__/helpers.js",
    "content": "const fs = require('fs');\nconst { createStrapi, compileStrapi } = require('@strapi/strapi');\n\nlet instance;\n\nasync function setupStrapi() {\n  if (!instance) {\n    const appContext = await compileStrapi();\n    await createStrapi(appContext).load();\n    instance = strapi;\n\n    await instance.server.mount();\n  }\n  return instance;\n}\n\nasync function cleanupStrapi() {\n  const dbSettings = strapi.config.get('database.connection');\n\n  // close server to release the db-file.\n  await strapi.server.httpServer.close();\n\n  // close the connection to the database before deletion.\n  await strapi.db.connection.destroy();\n\n  // delete test database after all tests have completed.\n  if (dbSettings && dbSettings.connection && dbSettings.connection.filename) {\n    const tmpDbFile = dbSettings.connection.filename;\n    if (fs.existsSync(tmpDbFile)) {\n      fs.unlinkSync(tmpDbFile);\n    }\n  }\n}\n\nmodule.exports = { setupStrapi, cleanupStrapi };\n"
  },
  {
    "path": "playground/__tests__/import-on-boostrap.test.js",
    "content": "const util = require('util');\nconst exec = util.promisify(require('child_process').exec);\n\nconst { setupStrapi, cleanupStrapi } = require('./helpers');\n\njest.setTimeout(20000);\n\nafterEach(async () => {\n  // Disable importOnBootstrap\n  await exec('sed -i \"s/importOnBootstrap: true/importOnBootstrap: false/g\" config/plugins.ts');\n\n  await cleanupStrapi();\n  await exec('rm -rf config/sync');\n});\n\ndescribe('Test the importOnBootstrap feature', () => {\n  test('Without a database', async () => {\n    // Do the initial export and remove the database.\n    await exec('yarn cs export -y');\n    await exec('rm -rf .tmp');\n\n    // Manually change the plugins.ts to enable importOnBoostrap.\n    await exec('sed -i \"s/importOnBootstrap: false/importOnBootstrap: true/g\" config/plugins.ts');\n\n    // Start up Strapi to initiate the importOnBootstrap function.\n    await setupStrapi();\n\n    expect(strapi).toBeDefined();\n  });\n\n  test('With a database', async () => {\n    // Delete any existing database and do an export.\n    await exec('rm -rf .tmp');\n    await exec('yarn cs export -y');\n\n    // Manually change the plugins.ts to enable importOnBoostrap.\n    await exec('sed -i \"s/importOnBootstrap: false/importOnBootstrap: true/g\" config/plugins.ts');\n\n    // Remove a config file to make sure the importOnBoostrap\n    // function actually attempts to import.\n    await exec('rm -rf config/sync/admin-role.strapi-editor.json');\n\n    // Start up Strapi to initiate the importOnBootstrap function.\n    await setupStrapi();\n\n    expect(strapi).toBeDefined();\n  });\n});\n"
  },
  {
    "path": "playground/config/admin.ts",
    "content": "export default ({ env }) => ({\n  auth: {\n    secret: env('ADMIN_JWT_SECRET'),\n  },\n  apiToken: {\n    salt: env('API_TOKEN_SALT'),\n  },\n  rateLimit: {\n    enabled: false,\n  },\n  transfer: {\n    token: {\n      salt: env('TRANSFER_TOKEN_SALT'),\n    },\n  },\n  flags: {\n    nps: env.bool('FLAG_NPS', true),\n    promoteEE: env.bool('FLAG_PROMOTE_EE', true),\n  },\n  watchIgnoreFiles: [\n    '**/config/sync/**',\n    '!**/.yalc/**/server/**',\n  ],\n});\n"
  },
  {
    "path": "playground/config/api.ts",
    "content": "export default {\n  rest: {\n    defaultLimit: 25,\n    maxLimit: 100,\n    withCount: true,\n  },\n};\n"
  },
  {
    "path": "playground/config/database.ts",
    "content": "import path from 'path';\n\nexport default ({ env }) => {\n\n  return {\n    connection: {\n      client: 'sqlite',\n      connection: {\n        filename: path.join(__dirname, '..', '..', env('DATABASE_FILENAME', '.tmp/data.db')),\n      },\n      useNullAsDefault: true,\n    },\n  };\n\n};\n"
  },
  {
    "path": "playground/config/middlewares.ts",
    "content": "export default [\n  'strapi::logger',\n  'strapi::errors',\n  'strapi::security',\n  'strapi::cors',\n  'strapi::poweredBy',\n  'strapi::query',\n  'strapi::body',\n  'strapi::session',\n  'strapi::favicon',\n  'strapi::public',\n];\n"
  },
  {
    "path": "playground/config/plugins.ts",
    "content": "export default () => ({\n  'config-sync': {\n    enabled: true,\n    config: {\n      importOnBootstrap: false,\n      minify: true,\n    },\n  },\n});\n"
  },
  {
    "path": "playground/config/server.ts",
    "content": "export default ({ env }) => ({\n  host: env('HOST', '0.0.0.0'),\n  port: env.int('PORT', 1337),\n  app: {\n    keys: env.array('APP_KEYS'),\n  },\n  webhooks: {\n    populateRelations: env.bool('WEBHOOKS_POPULATE_RELATIONS', false),\n  },\n});\n"
  },
  {
    "path": "playground/database/migrations/.gitkeep",
    "content": ""
  },
  {
    "path": "playground/jest.config.js",
    "content": "module.exports = {\n  name: 'Integration test',\n  testMatch: ['**/__tests__/?(*.)+(spec|test).js'],\n  transform: {},\n  coverageDirectory: '../coverage/',\n  collectCoverage: true,\n};\n"
  },
  {
    "path": "playground/package.json",
    "content": "{\n  \"name\": \"strapi-5-beta\",\n  \"private\": true,\n  \"version\": \"0.1.0\",\n  \"description\": \"A Strapi application\",\n  \"scripts\": {\n    \"develop\": \"strapi develop\",\n    \"start\": \"strapi start\",\n    \"build\": \"strapi build\",\n    \"strapi\": \"strapi\",\n    \"cs\": \"config-sync\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"^24.0.7\",\n    \"@types/react\": \"^19.1.8\",\n    \"@types/react-dom\": \"^19.1.6\",\n    \"jest\": \"^29.7.0\",\n    \"jest-cli\": \"^29.7.0\",\n    \"supertest\": \"^6.3.3\",\n    \"typescript\": \"^5.8.3\",\n    \"yalc\": \"^1.0.0-pre.53\"\n  },\n  \"dependencies\": {\n    \"@strapi/plugin-cloud\": \"5.37.1\",\n    \"@strapi/plugin-users-permissions\": \"5.37.1\",\n    \"@strapi/strapi\": \"5.37.1\",\n    \"better-sqlite3\": \"11.3.0\",\n    \"react\": \"^18.0.0\",\n    \"react-dom\": \"^18.0.0\",\n    \"react-router-dom\": \"^6.0.0\",\n    \"strapi-plugin-config-sync\": \"link:.yalc/strapi-plugin-config-sync\",\n    \"styled-components\": \"^6.0.0\"\n  },\n  \"author\": {\n    \"name\": \"A Strapi developer\"\n  },\n  \"strapi\": {\n    \"uuid\": \"edadddbd-0f25-4da7-833b-d4cd7dcae2fc\"\n  },\n  \"engines\": {\n    \"node\": \">=20.0.0 <=24.x.x\",\n    \"npm\": \">=6.0.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "playground/public/robots.txt",
    "content": "# To prevent search engines from seeing the site altogether, uncomment the next two lines:\n# User-Agent: *\n# Disallow: /\n"
  },
  {
    "path": "playground/public/uploads/.gitkeep",
    "content": ""
  },
  {
    "path": "playground/src/admin/app.example.tsx",
    "content": "import type { StrapiApp } from '@strapi/strapi/admin';\n\nexport default {\n  config: {\n    locales: [\n      // 'ar',\n      // 'fr',\n      // 'cs',\n      // 'de',\n      // 'dk',\n      // 'es',\n      // 'he',\n      // 'id',\n      // 'it',\n      // 'ja',\n      // 'ko',\n      // 'ms',\n      // 'nl',\n      // 'no',\n      // 'pl',\n      // 'pt-BR',\n      // 'pt',\n      // 'ru',\n      // 'sk',\n      // 'sv',\n      // 'th',\n      // 'tr',\n      // 'uk',\n      // 'vi',\n      // 'zh-Hans',\n      // 'zh',\n    ],\n  },\n  bootstrap(app: StrapiApp) {\n    console.log(app);\n  },\n};\n"
  },
  {
    "path": "playground/src/admin/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"Bundler\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"DOM\", \"DOM.Iterable\", \"ESNext\"],\n    \"allowJs\": false,\n    \"skipLibCheck\": true,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"strict\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"resolveJsonModule\": true,\n    \"noEmit\": true,\n    \"jsx\": \"react-jsx\"\n  },\n  \"include\": [\"../plugins/**/admin/src/**/*\", \"./\"],\n  \"exclude\": [\"node_modules/\", \"build/\", \"dist/\", \"**/*.test.ts\"]\n}\n"
  },
  {
    "path": "playground/src/admin/webpack.config.example.ts",
    "content": "import { mergeConfig, type UserConfig } from 'vite';\n\nexport default (config: UserConfig) => {\n  // Important: always return the modified config\n  return mergeConfig(config, {\n    resolve: {\n      alias: {\n        '@': '/src',\n      },\n    },\n  });\n};\n"
  },
  {
    "path": "playground/src/api/.gitkeep",
    "content": ""
  },
  {
    "path": "playground/src/api/home/content-types/home/schema.json",
    "content": "{\n  \"kind\": \"singleType\",\n  \"collectionName\": \"homes\",\n  \"info\": {\n    \"singularName\": \"home\",\n    \"pluralName\": \"homes\",\n    \"displayName\": \"Home\",\n    \"description\": \"\"\n  },\n  \"options\": {\n    \"draftAndPublish\": false\n  },\n  \"pluginOptions\": {},\n  \"attributes\": {\n    \"title\": {\n      \"type\": \"string\"\n    },\n    \"slug\": {\n      \"type\": \"uid\",\n      \"targetField\": \"title\"\n    }\n  }\n}\n"
  },
  {
    "path": "playground/src/api/home/controllers/home.ts",
    "content": "/**\n * home controller\n */\n\nimport { factories } from '@strapi/strapi';\n\nexport default factories.createCoreController('api::home.home');\n"
  },
  {
    "path": "playground/src/api/home/routes/home.ts",
    "content": "\n/**\n * home router\n */\n\nimport { factories } from '@strapi/strapi';\n\nexport default factories.createCoreRouter('api::home.home');\n"
  },
  {
    "path": "playground/src/api/home/services/home.ts",
    "content": "/**\n * home service\n */\n\nimport { factories } from '@strapi/strapi';\n\nexport default factories.createCoreService('api::home.home');\n"
  },
  {
    "path": "playground/src/api/page/content-types/page/schema.json",
    "content": "{\n  \"kind\": \"collectionType\",\n  \"collectionName\": \"pages\",\n  \"info\": {\n    \"singularName\": \"page\",\n    \"pluralName\": \"pages\",\n    \"displayName\": \"Page\"\n  },\n  \"options\": {\n    \"draftAndPublish\": false\n  },\n  \"pluginOptions\": {},\n  \"attributes\": {\n    \"title\": {\n      \"type\": \"string\",\n      \"required\": true\n    }\n  }\n}\n"
  },
  {
    "path": "playground/src/api/page/controllers/page.ts",
    "content": "/**\n * page controller\n */\n\nimport { factories } from '@strapi/strapi';\n\nexport default factories.createCoreController('api::page.page');\n"
  },
  {
    "path": "playground/src/api/page/routes/page.ts",
    "content": "/**\n * page router\n */\n\nimport { factories } from '@strapi/strapi';\n\nexport default factories.createCoreRouter('api::page.page');\n"
  },
  {
    "path": "playground/src/api/page/services/page.ts",
    "content": "/**\n * page service\n */\n\nimport { factories } from '@strapi/strapi';\n\nmodule.exports = factories.createCoreService('api::page.page');\n"
  },
  {
    "path": "playground/src/extensions/.gitkeep",
    "content": ""
  },
  {
    "path": "playground/src/index.ts",
    "content": "// import type { Core } from '@strapi/strapi';\n\nexport default {\n  /**\n   * An asynchronous register function that runs before\n   * your application is initialized.\n   *\n   * This gives you an opportunity to extend code.\n   */\n  register(/* { strapi }: { strapi: Core.Strapi } */) {},\n\n  /**\n   * An asynchronous bootstrap function that runs before\n   * your application gets started.\n   *\n   * This gives you an opportunity to set up your data model,\n   * run jobs, or perform some special logic.\n   */\n  bootstrap(/* { strapi }: { strapi: Core.Strapi } */) {},\n};\n"
  },
  {
    "path": "playground/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"CommonJS\",\n    \"moduleResolution\": \"Node\",\n    \"lib\": [\"ES2020\"],\n    \"target\": \"ES2019\",\n    \"strict\": false,\n    \"skipLibCheck\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"incremental\": true,\n    \"esModuleInterop\": true,\n    \"resolveJsonModule\": true,\n    \"noEmitOnError\": true,\n    \"noImplicitThis\": true,\n    \"outDir\": \"dist\",\n    \"rootDir\": \".\",\n  },\n  \"include\": [\n    // Include root files\n    \"./\",\n    // Include all ts files\n    \"./**/*.ts\",\n    // Include all js files\n    \"./**/*.js\",\n    // Force the JSON files in the src folder to be included\n    \"src/**/*.json\"\n  ],\n\n  \"exclude\": [\n    \"node_modules/\",\n    \"build/\",\n    \"dist/\",\n    \".cache/\",\n    \".tmp/\",\n\n    // Do not include admin files in the server compilation\n    \"src/admin/\",\n    // Do not include test files\n    \"**/*.test.*\",\n    // Do not include plugins in the server compilation\n    \"src/plugins/**\"\n  ]\n}\n"
  },
  {
    "path": "playground/types/generated/components.d.ts",
    "content": "/*\n * The app doesn't have any components yet.\n */\n"
  },
  {
    "path": "playground/types/generated/contentTypes.d.ts",
    "content": "import type { Schema, Struct } from '@strapi/strapi';\n\nexport interface AdminApiToken extends Struct.CollectionTypeSchema {\n  collectionName: 'strapi_api_tokens';\n  info: {\n    description: '';\n    displayName: 'Api Token';\n    name: 'Api Token';\n    pluralName: 'api-tokens';\n    singularName: 'api-token';\n  };\n  options: {\n    draftAndPublish: false;\n  };\n  pluginOptions: {\n    'content-manager': {\n      visible: false;\n    };\n    'content-type-builder': {\n      visible: false;\n    };\n  };\n  attributes: {\n    accessKey: Schema.Attribute.String &\n      Schema.Attribute.Required &\n      Schema.Attribute.SetMinMaxLength<{\n        minLength: 1;\n      }>;\n    createdAt: Schema.Attribute.DateTime;\n    createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &\n      Schema.Attribute.Private;\n    description: Schema.Attribute.String &\n      Schema.Attribute.SetMinMaxLength<{\n        minLength: 1;\n      }> &\n      Schema.Attribute.DefaultTo<''>;\n    encryptedKey: Schema.Attribute.Text &\n      Schema.Attribute.SetMinMaxLength<{\n        minLength: 1;\n      }>;\n    expiresAt: Schema.Attribute.DateTime;\n    lastUsedAt: Schema.Attribute.DateTime;\n    lifespan: Schema.Attribute.BigInteger;\n    locale: Schema.Attribute.String & Schema.Attribute.Private;\n    localizations: Schema.Attribute.Relation<'oneToMany', 'admin::api-token'> &\n      Schema.Attribute.Private;\n    name: Schema.Attribute.String &\n      Schema.Attribute.Required &\n      Schema.Attribute.Unique &\n      Schema.Attribute.SetMinMaxLength<{\n        minLength: 1;\n      }>;\n    permissions: Schema.Attribute.Relation<\n      'oneToMany',\n      'admin::api-token-permission'\n    >;\n    publishedAt: Schema.Attribute.DateTime;\n    type: Schema.Attribute.Enumeration<['read-only', 'full-access', 'custom']> &\n      Schema.Attribute.Required &\n      Schema.Attribute.DefaultTo<'read-only'>;\n    updatedAt: Schema.Attribute.DateTime;\n    updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &\n      Schema.Attribute.Private;\n  };\n}\n\nexport interface AdminApiTokenPermission extends Struct.CollectionTypeSchema {\n  collectionName: 'strapi_api_token_permissions';\n  info: {\n    description: '';\n    displayName: 'API Token Permission';\n    name: 'API Token Permission';\n    pluralName: 'api-token-permissions';\n    singularName: 'api-token-permission';\n  };\n  options: {\n    draftAndPublish: false;\n  };\n  pluginOptions: {\n    'content-manager': {\n      visible: false;\n    };\n    'content-type-builder': {\n      visible: false;\n    };\n  };\n  attributes: {\n    action: Schema.Attribute.String &\n      Schema.Attribute.Required &\n      Schema.Attribute.SetMinMaxLength<{\n        minLength: 1;\n      }>;\n    createdAt: Schema.Attribute.DateTime;\n    createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &\n      Schema.Attribute.Private;\n    locale: Schema.Attribute.String & Schema.Attribute.Private;\n    localizations: Schema.Attribute.Relation<\n      'oneToMany',\n      'admin::api-token-permission'\n    > &\n      Schema.Attribute.Private;\n    publishedAt: Schema.Attribute.DateTime;\n    token: Schema.Attribute.Relation<'manyToOne', 'admin::api-token'>;\n    updatedAt: Schema.Attribute.DateTime;\n    updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &\n      Schema.Attribute.Private;\n  };\n}\n\nexport interface AdminPermission extends Struct.CollectionTypeSchema {\n  collectionName: 'admin_permissions';\n  info: {\n    description: '';\n    displayName: 'Permission';\n    name: 'Permission';\n    pluralName: 'permissions';\n    singularName: 'permission';\n  };\n  options: {\n    draftAndPublish: false;\n  };\n  pluginOptions: {\n    'content-manager': {\n      visible: false;\n    };\n    'content-type-builder': {\n      visible: false;\n    };\n  };\n  attributes: {\n    action: Schema.Attribute.String &\n      Schema.Attribute.Required &\n      Schema.Attribute.SetMinMaxLength<{\n        minLength: 1;\n      }>;\n    actionParameters: Schema.Attribute.JSON & Schema.Attribute.DefaultTo<{}>;\n    conditions: Schema.Attribute.JSON & Schema.Attribute.DefaultTo<[]>;\n    createdAt: Schema.Attribute.DateTime;\n    createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &\n      Schema.Attribute.Private;\n    locale: Schema.Attribute.String & Schema.Attribute.Private;\n    localizations: Schema.Attribute.Relation<'oneToMany', 'admin::permission'> &\n      Schema.Attribute.Private;\n    properties: Schema.Attribute.JSON & Schema.Attribute.DefaultTo<{}>;\n    publishedAt: Schema.Attribute.DateTime;\n    role: Schema.Attribute.Relation<'manyToOne', 'admin::role'>;\n    subject: Schema.Attribute.String &\n      Schema.Attribute.SetMinMaxLength<{\n        minLength: 1;\n      }>;\n    updatedAt: Schema.Attribute.DateTime;\n    updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &\n      Schema.Attribute.Private;\n  };\n}\n\nexport interface AdminRole extends Struct.CollectionTypeSchema {\n  collectionName: 'admin_roles';\n  info: {\n    description: '';\n    displayName: 'Role';\n    name: 'Role';\n    pluralName: 'roles';\n    singularName: 'role';\n  };\n  options: {\n    draftAndPublish: false;\n  };\n  pluginOptions: {\n    'content-manager': {\n      visible: false;\n    };\n    'content-type-builder': {\n      visible: false;\n    };\n  };\n  attributes: {\n    code: Schema.Attribute.String &\n      Schema.Attribute.Required &\n      Schema.Attribute.Unique &\n      Schema.Attribute.SetMinMaxLength<{\n        minLength: 1;\n      }>;\n    createdAt: Schema.Attribute.DateTime;\n    createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &\n      Schema.Attribute.Private;\n    description: Schema.Attribute.String;\n    locale: Schema.Attribute.String & Schema.Attribute.Private;\n    localizations: Schema.Attribute.Relation<'oneToMany', 'admin::role'> &\n      Schema.Attribute.Private;\n    name: Schema.Attribute.String &\n      Schema.Attribute.Required &\n      Schema.Attribute.Unique &\n      Schema.Attribute.SetMinMaxLength<{\n        minLength: 1;\n      }>;\n    permissions: Schema.Attribute.Relation<'oneToMany', 'admin::permission'>;\n    publishedAt: Schema.Attribute.DateTime;\n    updatedAt: Schema.Attribute.DateTime;\n    updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &\n      Schema.Attribute.Private;\n    users: Schema.Attribute.Relation<'manyToMany', 'admin::user'>;\n  };\n}\n\nexport interface AdminSession extends Struct.CollectionTypeSchema {\n  collectionName: 'strapi_sessions';\n  info: {\n    description: 'Session Manager storage';\n    displayName: 'Session';\n    name: 'Session';\n    pluralName: 'sessions';\n    singularName: 'session';\n  };\n  options: {\n    draftAndPublish: false;\n  };\n  pluginOptions: {\n    'content-manager': {\n      visible: false;\n    };\n    'content-type-builder': {\n      visible: false;\n    };\n    i18n: {\n      localized: false;\n    };\n  };\n  attributes: {\n    absoluteExpiresAt: Schema.Attribute.DateTime & Schema.Attribute.Private;\n    childId: Schema.Attribute.String & Schema.Attribute.Private;\n    createdAt: Schema.Attribute.DateTime;\n    createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &\n      Schema.Attribute.Private;\n    deviceId: Schema.Attribute.String &\n      Schema.Attribute.Required &\n      Schema.Attribute.Private;\n    expiresAt: Schema.Attribute.DateTime &\n      Schema.Attribute.Required &\n      Schema.Attribute.Private;\n    locale: Schema.Attribute.String & Schema.Attribute.Private;\n    localizations: Schema.Attribute.Relation<'oneToMany', 'admin::session'> &\n      Schema.Attribute.Private;\n    origin: Schema.Attribute.String &\n      Schema.Attribute.Required &\n      Schema.Attribute.Private;\n    publishedAt: Schema.Attribute.DateTime;\n    sessionId: Schema.Attribute.String &\n      Schema.Attribute.Required &\n      Schema.Attribute.Private &\n      Schema.Attribute.Unique;\n    status: Schema.Attribute.String & Schema.Attribute.Private;\n    type: Schema.Attribute.String & Schema.Attribute.Private;\n    updatedAt: Schema.Attribute.DateTime;\n    updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &\n      Schema.Attribute.Private;\n    userId: Schema.Attribute.String &\n      Schema.Attribute.Required &\n      Schema.Attribute.Private;\n  };\n}\n\nexport interface AdminTransferToken extends Struct.CollectionTypeSchema {\n  collectionName: 'strapi_transfer_tokens';\n  info: {\n    description: '';\n    displayName: 'Transfer Token';\n    name: 'Transfer Token';\n    pluralName: 'transfer-tokens';\n    singularName: 'transfer-token';\n  };\n  options: {\n    draftAndPublish: false;\n  };\n  pluginOptions: {\n    'content-manager': {\n      visible: false;\n    };\n    'content-type-builder': {\n      visible: false;\n    };\n  };\n  attributes: {\n    accessKey: Schema.Attribute.String &\n      Schema.Attribute.Required &\n      Schema.Attribute.SetMinMaxLength<{\n        minLength: 1;\n      }>;\n    createdAt: Schema.Attribute.DateTime;\n    createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &\n      Schema.Attribute.Private;\n    description: Schema.Attribute.String &\n      Schema.Attribute.SetMinMaxLength<{\n        minLength: 1;\n      }> &\n      Schema.Attribute.DefaultTo<''>;\n    expiresAt: Schema.Attribute.DateTime;\n    lastUsedAt: Schema.Attribute.DateTime;\n    lifespan: Schema.Attribute.BigInteger;\n    locale: Schema.Attribute.String & Schema.Attribute.Private;\n    localizations: Schema.Attribute.Relation<\n      'oneToMany',\n      'admin::transfer-token'\n    > &\n      Schema.Attribute.Private;\n    name: Schema.Attribute.String &\n      Schema.Attribute.Required &\n      Schema.Attribute.Unique &\n      Schema.Attribute.SetMinMaxLength<{\n        minLength: 1;\n      }>;\n    permissions: Schema.Attribute.Relation<\n      'oneToMany',\n      'admin::transfer-token-permission'\n    >;\n    publishedAt: Schema.Attribute.DateTime;\n    updatedAt: Schema.Attribute.DateTime;\n    updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &\n      Schema.Attribute.Private;\n  };\n}\n\nexport interface AdminTransferTokenPermission\n  extends Struct.CollectionTypeSchema {\n  collectionName: 'strapi_transfer_token_permissions';\n  info: {\n    description: '';\n    displayName: 'Transfer Token Permission';\n    name: 'Transfer Token Permission';\n    pluralName: 'transfer-token-permissions';\n    singularName: 'transfer-token-permission';\n  };\n  options: {\n    draftAndPublish: false;\n  };\n  pluginOptions: {\n    'content-manager': {\n      visible: false;\n    };\n    'content-type-builder': {\n      visible: false;\n    };\n  };\n  attributes: {\n    action: Schema.Attribute.String &\n      Schema.Attribute.Required &\n      Schema.Attribute.SetMinMaxLength<{\n        minLength: 1;\n      }>;\n    createdAt: Schema.Attribute.DateTime;\n    createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &\n      Schema.Attribute.Private;\n    locale: Schema.Attribute.String & Schema.Attribute.Private;\n    localizations: Schema.Attribute.Relation<\n      'oneToMany',\n      'admin::transfer-token-permission'\n    > &\n      Schema.Attribute.Private;\n    publishedAt: Schema.Attribute.DateTime;\n    token: Schema.Attribute.Relation<'manyToOne', 'admin::transfer-token'>;\n    updatedAt: Schema.Attribute.DateTime;\n    updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &\n      Schema.Attribute.Private;\n  };\n}\n\nexport interface AdminUser extends Struct.CollectionTypeSchema {\n  collectionName: 'admin_users';\n  info: {\n    description: '';\n    displayName: 'User';\n    name: 'User';\n    pluralName: 'users';\n    singularName: 'user';\n  };\n  options: {\n    draftAndPublish: false;\n  };\n  pluginOptions: {\n    'content-manager': {\n      visible: false;\n    };\n    'content-type-builder': {\n      visible: false;\n    };\n  };\n  attributes: {\n    blocked: Schema.Attribute.Boolean &\n      Schema.Attribute.Private &\n      Schema.Attribute.DefaultTo<false>;\n    createdAt: Schema.Attribute.DateTime;\n    createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &\n      Schema.Attribute.Private;\n    email: Schema.Attribute.Email &\n      Schema.Attribute.Required &\n      Schema.Attribute.Private &\n      Schema.Attribute.Unique &\n      Schema.Attribute.SetMinMaxLength<{\n        minLength: 6;\n      }>;\n    firstname: Schema.Attribute.String &\n      Schema.Attribute.SetMinMaxLength<{\n        minLength: 1;\n      }>;\n    isActive: Schema.Attribute.Boolean &\n      Schema.Attribute.Private &\n      Schema.Attribute.DefaultTo<false>;\n    lastname: Schema.Attribute.String &\n      Schema.Attribute.SetMinMaxLength<{\n        minLength: 1;\n      }>;\n    locale: Schema.Attribute.String & Schema.Attribute.Private;\n    localizations: Schema.Attribute.Relation<'oneToMany', 'admin::user'> &\n      Schema.Attribute.Private;\n    password: Schema.Attribute.Password &\n      Schema.Attribute.Private &\n      Schema.Attribute.SetMinMaxLength<{\n        minLength: 6;\n      }>;\n    preferedLanguage: Schema.Attribute.String;\n    publishedAt: Schema.Attribute.DateTime;\n    registrationToken: Schema.Attribute.String & Schema.Attribute.Private;\n    resetPasswordToken: Schema.Attribute.String & Schema.Attribute.Private;\n    roles: Schema.Attribute.Relation<'manyToMany', 'admin::role'> &\n      Schema.Attribute.Private;\n    updatedAt: Schema.Attribute.DateTime;\n    updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &\n      Schema.Attribute.Private;\n    username: Schema.Attribute.String;\n  };\n}\n\nexport interface ApiHomeHome extends Struct.SingleTypeSchema {\n  collectionName: 'homes';\n  info: {\n    description: '';\n    displayName: 'Home';\n    pluralName: 'homes';\n    singularName: 'home';\n  };\n  options: {\n    draftAndPublish: false;\n  };\n  attributes: {\n    createdAt: Schema.Attribute.DateTime;\n    createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &\n      Schema.Attribute.Private;\n    locale: Schema.Attribute.String & Schema.Attribute.Private;\n    localizations: Schema.Attribute.Relation<'oneToMany', 'api::home.home'> &\n      Schema.Attribute.Private;\n    publishedAt: Schema.Attribute.DateTime;\n    slug: Schema.Attribute.UID<'title'>;\n    title: Schema.Attribute.String;\n    updatedAt: Schema.Attribute.DateTime;\n    updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &\n      Schema.Attribute.Private;\n  };\n}\n\nexport interface ApiPagePage extends Struct.CollectionTypeSchema {\n  collectionName: 'pages';\n  info: {\n    displayName: 'Page';\n    pluralName: 'pages';\n    singularName: 'page';\n  };\n  options: {\n    draftAndPublish: false;\n  };\n  attributes: {\n    createdAt: Schema.Attribute.DateTime;\n    createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &\n      Schema.Attribute.Private;\n    locale: Schema.Attribute.String & Schema.Attribute.Private;\n    localizations: Schema.Attribute.Relation<'oneToMany', 'api::page.page'> &\n      Schema.Attribute.Private;\n    publishedAt: Schema.Attribute.DateTime;\n    title: Schema.Attribute.String & Schema.Attribute.Required;\n    updatedAt: Schema.Attribute.DateTime;\n    updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &\n      Schema.Attribute.Private;\n  };\n}\n\nexport interface PluginContentReleasesRelease\n  extends Struct.CollectionTypeSchema {\n  collectionName: 'strapi_releases';\n  info: {\n    displayName: 'Release';\n    pluralName: 'releases';\n    singularName: 'release';\n  };\n  options: {\n    draftAndPublish: false;\n  };\n  pluginOptions: {\n    'content-manager': {\n      visible: false;\n    };\n    'content-type-builder': {\n      visible: false;\n    };\n  };\n  attributes: {\n    actions: Schema.Attribute.Relation<\n      'oneToMany',\n      'plugin::content-releases.release-action'\n    >;\n    createdAt: Schema.Attribute.DateTime;\n    createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &\n      Schema.Attribute.Private;\n    locale: Schema.Attribute.String & Schema.Attribute.Private;\n    localizations: Schema.Attribute.Relation<\n      'oneToMany',\n      'plugin::content-releases.release'\n    > &\n      Schema.Attribute.Private;\n    name: Schema.Attribute.String & Schema.Attribute.Required;\n    publishedAt: Schema.Attribute.DateTime;\n    releasedAt: Schema.Attribute.DateTime;\n    scheduledAt: Schema.Attribute.DateTime;\n    status: Schema.Attribute.Enumeration<\n      ['ready', 'blocked', 'failed', 'done', 'empty']\n    > &\n      Schema.Attribute.Required;\n    timezone: Schema.Attribute.String;\n    updatedAt: Schema.Attribute.DateTime;\n    updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &\n      Schema.Attribute.Private;\n  };\n}\n\nexport interface PluginContentReleasesReleaseAction\n  extends Struct.CollectionTypeSchema {\n  collectionName: 'strapi_release_actions';\n  info: {\n    displayName: 'Release Action';\n    pluralName: 'release-actions';\n    singularName: 'release-action';\n  };\n  options: {\n    draftAndPublish: false;\n  };\n  pluginOptions: {\n    'content-manager': {\n      visible: false;\n    };\n    'content-type-builder': {\n      visible: false;\n    };\n  };\n  attributes: {\n    contentType: Schema.Attribute.String & Schema.Attribute.Required;\n    createdAt: Schema.Attribute.DateTime;\n    createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &\n      Schema.Attribute.Private;\n    entryDocumentId: Schema.Attribute.String;\n    isEntryValid: Schema.Attribute.Boolean;\n    locale: Schema.Attribute.String & Schema.Attribute.Private;\n    localizations: Schema.Attribute.Relation<\n      'oneToMany',\n      'plugin::content-releases.release-action'\n    > &\n      Schema.Attribute.Private;\n    publishedAt: Schema.Attribute.DateTime;\n    release: Schema.Attribute.Relation<\n      'manyToOne',\n      'plugin::content-releases.release'\n    >;\n    type: Schema.Attribute.Enumeration<['publish', 'unpublish']> &\n      Schema.Attribute.Required;\n    updatedAt: Schema.Attribute.DateTime;\n    updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &\n      Schema.Attribute.Private;\n  };\n}\n\nexport interface PluginI18NLocale extends Struct.CollectionTypeSchema {\n  collectionName: 'i18n_locale';\n  info: {\n    collectionName: 'locales';\n    description: '';\n    displayName: 'Locale';\n    pluralName: 'locales';\n    singularName: 'locale';\n  };\n  options: {\n    draftAndPublish: false;\n  };\n  pluginOptions: {\n    'content-manager': {\n      visible: false;\n    };\n    'content-type-builder': {\n      visible: false;\n    };\n  };\n  attributes: {\n    code: Schema.Attribute.String & Schema.Attribute.Unique;\n    createdAt: Schema.Attribute.DateTime;\n    createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &\n      Schema.Attribute.Private;\n    locale: Schema.Attribute.String & Schema.Attribute.Private;\n    localizations: Schema.Attribute.Relation<\n      'oneToMany',\n      'plugin::i18n.locale'\n    > &\n      Schema.Attribute.Private;\n    name: Schema.Attribute.String &\n      Schema.Attribute.SetMinMax<\n        {\n          max: 50;\n          min: 1;\n        },\n        number\n      >;\n    publishedAt: Schema.Attribute.DateTime;\n    updatedAt: Schema.Attribute.DateTime;\n    updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &\n      Schema.Attribute.Private;\n  };\n}\n\nexport interface PluginReviewWorkflowsWorkflow\n  extends Struct.CollectionTypeSchema {\n  collectionName: 'strapi_workflows';\n  info: {\n    description: '';\n    displayName: 'Workflow';\n    name: 'Workflow';\n    pluralName: 'workflows';\n    singularName: 'workflow';\n  };\n  options: {\n    draftAndPublish: false;\n  };\n  pluginOptions: {\n    'content-manager': {\n      visible: false;\n    };\n    'content-type-builder': {\n      visible: false;\n    };\n  };\n  attributes: {\n    contentTypes: Schema.Attribute.JSON &\n      Schema.Attribute.Required &\n      Schema.Attribute.DefaultTo<'[]'>;\n    createdAt: Schema.Attribute.DateTime;\n    createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &\n      Schema.Attribute.Private;\n    locale: Schema.Attribute.String & Schema.Attribute.Private;\n    localizations: Schema.Attribute.Relation<\n      'oneToMany',\n      'plugin::review-workflows.workflow'\n    > &\n      Schema.Attribute.Private;\n    name: Schema.Attribute.String &\n      Schema.Attribute.Required &\n      Schema.Attribute.Unique;\n    publishedAt: Schema.Attribute.DateTime;\n    stageRequiredToPublish: Schema.Attribute.Relation<\n      'oneToOne',\n      'plugin::review-workflows.workflow-stage'\n    >;\n    stages: Schema.Attribute.Relation<\n      'oneToMany',\n      'plugin::review-workflows.workflow-stage'\n    >;\n    updatedAt: Schema.Attribute.DateTime;\n    updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &\n      Schema.Attribute.Private;\n  };\n}\n\nexport interface PluginReviewWorkflowsWorkflowStage\n  extends Struct.CollectionTypeSchema {\n  collectionName: 'strapi_workflows_stages';\n  info: {\n    description: '';\n    displayName: 'Stages';\n    name: 'Workflow Stage';\n    pluralName: 'workflow-stages';\n    singularName: 'workflow-stage';\n  };\n  options: {\n    draftAndPublish: false;\n    version: '1.1.0';\n  };\n  pluginOptions: {\n    'content-manager': {\n      visible: false;\n    };\n    'content-type-builder': {\n      visible: false;\n    };\n  };\n  attributes: {\n    color: Schema.Attribute.String & Schema.Attribute.DefaultTo<'#4945FF'>;\n    createdAt: Schema.Attribute.DateTime;\n    createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &\n      Schema.Attribute.Private;\n    locale: Schema.Attribute.String & Schema.Attribute.Private;\n    localizations: Schema.Attribute.Relation<\n      'oneToMany',\n      'plugin::review-workflows.workflow-stage'\n    > &\n      Schema.Attribute.Private;\n    name: Schema.Attribute.String;\n    permissions: Schema.Attribute.Relation<'manyToMany', 'admin::permission'>;\n    publishedAt: Schema.Attribute.DateTime;\n    updatedAt: Schema.Attribute.DateTime;\n    updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &\n      Schema.Attribute.Private;\n    workflow: Schema.Attribute.Relation<\n      'manyToOne',\n      'plugin::review-workflows.workflow'\n    >;\n  };\n}\n\nexport interface PluginUploadFile extends Struct.CollectionTypeSchema {\n  collectionName: 'files';\n  info: {\n    description: '';\n    displayName: 'File';\n    pluralName: 'files';\n    singularName: 'file';\n  };\n  options: {\n    draftAndPublish: false;\n  };\n  pluginOptions: {\n    'content-manager': {\n      visible: false;\n    };\n    'content-type-builder': {\n      visible: false;\n    };\n  };\n  attributes: {\n    alternativeText: Schema.Attribute.Text;\n    caption: Schema.Attribute.Text;\n    createdAt: Schema.Attribute.DateTime;\n    createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &\n      Schema.Attribute.Private;\n    ext: Schema.Attribute.String;\n    focalPoint: Schema.Attribute.JSON;\n    folder: Schema.Attribute.Relation<'manyToOne', 'plugin::upload.folder'> &\n      Schema.Attribute.Private;\n    folderPath: Schema.Attribute.String &\n      Schema.Attribute.Required &\n      Schema.Attribute.Private &\n      Schema.Attribute.SetMinMaxLength<{\n        minLength: 1;\n      }>;\n    formats: Schema.Attribute.JSON;\n    hash: Schema.Attribute.String & Schema.Attribute.Required;\n    height: Schema.Attribute.Integer;\n    locale: Schema.Attribute.String & Schema.Attribute.Private;\n    localizations: Schema.Attribute.Relation<\n      'oneToMany',\n      'plugin::upload.file'\n    > &\n      Schema.Attribute.Private;\n    mime: Schema.Attribute.String & Schema.Attribute.Required;\n    name: Schema.Attribute.String & Schema.Attribute.Required;\n    previewUrl: Schema.Attribute.Text;\n    provider: Schema.Attribute.String & Schema.Attribute.Required;\n    provider_metadata: Schema.Attribute.JSON;\n    publishedAt: Schema.Attribute.DateTime;\n    related: Schema.Attribute.Relation<'morphToMany'>;\n    size: Schema.Attribute.Decimal & Schema.Attribute.Required;\n    updatedAt: Schema.Attribute.DateTime;\n    updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &\n      Schema.Attribute.Private;\n    url: Schema.Attribute.Text & Schema.Attribute.Required;\n    width: Schema.Attribute.Integer;\n  };\n}\n\nexport interface PluginUploadFolder extends Struct.CollectionTypeSchema {\n  collectionName: 'upload_folders';\n  info: {\n    displayName: 'Folder';\n    pluralName: 'folders';\n    singularName: 'folder';\n  };\n  options: {\n    draftAndPublish: false;\n  };\n  pluginOptions: {\n    'content-manager': {\n      visible: false;\n    };\n    'content-type-builder': {\n      visible: false;\n    };\n  };\n  attributes: {\n    children: Schema.Attribute.Relation<'oneToMany', 'plugin::upload.folder'>;\n    createdAt: Schema.Attribute.DateTime;\n    createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &\n      Schema.Attribute.Private;\n    files: Schema.Attribute.Relation<'oneToMany', 'plugin::upload.file'>;\n    locale: Schema.Attribute.String & Schema.Attribute.Private;\n    localizations: Schema.Attribute.Relation<\n      'oneToMany',\n      'plugin::upload.folder'\n    > &\n      Schema.Attribute.Private;\n    name: Schema.Attribute.String &\n      Schema.Attribute.Required &\n      Schema.Attribute.SetMinMaxLength<{\n        minLength: 1;\n      }>;\n    parent: Schema.Attribute.Relation<'manyToOne', 'plugin::upload.folder'>;\n    path: Schema.Attribute.String &\n      Schema.Attribute.Required &\n      Schema.Attribute.SetMinMaxLength<{\n        minLength: 1;\n      }>;\n    pathId: Schema.Attribute.Integer &\n      Schema.Attribute.Required &\n      Schema.Attribute.Unique;\n    publishedAt: Schema.Attribute.DateTime;\n    updatedAt: Schema.Attribute.DateTime;\n    updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &\n      Schema.Attribute.Private;\n  };\n}\n\nexport interface PluginUsersPermissionsPermission\n  extends Struct.CollectionTypeSchema {\n  collectionName: 'up_permissions';\n  info: {\n    description: '';\n    displayName: 'Permission';\n    name: 'permission';\n    pluralName: 'permissions';\n    singularName: 'permission';\n  };\n  options: {\n    draftAndPublish: false;\n  };\n  pluginOptions: {\n    'content-manager': {\n      visible: false;\n    };\n    'content-type-builder': {\n      visible: false;\n    };\n  };\n  attributes: {\n    action: Schema.Attribute.String & Schema.Attribute.Required;\n    createdAt: Schema.Attribute.DateTime;\n    createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &\n      Schema.Attribute.Private;\n    locale: Schema.Attribute.String & Schema.Attribute.Private;\n    localizations: Schema.Attribute.Relation<\n      'oneToMany',\n      'plugin::users-permissions.permission'\n    > &\n      Schema.Attribute.Private;\n    publishedAt: Schema.Attribute.DateTime;\n    role: Schema.Attribute.Relation<\n      'manyToOne',\n      'plugin::users-permissions.role'\n    >;\n    updatedAt: Schema.Attribute.DateTime;\n    updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &\n      Schema.Attribute.Private;\n  };\n}\n\nexport interface PluginUsersPermissionsRole\n  extends Struct.CollectionTypeSchema {\n  collectionName: 'up_roles';\n  info: {\n    description: '';\n    displayName: 'Role';\n    name: 'role';\n    pluralName: 'roles';\n    singularName: 'role';\n  };\n  options: {\n    draftAndPublish: false;\n  };\n  pluginOptions: {\n    'content-manager': {\n      visible: false;\n    };\n    'content-type-builder': {\n      visible: false;\n    };\n  };\n  attributes: {\n    createdAt: Schema.Attribute.DateTime;\n    createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &\n      Schema.Attribute.Private;\n    description: Schema.Attribute.String;\n    locale: Schema.Attribute.String & Schema.Attribute.Private;\n    localizations: Schema.Attribute.Relation<\n      'oneToMany',\n      'plugin::users-permissions.role'\n    > &\n      Schema.Attribute.Private;\n    name: Schema.Attribute.String &\n      Schema.Attribute.Required &\n      Schema.Attribute.SetMinMaxLength<{\n        minLength: 3;\n      }>;\n    permissions: Schema.Attribute.Relation<\n      'oneToMany',\n      'plugin::users-permissions.permission'\n    >;\n    publishedAt: Schema.Attribute.DateTime;\n    type: Schema.Attribute.String & Schema.Attribute.Unique;\n    updatedAt: Schema.Attribute.DateTime;\n    updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &\n      Schema.Attribute.Private;\n    users: Schema.Attribute.Relation<\n      'oneToMany',\n      'plugin::users-permissions.user'\n    >;\n  };\n}\n\nexport interface PluginUsersPermissionsUser\n  extends Struct.CollectionTypeSchema {\n  collectionName: 'up_users';\n  info: {\n    description: '';\n    displayName: 'User';\n    name: 'user';\n    pluralName: 'users';\n    singularName: 'user';\n  };\n  options: {\n    draftAndPublish: false;\n    timestamps: true;\n  };\n  attributes: {\n    blocked: Schema.Attribute.Boolean & Schema.Attribute.DefaultTo<false>;\n    confirmationToken: Schema.Attribute.String & Schema.Attribute.Private;\n    confirmed: Schema.Attribute.Boolean & Schema.Attribute.DefaultTo<false>;\n    createdAt: Schema.Attribute.DateTime;\n    createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &\n      Schema.Attribute.Private;\n    email: Schema.Attribute.Email &\n      Schema.Attribute.Required &\n      Schema.Attribute.SetMinMaxLength<{\n        minLength: 6;\n      }>;\n    locale: Schema.Attribute.String & Schema.Attribute.Private;\n    localizations: Schema.Attribute.Relation<\n      'oneToMany',\n      'plugin::users-permissions.user'\n    > &\n      Schema.Attribute.Private;\n    password: Schema.Attribute.Password &\n      Schema.Attribute.Private &\n      Schema.Attribute.SetMinMaxLength<{\n        minLength: 6;\n      }>;\n    provider: Schema.Attribute.String;\n    publishedAt: Schema.Attribute.DateTime;\n    resetPasswordToken: Schema.Attribute.String & Schema.Attribute.Private;\n    role: Schema.Attribute.Relation<\n      'manyToOne',\n      'plugin::users-permissions.role'\n    >;\n    updatedAt: Schema.Attribute.DateTime;\n    updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &\n      Schema.Attribute.Private;\n    username: Schema.Attribute.String &\n      Schema.Attribute.Required &\n      Schema.Attribute.Unique &\n      Schema.Attribute.SetMinMaxLength<{\n        minLength: 3;\n      }>;\n  };\n}\n\ndeclare module '@strapi/strapi' {\n  export module Public {\n    export interface ContentTypeSchemas {\n      'admin::api-token': AdminApiToken;\n      'admin::api-token-permission': AdminApiTokenPermission;\n      'admin::permission': AdminPermission;\n      'admin::role': AdminRole;\n      'admin::session': AdminSession;\n      'admin::transfer-token': AdminTransferToken;\n      'admin::transfer-token-permission': AdminTransferTokenPermission;\n      'admin::user': AdminUser;\n      'api::home.home': ApiHomeHome;\n      'api::page.page': ApiPagePage;\n      'plugin::content-releases.release': PluginContentReleasesRelease;\n      'plugin::content-releases.release-action': PluginContentReleasesReleaseAction;\n      'plugin::i18n.locale': PluginI18NLocale;\n      'plugin::review-workflows.workflow': PluginReviewWorkflowsWorkflow;\n      'plugin::review-workflows.workflow-stage': PluginReviewWorkflowsWorkflowStage;\n      'plugin::upload.file': PluginUploadFile;\n      'plugin::upload.folder': PluginUploadFolder;\n      'plugin::users-permissions.permission': PluginUsersPermissionsPermission;\n      'plugin::users-permissions.role': PluginUsersPermissionsRole;\n      'plugin::users-permissions.user': PluginUsersPermissionsUser;\n    }\n  }\n}\n"
  },
  {
    "path": "server/bootstrap.js",
    "content": "'use strict';\n\nimport fs from 'fs';\n\nimport ConfigType from './config/type';\nimport defaultTypes from './config/types';\nimport { logMessage } from './utils';\n\n/**\n * An asynchronous bootstrap function that runs before\n * your application gets started.\n *\n * This gives you an opportunity to set up your data model,\n * run jobs, or perform some special logic.\n *\n * See more details here: https://strapi.io/documentation/v3.x/concepts/configurations.html#bootstrap\n */\n\nexport default async () => {\n  // Register config types.\n  const registerTypes = () => {\n    const types = {};\n\n    // The default types provided by the plugin.\n    defaultTypes(strapi).map((type) => {\n      if (!strapi.config.get('plugin::config-sync.excludedTypes').includes(type.configName)) {\n        types[type.configName] = new ConfigType(type);\n      }\n    });\n\n    // The types provided by other plugins.\n    strapi.plugin('config-sync').pluginTypes.map((type) => {\n      if (!strapi.config.get('plugin::config-sync.excludedTypes').includes(type.configName)) {\n        types[type.configName] = new ConfigType(type);\n      }\n    });\n\n    // The custom types provided by the user.\n    strapi.config.get('plugin::config-sync.customTypes').map((type) => {\n      if (!strapi.config.get('plugin::config-sync.excludedTypes').includes(type.configName)) {\n        types[type.configName] = new ConfigType(type);\n      }\n    });\n\n    return types;\n  };\n  strapi.plugin('config-sync').types = registerTypes();\n\n  // Import on bootstrap.\n  if (strapi.config.get('plugin::config-sync.importOnBootstrap')) {\n    if (strapi.server.app.env === 'development') {\n      strapi.log.warn(logMessage(`You can't use the 'importOnBootstrap' setting in the development env.`));\n    } else if (process.env.CONFIG_SYNC_CLI === 'true') {\n      strapi.log.warn(logMessage(`The 'importOnBootstrap' setting was ignored because Strapi was started from the config-sync CLI itself.`));\n    } else if (fs.existsSync(strapi.config.get('plugin::config-sync.syncDir'))) {\n      await strapi.plugin('config-sync').service('main').importAllConfig();\n    }\n  }\n\n  // Register permission actions.\n  const actions = [\n    {\n      section: 'plugins',\n      displayName: 'Access the plugin settings',\n      uid: 'settings.read',\n      pluginName: 'config-sync',\n    },\n    {\n      section: 'plugins',\n      displayName: 'Menu link to plugin settings',\n      uid: 'menu-link',\n      pluginName: 'config-sync',\n    },\n  ];\n  await strapi.admin.services.permission.actionProvider.registerMany(actions);\n};\n"
  },
  {
    "path": "server/cli.js",
    "content": "#!/usr/bin/env node\n\nimport fs from 'fs';\nimport { Command } from 'commander';\nimport Table from 'cli-table';\nimport chalk from 'chalk';\nimport inquirer from 'inquirer';\nimport isEmpty from 'lodash/isEmpty';\nimport { createStrapi, compileStrapi } from '@strapi/strapi';\nimport gitDiff from 'git-diff';\nimport tsUtils from '@strapi/typescript-utils';\n\nimport warnings from './warnings';\nimport packageJSON from '../package.json';\n\nconst program = new Command();\n\nconst getStrapiApp = async () => {\n  process.env.CONFIG_SYNC_CLI = 'true';\n\n  const appDir = process.cwd();\n  const isTSProject = await tsUtils.isUsingTypeScript(appDir);\n  const outDir = await tsUtils.resolveOutDir(appDir);\n  const alreadyCompiled = await fs.existsSync(outDir);\n\n  let appContext;\n  if (!isTSProject || !alreadyCompiled) {\n    appContext = await compileStrapi();\n  } else {\n    const distDir = isTSProject ? outDir : appDir;\n    appContext = { appDir, distDir };\n  }\n\n  const app = await createStrapi(appContext).load();\n  return app;\n};\n\nconst initTable = (head) => {\n  return new Table({\n    head: [chalk.green('Name'), chalk.green(head || 'State')],\n    colWidths: [65, 20],\n    chars: { top: '═',\n      'top-mid': '╤',\n      'top-left': '╔',\n      'top-right': '╗',\n      bottom: '═',\n      'bottom-mid': '╧',\n      'bottom-left': '╚',\n      'bottom-right': '╝',\n      left: '║',\n      'left-mid': '╟',\n      mid: '─',\n      'mid-mid': '┼',\n      right: '║',\n      'right-mid': '╢',\n      middle: '│',\n    },\n  });\n};\n\nconst getConfigState = (diff, configName, syncType) => {\n  if (\n    diff.fileConfig[configName]\n    && diff.databaseConfig[configName]\n  ) {\n    return chalk.yellow(syncType ? 'Update' : 'Different');\n  } else if (\n    diff.fileConfig[configName]\n    && !diff.databaseConfig[configName]\n  ) {\n    if (syncType === 'import') {\n      return chalk.green('Create');\n    } else if (syncType === 'export') {\n      return chalk.red('Delete');\n    } else {\n      return chalk.red('Only in sync dir');\n    }\n  } else if (\n    !diff.fileConfig[configName]\n    && diff.databaseConfig[configName]\n  ) {\n    if (syncType === 'import') {\n      return chalk.red('Delete');\n    } else if (syncType === 'export') {\n      return chalk.green('Create');\n    } else {\n      return chalk.green('Only in DB');\n    }\n  }\n};\n\nconst handleAction = async (syncType, skipConfirm, configType, partials, force) => {\n  const app = await getStrapiApp();\n  const hasSyncDir = fs.existsSync(app.config.get('plugin::config-sync.syncDir'));\n\n  // No import with empty sync dir.\n  if (!hasSyncDir && syncType === 'import') {\n    console.log(`${chalk.yellow.bold('[warning]')} You can't import an empty sync directory. Please export before continuing.`);\n    process.exit(0);\n  }\n\n  const diff = await app.plugin('config-sync').service('main').getFormattedDiff();\n\n  // No changes.\n  if (isEmpty(diff.diff)) {\n    console.log(`${chalk.cyan.bold('[notice]')} There are no changes to ${syncType}.`);\n    process.exit(0);\n  }\n\n  // Init table.\n  const table = initTable('Action');\n  const configNames = partials && partials.split(',');\n  const partialDiff = {};\n\n  // Fill partialDiff with arguments.\n  if (configNames) {\n    configNames.map((name) => {\n      if (diff.diff[name]) partialDiff[name] = diff.diff[name];\n    });\n  }\n  if (configType) {\n    Object.keys(diff.diff).map((name) => {\n      if (configType === name.split('.')[0]) {\n        partialDiff[name] = diff.diff[name];\n      }\n    });\n  }\n\n  // No changes for partial diff.\n  if ((partials || configType) && isEmpty(partialDiff)) {\n    console.log(`${chalk.cyan.bold('[notice]')} There are no changes for the specified config.`);\n    process.exit(0);\n  }\n\n  const finalDiff = (partials || configType) && partialDiff ? partialDiff : diff.diff;\n\n  // Add diff to table.\n  Object.keys(finalDiff).map((configName) => {\n    table.push([configName, getConfigState(diff, configName, syncType)]);\n  });\n\n  // Print table.\n  if (hasSyncDir) {\n    console.log(table.toString(), '\\n');\n  }\n\n  // Prompt to confirm.\n  let answer = {};\n  if (!skipConfirm) {\n    answer = await inquirer.prompt([{\n      type: 'confirm',\n      name: 'confirm',\n      message: `Are you sure you want to ${syncType} the config changes?`,\n    }]);\n    console.log('');\n  }\n\n  // Preform the action.\n  if (skipConfirm || answer.confirm) {\n    if (syncType === 'import') {\n      const onSuccess = (name) => console.log(`${chalk.cyan.bold('[notice]')} Imported ${name}`);\n      try {\n        await Promise.all(Object.keys(finalDiff).map(async (name) => {\n          let warning;\n          if (\n            getConfigState(diff, name, syncType) === chalk.red('Delete')\n            && warnings.delete[name]\n          ) warning = warnings.delete[name];\n\n          await app.plugin('config-sync').service('main').importSingleConfig(name, onSuccess, force);\n          if (warning) console.log(`${chalk.yellow.bold('[warning]')} ${warning}`);\n        }));\n        console.log(`${chalk.green.bold('[success]')} Finished import`);\n      } catch (e) {\n        console.log(`${chalk.red.bold('[error]')} ${e}`);\n      }\n    }\n    if (syncType === 'export') {\n      const onSuccess = (name) => console.log(`${chalk.cyan.bold('[notice]')} Exported ${name}`);\n\n      try {\n        await Promise.all(Object.keys(finalDiff).map(async (name) => {\n          await app.plugin('config-sync').service('main').exportSingleConfig(name, onSuccess);\n        }));\n        console.log(`${chalk.green.bold('[success]')} Finished export`);\n      } catch (e) {\n        console.log(`${chalk.red.bold('[error]')} ${e}`);\n      }\n    }\n  }\n\n  process.exit(0);\n};\n\n// Initial program setup\nprogram.storeOptionsAsProperties(false).allowUnknownOption(true);\n\nprogram.helpOption('-h, --help', 'Display help for command');\nprogram.addHelpCommand('help [command]', 'Display help for command');\n\n// `$ config-sync version` (--version synonym)\nprogram.version(packageJSON.version, '-v, --version', 'Output the version number');\nprogram\n  .command('version')\n  .description('Output your version of the config-sync plugin')\n  .action(() => {\n    process.stdout.write(`${packageJSON.version}\\n`);\n    process.exit(0);\n  });\n\n// `$ config-sync import`\nprogram\n  .command('import')\n  .alias('i')\n  .option('-t, --type <type>', 'The type of config')\n  .option('-p, --partial <partials>', 'A comma separated string of configs')\n  .option('-y, --yes', 'Skip the confirm prompt')\n  .option('-f, --force', 'Ignore the soft setting')\n  .description('Import the config')\n  .action(async ({ yes, type, partial, force }) => {\n    return handleAction('import', yes, type, partial, force);\n  });\n\n// `$ config-sync export`\nprogram\n  .command('export')\n  .alias('e')\n  .option('-t, --type <type>', 'The type of config')\n  .option('-p, --partial <partials>', 'A comma separated string of configs')\n  .option('-y, --yes', 'Skip the confirm prompt')\n  .description('Export the config')\n  .action(async ({ yes, type, partial }) => {\n    return handleAction('export', yes, type, partial);\n  });\n\n// `$ config-sync diff`\nprogram\n  .command('diff')\n  .alias('d')\n  .description('The config diff')\n  .action(async (options, { args }) => {\n    const single = args[0];\n    const app = await getStrapiApp();\n    const diff = await app.plugin('config-sync').service('main').getFormattedDiff();\n\n    // No changes.\n    if (isEmpty(diff.diff)) {\n      console.log(`${chalk.cyan.bold('[notice]')} No differences between DB and sync directory.`);\n      process.exit(0);\n    }\n\n    // Single config diff.\n    if (single) {\n      // No changes.\n      if (!diff.fileConfig[single] && !diff.databaseConfig[single]) {\n        console.log(`${chalk.cyan.bold('[notice]')} No differences between DB and sync directory for ${single}.`);\n        process.exit(0);\n      }\n\n      // Git diff.\n      console.log(gitDiff(\n        JSON.stringify(diff.fileConfig[single], null, 2),\n        JSON.stringify(diff.databaseConfig[single], null, 2),\n        { color: true },\n      ));\n\n      process.exit(1);\n    }\n\n    // Init table.\n    const table = initTable();\n\n    // Add diff to table.\n    Object.keys(diff.diff).map((configName) => {\n      table.push([configName, getConfigState(diff, configName)]);\n    });\n\n    // Print table.\n    console.log(table.toString());\n\n    process.exit(1);\n  });\n\nprogram.parseAsync(process.argv);\n"
  },
  {
    "path": "server/config/type.js",
    "content": "import isEmpty from 'lodash/isEmpty';\nimport { logMessage, sanitizeConfig, dynamicSort, noLimit, getCombinedUid, getCombinedUidWhereFilter, getUidParamsFromName } from '../utils';\nimport { difference, same } from '../utils/getArrayDiff';\nimport queryFallBack from '../utils/queryFallBack';\n\nconst ConfigType = class ConfigType {\n  constructor({ queryString, configName, uid, jsonFields, relations, components }) {\n    if (!configName) {\n      strapi.log.error(logMessage('A config type was registered without a config name.'));\n      process.exit(0);\n    }\n    if (!queryString) {\n      strapi.log.error(logMessage(`No query string found for the '${configName}' config type.`));\n      process.exit(0);\n    }\n    // uid could be a single key or an array for a combined uid. So the type of uid is either string or string[]\n    if (typeof uid === \"string\") {\n      this.uidKeys = [uid];\n    } else if (Array.isArray(uid)) {\n      this.uidKeys = uid.sort();\n    } else {\n      strapi.log.error(logMessage(`Wrong uid config for the '${configName}' config type.`));\n      process.exit(0);\n    }\n    this.queryString = queryString;\n    this.configPrefix = configName;\n    this.jsonFields = jsonFields || [];\n    this.relations = relations || [];\n    this.components = components || null;\n  }\n\n  /**\n   * Import a single role-permissions config file into the db.\n   *\n   * @param {string} configName - The name of the config file.\n   * @param {string} configContent - The JSON content of the config file.\n   * @param {boolean} force - Ignore the soft setting.\n   * @returns {void}\n   */\n  importSingle = async (configName, configContent, force) => {\n    // Check if the config should be excluded.\n    const shouldExclude = !isEmpty(strapi.config.get('plugin::config-sync.excludedConfig').filter((option) => `${this.configPrefix}.${configName}`.startsWith(option)));\n    if (shouldExclude) return;\n\n    const softImport = strapi.config.get('plugin::config-sync.soft');\n    const queryAPI = strapi.query(this.queryString);\n    const uidParams = getUidParamsFromName(this.uidKeys, configName);\n    const combinedUidWhereFilter = getCombinedUidWhereFilter(this.uidKeys, uidParams);\n    let existingConfig = await queryAPI\n      .findOne({\n        where: combinedUidWhereFilter,\n        populate: this.relations.map(({ relationName }) => relationName),\n      });\n\n    // Config exists in DB but no configfile content --> delete config from DB\n    if (existingConfig && configContent === null) {\n      // Don't preform action when soft setting is true.\n      if (softImport && !force) return false;\n\n      // Exit when trying to delete the super-admin role.\n      if (this.configPrefix === 'admin-role' && configName === 'strapi-super-admin') {\n        return false;\n      }\n\n      await Promise.all(this.relations.map(async ({ queryString, parentName }) => {\n        const relations = await noLimit(strapi.query(queryString), {\n          where: {\n            [parentName]: existingConfig.id,\n          },\n        });\n\n        await Promise.all(relations.map(async (relation) => {\n          await queryFallBack.delete(queryString, { where: {\n            id: relation.id,\n          }});\n        }));\n      }));\n\n      await queryFallBack.delete(this.queryString, { where: {\n        id: existingConfig.id,\n      }});\n\n      return;\n    }\n\n    // Config does not exist in DB --> create config in DB\n    if (!existingConfig) {\n      // Format JSON fields.\n      const query = { ...configContent };\n      this.jsonFields.map((field) => query[field] = JSON.stringify(configContent[field]));\n\n      // Create entity.\n      this.relations.map(({ relationName }) => delete query[relationName]);\n      const newEntity = await queryFallBack.create(this.queryString, {\n        data: query,\n      });\n\n      // Create relation entities.\n      await Promise.all(this.relations.map(async ({ queryString, relationName, parentName }) => {\n        await Promise.all(configContent[relationName].map(async (relationEntity) => {\n          const relationQuery = { ...relationEntity, [parentName]: newEntity };\n          await queryFallBack.create(queryString, {\n            data: relationQuery,\n          });\n        }));\n      }));\n    } else { // Config does exist in DB --> update config in DB\n      // Don't preform action when soft setting is true.\n      if (softImport && !force) return false;\n\n      // Format JSON fields.\n      configContent = sanitizeConfig({\n        config: configContent,\n        configName,\n      });\n      const query = { ...configContent };\n      this.jsonFields.map((field) => query[field] = JSON.stringify(configContent[field]));\n\n      // Update entity.\n      this.relations.map(({ relationName }) => delete query[relationName]);\n      const entity = await queryFallBack.update(this.queryString, { where: combinedUidWhereFilter, data: query });\n\n      // Delete/create relations.\n      await Promise.all(this.relations.map(async ({ queryString, relationName, parentName, relationSortFields }) => {\n        const relationQueryApi = strapi.query(queryString);\n        existingConfig = sanitizeConfig({ config: existingConfig, configName, relation: relationName, relationSortFields });\n        configContent = sanitizeConfig({ config: configContent, configName, relation: relationName, relationSortFields });\n\n        const configToAdd = difference(configContent[relationName], existingConfig[relationName], relationSortFields);\n        const configToDelete = difference(existingConfig[relationName], configContent[relationName], relationSortFields);\n        const configToUpdate = same(configContent[relationName], existingConfig[relationName], relationSortFields);\n\n        await Promise.all(configToDelete.map(async (config) => {\n          const whereClause = {};\n          relationSortFields.map((sortField) => {\n            whereClause[sortField] = config[sortField];\n          });\n          await relationQueryApi.delete({\n            where: {\n              ...whereClause,\n              [parentName]: entity.id,\n            },\n          });\n        }));\n\n        await Promise.all(configToAdd.map(async (config) => {\n          await queryFallBack.create(queryString, {\n            data: { ...config, [parentName]: entity.id },\n          });\n        }));\n\n        await Promise.all(configToUpdate.map(async (config, index) => {\n          const whereClause = {};\n          relationSortFields.map((sortField) => {\n            whereClause[sortField] = config[sortField];\n          });\n\n          await relationQueryApi.update({\n            where: {\n              ...whereClause,\n              [parentName]: entity.id,\n            },\n            data: { ...config, [parentName]: entity.id },\n          });\n        }));\n      }));\n    }\n  }\n\n  /**\n   * Export a single core-store config to a file.\n   *\n   * @param {string} configName - The name of the config file.\n   * @returns {void}\n   */\n  exportSingle = async (configName) => {\n    const formattedDiff = await strapi.plugin('config-sync').service('main').getFormattedDiff(this.configPrefix);\n\n    // Check if the config should be excluded.\n    const shouldExclude = !isEmpty(strapi.config.get('plugin::config-sync.excludedConfig').filter((option) => configName.startsWith(option)));\n    if (shouldExclude) return;\n\n    const currentConfig = formattedDiff.databaseConfig[configName];\n\n    if (\n      !currentConfig\n      && formattedDiff.fileConfig[configName]\n    ) {\n      await strapi.plugin('config-sync').service('main').deleteConfigFile(configName);\n    } else {\n      const combinedUid = getCombinedUid(this.uidKeys, currentConfig);\n      await strapi.plugin('config-sync').service('main').writeConfigFile(this.configPrefix, combinedUid, currentConfig);\n    }\n  }\n\n\n  /**\n   * Zip config files\n   *\n   * @param {string} configName - The name of the zip archive.\n   * @returns {void}\n   */\n  zipConfig = async () => {\n    return strapi.plugin('config-sync').service('main').zipConfigFiles();\n  }\n\n  /**\n   * Get all role-permissions config from the db.\n   *\n   * @returns {object} Object with key value pairs of configs.\n   */\n   getAllFromDatabase = async () => {\n    const AllConfig = await noLimit(strapi.query(this.queryString), {\n      populate: this.components,\n    });\n    const configs = {};\n\n    await Promise.all(Object.entries(AllConfig).map(async ([configName, config]) => {\n      const combinedUid = getCombinedUid(this.uidKeys, config);\n      const combinedUidWhereFilter = getCombinedUidWhereFilter(this.uidKeys, config);\n\n      if (!combinedUid) {\n        strapi.log.warn(logMessage(`Missing data for entity with id ${config.id} of type ${this.configPrefix}`));\n        return;\n      }\n\n      // Check if the config should be excluded.\n      const shouldExclude = !isEmpty(strapi.config.get('plugin::config-sync.excludedConfig').filter((option) => `${this.configPrefix}.${combinedUid}`.startsWith(option)));\n      if (shouldExclude) return;\n\n      const formattedConfig = { ...sanitizeConfig({ config, configName }) };\n      await Promise.all(this.relations.map(async ({ queryString, relationName, relationSortFields, parentName }) => {\n        const relations = await noLimit(strapi.query(queryString), {\n          where: { [parentName]: combinedUidWhereFilter },\n        });\n\n        relations.map((relation) => sanitizeConfig({ config: relation, configName: relationName }));\n        relationSortFields.map((sortField) => {\n          relations.sort(dynamicSort(sortField));\n        });\n        formattedConfig[relationName] = relations;\n      }));\n\n      this.jsonFields.map((field) => formattedConfig[field] = JSON.parse(config[field]));\n      configs[`${this.configPrefix}.${combinedUid}`] = formattedConfig;\n    }));\n\n\n    return configs;\n  }\n\n  /**\n   * Import all core-store config files into the db.\n   *\n   * @returns {void}\n   */\n  importAll = async () => {\n    // The main.importAllConfig service will loop the core-store.importSingle service.\n    await strapi.plugin('config-sync').service('main').importAllConfig(this.configPrefix);\n  }\n\n  /**\n   * Export all core-store config to files.\n   *\n   * @returns {void}\n   */\n  exportAll = async () => {\n    // The main.importAllConfig service will loop the core-store.importSingle service.\n    await strapi.plugin('config-sync').service('main').exportAllConfig(this.configPrefix);\n  }\n};\n\nexport default ConfigType;\n"
  },
  {
    "path": "server/config/types.js",
    "content": "'use strict';\n\nconst types = (strapi) => {\n  // Initiate Strapi 'core-store' and 'admin-role' types.\n  const typesArray = [\n    {\n      configName: 'core-store',\n      queryString: 'strapi::core-store',\n      uid: 'key',\n      jsonFields: ['value'],\n    },\n    {\n      configName: 'admin-role',\n      queryString: 'admin::role',\n      uid: 'code',\n      relations: [{\n        queryString: 'admin::permission',\n        relationName: 'permissions',\n        parentName: 'role',\n        relationSortFields: ['action', 'subject'],\n      }],\n    },\n  ];\n\n  // Register plugin users-permissions 'role' type.\n  if (strapi.plugin('users-permissions')) {\n    typesArray.push({\n      configName: 'user-role',\n      queryString: 'plugin::users-permissions.role',\n      uid: 'type',\n      relations: [{\n        queryString: 'plugin::users-permissions.permission',\n        relationName: 'permissions',\n        parentName: 'role',\n        relationSortFields: ['action'],\n      }],\n    });\n  }\n\n  // Register plugin i18n 'locale' type.\n  if (strapi.plugin('i18n')) {\n    typesArray.push({\n      configName: 'i18n-locale',\n      queryString: 'plugin::i18n.locale',\n      uid: 'code',\n    });\n  }\n\n  return typesArray;\n};\n\nexport default types;\n"
  },
  {
    "path": "server/config.js",
    "content": "'use strict';\n\nexport default {\n  default: {\n    syncDir: \"config/sync/\",\n    minify: false,\n    soft: false,\n    importOnBootstrap: false,\n    customTypes: [],\n    excludedTypes: [],\n    excludedConfig: [\n      \"core-store.plugin_users-permissions_grant\",\n      \"core-store.plugin_upload_metrics\",\n      \"core-store.plugin_upload_api-folder\",\n      \"core-store.strapi_content_types_schema\",\n      \"core-store.ee_information\",\n    ],\n  },\n  validator() {},\n};\n"
  },
  {
    "path": "server/controllers/config.js",
    "content": "'use strict';\n\nimport fs from 'fs';\nimport isEmpty from 'lodash/isEmpty';\n\n/**\n * Main controllers for config import/export.\n */\n\nexport default {\n  /**\n   * Export all config, from db to filesystem.\n   *\n   * @param {object} ctx - Request context object.\n   * @returns {void}\n   */\n  exportAll: async (ctx) => {\n    if (isEmpty(ctx.request.body)) {\n      await strapi.plugin('config-sync').service('main').exportAllConfig();\n    } else {\n      await Promise.all(ctx.request.body.map(async (configName) => {\n        await strapi.plugin('config-sync').service('main').exportSingleConfig(configName);\n      }));\n    }\n\n\n    ctx.send({\n      message: `Config was successfully exported to ${strapi.config.get('plugin::config-sync.syncDir')}.`,\n    });\n  },\n\n  /**\n   * Import all config, from filesystem to db.\n   *\n   * @param {object} ctx - Request context object.\n   * @returns {void}\n   */\n  importAll: async (ctx) => {\n    // Check for existance of the config file sync dir.\n    if (!fs.existsSync(strapi.config.get('plugin::config-sync.syncDir'))) {\n      ctx.send({\n        message: 'No config files were found.',\n      });\n\n      return;\n    }\n\n    if (!ctx.request.body.config) {\n      ctx.send({\n        message: 'No config was specified for the import endpoint.',\n      });\n\n      return;\n    }\n\n    await Promise.all(ctx.request.body.config.map(async (configName) => {\n      await strapi.plugin('config-sync').service('main').importSingleConfig(configName, null, ctx.request.body.force);\n    }));\n\n    ctx.send({\n      message: 'Config was successfully imported.',\n    });\n  },\n\n  /**\n   * Get config diff between filesystem & db.\n   *\n   * @param {object} ctx - Request context object.\n   * @returns {object} formattedDiff - The formatted diff object.\n   * @returns {object} formattedDiff.fileConfig - The config as found in the filesystem.\n   * @returns {object} formattedDiff.databaseConfig - The config as found in the database.\n   * @returns {object} formattedDiff.diff - The diff between the file config and databse config.\n   */\n  getDiff: async (ctx) => {\n    // Check for existance of the config file sync dir.\n    if (!fs.existsSync(strapi.config.get('plugin::config-sync.syncDir'))) {\n      ctx.send({\n        message: 'No config files were found.',\n      });\n\n      return;\n    }\n\n    return strapi.plugin('config-sync').service('main').getFormattedDiff();\n  },\n\n  zipConfig: async (ctx) => {\n    // Check for existance of the config file sync dir.\n    if (!fs.existsSync(strapi.config.get('plugin::config-sync.syncDir'))) {\n      ctx.send({\n        message: 'No config files were found.',\n      });\n\n      return;\n    }\n\n    return strapi.plugin('config-sync').service('main').zipConfigFiles();\n  },\n\n  /**\n   * Get the current Strapi env.\n   * @returns {string} The current Strapi environment.\n   */\n  getAppEnv: async () => {\n    return {\n      env: strapi.server.app.env,\n      config: strapi.config.get('plugin::config-sync'),\n    };\n  },\n};\n"
  },
  {
    "path": "server/controllers/index.js",
    "content": "'use strict';\n\nimport config from './config';\n\nexport default {\n  config: config,\n};\n"
  },
  {
    "path": "server/index.js",
    "content": "'use strict';\n\nimport register from './register';\nimport bootstrap from './bootstrap';\nimport services from './services';\nimport routes from './routes';\nimport config from './config';\nimport controllers from './controllers';\n\nexport default {\n  register,\n  bootstrap,\n  routes,\n  config,\n  controllers,\n  services,\n};\n"
  },
  {
    "path": "server/register.js",
    "content": "'use strict';\n\nexport default async ({ strapi }) => {\n  // Instantiate the pluginTypes array.\n  if (!strapi.plugin('config-sync').pluginTypes) {\n    strapi.plugin('config-sync').pluginTypes = [];\n  }\n};\n"
  },
  {
    "path": "server/routes/admin.js",
    "content": "'use strict';\n\nexport default {\n  type: 'admin',\n  routes: [\n    {\n      method: \"POST\",\n      path: \"/export\",\n      handler: \"config.exportAll\",\n      config: {\n        policies: [],\n      },\n    },\n    {\n      method: \"POST\",\n      path: \"/import\",\n      handler: \"config.importAll\",\n      config: {\n        policies: [],\n      },\n    },\n    {\n      method: \"GET\",\n      path: \"/diff\",\n      handler: \"config.getDiff\",\n      config: {\n        policies: [],\n      },\n    },\n    {\n      method: \"GET\",\n      path: \"/zip\",\n      handler: \"config.zipConfig\",\n      config: {\n        policies: [],\n      },\n    },\n    {\n      method: \"GET\",\n      path: \"/app-env\",\n      handler: \"config.getAppEnv\",\n      config: {\n        policies: [],\n      },\n    },\n  ],\n};\n"
  },
  {
    "path": "server/routes/index.js",
    "content": "'use strict';\n\nimport adminRoutes from './admin';\n\nexport default {\n  admin: adminRoutes,\n};\n"
  },
  {
    "path": "server/services/index.js",
    "content": "'use strict';\n\nimport main from './main';\n\nexport default {\n  main,\n};\n"
  },
  {
    "path": "server/services/main.js",
    "content": "'use strict';\n\nimport isEmpty from 'lodash/isEmpty';\nimport fs from 'fs';\nimport util from 'util';\nimport AdmZip from 'adm-zip';\n\nimport difference from '../utils/getObjectDiff';\nimport { logMessage } from '../utils';\n\n/**\n * Main services for config import/export.\n */\n\nexport default () => ({\n  /**\n   * Write a single config file.\n   *\n   * @param {string} configType - The type of the config.\n   * @param {string} configName - The name of the config file.\n   * @param {string} fileContents - The JSON content of the config file.\n   * @returns {void}\n   */\n  writeConfigFile: async (configType, configName, fileContents) => {\n    // Check if the config should be excluded.\n    const shouldExclude = !isEmpty(strapi.config.get('plugin::config-sync.excludedConfig').filter((option) => `${configType}.${configName}`.startsWith(option)));\n    if (shouldExclude) return;\n\n    // Replace reserved characters in filenames.\n    configName = configName.replace(/:/g, \"#\").replace(/\\//g, \"$\");\n\n    // Check if the JSON content should be minified.\n    const json = !strapi.config.get('plugin::config-sync').minify\n      ? JSON.stringify(fileContents, null, 2)\n      : JSON.stringify(fileContents);\n\n    if (!fs.existsSync(strapi.config.get('plugin::config-sync.syncDir'))) {\n      fs.mkdirSync(strapi.config.get('plugin::config-sync.syncDir'), { recursive: true });\n    }\n\n    const writeFile = util.promisify(fs.writeFile);\n    await writeFile(`${strapi.config.get('plugin::config-sync.syncDir')}${configType}.${configName}.json`, json)\n      .then(() => {\n        // @TODO:\n        // Add logging for successfull config export.\n      })\n      .catch(() => {\n        // @TODO:\n        // Add logging for failed config export.\n      });\n  },\n\n  /**\n   * Delete config file.\n   *\n   * @param {string} configName - The name of the config file.\n   * @returns {void}\n   */\n  deleteConfigFile: async (configName) => {\n    // Check if the config should be excluded.\n    const shouldExclude = !isEmpty(strapi.config.get('plugin::config-sync.excludedConfig').filter((option) => configName.startsWith(option)));\n    if (shouldExclude) return;\n\n    // Replace reserved characters in filenames.\n    configName = configName.replace(/:/g, \"#\").replace(/\\//g, \"$\");\n\n    fs.unlinkSync(`${strapi.config.get('plugin::config-sync.syncDir')}${configName}.json`);\n  },\n\n  /**\n   * Zip config files.\n   *\n   * @param {string} configName - The name of the config file.\n   * @returns {void}\n   */\n  zipConfigFiles: async () => {\n    const fileName = `config-sync-${new Date().toJSON()}.zip`;\n\n    const zip = new AdmZip();\n    zip.addLocalFolder(strapi.config.get('plugin::config-sync.syncDir'));\n    const base64Data = zip.toBuffer().toString('base64');\n\n    return { base64Data, name: fileName, message: 'Success' };\n  },\n\n  /**\n   * Read from a config file.\n   *\n   * @param {string} configType - The type of config.\n   * @param {string} configName - The name of the config file.\n   * @returns {object} The JSON content of the config file.\n   */\n  readConfigFile: async (configType, configName) => {\n    // Replace reserved characters in filenames.\n    configName = configName.replace(/:/g, \"#\").replace(/\\//g, \"$\");\n\n    const readFile = util.promisify(fs.readFile);\n    return readFile(`${strapi.config.get('plugin::config-sync.syncDir')}${configType}.${configName}.json`)\n      .then((data) => {\n        return JSON.parse(data);\n      })\n      .catch(() => {\n        return null;\n      });\n  },\n\n\n  /**\n   * Get all the config JSON from the filesystem.\n   *\n   * @param {string} configType - Type of config to gather. Leave empty to get all config.\n   * @returns {object} Object with key value pairs of configs.\n   */\n  getAllConfigFromFiles: async (configType = null) => {\n    if (!fs.existsSync(strapi.config.get('plugin::config-sync.syncDir'))) {\n      return {};\n    }\n\n    const configFiles = fs.readdirSync(strapi.config.get('plugin::config-sync.syncDir'));\n\n    const getConfigs = async () => {\n      const fileConfigs = {};\n\n      await Promise.all(configFiles.map(async (file) => {\n        const type = file.split('.')[0]; // Grab the first part of the filename.\n        const name = file.split(/\\.(.+)/)[1].split('.').slice(0, -1).join('.'); // Grab the rest of the filename minus the file extension.\n\n        // Put back reserved characters from filenames.\n        const formattedName = name.replace(/#/g, \":\").replace(/\\$/g, \"/\");\n\n        if (\n          configType && configType !== type\n          || !strapi.plugin('config-sync').types[type]\n          || !isEmpty(strapi.config.get('plugin::config-sync.excludedConfig').filter((option) => `${type}.${name}`.startsWith(option)))\n        ) {\n          return;\n        }\n\n        const fileContents = await strapi.plugin('config-sync').service('main').readConfigFile(type, name);\n\n        if (!fileContents) {\n          strapi.log.warn(logMessage(`An empty config file '${file}' was found in the sync directory`));\n          return;\n        }\n\n        fileConfigs[`${type}.${formattedName}`] = fileContents;\n      }));\n\n      return fileConfigs;\n    };\n\n    return getConfigs();\n  },\n\n  /**\n   * Get all the config JSON from the database.\n   *\n   * @param {string} configType - Type of config to gather. Leave empty to get all config.\n   * @returns {object} Object with key value pairs of configs.\n   */\n  getAllConfigFromDatabase: async (configType = null) => {\n    const getConfigs = async () => {\n      let databaseConfigs = {};\n\n      await Promise.all(Object.entries(strapi.plugin('config-sync').types).map(async ([name, type]) => {\n        if (configType && configType !== name) {\n          return;\n        }\n\n        const config = await type.getAllFromDatabase();\n        databaseConfigs = Object.assign(config, databaseConfigs);\n      }));\n\n      return databaseConfigs;\n    };\n\n    return getConfigs();\n  },\n\n  /**\n   * Import all config files into the db.\n   *\n   * @param {string} configType - Type of config to impor. Leave empty to import all config.\n   * @param {object} onSuccess - Success callback to run on each single successfull import.\n   * @returns {void}\n   */\n  importAllConfig: async (configType = null, onSuccess) => {\n    const fileConfig = await strapi.plugin('config-sync').service('main').getAllConfigFromFiles();\n    const databaseConfig = await strapi.plugin('config-sync').service('main').getAllConfigFromDatabase();\n\n    const diff = difference(databaseConfig, fileConfig);\n\n    await Promise.all(Object.keys(diff).map(async (file) => {\n      const type = file.split('.')[0]; // Grab the first part of the filename.\n      const name = file.split(/\\.(.+)/)[1]; // Grab the rest of the filename.\n\n      if (configType && configType !== type) {\n        return;\n      }\n\n      await strapi.plugin('config-sync').service('main').importSingleConfig(`${type}.${name}`, onSuccess);\n    }));\n  },\n\n  /**\n   * Export all config files.\n   *\n   * @param {string} configType - Type of config to export. Leave empty to export all config.\n   * @param {object} onSuccess - Success callback to run on each single successfull import.\n   * @returns {void}\n   */\n  exportAllConfig: async (configType = null, onSuccess) => {\n    const fileConfig = await strapi.plugin('config-sync').service('main').getAllConfigFromFiles();\n    const databaseConfig = await strapi.plugin('config-sync').service('main').getAllConfigFromDatabase();\n\n    const diff = difference(databaseConfig, fileConfig);\n\n    await Promise.all(Object.keys(diff).map(async (file) => {\n      const type = file.split('.')[0]; // Grab the first part of the filename.\n      const name = file.split(/\\.(.+)/)[1]; // Grab the rest of the filename.\n\n      if (configType && configType !== type) {\n        return;\n      }\n\n      await strapi.plugin('config-sync').service('main').exportSingleConfig(`${type}.${name}`, onSuccess);\n    }));\n  },\n\n  /**\n   * Import a single config file into the db.\n   *\n   * @param {string} configName - The name of the config file.\n   * @param {object} onSuccess - Success callback to run on each single successfull import.\n   * @param {boolean} force - Ignore the soft setting.\n   * @returns {void}\n   */\n  importSingleConfig: async (configName, onSuccess, force) => {\n    // Check if the config should be excluded.\n    const shouldExclude = !isEmpty(strapi.config.get('plugin::config-sync.excludedConfig').filter((option) => configName.startsWith(option)));\n    if (shouldExclude) return;\n\n    const type = configName.split('.')[0]; // Grab the first part of the filename.\n    const name = configName.split(/\\.(.+)/)[1]; // Grab the rest of the filename.\n    const fileContents = await strapi.plugin('config-sync').service('main').readConfigFile(type, name);\n\n    try {\n      const importState = await strapi.plugin('config-sync').types[type].importSingle(name, fileContents, force);\n      if (onSuccess && importState !== false) onSuccess(`${type}.${name}`);\n    } catch (e) {\n      throw new Error(`Error when trying to import ${type}.${name}. ${e}`);\n    }\n  },\n\n  /**\n   * Export a single config file.\n   *\n   * @param {string} configName - The name of the config file.\n   * @param {object} onSuccess - Success callback to run on each single successfull import.\n   *\n   * @returns {void}\n   */\n   exportSingleConfig: async (configName, onSuccess) => {\n     // Check if the config should be excluded.\n    const shouldExclude = !isEmpty(strapi.config.get('plugin::config-sync.excludedConfig').filter((option) => configName.startsWith(option)));\n    if (shouldExclude) return;\n\n    const type = configName.split('.')[0]; // Grab the first part of the filename.\n    const name = configName.split(/\\.(.+)/)[1]; // Grab the rest of the filename.\n\n    try {\n      await strapi.plugin('config-sync').types[type].exportSingle(configName);\n      if (onSuccess) onSuccess(`${type}.${name}`);\n    } catch (e) {\n      throw new Error(`Error when trying to export ${type}.${name}. ${e}`);\n    }\n  },\n\n  /**\n   * Get the formatted diff.\n   *\n   * @param {string} configType - Type of config to get the diff of. Leave empty to get the diff of all config.\n   *\n   * @returns {object} - the formatted diff.\n   */\n  getFormattedDiff: async (configType = null) => {\n    const formattedDiff = {\n      fileConfig: {},\n      databaseConfig: {},\n      diff: {},\n    };\n\n    const fileConfig = await strapi.plugin('config-sync').service('main').getAllConfigFromFiles(configType);\n    const databaseConfig = await strapi.plugin('config-sync').service('main').getAllConfigFromDatabase(configType);\n\n    const diff = difference(databaseConfig, fileConfig);\n\n    formattedDiff.diff = diff;\n\n    Object.keys(diff).map((changedConfigName) => {\n      formattedDiff.fileConfig[changedConfigName] = fileConfig[changedConfigName];\n      formattedDiff.databaseConfig[changedConfigName] = databaseConfig[changedConfigName];\n    });\n\n    return formattedDiff;\n  },\n});\n"
  },
  {
    "path": "server/utils/getArrayDiff.js",
    "content": "export const difference = (arrayOne, arrayTwo, compareKeys) => {\n  return arrayOne.filter(({\n    [compareKeys[0]]: id1,\n    [compareKeys[1]]: id2,\n  }) => {\n    return !arrayTwo.some(({\n      [compareKeys[0]]: id3,\n      [compareKeys[1]]: id4,\n    }) => id1 === id3 && id2 === id4);\n  });\n};\n\nexport const same = (arrayOne, arrayTwo, compareKeys) => {\n  return arrayOne.filter(({\n    [compareKeys[0]]: id1,\n    [compareKeys[1]]: id2,\n    ...restOne\n  }) => {\n    return !arrayTwo.some(({\n      [compareKeys[0]]: id3,\n      [compareKeys[1]]: id4,\n      ...restTwo\n    }) => id1 === id3\n      && id2 === id4\n      && JSON.stringify(restOne) === JSON.stringify(restTwo));\n  });\n};\n"
  },
  {
    "path": "server/utils/getObjectDiff.js",
    "content": "'use strict';\n\nimport transform from 'lodash/transform';\nimport isEqual from 'lodash/isEqual';\nimport isArray from 'lodash/isArray';\nimport isObject from 'lodash/isObject';\n\n/**\n * Find difference between two objects\n * @param  {object} origObj - Source object to compare newObj against\n * @param  {object} newObj  - New object with potential changes\n * @return {object} differences\n */\nconst difference = (origObj, newObj) => {\n  let arrayIndexCounter = 0;\n\n  const newObjChange = transform(newObj, (result, value, key) => {\n    if (!isEqual(value, origObj[key])) {\n      const resultKey = isArray(origObj) ? arrayIndexCounter++ : key;\n      result[resultKey] = (isObject(value) && isObject(origObj[key])) ? difference(value, origObj[key]) : value;\n    }\n  });\n  const origObjChange = transform(origObj, (result, value, key) => {\n    if (!isEqual(value, newObj[key])) {\n      const resultKey = isArray(newObj) ? arrayIndexCounter++ : key;\n      result[resultKey] = (isObject(value) && isObject(newObj[key])) ? difference(value, newObj[key]) : value;\n    }\n  });\n\n  return Object.assign(newObjChange, origObjChange);\n};\n\nexport default difference;\n"
  },
  {
    "path": "server/utils/index.js",
    "content": "'use strict';\n\nconst COMBINED_UID_JOINSTR = '.combine-uid.';\n\nconst escapeUid = (uid) => typeof uid === \"string\" ? uid.replace(/\\.combine-uid\\./g, '.combine-uid-escape.') : uid;\nconst unEscapeUid = (uid) => typeof uid === \"string\" ? uid.replace(/\\.combine-uid-escape\\./g, '.combine-uid.') : uid;\nexport const getCombinedUid = (uidKeys, params) => uidKeys.map((uidKey) => escapeUid(params[uidKey])).join(COMBINED_UID_JOINSTR);\nexport const getCombinedUidWhereFilter = (uidKeys, params) => uidKeys.reduce(((akku, uidKey) => ({ ...akku, [uidKey]: params[uidKey] })), {});\nexport const getUidParamsFromName = (uidKeys, configName) => configName.split(COMBINED_UID_JOINSTR).map(unEscapeUid).reduce((akku, param, i) => ({ ...akku, [uidKeys[i]]: param }), {});\n\nexport const getCoreStore = () => {\n  return strapi.store({ type: 'plugin', name: 'config-sync' });\n};\n\nexport const getService = (name) => {\n  return strapi.plugin('config-sync').service(name);\n};\n\nexport const logMessage = (msg = '') => `[strapi-plugin-config-sync]: ${msg}`;\n\nconst sortByKeys = (unordered) => {\n  return Object.keys(unordered).sort().reduce((obj, key) => {\n      obj[key] = unordered[key];\n      return obj;\n    },\n    {},\n  );\n};\n\nexport const dynamicSort = (property) => {\n  let sortOrder = 1;\n\n  if (property[0] === \"-\") {\n      sortOrder = -1;\n      property = property.substr(1);\n  }\n\n  return (a, b) => {\n    if (sortOrder === -1) {\n      if (b[property]) {\n        return b[property].localeCompare(a[property]);\n      }\n    } else if (a[property]) {\n      return a[property].localeCompare(b[property]);\n    }\n  };\n};\n\nexport const sanitizeConfig = ({\n  config,\n  relation,\n  relationSortFields,\n  configName,\n}) => {\n  delete config._id;\n  delete config.id;\n  delete config.updatedAt;\n  delete config.createdAt;\n  delete config.publishedAt;\n\n  if (relation) {\n    const formattedRelations = [];\n\n    config[relation].map((relationEntity) => {\n      delete relationEntity._id;\n      delete relationEntity.id;\n      delete relationEntity.updatedAt;\n      delete config.publishedAt;\n      delete relationEntity.createdAt;\n      relationEntity = sortByKeys(relationEntity);\n\n      formattedRelations.push(relationEntity);\n    });\n\n    if (relationSortFields) {\n      relationSortFields.map((sortField) => {\n        formattedRelations.sort(dynamicSort(sortField));\n      });\n    }\n\n    config[relation] = formattedRelations;\n  }\n\n  // We recursively sanitize the config to remove environment specific data.\n  // Except for the plugin_content_manager_configuration.\n  // This is because that stores the \"edit the view\" data which includes only configuration, not content.\n  if (configName && !configName.startsWith('plugin_content_manager_configuration_')) {\n    const recursiveSanitizeConfig = (recursivedSanitizedConfig) => {\n      delete recursivedSanitizedConfig._id;\n      delete recursivedSanitizedConfig.id;\n      delete recursivedSanitizedConfig.updatedAt;\n      delete recursivedSanitizedConfig.createdAt;\n\n      Object.keys(recursivedSanitizedConfig).map((key, index) => {\n        if (recursivedSanitizedConfig[key] && typeof recursivedSanitizedConfig[key] === \"object\") {\n          recursiveSanitizeConfig(recursivedSanitizedConfig[key]);\n        }\n      });\n    };\n\n    recursiveSanitizeConfig(config);\n  }\n\n  return config;\n};\n\nexport const noLimit = async (query, parameters, limit = 100) => {\n  let entries = [];\n  const amountOfEntries = await query.count(parameters);\n\n  for (let i = 0; i < (amountOfEntries / limit); i++) {\n    /* eslint-disable-next-line */\n    const chunk = await query.findMany({\n      ...parameters,\n      limit: limit,\n      offset: (i * limit),\n      orderBy: 'id',\n    });\n    entries = [...chunk, ...entries];\n  }\n\n  return entries;\n};\n"
  },
  {
    "path": "server/utils/queryFallBack.js",
    "content": "const queryFallBack = {\n  create: async (queryString, options) => {\n    try {\n      const newEntity = await strapi.documents(queryString).create(options);\n\n      return newEntity;\n    } catch (e) {\n      return strapi.query(queryString).create(options);\n    }\n  },\n  update: async (queryString, options) => {\n    try {\n      const entity = await strapi.query(queryString).findOne(options);\n      const updatedEntity = await strapi.documents(queryString).update({\n        documentId: entity.documentId,\n        ...options,\n      });\n\n      return updatedEntity;\n    } catch (e) {\n      return strapi.query(queryString).update(options);\n    }\n  },\n  delete: async (queryString, options) => {\n    try {\n      const entity = await strapi.query(queryString).findOne(options);\n      await strapi.documents(queryString).delete({\n        documentId: entity.documentId,\n      });\n    } catch (e) {\n      await strapi.query(queryString).delete(options);\n    }\n  },\n};\n\nexport default queryFallBack;\n"
  },
  {
    "path": "server/warnings.js",
    "content": "'use strict';\n\nexport default {\n  delete: {\n    'user-role.public': \"You've just deleted a default role from the users-permissions plugin.\",\n    'user-role.authenticated': \"You've just deleted a default role from the users-permissions plugin.\",\n    'admin-role.strapi-super-admin': \"You've just deleted one of the default admin roles from Strapi.\",\n    'admin-role.strapi-author': \"You've just deleted one of the default admin roles from Strapi.\",\n    'admin-role.strapi-editor': \"You've just deleted one of the default admin roles from Strapi.\",\n  },\n};\n"
  }
]