diff --git a/.cspell.json b/.cspell.json index 0724507f38d5..ba31d09cfd5d 100644 --- a/.cspell.json +++ b/.cspell.json @@ -21,16 +21,17 @@ ], "ignorePaths": [ "CHANGELOG.md", - "examples", "packages/docusaurus-theme-translations/locales", - "__tests__", "package.json", "yarn.lock", "project-words.txt", + "__snapshots__", "website/src/data/users.tsx", + "website/src/data/tweets.tsx", "*.xyz", "*.docx", - "versioned_docs" + "versioned_docs", + "*.min.*" ], "ignoreRegExpList": ["Email", "Urls", "#[\\w-]*"] } diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 80716af9c10f..1d41847172f3 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,10 +1,33 @@ { - "name": "Docusaurus Dev Container", - "image": "mcr.microsoft.com/vscode/devcontainers/typescript-node:14-buster", + "image": "mcr.microsoft.com/vscode/devcontainers/base:ubuntu-20.04", "settings": { - "terminal.integrated.shell.linux": "/bin/bash" + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true + }, + "[json]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true + }, + "[jsonc]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true + } }, - "extensions": ["dbaeumer.vscode-eslint", "orta.vscode-jest"], + "extensions": [ + "dbaeumer.vscode-eslint", + "orta.vscode-jest", + "esbenp.prettier-vscode", + "streetsidesoftware.code-spell-checker" + ], "forwardPorts": [3000], - "postCreateCommand": "yarn install" + "containerUser": "vscode", + "postCreateCommand": "yarn install", + "waitFor": "postCreateCommand", // otherwise automated jest tests fail + "features": { + "node": { + "version": "14" + }, + "github-cli": "latest" + } } diff --git a/.eslintignore b/.eslintignore index a82d6bdc1b81..f8b4a50d08ab 100644 --- a/.eslintignore +++ b/.eslintignore @@ -16,4 +16,6 @@ packages/stylelint-copyright/lib/ copyUntypedFiles.mjs packages/create-docusaurus/lib/* -packages/create-docusaurus/templates/facebook/.eslintrc.js +packages/create-docusaurus/templates/facebook + +website/_dogfooding/_swizzle_theme_tests diff --git a/.eslintrc.js b/.eslintrc.js index 28a000035f36..e30864158b11 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -22,15 +22,15 @@ module.exports = { allowImportExportEverywhere: true, }, globals: { - testStylelintRule: true, + JSX: true, }, extends: [ 'eslint:recommended', - 'plugin:@typescript-eslint/eslint-recommended', - 'plugin:@typescript-eslint/recommended', 'plugin:react-hooks/recommended', 'plugin:jest/recommended', 'airbnb', + 'plugin:@typescript-eslint/recommended', + 'plugin:regexp/recommended', 'prettier', ], settings: { @@ -41,21 +41,149 @@ module.exports = { }, }, reportUnusedDisableDirectives: true, - plugins: ['react-hooks', 'header', 'jest'], + plugins: ['react-hooks', 'header', 'jest', '@typescript-eslint', 'regexp'], rules: { - 'react-hooks/rules-of-hooks': ERROR, - 'react-hooks/exhaustive-deps': ERROR, + 'array-callback-return': WARNING, + camelcase: WARNING, 'class-methods-use-this': OFF, // It's a way of allowing private variables. - 'func-names': OFF, - // Ignore certain webpack alias because it can't be resolved - 'import/no-unresolved': [ - ERROR, + curly: [WARNING, 'all'], + 'global-require': WARNING, + 'lines-between-class-members': OFF, + 'max-classes-per-file': OFF, + 'max-len': [ + WARNING, { - ignore: ['^@theme', '^@docusaurus', '^@generated', '^@site'], + code: Infinity, // Code width is already enforced by Prettier + tabWidth: 2, + comments: 80, + ignoreUrls: true, + ignorePattern: '(eslint-disable|@)', }, ], - 'import/extensions': OFF, + 'no-await-in-loop': OFF, + 'no-case-declarations': WARNING, + 'no-console': OFF, + 'no-continue': OFF, + 'no-control-regex': WARNING, + 'no-else-return': [WARNING, {allowElseIf: true}], + 'no-empty': [WARNING, {allowEmptyCatch: true}], + 'no-lonely-if': WARNING, + 'no-nested-ternary': WARNING, + 'no-param-reassign': [WARNING, {props: false}], + 'no-prototype-builtins': WARNING, 'no-restricted-exports': OFF, + 'no-restricted-properties': [ + ERROR, + ...[ + // TODO: TS doesn't make Boolean a narrowing function yet, + // so filter(Boolean) is problematic type-wise + // ['compact', 'Array#filter(Boolean)'], + ['concat', 'Array#concat'], + ['drop', 'Array#slice(n)'], + ['dropRight', 'Array#slice(0, -n)'], + ['fill', 'Array#fill'], + ['filter', 'Array#filter'], + ['find', 'Array#find'], + ['findIndex', 'Array#findIndex'], + ['first', 'foo[0]'], + ['flatten', 'Array#flat'], + ['flattenDeep', 'Array#flat(Infinity)'], + ['flatMap', 'Array#flatMap'], + ['fromPairs', 'Object.fromEntries'], + ['head', 'foo[0]'], + ['indexOf', 'Array#indexOf'], + ['initial', 'Array#slice(0, -1)'], + ['join', 'Array#join'], + // Unfortunately there's no great alternative to _.last yet + // Candidates: foo.slice(-1)[0]; foo[foo.length - 1] + // Array#at is ES2022; could replace _.nth as well + // ['last'], + ['map', 'Array#map'], + ['reduce', 'Array#reduce'], + ['reverse', 'Array#reverse'], + ['slice', 'Array#slice'], + ['take', 'Array#slice(0, n)'], + ['takeRight', 'Array#slice(-n)'], + ['tail', 'Array#slice(1)'], + ].map(([property, alternative]) => ({ + object: '_', + property, + message: `Use ${alternative} instead.`, + })), + ...[ + 'readdirSync', + 'readFileSync', + 'statSync', + 'lstatSync', + 'existsSync', + 'pathExistsSync', + 'realpathSync', + 'mkdirSync', + 'mkdirpSync', + 'mkdirsSync', + 'writeFileSync', + 'writeJsonSync', + 'outputFileSync', + 'outputJsonSync', + 'moveSync', + 'copySync', + 'copyFileSync', + 'ensureFileSync', + 'ensureDirSync', + 'ensureLinkSync', + 'ensureSymlinkSync', + 'unlinkSync', + 'removeSync', + 'emptyDirSync', + ].map((property) => ({ + object: 'fs', + property, + message: 'Do not use sync fs methods.', + })), + ], + 'no-restricted-syntax': [ + WARNING, + // Copied from airbnb, removed for...of statement, added export all + { + selector: 'ForInStatement', + message: + 'for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array.', + }, + { + selector: 'LabeledStatement', + message: + 'Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand.', + }, + { + selector: 'WithStatement', + message: + '`with` is disallowed in strict mode because it makes code impossible to predict and optimize.', + }, + { + selector: 'ExportAllDeclaration', + message: + "Export all does't work well if imported in ESM due to how they are transpiled, and they can also lead to unexpected exposure of internal methods.", + }, + // TODO make an internal plugin to ensure this + // { + // selector: + // @ 'ExportDefaultDeclaration > Identifier, ExportNamedDeclaration[source=null] > ExportSpecifier', + // message: 'Export in one statement' + // }, + ...['path', 'fs-extra', 'webpack', 'lodash'].map((m) => ({ + selector: `ImportDeclaration[importKind=value]:has(Literal[value=${m}]) > ImportSpecifier[importKind=value]`, + message: + 'Default-import this, both for readability and interoperability with ESM', + })), + ], + 'no-template-curly-in-string': WARNING, + 'no-unused-expressions': [WARNING, {allowTaggedTemplates: true}], + 'no-useless-escape': WARNING, + 'prefer-destructuring': WARNING, + 'prefer-named-capture-group': WARNING, + 'prefer-template': WARNING, + yoda: WARNING, + 'header/header': [ ERROR, 'block', @@ -68,21 +196,58 @@ module.exports = { ' ', ], ], + + 'import/extensions': OFF, + // Ignore certain webpack aliases because they can't be resolved + 'import/no-unresolved': [ + ERROR, + { + ignore: [ + '^@theme', + '^@docusaurus', + '^@generated', + '^@site', + '^@testing-utils', + ], + }, + ], + 'import/order': OFF, + 'import/prefer-default-export': OFF, + + 'jest/consistent-test-it': WARNING, + 'jest/expect-expect': OFF, + 'jest/no-large-snapshots': [ + WARNING, + {maxSize: Infinity, inlineMaxSize: 10}, + ], + 'jest/no-test-return-statement': ERROR, + 'jest/prefer-expect-resolves': WARNING, + 'jest/prefer-lowercase-title': [WARNING, {ignore: ['describe']}], + 'jest/prefer-spy-on': WARNING, + 'jest/prefer-to-be': WARNING, + 'jest/prefer-to-have-length': WARNING, + 'jest/require-top-level-describe': ERROR, + 'jest/valid-title': [ + ERROR, + { + mustNotMatch: { + it: [ + '^should|\\.$', + 'Titles should not begin with "should" or end with a full-stop', + ], + }, + }, + ], + 'jsx-a11y/click-events-have-key-events': WARNING, 'jsx-a11y/no-noninteractive-element-interactions': WARNING, 'jsx-a11y/html-has-lang': OFF, - 'no-console': OFF, - 'no-else-return': OFF, - 'no-param-reassign': [WARNING, {props: false}], - 'no-underscore-dangle': OFF, - curly: [WARNING, 'all'], - 'react/jsx-filename-extension': OFF, - 'react/no-array-index-key': OFF, // Sometimes its ok, e.g. non-changing data. - 'react/prop-types': OFF, - 'react/destructuring-assignment': OFF, // Too many lines. - 'react/prefer-stateless-function': WARNING, - 'react/jsx-props-no-spreading': OFF, - 'react/require-default-props': [ERROR, {ignoreFunctionalComponents: true}], + + 'react-hooks/rules-of-hooks': ERROR, + 'react-hooks/exhaustive-deps': ERROR, + + // Sometimes we do need the props as a whole, e.g. when spreading + 'react/destructuring-assignment': OFF, 'react/function-component-definition': [ WARNING, { @@ -90,96 +255,62 @@ module.exports = { unnamedComponents: 'arrow-function', }, ], + 'react/jsx-filename-extension': OFF, + 'react/jsx-key': [ERROR, {checkFragmentShorthand: true}], + 'react/jsx-no-useless-fragment': [ERROR, {allowExpressions: true}], + 'react/jsx-props-no-spreading': OFF, + 'react/no-array-index-key': OFF, // We build a static site, and nearly all components don't change. 'react/no-unstable-nested-components': [WARNING, {allowAsProps: true}], - '@typescript-eslint/no-inferrable-types': OFF, + 'react/prefer-stateless-function': WARNING, + 'react/prop-types': OFF, + 'react/require-default-props': [ERROR, {ignoreFunctionalComponents: true}], + + '@typescript-eslint/ban-ts-comment': [ + ERROR, + {'ts-expect-error': 'allow-with-description'}, + ], + '@typescript-eslint/consistent-indexed-object-style': [ + WARNING, + 'index-signature', + ], '@typescript-eslint/consistent-type-imports': [ WARNING, {disallowTypeAnnotations: false}, ], - 'import/order': OFF, - 'import/prefer-default-export': OFF, - 'lines-between-class-members': OFF, - 'no-lonely-if': WARNING, - 'no-use-before-define': OFF, - '@typescript-eslint/no-use-before-define': [ - ERROR, - {functions: false, classes: false, variables: true}, - ], - 'no-unused-vars': OFF, - 'no-nested-ternary': WARNING, - '@typescript-eslint/no-empty-function': OFF, - '@typescript-eslint/no-non-null-assertion': OFF, - '@typescript-eslint/no-unused-vars': [ - ERROR, - {argsIgnorePattern: '^_', ignoreRestSiblings: true}, - ], '@typescript-eslint/explicit-module-boundary-types': WARNING, - '@typescript-eslint/ban-ts-comment': [ - ERROR, - {'ts-expect-error': 'allow-with-description'}, - ], - 'import/no-extraneous-dependencies': ERROR, - 'no-useless-escape': WARNING, - 'prefer-template': WARNING, - 'no-template-curly-in-string': WARNING, - 'array-callback-return': WARNING, - camelcase: WARNING, - 'no-restricted-syntax': WARNING, - 'no-unused-expressions': [WARNING, {allowTaggedTemplates: true}], - 'global-require': WARNING, - 'prefer-destructuring': WARNING, - yoda: WARNING, - 'no-await-in-loop': OFF, - 'no-control-regex': WARNING, - 'no-empty': [WARNING, {allowEmptyCatch: true}], - 'no-prototype-builtins': WARNING, - 'no-case-declarations': WARNING, - 'no-undef': OFF, - 'no-shadow': OFF, - '@typescript-eslint/no-shadow': ERROR, - 'no-redeclare': OFF, - '@typescript-eslint/no-redeclare': ERROR, + '@typescript-eslint/method-signature-style': ERROR, + '@typescript-eslint/no-empty-function': OFF, '@typescript-eslint/no-empty-interface': [ ERROR, { allowSingleExtends: true, }, ], - '@typescript-eslint/method-signature-style': ERROR, - 'no-restricted-imports': [ + '@typescript-eslint/no-inferrable-types': OFF, + '@typescript-eslint/no-namespace': [WARNING, {allowDeclarations: true}], + 'no-use-before-define': OFF, + '@typescript-eslint/no-use-before-define': [ ERROR, - { - paths: [ - { - name: 'lodash', - importNames: [ - // 'compact', // TODO: TS doesn't make Boolean a narrowing function yet, so filter(Boolean) is problematic type-wise - 'filter', - 'flatten', - 'flatMap', - 'map', - 'reduce', - 'take', - 'takeRight', - 'head', - 'tail', - 'initial', - ], - message: 'These APIs have their ES counterparts.', - }, - ], - }, + {functions: false, classes: false, variables: true}, ], - 'jest/prefer-expect-resolves': WARNING, - 'jest/expect-expect': OFF, - 'jest/valid-title': OFF, + '@typescript-eslint/no-non-null-assertion': OFF, + 'no-redeclare': OFF, + '@typescript-eslint/no-redeclare': ERROR, + 'no-shadow': OFF, + '@typescript-eslint/no-shadow': ERROR, + 'no-unused-vars': OFF, + // We don't provide any escape hatches for this rule. Rest siblings and + // function placeholder params are always ignored, and any other unused + // locals must be justified with a disable comment. + '@typescript-eslint/no-unused-vars': [ERROR, {ignoreRestSiblings: true}], + '@typescript-eslint/prefer-optional-chain': ERROR, }, overrides: [ { files: [ - 'packages/docusaurus-theme-*/src/theme/**/*.js', - 'packages/docusaurus-theme-*/src/theme/**/*.ts', - 'packages/docusaurus-theme-*/src/theme/**/*.tsx', + 'packages/docusaurus-*/src/theme/**/*.js', + 'packages/docusaurus-*/src/theme/**/*.ts', + 'packages/docusaurus-*/src/theme/**/*.tsx', ], rules: { 'import/no-named-export': ERROR, @@ -206,6 +337,7 @@ module.exports = { { files: ['*.ts', '*.tsx'], rules: { + 'no-undef': OFF, 'import/no-import-module-exports': OFF, }, }, @@ -217,5 +349,21 @@ module.exports = { '@typescript-eslint/explicit-module-boundary-types': OFF, }, }, + { + // Internal files where extraneous deps don't matter much at long as + // they run + files: [ + '*.test.ts', + '*.test.tsx', + 'admin/**', + 'jest/**', + 'website/**', + 'packages/docusaurus-theme-translations/update.mjs', + 'packages/docusaurus-theme-translations/src/utils.ts', + ], + rules: { + 'import/no-extraneous-dependencies': OFF, + }, + }, ], }; diff --git a/.gitattributes b/.gitattributes index 588d8d6e874a..4bbe761c210d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -27,3 +27,12 @@ *.bz2 binary *.swp binary *.webp binary + +# Make GitHub not index certain files in the languages overview +# See https://github.com/github/linguist/blob/master/docs/overrides.md +# generated files' diff will be minimized +**/__fixtures__/** linguist-generated +.husky/** linguist-vendored +jest/** linguist-vendored +admin/** linguist-documentation +website/** linguist-documentation diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 03e1b71eb18e..000000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,3 +0,0 @@ -## 👉 [Please follow one of these issue templates](https://github.com/facebook/docusaurus/issues/new/choose) 👈 - -Note: to keep the backlog clean and actionable, issues may be immediately closed if they do not follow one of the above issue templates. diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index e44a0826d3b6..32206c2aa615 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -7,7 +7,14 @@ body: value: | ## Please help us help you! - Make it obvious to understand and reproduce this bug. Ideally, we should be able to understand it without running any code. + Before filing your issue, ask yourself: + - Is this clearly a Docusaurus defect? + - Do I have basic ideas about where it goes wrong? (For example, if there are stack traces, are they pointing to one file?) + - Could it be because of my own mistakes? + + **The GitHub issue tracker is not a support forum**. If you are not sure whether it could be your mistakes, ask in the [Discord server](https://discord.gg/docusaurus) or [GitHub discussions](https://github.com/facebook/docusaurus/discussions) first. The quickest way to verify whether it's a Docusaurus defect is through a **reproduction**, starting with a fresh installation and making changes until the bug is reproduced. + + Make the bug obvious. Ideally, we should be able to understand it without running any code. Bugs are fixed faster if you include: - A repro repository to inspect the code @@ -41,13 +48,27 @@ body: validations: required: true + - type: input + attributes: + label: Reproducible demo + description: | + Paste the link to an example repo, including a `docusaurus.config.js`, and exact instructions to reproduce the issue. It can either be a playground link created from https://new.docusaurus.io, or a git repository. + + > **What happens if you skip this step?** Someone will read your bug report, and maybe will be able to help you, but it’s unlikely that it will get much attention from the team. Eventually, the issue will likely get closed in favor of issues that have reproducible demos. + + Please remember that: + + - Issues without reproducible demos have a very low priority. + - The person fixing the bug would have to do that anyway. Please be respectful of their time. + - You might figure out the issues yourself as you work on extracting it. + + Thanks for helping us help you! + - type: textarea attributes: label: Steps to reproduce - description: Use https://new.docusaurus.io to create a CodeSandbox reproducible demo of the bug. + description: Write down the steps to reproduce the bug. You should start with a fresh installation, or your git repository linked above. placeholder: | - Write your steps here. - 1. Step 1... 2. Step 2... 3. Step 3... @@ -55,7 +76,6 @@ body: required: true - type: textarea - attributes: label: Expected behavior description: | @@ -87,22 +107,6 @@ body: - Environment name and version (e.g. Chrome 89, Node.js 16.4): - Operating system and version (e.g. Ubuntu 20.04.2 LTS): - - type: input - attributes: - label: Reproducible demo - description: | - Paste the link to an example repo, including a `docusaurus.config.js`, and exact instructions to reproduce the issue. Use https://new.docusaurus.io to create a CodeSandbox reproducible demo of the bug. - - > **What happens if you skip this step?** Someone will read your bug report, and maybe will be able to help you, but it’s unlikely that it will get much attention from the team. Eventually, the issue will likely get closed in favor of issues that have reproducible demos. - - Please remember that: - - - Issues without reproducible demos have a very low priority. - - The person fixing the bug would have to do that anyway. Please be respectful of their time. - - You might figure out the issues yourself as you work on extracting it. - - Thanks for helping us help you! - - type: checkboxes attributes: label: Self-service diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index a742b0973faf..fa69d62f365f 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -5,22 +5,24 @@ Help us understand your motivation by explaining why you decided to make this ch You can learn more about contributing to Docusaurus here: https://github.com/facebook/docusaurus/blob/main/CONTRIBUTING.md +If this PR adds or changes functionality, please take some time to update the docs. + Happy contributing! --> ## Motivation -(Write your motivation here.) + ### Have you read the [Contributing Guidelines on pull requests](https://github.com/facebook/docusaurus/blob/main/CONTRIBUTING.md#pull-requests)? -(Write your answer here.) + ## Test Plan -(Write your test plan here. If you changed any code, please provide us with clear instructions on how you verified your changes work. Bonus points for screenshots and videos!) + ## Related PRs -(If this PR adds or changes functionality, please take some time to update the docs at https://github.com/facebook/docusaurus, and link to your PR here.) + diff --git a/.github/workflows/build-blog-only.yml b/.github/workflows/build-blog-only.yml index 92c5bbe9e6d8..c5e83a3df520 100644 --- a/.github/workflows/build-blog-only.yml +++ b/.github/workflows/build-blog-only.yml @@ -13,8 +13,8 @@ jobs: timeout-minutes: 30 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 with: node-version: '16' cache: yarn diff --git a/.github/workflows/build-perf.yml b/.github/workflows/build-perf.yml index 55d4b4e9a64d..ad7b3c5c247b 100644 --- a/.github/workflows/build-perf.yml +++ b/.github/workflows/build-perf.yml @@ -18,8 +18,8 @@ jobs: timeout-minutes: 30 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 with: node-version: '16' cache: yarn @@ -27,6 +27,7 @@ jobs: with: repo-token: ${{ secrets.GITHUB_TOKEN }} build-script: build:website:en + clean-script: clear:website # see https://github.com/facebook/docusaurus/pull/6838 pattern: '{website/build/assets/js/main*js,website/build/assets/css/styles*css,website/.docusaurus/globalData.json,website/build/index.html,website/build/blog/index.html,website/build/blog/**/introducing-docusaurus/*,website/build/docs/index.html,website/build/docs/installation/index.html,website/build/tests/docs/index.html,website/build/tests/docs/standalone/index.html}' strip-hash: '\.([^;]\w{7})\.' minimum-change-threshold: 30 @@ -36,8 +37,8 @@ jobs: timeout-minutes: 30 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 with: cache: yarn - name: Installation diff --git a/.github/workflows/canary-release.yml b/.github/workflows/canary-release.yml index 4e38bb08aa91..e7557b4d79f6 100644 --- a/.github/workflows/canary-release.yml +++ b/.github/workflows/canary-release.yml @@ -12,11 +12,11 @@ jobs: name: Publish Canary runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 0 # Needed to get the commit number with "git rev-list --count HEAD" - name: Set up Node - uses: actions/setup-node@v2 + uses: actions/setup-node@v3 with: node-version: '16' cache: yarn diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index bcaf22bfb38b..69cb617ef87f 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -27,7 +27,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Initialize CodeQL uses: github/codeql-action/init@v1 diff --git a/.github/workflows/lighthouse-report.yml b/.github/workflows/lighthouse-report.yml index 1c9834d77ef8..5e7fc7d70fe0 100644 --- a/.github/workflows/lighthouse-report.yml +++ b/.github/workflows/lighthouse-report.yml @@ -10,7 +10,7 @@ jobs: name: Lighthouse Report runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Wait for the Netlify Preview uses: jakepartusch/wait-for-netlify-action@v1 id: netlify @@ -19,7 +19,7 @@ jobs: max_timeout: 600 - name: Audit URLs using Lighthouse id: lighthouse_audit - uses: treosh/lighthouse-ci-action@8.2.0 + uses: treosh/lighthouse-ci-action@9.3.0 with: urls: | https://deploy-preview-$PR_NUMBER--docusaurus-2.netlify.app/ @@ -30,7 +30,7 @@ jobs: PR_NUMBER: ${{ github.event.pull_request.number}} - name: Format lighthouse score id: format_lighthouse_score - uses: actions/github-script@v5 + uses: actions/github-script@v6 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index ebc275a0c7c2..7a69a1362274 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -11,8 +11,8 @@ jobs: timeout-minutes: 30 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 with: node-version: '16' cache: yarn diff --git a/.github/workflows/showcase-test.yml b/.github/workflows/showcase-test.yml index 91fb8a9d5c8e..83ad82c4cd5c 100644 --- a/.github/workflows/showcase-test.yml +++ b/.github/workflows/showcase-test.yml @@ -13,9 +13,9 @@ jobs: timeout-minutes: 30 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Node - uses: actions/setup-node@v2 + uses: actions/setup-node@v3 with: node-version: '16' cache: yarn diff --git a/.github/workflows/tests-e2e.yml b/.github/workflows/tests-e2e.yml index 77cfa3ca062e..7bb9f842ffc6 100644 --- a/.github/workflows/tests-e2e.yml +++ b/.github/workflows/tests-e2e.yml @@ -21,9 +21,9 @@ jobs: matrix: node: ['14', '16', '17'] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Use Node.js ${{ matrix.node }} - uses: actions/setup-node@v2 + uses: actions/setup-node@v3 with: node-version: ${{ matrix.node }} cache: yarn @@ -31,8 +31,6 @@ jobs: run: yarn - name: Generate test-website project against main branch run: yarn test:build:website -s - env: - KEEP_CONTAINER: true - name: Install test-website project with Yarn v1 run: yarn install working-directory: ../test-website @@ -54,25 +52,26 @@ jobs: strategy: matrix: nodeLinker: [pnp, node-modules] + variant: [-s, -st] + exclude: + # Running tsc on PnP requires additional installations, which is not + # worthwhile for a simple E2E test + - variant: -st + nodeLinker: pnp steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Use Node.js 16 - uses: actions/setup-node@v2 + uses: actions/setup-node@v3 with: node-version: '16' cache: yarn - name: Installation run: yarn - - name: Generate test-website project against main branch - run: yarn test:build:website -s - env: - KEEP_CONTAINER: true + - name: Generate test-website project with ${{ matrix.variant }} against main branch + run: yarn test:build:website ${{ matrix.variant }} - name: Install test-website project with Yarn Berry and nodeLinker = ${{ matrix.nodeLinker }} run: | yarn set version berry - # https://github.com/facebook/docusaurus/pull/6350#issuecomment-1013214763 - # Remove this after Yarn 3.2 - yarn set version canary yarn config set nodeLinker ${{ matrix.nodeLinker }} yarn config set npmRegistryServer http://localhost:4873 @@ -83,10 +82,6 @@ jobs: # https://yarnpkg.com/features/pnp#fallback-mode yarn config set pnpFallbackMode none - # Patch package so that peer deps are provided. This has been fixed in terser by making acorn a direct dependency - # TODO watch out for the next terser release. Commit: https://github.com/terser/terser/commit/05b23eeb682d732484ad51b19bf528258fd5dc2a - yarn config set packageExtensions --json '{"terser-webpack-plugin@*": {"dependencies": {"acorn": "^8.6.0"}}, "html-minifier-terser@*": {"dependencies": {"acorn": "^8.6.0"}}}' - yarn install working-directory: ../test-website env: @@ -96,6 +91,10 @@ jobs: working-directory: ../test-website env: E2E_TEST: true + - name: Type check + if: matrix.variant == '-st' + run: yarn typecheck + working-directory: ../test-website - name: Build test-website project run: yarn build working-directory: ../test-website @@ -105,9 +104,9 @@ jobs: timeout-minutes: 30 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Use Node.js 16 - uses: actions/setup-node@v2 + uses: actions/setup-node@v3 with: node-version: '16' cache: yarn @@ -115,8 +114,6 @@ jobs: run: yarn - name: Generate test-website project against main branch run: yarn test:build:website -s - env: - KEEP_CONTAINER: true - name: Install test-website project with NPM run: npm install working-directory: ../test-website @@ -136,9 +133,9 @@ jobs: timeout-minutes: 30 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Use Node.js 16 - uses: actions/setup-node@v2 + uses: actions/setup-node@v3 with: node-version: '16' cache: yarn @@ -146,8 +143,6 @@ jobs: run: yarn - name: Generate test-website project against main branch run: yarn test:build:website -s - env: - KEEP_CONTAINER: true - name: Install test-website project with PNPM run: | curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm diff --git a/.github/workflows/tests-swizzle.yml b/.github/workflows/tests-swizzle.yml new file mode 100644 index 000000000000..611b92c8834a --- /dev/null +++ b/.github/workflows/tests-swizzle.yml @@ -0,0 +1,37 @@ +name: Swizzle Tests + +on: + pull_request: + branches: + - main + paths: + - packages/** + +jobs: + test: + name: Swizzle + timeout-minutes: 30 + runs-on: ubuntu-latest + strategy: + matrix: + action: ['eject', 'wrap'] + variant: ['js', 'ts'] + steps: + - uses: actions/checkout@v3 + - name: Use Node.js + uses: actions/setup-node@v3 + with: + node-version: 14 + cache: yarn + - name: Installation + run: yarn + + # Swizzle all the theme components + - name: Swizzle (${{matrix.action}} - ${{matrix.variant}}) + run: yarn workspace website test:swizzle:${{matrix.action}}:${{matrix.variant}} + # Build swizzled site + - name: Build website + run: yarn build:website:fast + # Ensure swizzled site still typechecks + - name: TypeCheck website + run: yarn workspace website typecheck diff --git a/.github/workflows/tests-windows.yml b/.github/workflows/tests-windows.yml index 6dc72654326b..213ef8a3e0f6 100644 --- a/.github/workflows/tests-windows.yml +++ b/.github/workflows/tests-windows.yml @@ -18,9 +18,9 @@ jobs: steps: - name: Support longpaths run: git config --system core.longpaths true - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Use Node.js ${{ matrix.node }} - uses: actions/setup-node@v2 + uses: actions/setup-node@v3 with: node-version: ${{ matrix.node }} - name: Installation @@ -34,5 +34,10 @@ jobs: mkdir -p "website/_dogfooding/_pages tests/deep-file-path-test/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar/foo/bar" cd "$_" echo "# hello" > test-file.md + # Lightweight version of tests-swizzle.yml workflow, but for Windows + - name: Swizzle Wrap TS + run: yarn workspace website test:swizzle:wrap:ts - name: Docusaurus Build - run: yarn build:website --locale en + run: yarn build:website:fast + - name: TypeCheck website + run: yarn workspace website typecheck diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 087410d0dc29..33992216fe48 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,9 +16,9 @@ jobs: matrix: node: ['14', '16', '17'] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Use Node.js ${{ matrix.node }} - uses: actions/setup-node@v2 + uses: actions/setup-node@v3 with: node-version: ${{ matrix.node }} cache: yarn diff --git a/.gitignore b/.gitignore index 04d22349cf1d..29b717df7f86 100644 --- a/.gitignore +++ b/.gitignore @@ -27,9 +27,12 @@ packages/stylelint-copyright/lib/ packages/docusaurus-*/lib-next/ website/netlifyDeployPreview/* +website/changelog !website/netlifyDeployPreview/index.html !website/netlifyDeployPreview/_redirects +website/_dogfooding/_swizzle_theme_tests + website/i18n/**/* #!website/i18n/fr #!website/i18n/fr/**/* diff --git a/.lintstagedrc.json b/.lintstagedrc.json new file mode 100644 index 000000000000..b25a96cf4886 --- /dev/null +++ b/.lintstagedrc.json @@ -0,0 +1,8 @@ +{ + "*.{js,jsx,ts,tsx,mjs}": ["eslint --fix"], + "*.css": ["stylelint --allow-empty-input --fix"], + "*": [ + "prettier --ignore-unknown --write", + "cspell --no-must-find-files --no-progress" + ] +} diff --git a/.nvmrc b/.nvmrc index 62df50f1eefe..832d38506443 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -14.17.0 +16.14.0 diff --git a/.prettierignore b/.prettierignore index 3c33d06c76e4..98de3b0f4a0f 100644 --- a/.prettierignore +++ b/.prettierignore @@ -19,3 +19,7 @@ website/docusaurus.config.js website/versioned_sidebars/*.json examples/ +website/static/katex/katex.min.css + +website/changelog/_swizzle_theme_tests +website/_dogfooding/_swizzle_theme_tests diff --git a/.stylelintignore b/.stylelintignore index 7ceb06a51afb..951af3a16ea9 100644 --- a/.stylelintignore +++ b/.stylelintignore @@ -1,3 +1,8 @@ +# Stylelint runs on everything by default; we only lint CSS files. +* +!*/ +!*.css +__tests__/ build coverage examples/ @@ -8,3 +13,4 @@ packages/docusaurus-*/lib/* packages/docusaurus-*/lib-next/ packages/create-docusaurus/lib/* packages/create-docusaurus/templates/ +website/static/katex/katex.min.css diff --git a/CHANGELOG.md b/CHANGELOG.md index 1dc67ac55bd5..f30ba836d46b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,866 @@ # Docusaurus 2 Changelog +## 2.0.0-beta.18 (2022-03-25) + +#### :rocket: New Feature + +- `docusaurus-mdx-loader`, `docusaurus-theme-classic` + - [#6990](https://github.com/facebook/docusaurus/pull/6990) feat: lazy-load external images + ability to customize image display ([@slorber](https://github.com/slorber)) +- `docusaurus-module-type-aliases`, `docusaurus-plugin-content-docs`, `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-types`, `docusaurus` + - [#6933](https://github.com/facebook/docusaurus/pull/6933) feat(core,theme): useRouteContext + HtmlClassNameProvider ([@slorber](https://github.com/slorber)) +- `docusaurus-plugin-debug`, `docusaurus-plugin-google-analytics`, `docusaurus-plugin-google-gtag`, `docusaurus-plugin-ideal-image`, `docusaurus-plugin-pwa`, `docusaurus-theme-classic`, `docusaurus-theme-live-codeblock`, `docusaurus-theme-search-algolia`, `docusaurus-types`, `docusaurus` + - [#6921](https://github.com/facebook/docusaurus/pull/6921) feat(core): allow plugin lifecycles to return relative paths ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-theme-classic` + - [#6697](https://github.com/facebook/docusaurus/pull/6697) feat: add SEO microdata for doc breadcrumbs ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6842](https://github.com/facebook/docusaurus/pull/6842) feat(theme-classic): MDXContent wrapper component ([@slorber](https://github.com/slorber)) +- `docusaurus-plugin-content-docs` + - [#6780](https://github.com/facebook/docusaurus/pull/6780) feat(content-docs): allow custom props through _category_.json ([@taejs](https://github.com/taejs)) + +#### :boom: Breaking Change + +- `docusaurus-plugin-content-docs` + - [#6859](https://github.com/facebook/docusaurus/pull/6859) feat(content-docs): autogenerate category with linked doc metadata as fallback ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-theme-classic` + - [#6989](https://github.com/facebook/docusaurus/pull/6989) refactor: extract MDX components ([@slorber](https://github.com/slorber)) +- `docusaurus-module-type-aliases`, `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-theme-search-algolia`, `docusaurus` + - [#6925](https://github.com/facebook/docusaurus/pull/6925) refactor(theme-{classic,common}): refactor site/page/search metadata + apply className on html element ([@slorber](https://github.com/slorber)) +- `docusaurus-theme-classic`, `docusaurus-theme-common` + - [#6895](https://github.com/facebook/docusaurus/pull/6895) refactor(theme-{classic,common}): split navbar into smaller components + cleanup + swizzle config ([@slorber](https://github.com/slorber)) + - [#6930](https://github.com/facebook/docusaurus/pull/6930) refactor(theme-{classic,common}): refactor ColorModeToggle + useColorMode() hook ([@lex111](https://github.com/lex111)) + +#### :bug: Bug Fix + +- `docusaurus` + - [#6993](https://github.com/facebook/docusaurus/pull/6993) fix(core): prevent useBaseUrl returning /base/base when on /base ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6936](https://github.com/facebook/docusaurus/pull/6936) fix: remove semicolon from HTML output ([@lex111](https://github.com/lex111)) + - [#6849](https://github.com/facebook/docusaurus/pull/6849) fix(cli): write-heading-id should not generate colliding slugs when not overwriting ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-theme-classic` + - [#6983](https://github.com/facebook/docusaurus/pull/6983) fix(search): bump Infima, fix search issue due to broken CSS selector ([@slorber](https://github.com/slorber)) +- `docusaurus-utils-validation` + - [#6977](https://github.com/facebook/docusaurus/pull/6977) fix(validation): allow non-object params to remark/rehype plugins ([@aloisklink](https://github.com/aloisklink)) +- `docusaurus-plugin-content-docs`, `docusaurus-utils` + - [#6973](https://github.com/facebook/docusaurus/pull/6973) fix(content-docs): suppress git error on multiple occurrences ([@felipecrs](https://github.com/felipecrs)) +- `docusaurus-plugin-content-blog` + - [#6947](https://github.com/facebook/docusaurus/pull/6947) fix(content-blog): only create archive route if there are blog posts ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6918](https://github.com/facebook/docusaurus/pull/6918) fix(content-blog): remove double leading slash in blog-only paginated view ([@heowc](https://github.com/heowc)) +- `docusaurus-theme-search-algolia` + - [#6888](https://github.com/facebook/docusaurus/pull/6888) fix(theme-algolia): declare content-docs as dependency ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-theme-translations` + - [#6847](https://github.com/facebook/docusaurus/pull/6847) fix: minor Chinese translation fixes ([@rccttwd](https://github.com/rccttwd)) + +#### :nail_care: Polish + +- `docusaurus-plugin-content-docs` + - [#6859](https://github.com/facebook/docusaurus/pull/6859) feat(content-docs): autogenerate category with linked doc metadata as fallback ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6887](https://github.com/facebook/docusaurus/pull/6887) fix(content-docs): give context about sidebar loading failure ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-plugin-content-docs`, `docusaurus-utils-validation`, `docusaurus` + - [#6997](https://github.com/facebook/docusaurus/pull/6997) fix(validation): improve error messages for a few schemas ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-theme-classic` + - [#6971](https://github.com/facebook/docusaurus/pull/6971) refactor: improve a11y of dropdown menu ([@lex111](https://github.com/lex111)) + - [#6987](https://github.com/facebook/docusaurus/pull/6987) refactor(theme-classic): cleanup of code blocks ([@lex111](https://github.com/lex111)) + - [#6950](https://github.com/facebook/docusaurus/pull/6950) refactor(theme-classic): clean up CSS of doc cards ([@lex111](https://github.com/lex111)) + - [#6994](https://github.com/facebook/docusaurus/pull/6994) refactor: better external link icon positioning ([@lex111](https://github.com/lex111)) + - [#6989](https://github.com/facebook/docusaurus/pull/6989) refactor: extract MDX components ([@slorber](https://github.com/slorber)) + - [#6985](https://github.com/facebook/docusaurus/pull/6985) refactor(theme-classic): remove span wrappers from layout links ([@lex111](https://github.com/lex111)) + - [#6986](https://github.com/facebook/docusaurus/pull/6986) fix(theme-classic): minor code copy button improvements ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6964](https://github.com/facebook/docusaurus/pull/6964) refactor: replace text-based copy code button with icons ([@lex111](https://github.com/lex111)) + - [#6932](https://github.com/facebook/docusaurus/pull/6932) refactor(theme-classic): little breadcrumbs improvements ([@lex111](https://github.com/lex111)) + - [#6914](https://github.com/facebook/docusaurus/pull/6914) feat(theme-classic): set aria-expanded on expandable sidebar categories ([@pkowaluk](https://github.com/pkowaluk)) + - [#6844](https://github.com/facebook/docusaurus/pull/6844) refactor(theme-classic): split sidebar into smaller parts ([@slorber](https://github.com/slorber)) + - [#6846](https://github.com/facebook/docusaurus/pull/6846) refactor(theme-classic): consistently add span wrapper for layout links ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-theme-classic`, `docusaurus-utils-validation`, `docusaurus-utils`, `docusaurus` + - [#6980](https://github.com/facebook/docusaurus/pull/6980) feat(utils): JSDoc for all APIs ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-theme-common` + - [#6974](https://github.com/facebook/docusaurus/pull/6974) feat(theme-common): JSDoc for all APIs ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus` + - [#6784](https://github.com/facebook/docusaurus/pull/6784) feat(core): allow configureWebpack to return undefined ([@yorkie](https://github.com/yorkie)) + - [#6941](https://github.com/facebook/docusaurus/pull/6941) refactor(core): improve error message when a page has no default-export ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6878](https://github.com/facebook/docusaurus/pull/6878) fix(core): ensure stable webpack theme aliases sorting ([@jrvidal](https://github.com/jrvidal)) + - [#6854](https://github.com/facebook/docusaurus/pull/6854) fix(core): fix swizzle legend typo ([@DigiPie](https://github.com/DigiPie)) + - [#6850](https://github.com/facebook/docusaurus/pull/6850) fix(core): make plugin lifecycles consistently bound to the plugin instance ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-utils` + - [#6937](https://github.com/facebook/docusaurus/pull/6937) fix(content-docs): warn when files are not tracked ([@felipecrs](https://github.com/felipecrs)) +- `docusaurus-module-type-aliases`, `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-theme-search-algolia`, `docusaurus` + - [#6925](https://github.com/facebook/docusaurus/pull/6925) refactor(theme-{classic,common}): refactor site/page/search metadata + apply className on html element ([@slorber](https://github.com/slorber)) +- `docusaurus-theme-classic`, `docusaurus-theme-common` + - [#6895](https://github.com/facebook/docusaurus/pull/6895) refactor(theme-{classic,common}): split navbar into smaller components + cleanup + swizzle config ([@slorber](https://github.com/slorber)) + - [#6930](https://github.com/facebook/docusaurus/pull/6930) refactor(theme-{classic,common}): refactor ColorModeToggle + useColorMode() hook ([@lex111](https://github.com/lex111)) + - [#6894](https://github.com/facebook/docusaurus/pull/6894) refactor(theme-classic): split theme footer into smaller components + swizzle config ([@slorber](https://github.com/slorber)) +- `docusaurus-types`, `docusaurus` + - [#6929](https://github.com/facebook/docusaurus/pull/6929) refactor(core): minor routes type improvement ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-plugin-client-redirects`, `docusaurus-plugin-ideal-image`, `docusaurus-plugin-pwa`, `docusaurus-plugin-sitemap` + - [#6928](https://github.com/facebook/docusaurus/pull/6928) chore(pwa, sitemap, client-redirects, ideal-image): JSDoc for types ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-plugin-content-blog`, `docusaurus-theme-classic`, `docusaurus-utils` + - [#6922](https://github.com/facebook/docusaurus/pull/6922) refactor(content-blog): clean up type definitions; in-code documentation ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-theme-translations` + - [#6781](https://github.com/facebook/docusaurus/pull/6781) feat(theme-translations): complete Russian translations ([@dragomano](https://github.com/dragomano)) + - [#6877](https://github.com/facebook/docusaurus/pull/6877) chore(theme-translations): complete Vietnamese translations ([@datlechin](https://github.com/datlechin)) +- `docusaurus-plugin-content-blog` + - [#6909](https://github.com/facebook/docusaurus/pull/6909) refactor(content-blog): improve error message of authors map validation ([@Josh-Cena](https://github.com/Josh-Cena)) +- `create-docusaurus` + - [#6860](https://github.com/facebook/docusaurus/pull/6860) fix(create): load entry file after node version checking ([@taejs](https://github.com/taejs)) + +#### :memo: Documentation + +- Other + - [#6988](https://github.com/facebook/docusaurus/pull/6988) docs: fix example admonition syntax ([@kaycebasques](https://github.com/kaycebasques)) + - [#6978](https://github.com/facebook/docusaurus/pull/6978) docs: npm run tsc -> npx tsc ([@jadonn](https://github.com/jadonn)) + - [#6952](https://github.com/facebook/docusaurus/pull/6952) docs: add K3ai to showcase ([@alefesta](https://github.com/alefesta)) + - [#6948](https://github.com/facebook/docusaurus/pull/6948) docs: add pdfme docs to showcase ([@hand-dot](https://github.com/hand-dot)) + - [#6943](https://github.com/facebook/docusaurus/pull/6943) docs: add SeaORM docs to showcase ([@billy1624](https://github.com/billy1624)) + - [#6926](https://github.com/facebook/docusaurus/pull/6926) docs: clarify the usage of slug ([@kaycebasques](https://github.com/kaycebasques)) + - [#6911](https://github.com/facebook/docusaurus/pull/6911) docs: add Reactive Button site to showcase ([@arifszn](https://github.com/arifszn)) + - [#6904](https://github.com/facebook/docusaurus/pull/6904) docs: update image for digital support services ([@PatelN123](https://github.com/PatelN123)) + - [#6892](https://github.com/facebook/docusaurus/pull/6892) docs: add EduLinks site to showcase ([@odarpi](https://github.com/odarpi)) + - [#6889](https://github.com/facebook/docusaurus/pull/6889) docs: editorial fixes ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6883](https://github.com/facebook/docusaurus/pull/6883) docs(cli): add info about development on github codespaces ([@vedantmgoyal2009](https://github.com/vedantmgoyal2009)) + - [#6856](https://github.com/facebook/docusaurus/pull/6856) docs: add Reddit Image Fetcher site to showcase ([@arifszn](https://github.com/arifszn)) + - [#6875](https://github.com/facebook/docusaurus/pull/6875) docs: update TRPG Engine showcase ([@moonrailgun](https://github.com/moonrailgun)) + - [#6871](https://github.com/facebook/docusaurus/pull/6871) docs: mark clutch and gulp as open-source ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6862](https://github.com/facebook/docusaurus/pull/6862) docs: update showcase data ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6837](https://github.com/facebook/docusaurus/pull/6837) docs: add PcapPlusPlus to showcase ([@seladb](https://github.com/seladb)) + - [#6832](https://github.com/facebook/docusaurus/pull/6832) docs: add Spicetify site to showcase ([@afonsojramos](https://github.com/afonsojramos)) + - [#6830](https://github.com/facebook/docusaurus/pull/6830) docs: simplify imported code blocks syntax ([@nathan-contino-mongo](https://github.com/nathan-contino-mongo)) +- `docusaurus-types` + - [#6881](https://github.com/facebook/docusaurus/pull/6881) docs: mention configureWebpack devServer return value ([@Josh-Cena](https://github.com/Josh-Cena)) +- `create-docusaurus` + - [#6833](https://github.com/facebook/docusaurus/pull/6833) docs: make tutorial code block directly copyable ([@samgutentag](https://github.com/samgutentag)) + +#### :house: Internal + +- `create-docusaurus`, `docusaurus-mdx-loader`, `docusaurus-migrate`, `docusaurus-module-type-aliases`, `docusaurus-plugin-client-redirects`, `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-plugin-content-pages`, `docusaurus-plugin-debug`, `docusaurus-plugin-google-gtag`, `docusaurus-plugin-ideal-image`, `docusaurus-remark-plugin-npm2yarn`, `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-theme-search-algolia`, `docusaurus-theme-translations`, `docusaurus-types`, `docusaurus-utils-validation`, `docusaurus-utils`, `docusaurus`, `lqip-loader` + - [#6995](https://github.com/facebook/docusaurus/pull/6995) refactor: ensure all types are using index signature instead of Record ([@Josh-Cena](https://github.com/Josh-Cena)) +- `create-docusaurus`, `docusaurus-cssnano-preset`, `docusaurus-plugin-pwa`, `docusaurus-theme-search-algolia`, `docusaurus-utils`, `docusaurus`, `lqip-loader` + - [#6991](https://github.com/facebook/docusaurus/pull/6991) chore: upgrade dependencies ([@Josh-Cena](https://github.com/Josh-Cena)) +- `lqip-loader` + - [#6992](https://github.com/facebook/docusaurus/pull/6992) refactor(lqip-loader): remove unused palette option ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus` + - [#6975](https://github.com/facebook/docusaurus/pull/6975) chore: update static-site-generator-webpack-plugin ([@slorber](https://github.com/slorber)) +- `stylelint-copyright` + - [#6967](https://github.com/facebook/docusaurus/pull/6967) chore: publish stylelint-copyright again ([@slorber](https://github.com/slorber)) +- `docusaurus-plugin-client-redirects`, `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-plugin-content-pages`, `docusaurus-plugin-google-analytics`, `docusaurus-plugin-google-gtag`, `docusaurus-plugin-ideal-image`, `docusaurus-plugin-pwa`, `docusaurus-plugin-sitemap`, `docusaurus-theme-classic`, `docusaurus-theme-live-codeblock`, `docusaurus-theme-search-algolia`, `docusaurus-types`, `docusaurus-utils-validation`, `docusaurus` + - [#6961](https://github.com/facebook/docusaurus/pull/6961) refactor: unify how validateOptions is handled ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-types` + - [#6957](https://github.com/facebook/docusaurus/pull/6957) chore(types): remove querystring from dependencies ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-theme-common`, `docusaurus` + - [#6956](https://github.com/facebook/docusaurus/pull/6956) test: improve test coverage; reorder theme-common files ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6955](https://github.com/facebook/docusaurus/pull/6955) refactor(core): move browserContext and docusaurusContext out of client exports ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6944](https://github.com/facebook/docusaurus/pull/6944) chore: migrate Jest and website to SWC ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-utils` + - [#6951](https://github.com/facebook/docusaurus/pull/6951) test: fix Windows test for gitUtils ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-mdx-loader`, `docusaurus-migrate`, `docusaurus-plugin-client-redirects`, `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-plugin-content-pages`, `docusaurus-plugin-debug`, `docusaurus-plugin-pwa`, `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-theme-translations`, `docusaurus-utils`, `docusaurus`, `stylelint-copyright` + - [#6931](https://github.com/facebook/docusaurus/pull/6931) chore: tighten ESLint config ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-module-type-aliases`, `docusaurus-plugin-client-redirects` + - [#6924](https://github.com/facebook/docusaurus/pull/6924) refactor(client-redirects): migrate validation to validateOptions lifecycle ([@Josh-Cena](https://github.com/Josh-Cena)) +- `create-docusaurus`, `docusaurus-cssnano-preset`, `docusaurus-mdx-loader`, `docusaurus-migrate`, `docusaurus-plugin-ideal-image`, `docusaurus-plugin-pwa`, `docusaurus-theme-classic`, `docusaurus-theme-search-algolia`, `docusaurus-utils`, `docusaurus`, `lqip-loader` + - [#6916](https://github.com/facebook/docusaurus/pull/6916) chore: upgrade dependencies ([@Josh-Cena](https://github.com/Josh-Cena)) +- `create-docusaurus`, `docusaurus-plugin-content-docs`, `docusaurus-theme-translations`, `docusaurus-types`, `docusaurus-utils-validation`, `docusaurus-utils`, `docusaurus`, `stylelint-copyright` + - [#6912](https://github.com/facebook/docusaurus/pull/6912) test: improve test coverage; multiple internal refactors ([@Josh-Cena](https://github.com/Josh-Cena)) +- Other + - [#6910](https://github.com/facebook/docusaurus/pull/6910) refactor: convert Jest infrastructure to TS ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6838](https://github.com/facebook/docusaurus/pull/6838) fix(website): changelog plugin leads to CI bugs on release ([@slorber](https://github.com/slorber)) +- `docusaurus-logger`, `docusaurus-mdx-loader`, `docusaurus-migrate`, `docusaurus-plugin-client-redirects`, `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-plugin-content-pages`, `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-utils`, `docusaurus` + - [#6908](https://github.com/facebook/docusaurus/pull/6908) chore: do not print prototype in jest snapshot ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-migrate`, `docusaurus-plugin-content-docs`, `docusaurus-theme-common`, `docusaurus-utils-validation`, `docusaurus-utils`, `docusaurus` + - [#6906](https://github.com/facebook/docusaurus/pull/6906) refactor: install eslint-plugin-regexp ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-mdx-loader`, `docusaurus-plugin-client-redirects`, `docusaurus-plugin-content-docs`, `docusaurus-theme-common`, `docusaurus-theme-search-algolia`, `docusaurus-utils`, `docusaurus` + - [#6905](https://github.com/facebook/docusaurus/pull/6905) test: improve test coverage; properly test core client APIs ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-logger`, `docusaurus-mdx-loader`, `docusaurus-migrate`, `docusaurus-plugin-client-redirects`, `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-plugin-sitemap`, `docusaurus-remark-plugin-npm2yarn`, `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-theme-live-codeblock`, `docusaurus-theme-translations`, `docusaurus-utils`, `docusaurus` + - [#6903](https://github.com/facebook/docusaurus/pull/6903) chore: spell-check test files ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-migrate`, `docusaurus-module-type-aliases`, `docusaurus-plugin-client-redirects`, `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-theme-common`, `docusaurus-types`, `docusaurus-utils-common`, `docusaurus-utils`, `docusaurus`, `lqip-loader` + - [#6902](https://github.com/facebook/docusaurus/pull/6902) test(theme-common): improve test coverage ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-cssnano-preset`, `docusaurus-logger`, `docusaurus-mdx-loader`, `docusaurus-migrate`, `docusaurus-plugin-client-redirects`, `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-plugin-content-pages`, `docusaurus-plugin-sitemap`, `docusaurus-remark-plugin-npm2yarn`, `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-theme-live-codeblock`, `docusaurus-theme-search-algolia`, `docusaurus-theme-translations`, `docusaurus-utils-common`, `docusaurus-utils-validation`, `docusaurus-utils`, `docusaurus`, `lqip-loader`, `stylelint-copyright` + - [#6900](https://github.com/facebook/docusaurus/pull/6900) test: enable a few jest eslint rules ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-logger`, `docusaurus-mdx-loader`, `docusaurus-migrate`, `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-theme-translations`, `docusaurus-utils-validation`, `docusaurus-utils`, `docusaurus`, `lqip-loader` + - [#6898](https://github.com/facebook/docusaurus/pull/6898) refactor: import jest as global; unify import style of some modules ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-theme-classic`, `docusaurus-theme-common` + - [#6891](https://github.com/facebook/docusaurus/pull/6891) refactor(theme-classic): avoid using clsx class dict with CSS modules ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-migrate`, `docusaurus-plugin-client-redirects`, `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-theme-translations`, `docusaurus-utils`, `docusaurus` + - [#6880](https://github.com/facebook/docusaurus/pull/6880) refactor: prefer fs.outputFile to ensureDir + writeFile ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-plugin-pwa`, `docusaurus-types`, `docusaurus` + - [#6866](https://github.com/facebook/docusaurus/pull/6866) refactor: improve types ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-mdx-loader`, `docusaurus-plugin-client-redirects`, `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-plugin-content-pages`, `docusaurus-plugin-pwa`, `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-types`, `docusaurus`, `lqip-loader` + - [#6864](https://github.com/facebook/docusaurus/pull/6864) refactor: remove unnecessary default values normalized during validation ([@Josh-Cena](https://github.com/Josh-Cena)) +- `create-docusaurus`, `docusaurus-migrate`, `docusaurus` + - [#6861](https://github.com/facebook/docusaurus/pull/6861) refactor: make JS executables included in the tsconfig for editor hints ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-types`, `docusaurus` + - [#6857](https://github.com/facebook/docusaurus/pull/6857) test: improve test coverage ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-logger`, `docusaurus-mdx-loader`, `docusaurus-migrate`, `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-remark-plugin-npm2yarn`, `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-theme-search-algolia`, `docusaurus-utils-common`, `docusaurus-utils`, `docusaurus`, `lqip-loader` + - [#6852](https://github.com/facebook/docusaurus/pull/6852) refactor: enable a few TS flags ([@Josh-Cena](https://github.com/Josh-Cena)) + +#### Committers: 28 + +- Afonso Jorge Ramos ([@afonsojramos](https://github.com/afonsojramos)) +- Alessandro Festa ([@alefesta](https://github.com/alefesta)) +- Alexey Pyltsyn ([@lex111](https://github.com/lex111)) +- Alois Klink ([@aloisklink](https://github.com/aloisklink)) +- Ariful Alam ([@arifszn](https://github.com/arifszn)) +- Begula ([@vedantmgoyal2009](https://github.com/vedantmgoyal2009)) +- Billy Chan ([@billy1624](https://github.com/billy1624)) +- Bugo ([@dragomano](https://github.com/dragomano)) +- Evan ([@DigiPie](https://github.com/DigiPie)) +- Felipe Santos ([@felipecrs](https://github.com/felipecrs)) +- Jadon N ([@jadonn](https://github.com/jadonn)) +- Joshua Chen ([@Josh-Cena](https://github.com/Josh-Cena)) +- Kayce Basques ([@kaycebasques](https://github.com/kaycebasques)) +- Kyohei Fukuda ([@hand-dot](https://github.com/hand-dot)) +- Nayan Patel ([@PatelN123](https://github.com/PatelN123)) +- Ngô Quốc Đạt ([@datlechin](https://github.com/datlechin)) +- Odarpi ([@odarpi](https://github.com/odarpi)) +- Pawel Kowaluk ([@pkowaluk](https://github.com/pkowaluk)) +- Roberto Vidal ([@jrvidal](https://github.com/jrvidal)) +- Sam Gutentag ([@samgutentag](https://github.com/samgutentag)) +- Sébastien Lorber ([@slorber](https://github.com/slorber)) +- Tsz W. TAM ([@rccttwd](https://github.com/rccttwd)) +- WonChul Heo ([@heowc](https://github.com/heowc)) +- Yorkie Liu ([@yorkie](https://github.com/yorkie)) +- [@seladb](https://github.com/seladb) +- moonrailgun ([@moonrailgun](https://github.com/moonrailgun)) +- nate contino ([@nathan-contino-mongo](https://github.com/nathan-contino-mongo)) +- tae ([@taejs](https://github.com/taejs)) + +## 2.0.0-beta.17 (2022-03-03) + +#### :rocket: New Feature + +- `docusaurus-plugin-content-blog`, `docusaurus-theme-classic` + - [#6783](https://github.com/facebook/docusaurus/pull/6783) feat: allow blog authors email ([@Josh-Cena](https://github.com/Josh-Cena)) + +#### :boom: Breaking Change + +- `docusaurus-theme-classic`, `docusaurus-theme-common` + - [#6771](https://github.com/facebook/docusaurus/pull/6771) refactor(theme-classic): replace color mode toggle with button; remove switchConfig ([@Josh-Cena](https://github.com/Josh-Cena)) + +#### :bug: Bug Fix + +- `docusaurus-theme-classic` + - [#6827](https://github.com/facebook/docusaurus/pull/6827) fix(theme-classic): restore docusaurus search meta ([@slorber](https://github.com/slorber)) + - [#6767](https://github.com/facebook/docusaurus/pull/6767) fix(theme-classic): allow code tags containing inline elements to stay inline ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-theme-common` + - [#6824](https://github.com/facebook/docusaurus/pull/6824) fix(theme-common): breadcrumbs home bug in docs-only ([@slorber](https://github.com/slorber)) + - [#6816](https://github.com/facebook/docusaurus/pull/6816) fix(theme-common): docs breadcrumbs not working with baseUrl ([@slorber](https://github.com/slorber)) +- `docusaurus-plugin-content-docs` + - [#6700](https://github.com/facebook/docusaurus/pull/6700) fix(content-docs): always sort autogenerated sidebar items by file/folder name by default ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus` + - [#6812](https://github.com/facebook/docusaurus/pull/6812) fix(core): remove hash/query when filtering existing files for broken link check ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-mdx-loader` + - [#6779](https://github.com/facebook/docusaurus/pull/6779) fix(mdx-loader): suppress image reading warning in Yarn PnP; log warning instead of error ([@Josh-Cena](https://github.com/Josh-Cena)) +- `create-docusaurus` + - [#6762](https://github.com/facebook/docusaurus/pull/6762) fix(create): update broken SVG paths in templates ([@anicholls](https://github.com/anicholls)) + +#### :nail_care: Polish + +- `docusaurus-theme-common` + - [#6826](https://github.com/facebook/docusaurus/pull/6826) refactor(theme-common): unify missing context errors ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-theme-classic`, `docusaurus-theme-common` + - [#6771](https://github.com/facebook/docusaurus/pull/6771) refactor(theme-classic): replace color mode toggle with button; remove switchConfig ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-theme-classic` + - [#6769](https://github.com/facebook/docusaurus/pull/6769) refactor(theme-classic): use Material icon for language dropdown ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-mdx-loader` + - [#6792](https://github.com/facebook/docusaurus/pull/6792) fix(mdx-loader): allow image paths to be URL encoded ([@Josh-Cena](https://github.com/Josh-Cena)) + +#### :memo: Documentation + +- Other + - [#6825](https://github.com/facebook/docusaurus/pull/6825) docs: Adds Netlify one click deploy to README ([@PatelN123](https://github.com/PatelN123)) + - [#6818](https://github.com/facebook/docusaurus/pull/6818) docs: add deploy with vercel button to README ([@PatelN123](https://github.com/PatelN123)) + - [#6817](https://github.com/facebook/docusaurus/pull/6817) docs: fix broken links ([@PatelN123](https://github.com/PatelN123)) + - [#6811](https://github.com/facebook/docusaurus/pull/6811) docs: add homepage banner in support of Ukraine ([@dmitryvinn](https://github.com/dmitryvinn)) + - [#6813](https://github.com/facebook/docusaurus/pull/6813) docs: mark dyte as opensource in showcase ([@vaibhavshn](https://github.com/vaibhavshn)) + - [#6776](https://github.com/facebook/docusaurus/pull/6776) docs: make GitHub actions explanation aligned with the code ([@arifszn](https://github.com/arifszn)) + - [#6772](https://github.com/facebook/docusaurus/pull/6772) docs: add basic documentation about client modules ([@Josh-Cena](https://github.com/Josh-Cena)) +- `create-docusaurus` + - [#6815](https://github.com/facebook/docusaurus/pull/6815) fix: consistently use `max-width: 996px` in media queries ([@dstotijn](https://github.com/dstotijn)) + +#### :house: Internal + +- `docusaurus-plugin-content-docs` + - [#6821](https://github.com/facebook/docusaurus/pull/6821) test(content-docs): refactor navigation test snapshot ([@Josh-Cena](https://github.com/Josh-Cena)) +- Other + - [#6768](https://github.com/facebook/docusaurus/pull/6768) test: add TypeScript template to E2E test matrix ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-utils` + - [#6773](https://github.com/facebook/docusaurus/pull/6773) refactor(utils): categorize functions into separate files ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-migrate` + - [#6761](https://github.com/facebook/docusaurus/pull/6761) chore: various internal fixes ([@Josh-Cena](https://github.com/Josh-Cena)) + +#### Committers: 8 + +- Alex Nicholls ([@anicholls](https://github.com/anicholls)) +- Ariful Alam ([@arifszn](https://github.com/arifszn)) +- David Stotijn ([@dstotijn](https://github.com/dstotijn)) +- Dmitry Vinnik ([@dmitryvinn](https://github.com/dmitryvinn)) +- Joshua Chen ([@Josh-Cena](https://github.com/Josh-Cena)) +- Nayan Patel ([@PatelN123](https://github.com/PatelN123)) +- Sébastien Lorber ([@slorber](https://github.com/slorber)) +- Vaibhav Shinde ([@vaibhavshn](https://github.com/vaibhavshn)) + +## 2.0.0-beta.16 (2022-02-25) + +#### :rocket: New Feature + +- `docusaurus-logger`, `docusaurus-module-type-aliases`, `docusaurus-plugin-debug`, `docusaurus-plugin-pwa`, `docusaurus-theme-classic`, `docusaurus-theme-search-algolia`, `docusaurus-types`, `docusaurus` + - [#6243](https://github.com/facebook/docusaurus/pull/6243) feat(core): brand new swizzle CLI experience ([@Josh-Cena](https://github.com/Josh-Cena)) +- `create-docusaurus` + - [#6750](https://github.com/facebook/docusaurus/pull/6750) feat(create): new --package-manager option; interactive package manager selection ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6610](https://github.com/facebook/docusaurus/pull/6610) feat(create): allow specifying a git clone strategy ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-theme-classic`, `docusaurus-theme-common` + - [#6723](https://github.com/facebook/docusaurus/pull/6723) feat: sync color mode between browser tabs ([@lex111](https://github.com/lex111)) +- `docusaurus-theme-search-algolia` + - [#6692](https://github.com/facebook/docusaurus/pull/6692) feat(search-algolia): allow disabling search page and configuring path ([@lex111](https://github.com/lex111)) +- `docusaurus-plugin-content-docs`, `docusaurus-theme-classic`, `docusaurus-theme-common` + - [#6517](https://github.com/facebook/docusaurus/pull/6517) feat(docs,theme-classic): docs breadcrumbs ([@jodyheavener](https://github.com/jodyheavener)) + - [#6519](https://github.com/facebook/docusaurus/pull/6519) feat(content-docs): sidebar item type "html" for rendering pure markup ([@jodyheavener](https://github.com/jodyheavener)) +- `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-utils` + - [#6593](https://github.com/facebook/docusaurus/pull/6593) feat(content-blog): infer blog post date from git history ([@felipecrs](https://github.com/felipecrs)) +- `docusaurus-plugin-content-docs` + - [#6619](https://github.com/facebook/docusaurus/pull/6619) feat(content-docs): add custom props front matter ([@TheCatLady](https://github.com/TheCatLady)) + - [#6452](https://github.com/facebook/docusaurus/pull/6452) feat(content-docs): allow explicitly disabling index page for generated category ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-plugin-content-blog` + - [#6603](https://github.com/facebook/docusaurus/pull/6603) feat(content-blog): allow customizing blog archive component through option ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-plugin-content-blog`, `docusaurus-theme-classic` + - [#6221](https://github.com/facebook/docusaurus/pull/6221) feat(content-blog): Allow pagination for BlogTagsPostsPage ([@redhoyasa](https://github.com/redhoyasa)) + +#### :boom: Breaking Change + +- `create-docusaurus`, `docusaurus-mdx-loader`, `docusaurus-migrate`, `docusaurus-plugin-client-redirects`, `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-plugin-content-pages`, `docusaurus-plugin-debug`, `docusaurus-plugin-ideal-image`, `docusaurus-plugin-pwa`, `docusaurus-plugin-sitemap`, `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-theme-live-codeblock`, `docusaurus-theme-search-algolia`, `docusaurus-theme-translations`, `docusaurus-utils`, `docusaurus`, `stylelint-copyright` + - [#6752](https://github.com/facebook/docusaurus/pull/6752) chore: upgrade docsearch-react to v3 stable, bump dependencies ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-mdx-loader`, `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-types` + - [#6729](https://github.com/facebook/docusaurus/pull/6729) refactor: make MDX export a flat TOC list instead of tree ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-types`, `docusaurus-utils-validation`, `docusaurus` + - [#6740](https://github.com/facebook/docusaurus/pull/6740) refactor: remove deprecated Webpack utils & validation escape hatch ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-theme-classic`, `docusaurus-theme-search-algolia` + - [#6707](https://github.com/facebook/docusaurus/pull/6707) refactor(theme-classic): bias again search metadata toward Algolia DocSearch ([@slorber](https://github.com/slorber)) +- `docusaurus-module-type-aliases`, `docusaurus-theme-common`, `docusaurus` + - [#6651](https://github.com/facebook/docusaurus/pull/6651) refactor: reduce exported members of docusaurus router ([@Josh-Cena](https://github.com/Josh-Cena)) + +#### :bug: Bug Fix + +- `docusaurus-theme-common` + - [#6758](https://github.com/facebook/docusaurus/pull/6758) fix(theme-common): isSamePath should be case-insensitive ([@slorber](https://github.com/slorber)) + - [#6748](https://github.com/facebook/docusaurus/pull/6748) fix(theme-classic): temporarily disable toc heading autoscrolling ([@slorber](https://github.com/slorber)) + - [#6696](https://github.com/facebook/docusaurus/pull/6696) fix(theme-common): do not run useLocationChange when hot reloading ([@lex111](https://github.com/lex111)) + - [#6490](https://github.com/facebook/docusaurus/pull/6490) fix(theme-classic): do not switch color modes when printing ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-module-type-aliases`, `docusaurus-theme-classic`, `docusaurus-theme-common` + - [#6749](https://github.com/facebook/docusaurus/pull/6749) fix(theme-classic): fix breadcrumb home link bug with new useHomePageRoute() hook ([@slorber](https://github.com/slorber)) +- `docusaurus-plugin-content-docs` + - [#6720](https://github.com/facebook/docusaurus/pull/6720) fix(content-docs): create assets for frontmatter images ([@lebalz](https://github.com/lebalz)) + - [#6592](https://github.com/facebook/docusaurus/pull/6592) fix(content-docs): read last update from inner git repositories ([@felipecrs](https://github.com/felipecrs)) + - [#6477](https://github.com/facebook/docusaurus/pull/6477) fix(content-docs): export versioning utils ([@milesj](https://github.com/milesj)) +- `docusaurus-mdx-loader` + - [#6712](https://github.com/facebook/docusaurus/pull/6712) fix(mdx-loader): make headings containing links properly formatted in ToC ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus` + - [#6701](https://github.com/facebook/docusaurus/pull/6701) fix(cli): disable directory listing in serve ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6607](https://github.com/facebook/docusaurus/pull/6607) fix(cli): log error itself on unhandled rejection ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6500](https://github.com/facebook/docusaurus/pull/6500) fix(cli): allow passing a list of file names to write-heading-ids ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6496](https://github.com/facebook/docusaurus/pull/6496) fix(core): configValidation should allow inline theme functions ([@slorber](https://github.com/slorber)) +- `docusaurus-theme-classic` + - [#6652](https://github.com/facebook/docusaurus/pull/6652) fix(theme-classic): minor BTT button fixes ([@lex111](https://github.com/lex111)) + - [#6612](https://github.com/facebook/docusaurus/pull/6612) fix(theme-classic): make Prism additional languages properly server-side rendered ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6599](https://github.com/facebook/docusaurus/pull/6599) fix(theme-classic): add docSidebar as allowed item in dropdown ([@homotechsual](https://github.com/homotechsual)) + - [#6531](https://github.com/facebook/docusaurus/pull/6531) fix(theme-classic): highlight active collapsible doc category properly ([@lex111](https://github.com/lex111)) + - [#6515](https://github.com/facebook/docusaurus/pull/6515) fix(theme-classic): add key prop for SimpleLinks map ([@kgajera](https://github.com/kgajera)) + - [#6508](https://github.com/facebook/docusaurus/pull/6508) fix(theme-classic): apply width/height for footer logos without href ([@kgajera](https://github.com/kgajera)) +- `docusaurus-utils` + - [#6617](https://github.com/facebook/docusaurus/pull/6617) fix(utils): convert Markdown links in reference-style links with multiple spaces ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6489](https://github.com/facebook/docusaurus/pull/6489) fix(utils): do not resolve Markdown paths with @site prefix ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6478](https://github.com/facebook/docusaurus/pull/6478) fix(utils): Markdown linkification match local paths beginning with http ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-plugin-content-docs`, `docusaurus-theme-classic` + - [#6495](https://github.com/facebook/docusaurus/pull/6495) fix(content-docs): render category with no subitems as a normal link ([@Josh-Cena](https://github.com/Josh-Cena)) + +#### :nail_care: Polish + +- `docusaurus-mdx-loader`, `docusaurus-migrate`, `docusaurus-plugin-client-redirects`, `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-theme-common`, `docusaurus-theme-search-algolia`, `docusaurus-utils`, `docusaurus`, `lqip-loader` + - [#6755](https://github.com/facebook/docusaurus/pull/6755) refactor: unify error handling behavior ([@Josh-Cena](https://github.com/Josh-Cena)) +- `create-docusaurus` + - [#6679](https://github.com/facebook/docusaurus/pull/6679) feat(create): better detection of package manager preference ([@lex111](https://github.com/lex111)) + - [#6481](https://github.com/facebook/docusaurus/pull/6481) refactor(init): promote good practices; use site alias ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-plugin-content-docs` + - [#6745](https://github.com/facebook/docusaurus/pull/6745) fix(content-docs): improve sidebar shorthand normalization error message ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6602](https://github.com/facebook/docusaurus/pull/6602) feat(content-docs): allow omitting enclosing array consistently for category shorthand ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6596](https://github.com/facebook/docusaurus/pull/6596) refactor(content-docs): clean up sidebars logic; validate generator returns ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6586](https://github.com/facebook/docusaurus/pull/6586) refactor(content-docs): read category metadata files before autogenerating ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-module-type-aliases`, `docusaurus-plugin-ideal-image`, `docusaurus-plugin-pwa`, `docusaurus-theme-classic`, `docusaurus` + - [#6730](https://github.com/facebook/docusaurus/pull/6730) refactor: declare all props as interfaces ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-theme-translations` + - [#6711](https://github.com/facebook/docusaurus/pull/6711) chore(theme-translations): complete Korean translations ([@revi](https://github.com/revi)) + - [#6686](https://github.com/facebook/docusaurus/pull/6686) fix(theme-translations): improve Korean translations ([@winterlood](https://github.com/winterlood)) + - [#6635](https://github.com/facebook/docusaurus/pull/6635) refactor(theme-translation): improve Traditional Chinese translation quality ([@toto6038](https://github.com/toto6038)) +- `docusaurus-theme-classic`, `docusaurus-theme-translations` + - [#6674](https://github.com/facebook/docusaurus/pull/6674) fix(theme-classic): improve aria label of color mode toggle ([@Josh-Cena](https://github.com/Josh-Cena)) +- `create-docusaurus`, `docusaurus-theme-classic` + - [#6668](https://github.com/facebook/docusaurus/pull/6668) refactor: recommend using data-theme without html element selector ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-theme-classic` + - [#6622](https://github.com/facebook/docusaurus/pull/6622) refactor(theme-classic): clean up CSS of doc sidebar item ([@lex111](https://github.com/lex111)) +- `docusaurus` + - [#6644](https://github.com/facebook/docusaurus/pull/6644) fix(core): forward ref to Link's anchor element ([@koistya](https://github.com/koistya)) + - [#6646](https://github.com/facebook/docusaurus/pull/6646) fix(cli): make docusaurus clear also remove .yarn/.cache folder ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6306](https://github.com/facebook/docusaurus/pull/6306) feat(core): use react-helmet-async ([@seyoon20087](https://github.com/seyoon20087)) +- `docusaurus-utils-validation` + - [#6656](https://github.com/facebook/docusaurus/pull/6656) feat: allow numbers in plugin ID ([@cdemonchy-pro](https://github.com/cdemonchy-pro)) +- `docusaurus-mdx-loader`, `docusaurus-utils`, `lqip-loader` + - [#6650](https://github.com/facebook/docusaurus/pull/6650) refactor(utils): replace hash with contenthash for file loader ([@Josh-Cena](https://github.com/Josh-Cena)) +- `create-docusaurus`, `docusaurus-mdx-loader`, `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-plugin-content-pages`, `docusaurus-plugin-debug`, `docusaurus-plugin-google-analytics`, `docusaurus-plugin-google-gtag`, `docusaurus-plugin-ideal-image`, `docusaurus-plugin-pwa`, `docusaurus-plugin-sitemap`, `docusaurus-preset-classic`, `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-theme-search-algolia`, `docusaurus-utils-validation`, `docusaurus-utils` + - [#6615](https://github.com/facebook/docusaurus/pull/6615) fix: remove more peer dependency warnings ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-mdx-loader` + - [#6598](https://github.com/facebook/docusaurus/pull/6598) feat: make Markdown images lazy loaded ([@johnnyreilly](https://github.com/johnnyreilly)) +- `docusaurus-theme-classic`, `docusaurus-theme-common` + - [#6505](https://github.com/facebook/docusaurus/pull/6505) fix(theme-classic): make focused link outlined with JS disabled ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-plugin-content-docs`, `docusaurus-theme-common`, `docusaurus-theme-search-algolia`, `docusaurus-types`, `docusaurus-utils`, `docusaurus` + - [#6507](https://github.com/facebook/docusaurus/pull/6507) refactor: improve internal typing ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-mdx-loader`, `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-plugin-content-pages`, `docusaurus-plugin-debug`, `docusaurus-plugin-google-analytics`, `docusaurus-plugin-google-gtag`, `docusaurus-plugin-sitemap`, `docusaurus-preset-classic`, `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-theme-search-algolia`, `docusaurus-utils-validation`, `docusaurus-utils` + - [#6498](https://github.com/facebook/docusaurus/pull/6498) fix: updating peerDependency fields for yarn berry ([@vidarc](https://github.com/vidarc)) +- `docusaurus-theme-classic`, `docusaurus-theme-search-algolia`, `docusaurus-theme-translations` + - [#6482](https://github.com/facebook/docusaurus/pull/6482) feat: mark some text labels as translatable ([@Josh-Cena](https://github.com/Josh-Cena)) + +#### :memo: Documentation + +- Other + - [#6727](https://github.com/facebook/docusaurus/pull/6727) docs: add Blog Matheus Brunelli site to showcase ([@mrbrunelli](https://github.com/mrbrunelli)) + - [#6721](https://github.com/facebook/docusaurus/pull/6721) docs: add Butterfly Documentation to showcase ([@CodeDoctorDE](https://github.com/CodeDoctorDE)) + - [#6710](https://github.com/facebook/docusaurus/pull/6710) docs(website): Add techharvesting to showcase ([@NaseelNiyas](https://github.com/NaseelNiyas)) + - [#6708](https://github.com/facebook/docusaurus/pull/6708) docs: add doc for generated-index keyword/image metadata ([@slorber](https://github.com/slorber)) + - [#6709](https://github.com/facebook/docusaurus/pull/6709) docs(website): fix video responsiveness ([@lex111](https://github.com/lex111)) + - [#6687](https://github.com/facebook/docusaurus/pull/6687) docs: add deep dive video for Docusaurus ([@dmitryvinn](https://github.com/dmitryvinn)) + - [#6704](https://github.com/facebook/docusaurus/pull/6704) docs(website): search doc typo searchParameters ([@slorber](https://github.com/slorber)) + - [#6682](https://github.com/facebook/docusaurus/pull/6682) docs: add redux-cool site to showcase ([@Ruben-Arushanyan](https://github.com/Ruben-Arushanyan)) + - [#6677](https://github.com/facebook/docusaurus/pull/6677) docs: add Rivalis to showcase ([@kalevski](https://github.com/kalevski)) + - [#6676](https://github.com/facebook/docusaurus/pull/6676) docs: add SmartCookieWeb site to showcase ([@CookieJarApps](https://github.com/CookieJarApps)) + - [#6675](https://github.com/facebook/docusaurus/pull/6675) docs: mention that all official themes are TypeScript-covered ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6673](https://github.com/facebook/docusaurus/pull/6673) docs: mention about blog date in front matter ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6672](https://github.com/facebook/docusaurus/pull/6672) refactor(website): extract homepage data from UI; feature text updates ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6670](https://github.com/facebook/docusaurus/pull/6670) docs: add CyberDrain Improved Partner Portal (CIPP) to showcase ([@homotechsual](https://github.com/homotechsual)) + - [#6667](https://github.com/facebook/docusaurus/pull/6667) fix(website): make YT iframe responsive ([@lex111](https://github.com/lex111)) + - [#6659](https://github.com/facebook/docusaurus/pull/6659) docs: add eli5 video to home page ([@dmitryvinn-fb](https://github.com/dmitryvinn-fb)) + - [#6633](https://github.com/facebook/docusaurus/pull/6633) docs: improve wording of using Markdown file paths ([@BigDataWriter](https://github.com/BigDataWriter)) + - [#6624](https://github.com/facebook/docusaurus/pull/6624) docs: add Resoto & Some Engineering Inc. to showcase ([@TheCatLady](https://github.com/TheCatLady)) + - [#6611](https://github.com/facebook/docusaurus/pull/6611) docs: fix bad anchor link syntax ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6591](https://github.com/facebook/docusaurus/pull/6591) docs: improve Github Actions example jobs ([@ebarojas](https://github.com/ebarojas)) + - [#6426](https://github.com/facebook/docusaurus/pull/6426) feat(website): add Tweets section ([@yangshun](https://github.com/yangshun)) + - [#6532](https://github.com/facebook/docusaurus/pull/6532) docs: add SAP Cloud SDK to showcase ([@artemkovalyov](https://github.com/artemkovalyov)) + - [#6513](https://github.com/facebook/docusaurus/pull/6513) docs: clean up CONTRIBUTING ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6501](https://github.com/facebook/docusaurus/pull/6501) docs: add Cloudflare pages deployment guide ([@apidev234](https://github.com/apidev234)) + - [#6499](https://github.com/facebook/docusaurus/pull/6499) docs: mention how env vars can be read ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6492](https://github.com/facebook/docusaurus/pull/6492) docs: mention where to find the sitemap ([@tamalweb](https://github.com/tamalweb)) + - [#6491](https://github.com/facebook/docusaurus/pull/6491) docs: add developers.verida to showcase ([@nick-verida](https://github.com/nick-verida)) + - [#6414](https://github.com/facebook/docusaurus/pull/6414) feat(website): new plugin to load CHANGELOG and render as blog ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6404](https://github.com/facebook/docusaurus/pull/6404) docs: elaborate on Markdown asset linking; document pathname:// protocol ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6484](https://github.com/facebook/docusaurus/pull/6484) docs: remove mention that CDN resources are cached cross-domain ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6429](https://github.com/facebook/docusaurus/pull/6429) refactor: self-host KaTeX assets ([@pranabdas](https://github.com/pranabdas)) + - [#6483](https://github.com/facebook/docusaurus/pull/6483) docs: mark a lot of website texts as translatable ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-preset-classic` + - [#6627](https://github.com/facebook/docusaurus/pull/6627) docs: fix presets documentation link ([@thedanielhanke](https://github.com/thedanielhanke)) + +#### :house: Internal + +- `docusaurus-theme-classic` + - [#6759](https://github.com/facebook/docusaurus/pull/6759) refactor(theme-classic): merge CSS files for Heading ([@slorber](https://github.com/slorber)) + - [#6584](https://github.com/facebook/docusaurus/pull/6584) misc: enable jsx-key eslint rule ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-migrate` + - [#6756](https://github.com/facebook/docusaurus/pull/6756) test: sort migration test FS mock calls ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6609](https://github.com/facebook/docusaurus/pull/6609) refactor(migrate): change internal methods' parameter style ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6476](https://github.com/facebook/docusaurus/pull/6476) chore: fix Stylelint globs for editor support ([@nschonni](https://github.com/nschonni)) +- `docusaurus-plugin-content-docs`, `docusaurus-theme-classic` + - [#6744](https://github.com/facebook/docusaurus/pull/6744) fix(content-docs): properly display collocated social card image ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-module-type-aliases`, `docusaurus-types`, `docusaurus` + - [#6742](https://github.com/facebook/docusaurus/pull/6742) refactor: improve client modules types ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-module-type-aliases` + - [#6741](https://github.com/facebook/docusaurus/pull/6741) chore(module-type-aliases): add react as peer dependency ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6658](https://github.com/facebook/docusaurus/pull/6658) refactor(module-aliases): remove react-helmet dependency ([@Josh-Cena](https://github.com/Josh-Cena)) +- Other + - [#6726](https://github.com/facebook/docusaurus/pull/6726) misc: improve bug report template ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6512](https://github.com/facebook/docusaurus/pull/6512) misc: configure linguist behavior to show better language stats ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6487](https://github.com/facebook/docusaurus/pull/6487) chore: fix codesandbox example link + mention npm publish recovery ([@slorber](https://github.com/slorber)) + - [#6486](https://github.com/facebook/docusaurus/pull/6486) chore: update examples for beta.15 ([@slorber](https://github.com/slorber)) + - [#6485](https://github.com/facebook/docusaurus/pull/6485) fix(website): bad translate tags without default translation ([@slorber](https://github.com/slorber)) +- `docusaurus-plugin-client-redirects`, `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-theme-search-algolia`, `docusaurus-theme-translations`, `docusaurus-utils`, `docusaurus`, `lqip-loader` + - [#6716](https://github.com/facebook/docusaurus/pull/6716) refactor: ensure lodash is default-imported ([@Josh-Cena](https://github.com/Josh-Cena)) +- `create-docusaurus`, `docusaurus-logger`, `docusaurus-migrate`, `docusaurus` + - [#6661](https://github.com/facebook/docusaurus/pull/6661) refactor: convert CLI entry points to ESM; migrate create-docusaurus to ESM ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-module-type-aliases`, `docusaurus-theme-common`, `docusaurus` + - [#6651](https://github.com/facebook/docusaurus/pull/6651) refactor: reduce exported members of docusaurus router ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-theme-classic` + - [#6629](https://github.com/facebook/docusaurus/pull/6629) refactor: move module declarations for non-route components to theme-classic ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-plugin-pwa`, `docusaurus-theme-classic` + - [#6614](https://github.com/facebook/docusaurus/pull/6614) refactor: remove Babel plugins that are included in preset-env ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-module-type-aliases`, `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-theme-live-codeblock`, `docusaurus-theme-translations`, `docusaurus-utils-validation`, `docusaurus-utils`, `docusaurus` + - [#6605](https://github.com/facebook/docusaurus/pull/6605) chore: fix ESLint warnings, restrict export all syntax ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-theme-live-codeblock`, `docusaurus-theme-search-algolia` + - [#6583](https://github.com/facebook/docusaurus/pull/6583) refactor(live-codeblock): migrate theme to TS ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-migrate`, `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-plugin-content-pages`, `docusaurus-plugin-ideal-image`, `docusaurus-plugin-pwa`, `docusaurus-theme-common`, `docusaurus-utils`, `docusaurus`, `lqip-loader` + - [#6524](https://github.com/facebook/docusaurus/pull/6524) refactor: enforce named capture groups; clean up regexes ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-migrate`, `docusaurus-plugin-content-docs`, `docusaurus` + - [#6521](https://github.com/facebook/docusaurus/pull/6521) refactor: mark all functions that import external modules as async ([@Josh-Cena](https://github.com/Josh-Cena)) +- `create-docusaurus`, `docusaurus-cssnano-preset`, `docusaurus-logger`, `docusaurus-mdx-loader`, `docusaurus-migrate`, `docusaurus-module-type-aliases`, `docusaurus-plugin-client-redirects`, `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-plugin-content-pages`, `docusaurus-plugin-debug`, `docusaurus-plugin-google-analytics`, `docusaurus-plugin-google-gtag`, `docusaurus-plugin-ideal-image`, `docusaurus-plugin-pwa`, `docusaurus-remark-plugin-npm2yarn`, `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-theme-search-algolia`, `docusaurus-theme-translations`, `docusaurus-types`, `docusaurus-utils-common`, `docusaurus-utils-validation`, `docusaurus-utils`, `docusaurus`, `stylelint-copyright` + - [#6514](https://github.com/facebook/docusaurus/pull/6514) chore: clean up ESLint config, enable a few rules ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-types`, `docusaurus` + - [#6511](https://github.com/facebook/docusaurus/pull/6511) refactor(core): convert theme-fallback to TS ([@Josh-Cena](https://github.com/Josh-Cena)) +- `create-docusaurus`, `docusaurus-utils` + - [#6506](https://github.com/facebook/docusaurus/pull/6506) test: add test for readOutputHTMLFile ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-migrate`, `docusaurus-theme-common` + - [#6502](https://github.com/facebook/docusaurus/pull/6502) refactor: fix all eslint warnings ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-mdx-loader`, `docusaurus-remark-plugin-npm2yarn`, `docusaurus` + - [#6474](https://github.com/facebook/docusaurus/pull/6474) test: rename 'fixtures' to '**fixtures**' ([@nschonni](https://github.com/nschonni)) + +#### :running_woman: Performance + +- `create-docusaurus`, `docusaurus-mdx-loader`, `docusaurus-migrate`, `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-theme-search-algolia`, `docusaurus-theme-translations`, `docusaurus-utils`, `docusaurus` + - [#6725](https://github.com/facebook/docusaurus/pull/6725) refactor: convert all fs methods to async ([@Josh-Cena](https://github.com/Josh-Cena)) + +#### Committers: 38 + +- Alexey Pyltsyn ([@lex111](https://github.com/lex111)) +- Artem Kovalov ([@artemkovalyov](https://github.com/artemkovalyov)) +- Balthasar Hofer ([@lebalz](https://github.com/lebalz)) +- Clement Demonchy ([@cdemonchy-pro](https://github.com/cdemonchy-pro)) +- CodeDoctor ([@CodeDoctorDE](https://github.com/CodeDoctorDE)) +- Daniel Hanke ([@thedanielhanke](https://github.com/thedanielhanke)) +- Daniel Kalevski ([@kalevski](https://github.com/kalevski)) +- Dmitry Vinnik ([@dmitryvinn](https://github.com/dmitryvinn)) +- Dmitry Vinnik | Meta ([@dmitryvinn-fb](https://github.com/dmitryvinn-fb)) +- Erick Zhao ([@erickzhao](https://github.com/erickzhao)) +- Everardo J. Barojas M. ([@ebarojas](https://github.com/ebarojas)) +- Felipe Santos ([@felipecrs](https://github.com/felipecrs)) +- Gaurish ([@apidev234](https://github.com/apidev234)) +- Hong Yongmin ([@revi](https://github.com/revi)) +- Jody Heavener ([@jodyheavener](https://github.com/jodyheavener)) +- John Reilly ([@johnnyreilly](https://github.com/johnnyreilly)) +- Joshua Chen ([@Josh-Cena](https://github.com/Josh-Cena)) +- Kishan Gajera ([@kgajera](https://github.com/kgajera)) +- Konstantin Tarkus ([@koistya](https://github.com/koistya)) +- Matheus Ricardo Brunelli ([@mrbrunelli](https://github.com/mrbrunelli)) +- Matthew Ailes ([@vidarc](https://github.com/vidarc)) +- Mikey O'Toole ([@homotechsual](https://github.com/homotechsual)) +- Miles Johnson ([@milesj](https://github.com/milesj)) +- Muhammad Redho Ayassa ([@redhoyasa](https://github.com/redhoyasa)) +- Naseel Niyas ([@NaseelNiyas](https://github.com/NaseelNiyas)) +- Nick Schonning ([@nschonni](https://github.com/nschonni)) +- Pranab Das ([@pranabdas](https://github.com/pranabdas)) +- Ruben Arushanyan ([@Ruben-Arushanyan](https://github.com/Ruben-Arushanyan)) +- Sébastien Lorber ([@slorber](https://github.com/slorber)) +- Tamal Web ([@tamalweb](https://github.com/tamalweb)) +- Yangshun Tay ([@yangshun](https://github.com/yangshun)) +- [@BigDataWriter](https://github.com/BigDataWriter) +- [@CookieJarApps](https://github.com/CookieJarApps) +- [@TheCatLady](https://github.com/TheCatLady) +- [@nick-verida](https://github.com/nick-verida) +- [@seyoon20087](https://github.com/seyoon20087) +- [@toto6038](https://github.com/toto6038) +- 이정환 ([@winterlood](https://github.com/winterlood)) + +## 2.0.0-beta.15 (2022-01-26) + +#### :rocket: New Feature + +- `docusaurus-plugin-content-docs` + - [#6451](https://github.com/facebook/docusaurus/pull/6451) feat(content-docs): expose isCategoryIndex matcher to customize conventions ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#5782](https://github.com/facebook/docusaurus/pull/5782) feat(content-docs): displayed_sidebar front matter ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-theme-classic`, `docusaurus-theme-common` + - [#6466](https://github.com/facebook/docusaurus/pull/6466) feat(theme-classic): add stable class for DocSidebarContainer ([@homotechsual](https://github.com/homotechsual)) + - [#3811](https://github.com/facebook/docusaurus/pull/3811) feat(theme-classic): auto-collapse sibling categories in doc sidebar ([@josephriosIO](https://github.com/josephriosIO)) + - [#6216](https://github.com/facebook/docusaurus/pull/6216) feat(theme-classic): usable CodeBlock outside markdown ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-plugin-content-blog`, `docusaurus-theme-classic` + - [#6416](https://github.com/facebook/docusaurus/pull/6416) feat(content-blog): allow authors list to contain images only ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-plugin-content-blog` + - [#6415](https://github.com/facebook/docusaurus/pull/6415) feat(content-blog): allow disabling generating archive ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6245](https://github.com/facebook/docusaurus/pull/6245) feat(content-blog): parse date from middle of file path ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6388](https://github.com/facebook/docusaurus/pull/6388) feat(content-blog): include tags in feed ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-module-type-aliases`, `docusaurus-theme-classic`, `docusaurus-types`, `docusaurus` + - [#6371](https://github.com/facebook/docusaurus/pull/6371) feat(core, theme-classic): allow overriding htmlLang ([@noomorph](https://github.com/noomorph)) +- `docusaurus-mdx-loader` + - [#6323](https://github.com/facebook/docusaurus/pull/6323) feat(mdx-loader): preserve hash in image src; support GH themed images ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-plugin-content-docs`, `docusaurus-theme-classic` + - [#6139](https://github.com/facebook/docusaurus/pull/6139) feat(theme-classic): new navbar item linking to a sidebar ([@lmpham1](https://github.com/lmpham1)) + - [#6239](https://github.com/facebook/docusaurus/pull/6239) feat(content-docs): allow SEO metadata for category index pages ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-plugin-sitemap` + - [#6248](https://github.com/facebook/docusaurus/pull/6248) feat(sitemap): remove trailingSlash option; respect noIndex config ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-plugin-ideal-image`, `docusaurus-theme-translations` + - [#6173](https://github.com/facebook/docusaurus/pull/6173) feat(ideal-image): allow translating status messages ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-plugin-ideal-image` + - [#6155](https://github.com/facebook/docusaurus/pull/6155) feat(ideal-image): new option disableInDev ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-theme-translations` + - [#6169](https://github.com/facebook/docusaurus/pull/6169) feat(theme-translations): add Italian translations ([@mcallisto](https://github.com/mcallisto)) +- `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-plugin-content-pages`, `docusaurus-types`, `docusaurus` + - [#6166](https://github.com/facebook/docusaurus/pull/6166) feat: async plugin creator functions ([@slorber](https://github.com/slorber)) +- `docusaurus` + - [#6165](https://github.com/facebook/docusaurus/pull/6165) feat(core): async docusaurus.config.js creator function ([@slorber](https://github.com/slorber)) + +#### :boom: Breaking Change + +- `docusaurus-theme-search-algolia` + - [#6407](https://github.com/facebook/docusaurus/pull/6407) feat(search): enable contextual search by default ([@slorber](https://github.com/slorber)) +- `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-theme-live-codeblock`, `docusaurus-theme-search-algolia` + - [#6289](https://github.com/facebook/docusaurus/pull/6289) refactor: move @theme/hooks to @docusaurus/theme-common ([@slorber](https://github.com/slorber)) +- `docusaurus-theme-classic` + - [#6283](https://github.com/facebook/docusaurus/pull/6283) refactor(theme-classic): apply import/no-named-export eslint rule ([@slorber](https://github.com/slorber)) +- `docusaurus-plugin-sitemap` + - [#6248](https://github.com/facebook/docusaurus/pull/6248) feat(sitemap): remove trailingSlash option; respect noIndex config ([@Josh-Cena](https://github.com/Josh-Cena)) + +#### :bug: Bug Fix + +- `docusaurus-plugin-content-blog`, `docusaurus-theme-classic`, `docusaurus-types`, `docusaurus-utils-common`, `docusaurus-utils`, `docusaurus` + - [#6454](https://github.com/facebook/docusaurus/pull/6454) fix(content-blog): generate feed by reading build output ([@Josh-Cena](https://github.com/Josh-Cena)) +- `create-docusaurus` + - [#6468](https://github.com/facebook/docusaurus/pull/6468) fix(init): cd to correct path when installing ([@gabrielcsapo](https://github.com/gabrielcsapo)) +- `docusaurus-mdx-loader` + - [#4827](https://github.com/facebook/docusaurus/pull/4827) fix: allow links to JSON in .md files to be transformed as asset links ([@antmcc49](https://github.com/antmcc49)) +- `docusaurus-plugin-content-docs` + - [#6435](https://github.com/facebook/docusaurus/pull/6435) fix(content-docs): make getActivePlugin match plugin paths more exactly ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6310](https://github.com/facebook/docusaurus/pull/6310) fix: highlight appropriate navItem when browsing generated category index ([@tapanchudasama](https://github.com/tapanchudasama)) + - [#6202](https://github.com/facebook/docusaurus/pull/6202) fix(content-docs): quotify path when retrieving git history ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus` + - [#6424](https://github.com/facebook/docusaurus/pull/6424) fix(core): fix css url("image.png"), use css-loader v6 with esModules: false ([@slorber](https://github.com/slorber)) + - [#6378](https://github.com/facebook/docusaurus/pull/6378) fix(core): do not coerce webpack warning to string ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6197](https://github.com/facebook/docusaurus/pull/6197) fix(cli): quotify temp path in deploy command ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6168](https://github.com/facebook/docusaurus/pull/6168) fix(core): update webpack-dev-server + fix deprecation warning ([@slorber](https://github.com/slorber)) +- `docusaurus-logger`, `docusaurus-utils` + - [#6384](https://github.com/facebook/docusaurus/pull/6384) fix(logger): properly stringify objects for logging ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-module-type-aliases`, `docusaurus-theme-classic`, `docusaurus` + - [#6338](https://github.com/facebook/docusaurus/pull/6338) fix(core): error boundary should allow no children ([@slorber](https://github.com/slorber)) +- `docusaurus-theme-classic` + - [#6314](https://github.com/facebook/docusaurus/pull/6314) fix(theme-classic): fix mobile version dropdown label with only one version ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6288](https://github.com/facebook/docusaurus/pull/6288) fix(theme-classic): add missing role=region to SkipToContent ([@JoshuaKGoldberg](https://github.com/JoshuaKGoldberg)) + - [#6213](https://github.com/facebook/docusaurus/pull/6213) refactor(theme-classic): extract common PaginatorNavLink component ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6177](https://github.com/facebook/docusaurus/pull/6177) fix(theme-classic): make React elements in pre render correctly ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-plugin-client-redirects`, `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-plugin-content-pages`, `docusaurus-preset-classic`, `docusaurus-theme-classic`, `docusaurus-theme-search-algolia` + - [#6300](https://github.com/facebook/docusaurus/pull/6300) refactor: move exported type definitions to declaration file ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-migrate` + - [#6276](https://github.com/facebook/docusaurus/pull/6276) fix(migrate): migration CLI should correctly migrate gtag options ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-plugin-content-blog` + - [#6244](https://github.com/facebook/docusaurus/pull/6244) fix(content-blog): always convert front matter date as UTC ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-mdx-loader`, `docusaurus-utils`, `docusaurus` + - [#6190](https://github.com/facebook/docusaurus/pull/6190) fix(utils): properly escape Windows paths ([@Josh-Cena](https://github.com/Josh-Cena)) + +#### :nail_care: Polish + +- `docusaurus-module-type-aliases` + - [#6469](https://github.com/facebook/docusaurus/pull/6469) fix(module-type-aliases): fix type def for translate params ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-theme-search-algolia` + - [#6407](https://github.com/facebook/docusaurus/pull/6407) feat(search): enable contextual search by default ([@slorber](https://github.com/slorber)) +- `docusaurus-mdx-loader` + - [#6443](https://github.com/facebook/docusaurus/pull/6443) refactor(mdx-loader): use vfile.path to access Markdown file path ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-theme-classic` + - [#6427](https://github.com/facebook/docusaurus/pull/6427) feat(theme-classic): add aria-current to sidebar category link ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6391](https://github.com/facebook/docusaurus/pull/6391) refactor(theme-classic): add comments to Prism setup; minor refactor ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6240](https://github.com/facebook/docusaurus/pull/6240) refactor(theme-classic): use front matter from metadata for BlogPostPage ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus` + - [#6419](https://github.com/facebook/docusaurus/pull/6419) feat(core): warn users about hand-modifying generated files ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6405](https://github.com/facebook/docusaurus/pull/6405) feat(core): check imported API name when extracting translations ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6291](https://github.com/facebook/docusaurus/pull/6291) feat(core): improve error message for BrowserOnly; better docs ([@Josh-Cena](https://github.com/Josh-Cena)) +- `create-docusaurus` + - [#5822](https://github.com/facebook/docusaurus/pull/5822) feat: update website & init template palette to pass WCAG test; include contrast check in ColorGenerator ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6368](https://github.com/facebook/docusaurus/pull/6368) fix(create-docusaurus): add useBaseUrl for image URLs ([@alias-mac](https://github.com/alias-mac)) +- `docusaurus-plugin-content-pages`, `docusaurus-theme-classic` + - [#6400](https://github.com/facebook/docusaurus/pull/6400) feat(content-pages): front matter validation, include front matter in metadata ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-mdx-loader`, `docusaurus-theme-classic` + - [#6339](https://github.com/facebook/docusaurus/pull/6339) feat(mdx-loader): read image dimensions when processing Markdown ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-plugin-content-blog` + - [#6388](https://github.com/facebook/docusaurus/pull/6388) feat(content-blog): include tags in feed ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6189](https://github.com/facebook/docusaurus/pull/6189) feat(content-blog): include front matter in loaded content metadata ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-theme-common` + - [#6317](https://github.com/facebook/docusaurus/pull/6317) feat(theme-classic): autoscroll TOC with active link ([@cerkiewny](https://github.com/cerkiewny)) +- `docusaurus-mdx-loader`, `docusaurus-plugin-client-redirects`, `docusaurus-plugin-content-blog`, `docusaurus-theme-search-algolia`, `docusaurus-utils-validation`, `docusaurus-utils`, `docusaurus` + - [#6303](https://github.com/facebook/docusaurus/pull/6303) test(utils, mdx-loader, core): improve coverage ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-plugin-google-analytics`, `docusaurus-plugin-google-gtag`, `docusaurus-preset-classic` + - [#6284](https://github.com/facebook/docusaurus/pull/6284) fix(preset-classic): throw if preset finds GA options in theme config ([@Josh-Cena](https://github.com/Josh-Cena)) +- `create-docusaurus`, `docusaurus` + - [#6186](https://github.com/facebook/docusaurus/pull/6186) refactor: print trailing new line when outputting JSON ([@Josh-Cena](https://github.com/Josh-Cena)) + +#### :memo: Documentation + +- Other + - [#6296](https://github.com/facebook/docusaurus/pull/6296) docs: add advanced guides ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6459](https://github.com/facebook/docusaurus/pull/6459) docs: add replicad to showcase ([@sgenoud](https://github.com/sgenoud)) + - [#6334](https://github.com/facebook/docusaurus/pull/6334) docs: 2021 recap blog post ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6458](https://github.com/facebook/docusaurus/pull/6458) docs: add Kuizuo's Personal Website to showcase ([@kuizuo](https://github.com/kuizuo)) + - [#6431](https://github.com/facebook/docusaurus/pull/6431) docs: add Koyeb as a deployment option ([@edouardb](https://github.com/edouardb)) + - [#6455](https://github.com/facebook/docusaurus/pull/6455) docs: add Sass Fairy to showcase ([@roydukkey](https://github.com/roydukkey)) + - [#6453](https://github.com/facebook/docusaurus/pull/6453) docs: document embedding generated index in doc page ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6450](https://github.com/facebook/docusaurus/pull/6450) docs: split sidebar documentation into sections ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6449](https://github.com/facebook/docusaurus/pull/6449) docs: multiple doc improvements ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6448](https://github.com/facebook/docusaurus/pull/6448) fix(website): update colors correctly when palette is only customized in one color mode ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6385](https://github.com/facebook/docusaurus/pull/6385) chore: add height/width for front page images ([@nschonni](https://github.com/nschonni)) + - [#6445](https://github.com/facebook/docusaurus/pull/6445) docs: update showcase data of InfraQL ([@jeffreyaven](https://github.com/jeffreyaven)) + - [#6433](https://github.com/facebook/docusaurus/pull/6433) docs: add kube-green to showcase ([@davidebianchi](https://github.com/davidebianchi)) + - [#6428](https://github.com/facebook/docusaurus/pull/6428) docs: elaborate on i18n tutorial ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6422](https://github.com/facebook/docusaurus/pull/6422) docs: add 404Lab wiki to showcase ([@HiChen404](https://github.com/HiChen404)) + - [#6420](https://github.com/facebook/docusaurus/pull/6420) fix(website): restore some site CSS in light mode ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6410](https://github.com/facebook/docusaurus/pull/6410) docs: add SODA for SPARC to showcase ([@megasanjay](https://github.com/megasanjay)) + - [#6417](https://github.com/facebook/docusaurus/pull/6417) docs: fix accessibility of search modal ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6406](https://github.com/facebook/docusaurus/pull/6406) docs(i18n): add docs for htmlLang config ([@noomorph](https://github.com/noomorph)) + - [#6393](https://github.com/facebook/docusaurus/pull/6393) docs: update Algolia docs for new DocSearch infra ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6383](https://github.com/facebook/docusaurus/pull/6383) docs: elaborate on different CSS class names ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6348](https://github.com/facebook/docusaurus/pull/6348) docs: add KaustubhK24's site to showcase ([@kaustubhk24](https://github.com/kaustubhk24)) + - [#6333](https://github.com/facebook/docusaurus/pull/6333) feat(website): search in showcase ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6344](https://github.com/facebook/docusaurus/pull/6344) fix(website): make ColorGenerator functional ([@shwaka](https://github.com/shwaka)) + - [#6340](https://github.com/facebook/docusaurus/pull/6340) docs: minor fix in the sample config for ESM ([@pranabdas](https://github.com/pranabdas)) + - [#6336](https://github.com/facebook/docusaurus/pull/6336) docs: make upgrade guide always show the latest version ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6320](https://github.com/facebook/docusaurus/pull/6320) chore: upgrade rehype-katex with ESM support, update docs ([@pranabdas](https://github.com/pranabdas)) + - [#6335](https://github.com/facebook/docusaurus/pull/6335) docs: add Pglet website to showcase ([@FeodorFitsner](https://github.com/FeodorFitsner)) + - [#6327](https://github.com/facebook/docusaurus/pull/6327) docs: remove typo bracket ([@MorookaKotaro](https://github.com/MorookaKotaro)) + - [#6316](https://github.com/facebook/docusaurus/pull/6316) docs: add bandwidth.com to showcase ([@ajrice6713](https://github.com/ajrice6713)) + - [#6313](https://github.com/facebook/docusaurus/pull/6313) docs: add Refine site to showcase ([@omeraplak](https://github.com/omeraplak)) + - [#6318](https://github.com/facebook/docusaurus/pull/6318) fix(website): various anchor link fixes ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6308](https://github.com/facebook/docusaurus/pull/6308) fix(website): wrap details in mdx-code-block ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6307](https://github.com/facebook/docusaurus/pull/6307) docs: document MD and JSX interoperability issues ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6299](https://github.com/facebook/docusaurus/pull/6299) docs: add icodex to showcase ([@wood3n](https://github.com/wood3n)) + - [#6297](https://github.com/facebook/docusaurus/pull/6297) docs: mention setup in monorepo ([@PatelN123](https://github.com/PatelN123)) + - [#6293](https://github.com/facebook/docusaurus/pull/6293) docs: remove GraphQL mesh from showcase ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6231](https://github.com/facebook/docusaurus/pull/6231) docs: update showcase images; remove GraphQL Code Generator site ([@PatelN123](https://github.com/PatelN123)) + - [#6285](https://github.com/facebook/docusaurus/pull/6285) refactor(website): further optimize showcase images ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6281](https://github.com/facebook/docusaurus/pull/6281) docs: Add kwatch to showcase ([@abahmed](https://github.com/abahmed)) + - [#6280](https://github.com/facebook/docusaurus/pull/6280) docs: elaborate on doc versioning ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6043](https://github.com/facebook/docusaurus/pull/6043) fix(website): resize showcase images, tighten CI check ([@armano2](https://github.com/armano2)) + - [#6274](https://github.com/facebook/docusaurus/pull/6274) docs: add dyte docs to showcase ([@vaibhavshn](https://github.com/vaibhavshn)) + - [#6278](https://github.com/facebook/docusaurus/pull/6278) docs: add Khyron Realm to showcase ([@alexgrigoras](https://github.com/alexgrigoras)) + - [#6271](https://github.com/facebook/docusaurus/pull/6271) docs: add FlatifyCSS to showcase ([@amir2mi](https://github.com/amir2mi)) + - [#6275](https://github.com/facebook/docusaurus/pull/6275) fix(website): fix config-tabs breaking after translation ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6269](https://github.com/facebook/docusaurus/pull/6269) docs: add Ionic to showcase ([@ltm](https://github.com/ltm)) + - [#6272](https://github.com/facebook/docusaurus/pull/6272) docs: make tsconfig work OOTB in typescript guide ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6265](https://github.com/facebook/docusaurus/pull/6265) docs: add Eric JiuRan's blog to showcase ([@1084350607](https://github.com/1084350607)) + - [#6242](https://github.com/facebook/docusaurus/pull/6242) docs(showcase): update oxidizer website url ([@vandreleal](https://github.com/vandreleal)) + - [#6226](https://github.com/facebook/docusaurus/pull/6226) docs: update showcase data for digital support notes ([@PatelN123](https://github.com/PatelN123)) + - [#6224](https://github.com/facebook/docusaurus/pull/6224) docs: add TalentBrick to showcase ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6223](https://github.com/facebook/docusaurus/pull/6223) docs: normalize CodeBlock highlighting ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6214](https://github.com/facebook/docusaurus/pull/6214) feat(website): improve prism themes ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6215](https://github.com/facebook/docusaurus/pull/6215) docs: use BrowserWindow for Markdown demos ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6193](https://github.com/facebook/docusaurus/pull/6193) docs: normalize plugin API documentation ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6209](https://github.com/facebook/docusaurus/pull/6209) docs: elaborate on static asset resolution ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6207](https://github.com/facebook/docusaurus/pull/6207) docs: add default value for BrowserWindow URL field ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6206](https://github.com/facebook/docusaurus/pull/6206) docs: fix highlighting of YAML front matter ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6191](https://github.com/facebook/docusaurus/pull/6191) docs: fix react live scope button color in dark mode ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6188](https://github.com/facebook/docusaurus/pull/6188) docs: add Layer0 to deployment guide ([@rishi-raj-jain](https://github.com/rishi-raj-jain)) + - [#6184](https://github.com/facebook/docusaurus/pull/6184) docs: remove mention of 'UA-' in gtag ([@johnnyreilly](https://github.com/johnnyreilly)) + - [#6181](https://github.com/facebook/docusaurus/pull/6181) docs: add GTFS-to-HTML to showcase ([@brendannee](https://github.com/brendannee)) + - [#6178](https://github.com/facebook/docusaurus/pull/6178) docs: add Digital Support Notes to showcase ([@PatelN123](https://github.com/PatelN123)) + - [#6170](https://github.com/facebook/docusaurus/pull/6170) docs: add LabVIEW coding experience to showcase ([@ruanqizhen](https://github.com/ruanqizhen)) + - [#6164](https://github.com/facebook/docusaurus/pull/6164) docs: fix import module name of theme/Admonition ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6158](https://github.com/facebook/docusaurus/pull/6158) docs: add Astronomer to showcase ([@jwitz](https://github.com/jwitz)) +- `create-docusaurus` + - [#5822](https://github.com/facebook/docusaurus/pull/5822) feat: update website & init template palette to pass WCAG test; include contrast check in ColorGenerator ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6187](https://github.com/facebook/docusaurus/pull/6187) docs: make installation guide more beginner-friendly ([@PatelN123](https://github.com/PatelN123)) +- `docusaurus-utils` + - [#6204](https://github.com/facebook/docusaurus/pull/6204) docs: recommend highlighting with comments than number range ([@Josh-Cena](https://github.com/Josh-Cena)) +- `create-docusaurus`, `docusaurus-theme-classic` + - [#6203](https://github.com/facebook/docusaurus/pull/6203) docs: audit grammar issues ([@Josh-Cena](https://github.com/Josh-Cena)) + +#### :house: Internal + +- `docusaurus-mdx-loader`, `docusaurus-migrate`, `docusaurus-plugin-client-redirects`, `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-theme-search-algolia`, `docusaurus-utils-validation`, `docusaurus-utils`, `docusaurus` + - [#6456](https://github.com/facebook/docusaurus/pull/6456) chore: add cSpell for spell checking ([@nschonni](https://github.com/nschonni)) +- Other + - [#6444](https://github.com/facebook/docusaurus/pull/6444) misc: update nvmrc to 14.17.0 to meet dependency requirements ([@jodyheavener](https://github.com/jodyheavener)) + - [#6441](https://github.com/facebook/docusaurus/pull/6441) misc: fix stylelint erroring when lint-staged passed ignored file ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6421](https://github.com/facebook/docusaurus/pull/6421) chore: fix yarn build:website:fast ([@slorber](https://github.com/slorber)) + - [#6381](https://github.com/facebook/docusaurus/pull/6381) chore(website): set cache-control for static assets ([@nschonni](https://github.com/nschonni)) + - [#6364](https://github.com/facebook/docusaurus/pull/6364) chore: remove Intl polyfills for Jest ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6325](https://github.com/facebook/docusaurus/pull/6325) chore: add Dependabot for dependency updates ([@nschonni](https://github.com/nschonni)) + - [#6328](https://github.com/facebook/docusaurus/pull/6328) chore(ci): upgrade actions/github-script to v5 ([@nschonni](https://github.com/nschonni)) + - [#6332](https://github.com/facebook/docusaurus/pull/6332) chore(deps): bump follow-redirects from 1.14.5 to 1.14.7 ([@dependabot[bot]](https://github.com/apps/dependabot)) + - [#6326](https://github.com/facebook/docusaurus/pull/6326) misc(ci): remove redundant "CI: true" env ([@nschonni](https://github.com/nschonni)) + - [#6304](https://github.com/facebook/docusaurus/pull/6304) chore: upgrade to Husky 7 ([@nschonni](https://github.com/nschonni)) + - [#6222](https://github.com/facebook/docusaurus/pull/6222) test: ensure consistent CSS ordering ([@slorber](https://github.com/slorber)) + - [#6159](https://github.com/facebook/docusaurus/pull/6159) docs: remove useless comment ([@slorber](https://github.com/slorber)) + - [#6148](https://github.com/facebook/docusaurus/pull/6148) chore(examples): update examples to 2.0.0-beta.14 ([@slorber](https://github.com/slorber)) +- `docusaurus-plugin-debug`, `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-theme-live-codeblock`, `docusaurus-theme-search-algolia`, `docusaurus` + - [#6442](https://github.com/facebook/docusaurus/pull/6442) chore: enable stylelint standard config ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-mdx-loader`, `docusaurus-plugin-client-redirects`, `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-plugin-ideal-image`, `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-theme-live-codeblock`, `docusaurus` + - [#6440](https://github.com/facebook/docusaurus/pull/6440) chore: remove some unused dependencies from package.json ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-theme-classic` + - [#6436](https://github.com/facebook/docusaurus/pull/6436) refactor(theme-classic): render BlogPostItem as one JSX element ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6283](https://github.com/facebook/docusaurus/pull/6283) refactor(theme-classic): apply import/no-named-export eslint rule ([@slorber](https://github.com/slorber)) +- `docusaurus-plugin-content-pages` + - [#6413](https://github.com/facebook/docusaurus/pull/6413) fix(content-pages): declare hide_table_of_contents as boolean ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-migrate`, `docusaurus-module-type-aliases`, `docusaurus-theme-classic`, `docusaurus` + - [#6399](https://github.com/facebook/docusaurus/pull/6399) refactor: clean up TODO comments ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-cssnano-preset`, `docusaurus-plugin-client-redirects`, `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-theme-common`, `docusaurus-theme-translations`, `docusaurus` + - [#6387](https://github.com/facebook/docusaurus/pull/6387) test: improve test coverage ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-utils` + - [#6380](https://github.com/facebook/docusaurus/pull/6380) chore: enable a few fixable ESLint rules ([@nschonni](https://github.com/nschonni)) +- `docusaurus-mdx-loader`, `docusaurus-plugin-content-docs`, `docusaurus-utils`, `docusaurus` + - [#6377](https://github.com/facebook/docusaurus/pull/6377) refactor: use findAsyncSequential in a few places ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-mdx-loader`, `docusaurus-migrate`, `docusaurus-plugin-client-redirects`, `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-plugin-content-pages`, `docusaurus-theme-classic`, `docusaurus-theme-search-algolia`, `docusaurus-utils-common`, `docusaurus-utils`, `docusaurus`, `stylelint-copyright` + - [#6375](https://github.com/facebook/docusaurus/pull/6375) chore: enable eslint-plugin-jest ([@Josh-Cena](https://github.com/Josh-Cena)) +- `create-docusaurus` + - [#6373](https://github.com/facebook/docusaurus/pull/6373) chore: enable react/jsx-closing-bracket-location ([@nschonni](https://github.com/nschonni)) +- `docusaurus-theme-classic`, `stylelint-copyright` + - [#6374](https://github.com/facebook/docusaurus/pull/6374) feat(stylelint-copyright): autofix, stricter config ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-cssnano-preset`, `docusaurus-theme-classic` + - [#6372](https://github.com/facebook/docusaurus/pull/6372) chore: add baseline stylelint rules ([@nschonni](https://github.com/nschonni)) +- `create-docusaurus`, `docusaurus-plugin-debug`, `docusaurus-theme-classic` + - [#6369](https://github.com/facebook/docusaurus/pull/6369) chore: upgrade lint-staged and globs ([@nschonni](https://github.com/nschonni)) +- `docusaurus-theme-search-algolia`, `docusaurus-utils-validation`, `docusaurus` + - [#6341](https://github.com/facebook/docusaurus/pull/6341) chore: regenerate yarn.lock ([@slorber](https://github.com/slorber)) +- `docusaurus-mdx-loader`, `docusaurus-plugin-client-redirects`, `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-pages`, `docusaurus-remark-plugin-npm2yarn`, `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus` + - [#6324](https://github.com/facebook/docusaurus/pull/6324) chore: minor typo cleanup ([@nschonni](https://github.com/nschonni)) +- `create-docusaurus`, `docusaurus-logger`, `docusaurus-mdx-loader`, `docusaurus-migrate`, `docusaurus-plugin-client-redirects`, `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-plugin-content-pages`, `docusaurus-plugin-debug`, `docusaurus-plugin-google-analytics`, `docusaurus-plugin-google-gtag`, `docusaurus-plugin-ideal-image`, `docusaurus-plugin-pwa`, `docusaurus-plugin-sitemap`, `docusaurus-preset-classic`, `docusaurus-remark-plugin-npm2yarn`, `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-theme-live-codeblock`, `docusaurus-theme-search-algolia`, `docusaurus-theme-translations`, `docusaurus-utils-common`, `docusaurus-utils-validation`, `docusaurus-utils`, `docusaurus`, `lqip-loader`, `stylelint-copyright` + - [#6286](https://github.com/facebook/docusaurus/pull/6286) misc: convert all internal scripts to ESM ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-theme-live-codeblock`, `docusaurus-theme-search-algolia` + - [#6289](https://github.com/facebook/docusaurus/pull/6289) refactor: move @theme/hooks to @docusaurus/theme-common ([@slorber](https://github.com/slorber)) +- `docusaurus-plugin-content-docs`, `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-theme-search-algolia` + - [#6287](https://github.com/facebook/docusaurus/pull/6287) refactor: new @docusaurus/plugin-content-docs/client interface ([@slorber](https://github.com/slorber)) +- `docusaurus` + - [#6279](https://github.com/facebook/docusaurus/pull/6279) refactor(core): use native types from webpack-dev-server ([@RDIL](https://github.com/RDIL)) +- `docusaurus-plugin-content-docs` + - [#6277](https://github.com/facebook/docusaurus/pull/6277) refactor(content-docs): make readVersionsMetadata async ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-types`, `docusaurus` + - [#6237](https://github.com/facebook/docusaurus/pull/6237) refactor(core): convert serverEntry.js to TS ([@Josh-Cena](https://github.com/Josh-Cena)) +- `create-docusaurus`, `docusaurus-logger`, `docusaurus-mdx-loader`, `docusaurus-migrate`, `docusaurus-module-type-aliases`, `docusaurus-plugin-client-redirects`, `docusaurus-plugin-content-blog`, `docusaurus-plugin-content-docs`, `docusaurus-plugin-content-pages`, `docusaurus-plugin-debug`, `docusaurus-plugin-pwa`, `docusaurus-plugin-sitemap`, `docusaurus-theme-classic`, `docusaurus-theme-common`, `docusaurus-theme-live-codeblock`, `docusaurus-utils-common`, `docusaurus-utils-validation`, `docusaurus-utils`, `docusaurus`, `lqip-loader` + - [#6230](https://github.com/facebook/docusaurus/pull/6230) refactor: enforce type import specifiers ([@Josh-Cena](https://github.com/Josh-Cena)) +- `create-docusaurus`, `docusaurus-plugin-content-blog`, `docusaurus-utils`, `docusaurus` + - [#6229](https://github.com/facebook/docusaurus/pull/6229) refactor(utils): reorganize functions; move authors file resolution to utils ([@Josh-Cena](https://github.com/Josh-Cena)) +- `docusaurus-theme-translations` + - [#6225](https://github.com/facebook/docusaurus/pull/6225) refactor(theme-translations): improve typing for update script ([@Josh-Cena](https://github.com/Josh-Cena)) + - [#6174](https://github.com/facebook/docusaurus/pull/6174) misc(theme-translations): multiple improvements to the update CLI ([@Josh-Cena](https://github.com/Josh-Cena)) + +#### Committers: 46 + +- AJ Rice ([@ajrice6713](https://github.com/ajrice6713)) +- Abdelrahman Ahmed ([@abahmed](https://github.com/abahmed)) +- Alexandru Grigoras ([@alexgrigoras](https://github.com/alexgrigoras)) +- Amir M. Mohamadi ([@amir2mi](https://github.com/amir2mi)) +- Anthony McCaigue ([@antmcc49](https://github.com/antmcc49)) +- Armano ([@armano2](https://github.com/armano2)) +- Brendan Nee ([@brendannee](https://github.com/brendannee)) +- Chen ([@HiChen404](https://github.com/HiChen404)) +- Davide Bianchi ([@davidebianchi](https://github.com/davidebianchi)) +- Devtato ([@cerkiewny](https://github.com/cerkiewny)) +- Edouard Bonlieu ([@edouardb](https://github.com/edouardb)) +- Feodor Fitsner ([@FeodorFitsner](https://github.com/FeodorFitsner)) +- Filipe Guerra ([@alias-mac](https://github.com/alias-mac)) +- Gabriel Csapo ([@gabrielcsapo](https://github.com/gabrielcsapo)) +- Jake Witz ([@jwitz](https://github.com/jwitz)) +- Jeffrey Aven ([@jeffreyaven](https://github.com/jeffreyaven)) +- Jody Heavener ([@jodyheavener](https://github.com/jodyheavener)) +- John Reilly ([@johnnyreilly](https://github.com/johnnyreilly)) +- Joseph ([@josephriosIO](https://github.com/josephriosIO)) +- Josh Goldberg ([@JoshuaKGoldberg](https://github.com/JoshuaKGoldberg)) +- Joshua Chen ([@Josh-Cena](https://github.com/Josh-Cena)) +- Kaustubh Kulkarni ([@kaustubhk24](https://github.com/kaustubhk24)) +- Lars Mikkelsen ([@ltm](https://github.com/ltm)) +- Mikey O'Toole ([@homotechsual](https://github.com/homotechsual)) +- Minh Pham ([@lmpham1](https://github.com/lmpham1)) +- Morooka Kotaro ([@MorookaKotaro](https://github.com/MorookaKotaro)) +- Nayan Patel ([@PatelN123](https://github.com/PatelN123)) +- Nick Schonning ([@nschonni](https://github.com/nschonni)) +- Pranab Das ([@pranabdas](https://github.com/pranabdas)) +- Reece Dunham ([@RDIL](https://github.com/RDIL)) +- Rishi Raj Jain ([@rishi-raj-jain](https://github.com/rishi-raj-jain)) +- Sanjay Soundarajan ([@megasanjay](https://github.com/megasanjay)) +- Shun Wakatsuki ([@shwaka](https://github.com/shwaka)) +- Sébastien Lorber ([@slorber](https://github.com/slorber)) +- Tapan Chudasama ([@tapanchudasama](https://github.com/tapanchudasama)) +- Vaibhav Shinde ([@vaibhavshn](https://github.com/vaibhavshn)) +- Vandré Leal ([@vandreleal](https://github.com/vandreleal)) +- Yaroslav Serhieiev ([@noomorph](https://github.com/noomorph)) +- [@mcallisto](https://github.com/mcallisto) +- [@ruanqizhen](https://github.com/ruanqizhen) +- [@wood3n](https://github.com/wood3n) +- kuizuo ([@kuizuo](https://github.com/kuizuo)) +- sgenoud ([@sgenoud](https://github.com/sgenoud)) +- trent ([@roydukkey](https://github.com/roydukkey)) +- Ömer Faruk APLAK ([@omeraplak](https://github.com/omeraplak)) +- 久染 | JiuRan ([@1084350607](https://github.com/1084350607)) + ## 2.0.0-beta.14 (2021-12-21) #### :rocket: New Feature @@ -3372,7 +4233,7 @@ Failed release - ehsan jso ([@ehsanjso](https://github.com/ehsanjso)) - matbub ([@hi-matbub](https://github.com/hi-matbub)) -## 2.0.0-alpha.58 +## 2.0.0-alpha.58 (2020-06-18) #### :rocket: New Feature @@ -3511,11 +4372,11 @@ Failed release - moonrailgun ([@moonrailgun](https://github.com/moonrailgun)) - tetunori ([@tetunori](https://github.com/tetunori)) -## 2.0.0-alpha.57 +## 2.0.0-alpha.57 (2020-06-18) Bad release, check ## 2.0.0-alpha.58 -## 2.0.0-alpha.56 +## 2.0.0-alpha.56 (2020-05-28) #### :boom: Breaking Change @@ -3584,7 +4445,7 @@ Bad release, check ## 2.0.0-alpha.58 - Sam Zhou ([@SamChou19815](https://github.com/SamChou19815)) - Sylvain Pace ([@s-pace](https://github.com/s-pace)) -## 2.0.0-alpha.55 +## 2.0.0-alpha.55 (2020-05-19) #### :boom: Breaking Change @@ -3677,7 +4538,7 @@ Bad release, check ## 2.0.0-alpha.58 - Yamagishi Kazutoshi ([@ykzts](https://github.com/ykzts)) - Yangshun Tay ([@yangshun](https://github.com/yangshun)) -## 2.0.0-alpha.54 +## 2.0.0-alpha.54 (2020-04-28) **HOTFIX for 2.0.0-alpha.53**. @@ -3698,7 +4559,7 @@ Bad release, check ## 2.0.0-alpha.58 - Joe Previte ([@jsjoeio](https://github.com/jsjoeio)) - Sam Zhou ([@SamChou19815](https://github.com/SamChou19815)) -## 2.0.0-alpha.53 +## 2.0.0-alpha.53 (2020-04-27) **HOTFIX for 2.0.0-alpha.51**. @@ -3711,7 +4572,7 @@ Bad release, check ## 2.0.0-alpha.58 - Alexey Pyltsyn ([@lex111](https://github.com/lex111)) -## 2.0.0-alpha.51 +## 2.0.0-alpha.51 (2020-04-27) #### :boom: Breaking Change @@ -4279,7 +5140,7 @@ Bad release, check ## 2.0.0-alpha.58 - Yangshun Tay ([@yangshun](https://github.com/yangshun)) - t11s ([@TransmissionsDev](https://github.com/TransmissionsDev)) -## 2.0.0-alpha.39 +## 2.0.0-alpha.39 (2019-12-07) #### :bug: Bug Fix @@ -4299,7 +5160,7 @@ Bad release, check ## 2.0.0-alpha.58 - Endi ([@endiliey](https://github.com/endiliey)) -## 2.0.0-alpha.38 +## 2.0.0-alpha.38 (2019-12-06) #### :boom: Breaking Change @@ -4352,7 +5213,7 @@ For example, if you've swizzled `@theme/DocItem`. You'll have to update - Shivangna Kaistha ([@shivangna](https://github.com/shivangna)) - kaichu ([@qshiwu](https://github.com/qshiwu)) -## 2.0.0-alpha.37 +## 2.0.0-alpha.37 (2019-12-01) #### :boom: Breaking Change @@ -4436,7 +5297,7 @@ For example, if you've swizzled `@theme/DocItem`. You'll have to update - Endi ([@endiliey](https://github.com/endiliey)) - Wei Gao ([@wgao19](https://github.com/wgao19)) -## 2.0.0-alpha.36 +## 2.0.0-alpha.36 (2019-11-22) #### :boom: Breaking Change @@ -4482,7 +5343,7 @@ For example, if you've swizzled `@theme/DocItem`. You'll have to update - Endi ([@endiliey](https://github.com/endiliey)) - Yangshun Tay ([@yangshun](https://github.com/yangshun)) -## 2.0.0-alpha.35 +## 2.0.0-alpha.35 (2019-11-17) #### :rocket: New Feature @@ -4557,7 +5418,7 @@ For example, if you've swizzled `@theme/DocItem`. You'll have to update - Nick McCormick ([@kenning](https://github.com/kenning)) - Vincent van der Walt ([@vinnytheviking](https://github.com/vinnytheviking)) -## 2.0.0-alpha.34 +## 2.0.0-alpha.34 (2019-11-11) #### :rocket: New Feature @@ -4602,7 +5463,7 @@ For example, if you've swizzled `@theme/DocItem`. You'll have to update - Endi ([@endiliey](https://github.com/endiliey)) - Yangshun Tay ([@yangshun](https://github.com/yangshun)) -## 2.0.0-alpha.33 +## 2.0.0-alpha.33 (2019-11-08) #### Features @@ -4632,7 +5493,7 @@ For example, if you've swizzled `@theme/DocItem`. You'll have to update - Misc dependency upgrades. - Stability improvement (more tests) & refactoring on docs plugin to prevent regression. -## 2.0.0-alpha.32 +## 2.0.0-alpha.32 (2019-11-04) #### Features @@ -4679,7 +5540,7 @@ function Home() { - Convert sitemap plugin to TypeScript. ([#1894](https://github.com/facebook/Docusaurus/issues/1894)) - Refactor dark mode toggle into a hook. ([#1899](https://github.com/facebook/Docusaurus/issues/1899)) -## 2.0.0-alpha.31 +## 2.0.0-alpha.31 (2019-10-26) - Footer is now sticky/ pinned to the bottom of the viewport in desktop browsers. - Footer is now also displayed in docs page for consistency. @@ -4696,13 +5557,13 @@ function Home() { - Increase sidebar width - etc -## 2.0.0-alpha.30 +## 2.0.0-alpha.30 (2019-10-22) - Fix babel transpilation include/exclude logic to be more efficient. This also fix a very weird bug `TypeError: Cannot assign to read only property 'exports' of object '#'`.([#1868](https://github.com/facebook/docusaurus/pull/1868)) If you are still encountering the error. Please check whether you use `module.exports` for your `.js` file instead of doing `export` (mixing CJS and ES). See https://github.com/webpack/webpack/issues/4039#issuecomment-477779322 and https://github.com/webpack/webpack/issues/4039#issuecomment-273804003 for more context. -## 2.0.0-alpha.29 +## 2.0.0-alpha.29 (2019-10-21) **HOTFIX for 2.0.0-alpha.28**. @@ -4710,7 +5571,7 @@ If you are still encountering the error. Please check whether you use `module.ex - Fix wrong `@babel/env` preset configuration that causes build compilation error. - New UI for webpack compilation progress bar. -## 2.0.0-alpha.28 +## 2.0.0-alpha.28 (2019-10-21) - Further reduce memory usage to avoid heap memory allocation failure. - Fix `keywords` frontmatter for SEO not working properly. @@ -4725,14 +5586,14 @@ If you are still encountering the error. Please check whether you use `module.ex - Fix potential security vulnerability because we're exposing the directory structure of the host machine. - Upgrade dependencies. -## 2.0.0-alpha.27 +## 2.0.0-alpha.27 (2019-10-14) - Add `@theme/Tabs` which can be used to implement multi-language code tabs. - Implement `custom_edit_url` and `hide_title` markdown header for docusaurus v1 feature parity. - Reduce memory usage and slightly faster production build. - Misc dependency upgrades. -## 2.0.0-alpha.26 +## 2.0.0-alpha.26 (2019-10-12) - Docs, pages plugin is rewritten in TypeScript - Docs improvements and tweaks @@ -4749,7 +5610,7 @@ If you are still encountering the error. Please check whether you use `module.ex - Add `scripts` and `stylesheets` field to `docusaurus.config.js` - More documentation... -## 2.0.0-alpha.25 +## 2.0.0-alpha.25 (2019-10-01) - Blog plugin is rewritten in TypeScript and can now support CJK - Upgrade key direct dependencies such as webpack, mdx and babel to latest @@ -4759,7 +5620,7 @@ If you are still encountering the error. Please check whether you use `module.ex - Add `truncateMarker` option to blog plugin, support string or regex. - Webpack `optimization.removeAvailableModules` is now disabled for performance gain. See https://github.com/webpack/webpack/releases/tag/v4.38.0 for more context. -## 2.0.0-alpha.24 +## 2.0.0-alpha.24 (2019-07-24) - Remove unused metadata for pages. This minimize number of http request & smaller bundle size. - Upgrade dependencies of css-loader from 2.x to 3.x. Css modules localIdentName hash now only use the last 4 characters instead of 8. @@ -4768,11 +5629,11 @@ If you are still encountering the error. Please check whether you use `module.ex - Use contenthash instead of chunkhash for better long term caching - Allow user to customize generated heading from MDX. Swizzle `@theme/Heading` -## 2.0.0-alpha.23 +## 2.0.0-alpha.23 (2019-07-21) - Fix docusaurus route config generation for certain edge case -## 2.0.0-alpha.22 +## 2.0.0-alpha.22 (2019-07-20) - Add missing dependencies on `@docusaurus/preset-classic` - New plugin `@docusaurus/plugin-ideal-image` to generate an almost ideal image (responsive, lazy-loading, and low quality placeholder) @@ -4783,11 +5644,11 @@ If you are still encountering the error. Please check whether you use `module.ex - `@docusaurus/theme-live-codeblock` is now much smaller in size and no longer only load on viewport - Blog markdown files now support using the id field to specify the path -## 2.0.0-alpha.21 +## 2.0.0-alpha.21 (2019-07-14) - Fix babel-loader not transpiling docusaurus package -## 2.0.0-alpha.20 +## 2.0.0-alpha.20 (2019-07-14) - Add copy codeblock button - Add Google analytics and Google gtag plugins. @@ -4803,7 +5664,7 @@ If you are still encountering the error. Please check whether you use `module.ex - Drop cache-loader in CI and test environment because it has an initial overhead. We always start from scratch in vm instance like CI so cache-loader is useless - Better splitchunks and babel default webpack config -## 2.0.0-alpha.19 +## 2.0.0-alpha.19 (2019-06-07) - Add a sensible default for browserslist config. - UI @@ -4842,7 +5703,7 @@ presets: [ - Minify css for production build - Fix weird scrolling problem when navigating to a route with a `hash` location -## V2 Changelog +## V2 Changelog (2019-04-10) ### `siteConfig.js` changes @@ -4875,7 +5736,7 @@ themeConfig: { } ``` -# Migration Guide +### Migration Guide _Work in Progress_ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4dc6c1d6fa27..ff86db3a13f2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -46,24 +46,20 @@ All pull requests will be checked by the continuous integration system, GitHub a Docusaurus has one primary branch `main` and we use feature branches with deploy previews to deliver new features with pull requests. -## Proposing a Change +## Issues -If you would like to request a new feature or enhancement but are not yet thinking about opening a pull request, you can also file an issue with the [feature template](https://github.com/facebook/docusaurus/issues/new?assignees=&labels=feature%2Cneeds+triage&template=feature.yml). - -If you intend to change the public API (e.g., something in `siteConfig.js`) or make any non-trivial changes to the implementation, we recommend filing an issue with the [proposal template](https://github.com/facebook/docusaurus/issues/new?assignees=&labels=proposal%2Cneeds+triage&template=proposal.yml). This lets us reach an agreement on your proposal before you put significant effort into it. These types of issues should be rare. +When [opening a new issue](https://github.com/facebook/docusaurus/issues/new/choose), always make sure to fill out the issue template. **This step is very important!** Not doing so may result in your issue not being managed in a timely fashion. Don't take this personally if this happens, and feel free to open a new issue once you've gathered all the information required by the template. -If you're only fixing a bug, it's fine to submit a pull request right away but we still recommend [filing an issue](https://github.com/facebook/docusaurus/issues/new?assignees=&labels=bug%2Cneeds+triage&template=bug.yml) detailing what you're fixing. This is helpful in case we don't accept that specific fix but want to keep track of the issue. +**Please don't use the GitHub issue tracker for questions.** If you have questions about using Docusaurus, use any of our [support channels](https://docusaurus.io/community/support), and we will do our best to answer your questions. -### Reporting New Issues +### Bugs -When [opening a new issue](https://github.com/facebook/docusaurus/issues/new/choose), always make sure to fill out the issue template. **This step is very important!** Not doing so may result in your issue not being managed in a timely fashion. Don't take this personally if this happens, and feel free to open a new issue once you've gathered all the information required by the template. +We use [GitHub Issues](https://github.com/facebook/docusaurus/issues) for our public bugs. If you would like to report a problem, take a look around and see if someone already opened an issue about it. If you are certain this is a new, unreported bug, you can submit a [bug report](https://github.com/facebook/docusaurus/issues/new?assignees=&labels=bug%2Cstatus%3A+needs+triage&template=bug.yml). - **One issue, one bug:** Please report a single bug per issue. - **Provide reproduction steps:** List all the steps necessary to reproduce the issue. The person reading your bug report should be able to follow these steps to reproduce your issue with minimal effort. -### Bugs - -We use [GitHub Issues](https://github.com/facebook/docusaurus/issues) for our public bugs. If you would like to report a problem, take a look around and see if someone already opened an issue about it. If you are certain this is a new, unreported bug, you can submit a [bug report](#reporting-new-issues). +If you're only fixing a bug, it's fine to submit a pull request right away but we still recommend filing an issue detailing what you're fixing. This is helpful in case we don't accept that specific fix but want to keep track of the issue. ### Security Bugs @@ -71,40 +67,26 @@ Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe ### Feature requests -You can also file issues as [feature requests or enhancements](https://github.com/facebook/docusaurus/labels/feature%20request) in the form of an **elaborated RFC**. If you see anything you'd like to be implemented, create an issue with [feature template](https://raw.githubusercontent.com/facebook/docusaurus/main/.github/ISSUE_TEMPLATE/feature.md) +If you would like to request a new feature or enhancement but are not yet thinking about opening a pull request, you can file an issue with the [feature template](https://github.com/facebook/docusaurus/issues/new?assignees=&labels=feature%2Cstatus%3A+needs+triage&template=feature.yml) in the form of an **elaborated RFC**. Alternatively, you can use the [Canny board](https://docusaurus.io/feature-requests) for more casual feature requests and gain enough traction before proposing an RFC. -### Questions +### Proposals -If you have questions about using Docusaurus, contact the Docusaurus Twitter account at [@docusaurus](https://twitter.com/docusaurus), and we will do our best to answer your questions. - -## Pull Requests +If you intend to make any non-trivial changes to existing implementations, we recommend filing an issue with the [proposal template](https://github.com/facebook/docusaurus/issues/new?assignees=&labels=proposal%2Cstatus%3A+needs+triage&template=proposal.yml). This lets us reach an agreement on your proposal before you put significant effort into it. These types of issues should be rare. -### Your First Pull Request - -So you have decided to contribute code back to upstream by opening a pull request. You've invested a good chunk of time, and we appreciate it. We will do our best to work with you and get the PR looked at. - -Working on your first Pull Request? You can learn how from this free video series: - -[**How to Contribute to an Open Source Project on GitHub**](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github) +### Claiming issues We have a list of [beginner-friendly issues](https://github.com/facebook/docusaurus/labels/good%20first%20issue) to help you get your feet wet in the Docusaurus codebase and familiar with our contribution process. This is a great place to get started. -### Versioned Docs +Apart from the `good first issue`, the following labels are also worth looking at: -If you only want to make content changes you just need to be aware of versioned docs. +- [`help wanted`](https://github.com/facebook/docusaurus/labels/help%20wanted): if you have specific knowledge in one domain, working on these issues can make your expertise shine. +- [`status: accepting pr`](https://github.com/facebook/docusaurus/labels/status%3A%20accepting%20pr): community contributors can feel free to claim any of these. -- `website/docs` - The files here are responsible for the "next" version at https://docusaurus.io/docs/next/installation. -- `website/versioned_docs/version-X.Y.Z` - These are the docs for the X.Y.Z version at https://docusaurus.io/docs/X.Y.Z/installation. +If you want to work on any of these issues, just drop a message saying "I'd like to work on this", and we will assign the issue to you and update the issue's status as "claimed". **You are expected to send a pull request within seven days** after that, so we can still delegate the issue to someone else if you are unavailable. -To make a fix to the published versions you must edit the corresponding markdown file in both folders. If you only made changes in `docs`, be sure to be viewing the `next` version to see the updates (ensure there's `next` in the URL). +Alternatively, when opening an issue, you can also click the "self service" checkbox to indicate that you'd like to work on the issue yourself, which will also make us see the issue as "claimed". -> Do not edit the auto-generated files within `versioned_docs/` or `versioned_sidebars/` unless you are sure it is necessary. For example, information about new features should not be documented in versioned docs. Edits made to older versions will not be propagated to newer versions of the docs. - -### Installation - -1. Ensure you have [Yarn](https://yarnpkg.com/) installed. -1. After cloning the repository, run `yarn install` in the root of the repository. -1. To start a development server, run `yarn workspace website start`. +## Development ### Online one-click setup for contributing @@ -120,74 +102,41 @@ So that you can start contributing straight away. You can also try using the new [github.dev](https://github.dev/facebook/docusaurus) feature. While you are browsing any file, changing the domain name from `github.com` to `github.dev` will turn your browser into an online editor. You can start making changes and send pull requests right away. -### Sending a Pull Request - -Small pull requests are much easier to review and more likely to get merged. Make sure the PR does only one thing, otherwise please split it. It is recommended to follow this [commit message style](#semantic-commit-messages). - -Please make sure the following is done when submitting a pull request: - -1. Fork [the repository](https://github.com/facebook/docusaurus) and create your branch from `main`. -1. Add the [copyright notice](#copyright-header-for-source-code) to the top of any code new files you've added. -1. Describe your [**test plan**](#test-plan) in your pull request description. Make sure to [test your changes](https://github.com/facebook/docusaurus/blob/main/admin/testing-changes-on-Docusaurus-itself.md)! -1. Make sure your code lints (`yarn prettier && yarn lint`). -1. Make sure your Jest tests pass (`yarn test`). -1. If you haven't already, [sign the CLA](https://code.facebook.com/cla). - -All pull requests should be opened against the `main` branch. - -#### Test Plan - -A good test plan has the exact commands you ran and their output and provides screenshots or videos if the pull request changes UI. If you've changed APIs, update the documentation. - -If you need help testing your changes locally, you can check out the doc on doing [local third-party testing](https://github.com/facebook/docusaurus/blob/main/admin/local-third-party-project-testing.md). - -#### Breaking Changes - -When adding a new breaking change, follow this template in your pull request: - -```md -### New breaking change here - -- **Who does this affect**: -- **How to migrate**: -- **Why make this breaking change**: -- **Severity (number of people affected x effort)**: -``` +### Installation -#### Copyright Header for Source Code +1. Ensure you have [Yarn](https://yarnpkg.com/) installed. +2. After cloning the repository, run `yarn install` in the root of the repository. This will install all dependencies as well as build all local packages. +3. To start a development server, run `yarn workspace website start`. -Copy and paste this to the top of your new file(s): +### Code Conventions -```js -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ -``` +- **Most important: Look around.** Match the style you see used in the rest of the project. This includes formatting, naming files, naming things in code, naming things in documentation, etc. +- "Attractive" +- We do have Prettier (a formatter) and ESLint (a syntax linter) to catch most stylistic problems. If you are working locally, they should automatically fix some issues during every git commit. +- **For documentation**: Do not wrap lines at 80 characters - configure your editor to soft-wrap when editing documentation. -#### Contributor License Agreement (CLA) +Don't worry too much about styles in general—the maintainers will help you fix them as they review your code. -In order to accept your pull request, we need you to submit a CLA. You only need to do this once, so if you've done this for another Facebook open source project, you're good to go. If you are submitting a pull request for the first time, the Facebook GitHub Bot will reply with a link to the CLA form. You may also [complete your CLA here](https://code.facebook.com/cla). - -After you have signed the CLA, the CLA bot would automatically update the PR status. There's no need to open a new PR. +## Pull Requests -**CLAs are required for us to merge your pull request.** While we value your effort and are willing to wait for you to come back and address the reviews in case you are unavailable after sending the pull request, pull requests that are ready to merge but have CLA missing and no response from the author **will be closed within two weeks of opening**. If you have further questions about the CLA, please stay in touch with us. +So you have decided to contribute code back to upstream by opening a pull request. You've invested a good chunk of time, and we appreciate it. We will do our best to work with you and get the PR looked at. -If it happens that you were unavailable and your PR gets closed, feel free to reopen once it's ready! We are still happy to review it, help you complete it, and eventually merge it. +Working on your first Pull Request? You can learn how from this free video series: -### What Happens Next? +[**How to Contribute to an Open Source Project on GitHub**](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github) -The core Docusaurus team will be monitoring for pull requests. Do help us by keeping pull requests consistent by following the guidelines above. +Please make sure the following is done when submitting a pull request: -## Style Guide +1. **Keep your PR small.** Small pull requests (~300 lines of diff) are much easier to review and more likely to get merged. Make sure the PR does only one thing, otherwise please split it. +2. **Use descriptive titles.** It is recommended to follow this [commit message style](#semantic-commit-messages). +3. **Test your changes.** Describe your [**test plan**](#test-plan) in your pull request description. +4. **CLA.** If you haven't already, [sign the CLA](https://code.facebook.com/cla). -[Prettier](https://prettier.io) will catch most styling issues that may exist in your code. You can check the status of your code styling by simply running `yarn prettier`. +All pull requests should be opened against the `main` branch. -However, there are still some styles that Prettier cannot pick up. +We have a lot of integration systems that run automated tests to guard against mistakes. The maintainers will also review your code and fix obvious issues for you. These systems' duty is to make you worry as little about the chores as possible. Your code contributions are more important than sticking to any procedures, although completing the checklist will surely save everyone's time. -## Semantic Commit Messages +### Semantic Commit Messages See how a minor change to your commit message style can make you a better programmer. @@ -205,35 +154,79 @@ The various types of commits: - `chore`: upgrading dependencies, releasing new versions... Chores that are **regularly done** for maintenance purposes. - `misc`: anything else that doesn't change production code, yet is not `test` or `chore`. e.g. updating GitHub actions workflow. -Do not get too stressed about PR titles, however. The maintainers will help you get them right, and we also have a PR label system that doesn't equate with the commit message types. Your code is more important than conventions! +Do not get too stressed about PR titles, however. Your PR will be squash-merged and your commit to the `main` branch will get the title of your PR, so commits within a branch don't need to be semantically named. The maintainers will help you get the PR title right, and we also have a PR label system that doesn't equate with the commit message types. Your code is more important than conventions! -### Example +Example: ``` feat(core): allow overriding of webpack config ^--^^----^ ^------------^ | | | -| | +-> Summary in present tense. +| | +-> Summary in present tense. Use lower case not title case! | | | +-> The package(s) that this change affected. | +-------> Type: see below for the list we use. ``` -Use lower case not title case! +### Versioned Docs + +If you only want to make doc changes, you just need to be aware of versioned docs. -## Code Conventions +- `website/docs` - The files here are responsible for the "next" version at https://docusaurus.io/docs/next/installation. +- `website/versioned_docs/version-X.Y.Z` - These are the docs for the X.Y.Z version at https://docusaurus.io/docs/X.Y.Z/installation. -### General +Do not edit the auto-generated files within `versioned_docs/` or `versioned_sidebars/` unless you are sure it is necessary. For example, information about new features should not be documented in versioned docs. Edits made to older versions will not be propagated to newer versions of the docs. -- **Most important: Look around.** Match the style you see used in the rest of the project. This includes formatting, naming files, naming things in code, naming things in documentation, etc. -- "Attractive" -- We do have Prettier (a formatter) and ESLint (a syntax linter) to catch most stylistic problems. If you are working locally, they should automatically fix some issues during every git commit. +### Test Plan + +A good test plan has the exact commands you ran and their output and provides screenshots or videos if the pull request changes UI. If you've changed APIs, update the documentation. + +Tests are integrated into our continuous integration system, so you don't always need to run local tests. However, for significant code changes, it's saves both your and the maintainers' time if you can do exhaustive tests locally first to make sure your PR is in good shape. There are many types of tests: + +- **Build and typecheck.** We use TypeScript in our codebase, which can make sure your code is consistent and catches some obvious mistakes early. +- **Unit tests.** We use [Jest](https://jestjs.io/) for unit tests of API endpoints' behavior. You can run `yarn test` in the root directory to run all tests, or `yarn test path/to/your/file.test.ts` to run a specific test. +- **Dogfooding.** Our website itself covers all kinds of potential configuration cases and we even have a dedicated [tests area](https://docusaurus.io/tests). Don't be afraid to update our website's configuration in your PR—it can help the maintainers preview the effects. We can decide if the website change should be kept when merging and deploying for production. +- **E2E tests.** You can simulate the distribution and installation of the code with your fresh changes. If you need help testing your changes locally, you can check out the doc on doing [local third-party testing](https://github.com/facebook/docusaurus/blob/main/admin/local-third-party-project-testing.md). + +### Licensing + +By contributing to Docusaurus, you agree that your contributions will be licensed under its MIT license. Copy and paste this to the top of your new file(s): + +```js +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +``` + +This is also auto-fixable with the `header/header` ESLint rule. + +### Contributor License Agreement (CLA) + +In order to accept your pull request, we need you to submit a CLA. You only need to do this once, so if you've done this for another Facebook open source project, you're good to go. If you are submitting a pull request for the first time, the Facebook GitHub Bot will reply with a link to the CLA form. You may also [complete your CLA here](https://code.facebook.com/cla). + +After you have signed the CLA, the CLA bot would automatically update the PR status. There's no need to open a new PR. + +**CLAs are required for us to merge your pull request.** While we value your effort and are willing to wait for you to come back and address the reviews in case you are unavailable after sending the pull request, pull requests that are ready to merge but have CLA missing and no response from the author **will be closed within two weeks of opening**. If you have further questions about the CLA, please stay in touch with us. -### Documentation +If it happens that you were unavailable and your PR gets closed, feel free to reopen once it's ready! We are still happy to review it, help you complete it, and eventually merge it. + +### Breaking Changes + +When adding a new breaking change, follow this template in your pull request: + +```md +### New breaking change here -- Do not wrap lines at 80 characters - configure your editor to soft-wrap when editing documentation. +- **Who does this affect**: +- **How to migrate**: +- **Why make this breaking change**: +- **Severity (number of people affected x effort)**: +``` -## License +### What Happens Next? -By contributing to Docusaurus, you agree that your contributions will be licensed under its MIT license. +The core Docusaurus team will be monitoring for pull requests. Do help us by keeping pull requests consistent by following the guidelines above. diff --git a/README.md b/README.md index 30cfb48e7ce9..c2fe1a76ae37 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,8 @@ Gitpod Ready-to-Code Netlify Status CI Score + Deploy with Vercel + Deploy to Netlify

> **We are working hard on Docusaurus v2. If you are new to Docusaurus, try using the new version instead of v1. See the [Docusaurus v2 website](https://docusaurus.io/) for more details.** diff --git a/__tests__/validate-package-json.test.ts b/__tests__/validate-package-json.test.ts index ad2d67f1eb96..5f3d605d5a35 100644 --- a/__tests__/validate-package-json.test.ts +++ b/__tests__/validate-package-json.test.ts @@ -5,37 +5,44 @@ * LICENSE file in the root directory of this source tree. */ -import util from 'util'; -import globCb from 'glob'; -import fsCb from 'fs'; - -const glob = util.promisify(globCb); -const readFile = util.promisify(fsCb.readFile); +import {Globby} from '@docusaurus/utils'; +import fs from 'fs-extra'; type PackageJsonFile = { file: string; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - content: any; + content: { + name?: string; + private?: boolean; + version?: string; + repository?: { + type?: string; + url?: string; + directory?: string; + }; + publishConfig?: { + access?: string; + }; + }; }; async function getPackagesJsonFiles(): Promise { - const files = await glob('packages/*/package.json'); + const files = await Globby('packages/*/package.json'); return Promise.all( files.map(async (file) => ({ file, - content: JSON.parse(await readFile(file, 'utf8')), + content: JSON.parse(await fs.readFile(file, 'utf8')), })), ); } describe('packages', () => { - test('should be found', async () => { + it('are found', async () => { const packageJsonFiles = await getPackagesJsonFiles(); expect(packageJsonFiles.length).toBeGreaterThan(0); }); - test('should contain repository and directory for every package', async () => { + it('contain repository and directory', async () => { const packageJsonFiles = await getPackagesJsonFiles(); packageJsonFiles @@ -51,20 +58,22 @@ describe('packages', () => { /* If a package starts with @, if won't be published to public npm registry - without an additional publishConfig.acces: "public" config + without an additional publishConfig.access: "public" config This will make you publish an incomplete list of Docusaurus packages when trying to release with lerna-publish */ - test('should have publishConfig.access: "public" when name starts with @', async () => { + it('have publishConfig.access: "public" when name starts with @', async () => { const packageJsonFiles = await getPackagesJsonFiles(); packageJsonFiles .filter((packageJsonFile) => packageJsonFile.content.name.startsWith('@')) .forEach((packageJsonFile) => { if (packageJsonFile) { - // Unfortunately jest custom message do not exist in loops, so using an exception instead to show failing package file + // Unfortunately jest custom message do not exist in loops, + // so using an exception instead to show failing package file // (see https://github.com/facebook/jest/issues/3293) - // expect(packageJsonFile.content.publishConfig?.access).toEqual('public'); + // expect(packageJsonFile.content.publishConfig?.access) + // .toEqual('public'); if (packageJsonFile.content.publishConfig?.access !== 'public') { throw new Error( `Package ${packageJsonFile.file} does not have publishConfig.access: 'public'`, diff --git a/admin/new.docusaurus.io/functionUtils/playgroundUtils.ts b/admin/new.docusaurus.io/functionUtils/playgroundUtils.ts index d5c1a28feed5..85bd7ef4bd81 100644 --- a/admin/new.docusaurus.io/functionUtils/playgroundUtils.ts +++ b/admin/new.docusaurus.io/functionUtils/playgroundUtils.ts @@ -10,7 +10,11 @@ import type {HandlerEvent, HandlerResponse} from '@netlify/functions'; const CookieName = 'DocusaurusPlaygroundName'; const PlaygroundConfigs = { - codesandbox: 'https://codesandbox.io/s/docusaurus', + // Do not use this one, see + // https://github.com/codesandbox/codesandbox-client/issues/5683#issuecomment-1023252459 + // codesandbox: 'https://codesandbox.io/s/docusaurus', + codesandbox: + 'https://codesandbox.io/s/github/facebook/docusaurus/tree/main/examples/classic', // stackblitz: 'https://stackblitz.com/fork/docusaurus', // not updated // stackblitz: 'https://stackblitz.com/github/facebook/docusaurus/tree/main/examples/classic', // slow to load @@ -22,9 +26,11 @@ const PlaygroundDocumentationUrl = 'https://docusaurus.io/docs/playground'; export type PlaygroundName = keyof typeof PlaygroundConfigs; function isValidPlaygroundName( - playgroundName: string, + playgroundName: string | undefined, ): playgroundName is PlaygroundName { - return Object.keys(PlaygroundConfigs).includes(playgroundName); + return ( + !!playgroundName && Object.keys(PlaygroundConfigs).includes(playgroundName) + ); } export function createPlaygroundDocumentationResponse(): HandlerResponse { @@ -50,10 +56,10 @@ export function createPlaygroundResponse( } // Inspired by https://stackoverflow.com/a/3409200/82609 -function parseCookieString(cookieString: string): Record { - const result: Record = {}; +function parseCookieString(cookieString: string): {[key: string]: string} { + const result: {[key: string]: string} = {}; cookieString.split(';').forEach((cookie) => { - const [name, value] = cookie.split('='); + const [name, value] = cookie.split('=') as [string, string]; result[name.trim()] = decodeURI(value); }); return result; @@ -62,19 +68,16 @@ function parseCookieString(cookieString: string): Record { export function readPlaygroundName( event: HandlerEvent, ): PlaygroundName | undefined { - const parsedCookie: Record = event.headers.cookie + const parsedCookie: {[key: string]: string} = event.headers.cookie ? parseCookieString(event.headers.cookie) : {}; const playgroundName: string | undefined = parsedCookie[CookieName]; - if (playgroundName) { - if (isValidPlaygroundName(playgroundName)) { - return playgroundName; - } else { - console.error( - `playgroundName found in cookie was invalid: ${playgroundName}`, - ); - } + if (!isValidPlaygroundName(playgroundName)) { + console.error( + `playgroundName found in cookie was invalid: ${playgroundName}`, + ); + return undefined; } - return undefined; + return playgroundName; } diff --git a/admin/new.docusaurus.io/functions/codesandbox.ts b/admin/new.docusaurus.io/functions/codesandbox.ts index fa57531f88c0..365ac26911f4 100644 --- a/admin/new.docusaurus.io/functions/codesandbox.ts +++ b/admin/new.docusaurus.io/functions/codesandbox.ts @@ -9,6 +9,6 @@ import type {Handler} from '@netlify/functions'; import {createPlaygroundResponse} from '../functionUtils/playgroundUtils'; -export const handler: Handler = async function (_event, _context) { +export const handler: Handler = async function handler() { return createPlaygroundResponse('codesandbox'); }; diff --git a/admin/new.docusaurus.io/functions/index.ts b/admin/new.docusaurus.io/functions/index.ts index d9bca91c359e..a137fecb41c3 100644 --- a/admin/new.docusaurus.io/functions/index.ts +++ b/admin/new.docusaurus.io/functions/index.ts @@ -13,7 +13,7 @@ import { createPlaygroundDocumentationResponse, } from '../functionUtils/playgroundUtils'; -export const handler: Handler = async (event, _context) => { +export const handler: Handler = async (event) => { const playgroundName = readPlaygroundName(event); return playgroundName ? createPlaygroundResponse(playgroundName) diff --git a/admin/new.docusaurus.io/functions/stackblitz.ts b/admin/new.docusaurus.io/functions/stackblitz.ts index cc80bf79159a..e7762f20dce0 100644 --- a/admin/new.docusaurus.io/functions/stackblitz.ts +++ b/admin/new.docusaurus.io/functions/stackblitz.ts @@ -9,6 +9,6 @@ import type {Handler} from '@netlify/functions'; import {createPlaygroundResponse} from '../functionUtils/playgroundUtils'; -export const handler: Handler = async function (_event, _context) { +export const handler: Handler = async function handler() { return createPlaygroundResponse('stackblitz'); }; diff --git a/admin/new.docusaurus.io/package.json b/admin/new.docusaurus.io/package.json index 743c047e8667..923af87d97ab 100644 --- a/admin/new.docusaurus.io/package.json +++ b/admin/new.docusaurus.io/package.json @@ -1,14 +1,14 @@ { "name": "new.docusaurus.io", - "version": "2.0.0-beta.14", + "version": "2.0.0-beta.18", "private": true, "scripts": { "start": "netlify dev" }, "dependencies": { - "@netlify/functions": "^0.10.0" + "@netlify/functions": "^1.0.0" }, "devDependencies": { - "netlify-cli": "^8.0.5" + "netlify-cli": "^9.13.6" } } diff --git a/admin/publish.md b/admin/publish.md index 19c704488bd4..fef93b58796c 100644 --- a/admin/publish.md +++ b/admin/publish.md @@ -118,7 +118,7 @@ You should still be on your local branch `/` Make a commit/push, create a pull request with the changes. -Example PR: [#3114](https://github.com/facebook/docusaurus/pull/5098), using title such as `chore(v2): prepare v2.0.0-beta.0 release` +Example PR: [#3114](https://github.com/facebook/docusaurus/pull/5098), using title such as `chore: prepare v2.0.0-beta.0 release` **Don't merge it yet**, but wait for the CI checks to complete. @@ -187,6 +187,8 @@ This command does a few things: You should receive many emails notifying you that a new version of the packages has been published. +If above command fail (network issue or whatever), you can try to recover with `yarn lerna publish from-package`: it will try to publish the packages that are missing on npm. + Now that the release is done, **merge the pull request**. ### 7. Create a release on GitHub diff --git a/admin/scripts/generateExamples.mjs b/admin/scripts/generateExamples.mjs index eabdb6b880ba..748640ae0dcb 100644 --- a/admin/scripts/generateExamples.mjs +++ b/admin/scripts/generateExamples.mjs @@ -5,8 +5,6 @@ * LICENSE file in the root directory of this source tree. */ -/* eslint-disable import/no-extraneous-dependencies */ - import fs from 'fs-extra'; import shell from 'shelljs'; @@ -26,16 +24,16 @@ async function generateTemplateExample(template) { `generating ${template} template for codesandbox in the examples folder...`, ); - // run the docusaurus script to bootstrap the template in the examples folder + // run the docusaurus script to create the template in the examples folder const command = template.endsWith('-typescript') ? template.replace('-typescript', ' -- --typescript') : template; shell.exec( // /!\ we use the published init script on purpose, - // because using the local init script is too early and could generate upcoming/unavailable config options - // remember CodeSandbox templates will use the published version, not the repo version + // because using the local init script is too early and could generate + // upcoming/unavailable config options. Remember CodeSandbox templates + // will use the published version, not the repo version `npm init docusaurus@latest examples/${template} ${command}`, - // `node ./packages/docusaurus-init/bin/index.js init examples/${template} ${template}`, ); // read the content of the package.json @@ -49,10 +47,10 @@ async function generateTemplateExample(template) { // these example projects are not meant to be published to npm templatePackageJson.private = true; - // make sure package.json name is not "examples-classic" - // the package.json name appear in CodeSandbox UI so let's display a good name! - // unfortunately we can't use uppercase or spaces - // see also https://github.com/codesandbox/codesandbox-client/pull/5136#issuecomment-763521662 + // Make sure package.json name is not "examples-classic". The package.json + // name appears in CodeSandbox UI so let's display a good name! + // Unfortunately we can't use uppercase or spaces... See also + // https://github.com/codesandbox/codesandbox-client/pull/5136#issuecomment-763521662 templatePackageJson.name = template === 'classic' ? 'docusaurus' : `docusaurus-${template}`; templatePackageJson.description = @@ -92,18 +90,19 @@ async function generateTemplateExample(template) { ); console.log(`Generated example for template ${template}`); - } catch (error) { + } catch (err) { console.error(`Failed to generated example for template ${template}`); - throw error; + throw err; } } -/* -Starters are repositories/branches that only contains a newly initialized Docusaurus site -Those are useful for users to inspect (may be more convenient than "examples/classic) -Also some tools like Netlify deploy button currently require using the main branch of a dedicated repo -See https://github.com/jamstack/jamstack.org/pull/609 -Button visible here: https://jamstack.org/generators/ +/** + * Starters are repositories/branches that only contains a newly initialized + * Docusaurus site. Those are useful for users to inspect (may be more + * convenient than "examples/classic) Also some tools like Netlify deploy button + * currently require using the main branch of a dedicated repo. + * See https://github.com/jamstack/jamstack.org/pull/609 + * Button visible here: https://jamstack.org/generators/ */ function updateStarters() { function forcePushGitSubtree({subfolder, remote, remoteBranch}) { @@ -114,12 +113,12 @@ function updateStarters() { console.log(`forcePushGitSubtree command: ${command}`); shell.exec(command); console.log('forcePushGitSubtree success!'); - } catch (e) { + } catch (err) { console.error( `Can't force push to git subtree with command '${command}'`, ); console.error(`If it's a permission problem, ask @slorber`); - console.error(e); + console.error(err); } console.log(''); } @@ -181,7 +180,6 @@ const templates = ( await fs.readdir('./packages/create-docusaurus/templates') ).filter((name) => !excludes.includes(name)); console.log(`Will generate examples for templates: ${templates.join(',')}`); -// eslint-disable-next-line no-restricted-syntax for (const template of templates) { await generateTemplateExample(template); } diff --git a/admin/scripts/image-resize.mjs b/admin/scripts/image-resize.mjs index 2b221835be81..f19b877f1be8 100644 --- a/admin/scripts/image-resize.mjs +++ b/admin/scripts/image-resize.mjs @@ -5,13 +5,12 @@ * LICENSE file in the root directory of this source tree. */ -/* eslint-disable import/no-extraneous-dependencies */ - import sharp from 'sharp'; import fs from 'fs-extra'; import path from 'path'; import imageSize from 'image-size'; import {fileURLToPath} from 'url'; +import logger from '@docusaurus/logger'; const allImages = ( await fs.readdir(new URL('../../website/src/data/showcase', import.meta.url)) @@ -27,10 +26,11 @@ await Promise.all( ); const {width, height} = imageSize(imgPath); if (width === 640 && height === 320) { - // Do not emit if no resized. Important because we - // can't guarantee idempotency during resize -> optimization + // Do not emit if not resized. Important because we can't guarantee + // idempotency during resize -> optimization return; } + logger.info`Resized path=${imgPath}: Before number=${width}×number=${height}`; const data = await sharp(imgPath) .resize(640, 320, {fit: 'cover', position: 'top'}) .png() @@ -39,7 +39,8 @@ await Promise.all( }), ); -// You should also run optimizt `find website/src/data/showcase -type f -name '*.png'`. -// This is not included here because @funboxteam/optimizt doesn't seem to play well with M1 -// so I had to run this in a Rosetta terminal. +// You should also run +// optimizt `find website/src/data/showcase -type f -name '*.png'`. +// This is not included here because @funboxteam/optimizt doesn't seem to play +// well with M1 so I had to run this in a Rosetta terminal. // TODO integrate this as part of the script diff --git a/admin/scripts/test-release.sh b/admin/scripts/test-release.sh index 57640225a54e..1b968aca8660 100755 --- a/admin/scripts/test-release.sh +++ b/admin/scripts/test-release.sh @@ -12,16 +12,16 @@ NEW_VERSION="$(node -p "require('./packages/docusaurus/package.json').version"). CONTAINER_NAME="verdaccio" EXTRA_OPTS="" -usage() { echo "Usage: $0 [-n] [-s]" 1>&2; exit 1; } +usage() { echo "Usage: $0 [-s] [-t]" 1>&2; exit 1; } -while getopts ":ns" o; do +while getopts ":st" o; do case "${o}" in - n) - EXTRA_OPTS="${EXTRA_OPTS} --use-npm" - ;; s) EXTRA_OPTS="${EXTRA_OPTS} --skip-install" ;; + t) + EXTRA_OPTS="${EXTRA_OPTS} --typescript" + ;; *) usage ;; @@ -55,7 +55,7 @@ cd .. npm_config_registry="$CUSTOM_REGISTRY_URL" npx create-docusaurus@"$NEW_VERSION" test-website classic $EXTRA_OPTS # Stop Docker container -if [[ -z "${KEEP_CONTAINER:-}" ]] && ( $(docker container inspect "$CONTAINER_NAME" > /dev/null 2>&1) ); then +if [[ -z "${KEEP_CONTAINER:-true}" ]] && ( $(docker container inspect "$CONTAINER_NAME" > /dev/null 2>&1) ); then # Remove Docker container docker container stop $CONTAINER_NAME > /dev/null fi diff --git a/babel.config.js b/babel.config.js deleted file mode 100644 index 3e4ad9e80b29..000000000000 --- a/babel.config.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -module.exports = { - presets: [ - [ - '@babel/env', - { - targets: { - node: 'current', - }, - }, - ], - '@babel/react', - '@babel/preset-typescript', - ], - plugins: [ - '@babel/plugin-proposal-class-properties', - '@babel/plugin-proposal-object-rest-spread', - '@babel/plugin-proposal-nullish-coalescing-operator', - '@babel/plugin-proposal-optional-chaining', - ], -}; diff --git a/examples/classic-typescript/.stackblitzrc b/examples/classic-typescript/.stackblitzrc index a8c490e81a63..5490eb1ecc12 100644 --- a/examples/classic-typescript/.stackblitzrc +++ b/examples/classic-typescript/.stackblitzrc @@ -1,4 +1,4 @@ { "installDependencies": true, "startCommand": "npm start" -} \ No newline at end of file +} diff --git a/examples/classic-typescript/docs/intro.md b/examples/classic-typescript/docs/intro.md index 440ad3373eb7..500260230bfc 100644 --- a/examples/classic-typescript/docs/intro.md +++ b/examples/classic-typescript/docs/intro.md @@ -12,24 +12,36 @@ Get started by **creating a new site**. Or **try Docusaurus immediately** with **[docusaurus.new](https://docusaurus.new)**. +### What you'll need + +- [Node.js](https://nodejs.org/en/download/) version 14 or above: + - When installing Node.js, you are recommended to check all checkboxes related to dependencies. + ## Generate a new site -Generate a new Docusaurus site using the **classic template**: +Generate a new Docusaurus site using the **classic template**. + +The classic template will automatically be added to your project after you run the command: ```bash npm init docusaurus@latest my-website classic ``` +You can type this command into Command Prompt, Powershell, Terminal, or any other integrated terminal of your code editor. + +The command also installs all necessary dependencies you need to run Docusaurus. + ## Start your site Run the development server: ```bash cd my-website - -npx docusaurus start +npm run start ``` -Your site starts at `http://localhost:3000`. +The `cd` command changes the directory you're working with. In order to work with your newly created Docusaurus site, you'll need to navigate the terminal there. + +The `npm run start` command builds your website locally and serves it through a development server, ready for you to view at http://localhost:3000/. -Open `docs/intro.md` and edit some lines: the site **reloads automatically** and displays your changes. +Open `docs/intro.md` (this page) and edit some lines: the site **reloads automatically** and displays your changes. diff --git a/examples/classic-typescript/docs/tutorial-basics/create-a-document.md b/examples/classic-typescript/docs/tutorial-basics/create-a-document.md index feaced79d0a3..a9bb9a4140b1 100644 --- a/examples/classic-typescript/docs/tutorial-basics/create-a-document.md +++ b/examples/classic-typescript/docs/tutorial-basics/create-a-document.md @@ -41,14 +41,14 @@ This is my **first Docusaurus document**! It is also possible to create your sidebar explicitly in `sidebars.js`: -```diff title="sidebars.js" +```js title="sidebars.js" module.exports = { tutorialSidebar: [ { type: 'category', label: 'Tutorial', -- items: [...], -+ items: ['hello'], + // highlight-next-line + items: ['hello'], }, ], }; diff --git a/examples/classic-typescript/package.json b/examples/classic-typescript/package.json index ab27fe03fba7..acd799430570 100644 --- a/examples/classic-typescript/package.json +++ b/examples/classic-typescript/package.json @@ -16,18 +16,18 @@ "dev": "docusaurus start" }, "dependencies": { - "@docusaurus/core": "2.0.0-beta.14", - "@docusaurus/preset-classic": "2.0.0-beta.14", - "@mdx-js/react": "^1.6.21", + "@docusaurus/core": "2.0.0-beta.18", + "@docusaurus/preset-classic": "2.0.0-beta.18", + "@mdx-js/react": "^1.6.22", "clsx": "^1.1.1", - "prism-react-renderer": "^1.2.1", - "react": "^17.0.1", - "react-dom": "^17.0.1" + "prism-react-renderer": "^1.3.1", + "react": "^17.0.2", + "react-dom": "^17.0.2" }, "devDependencies": { - "@docusaurus/module-type-aliases": "2.0.0-beta.14", - "@tsconfig/docusaurus": "^1.0.4", - "typescript": "^4.5.2" + "@docusaurus/module-type-aliases": "2.0.0-beta.18", + "@tsconfig/docusaurus": "^1.0.5", + "typescript": "^4.6.3" }, "browserslist": { "production": [ @@ -42,4 +42,4 @@ ] }, "description": "Docusaurus example project (classic-typescript template)" -} \ No newline at end of file +} diff --git a/examples/classic-typescript/sandbox.config.json b/examples/classic-typescript/sandbox.config.json index 069492640e53..95df40889ad5 100644 --- a/examples/classic-typescript/sandbox.config.json +++ b/examples/classic-typescript/sandbox.config.json @@ -7,4 +7,4 @@ "container": { "node": "14" } -} \ No newline at end of file +} diff --git a/packages/create-docusaurus/templates/classic-typescript/src/components/HomepageFeatures.tsx b/examples/classic-typescript/src/components/HomepageFeatures/index.tsx similarity index 76% rename from packages/create-docusaurus/templates/classic-typescript/src/components/HomepageFeatures.tsx rename to examples/classic-typescript/src/components/HomepageFeatures/index.tsx index e1d1c7908a84..91ef4601d2fc 100644 --- a/packages/create-docusaurus/templates/classic-typescript/src/components/HomepageFeatures.tsx +++ b/examples/classic-typescript/src/components/HomepageFeatures/index.tsx @@ -1,18 +1,17 @@ -import useBaseUrl from '@docusaurus/useBaseUrl'; import React from 'react'; import clsx from 'clsx'; -import styles from './HomepageFeatures.module.css'; +import styles from './styles.module.css'; type FeatureItem = { title: string; - image: string; + Svg: React.ComponentType>; description: JSX.Element; }; const FeatureList: FeatureItem[] = [ { title: 'Easy to Use', - image: '/img/undraw_docusaurus_mountain.svg', + Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default, description: ( <> Docusaurus was designed from the ground up to be easily installed and @@ -22,7 +21,7 @@ const FeatureList: FeatureItem[] = [ }, { title: 'Focus on What Matters', - image: '/img/undraw_docusaurus_tree.svg', + Svg: require('@site/static/img/undraw_docusaurus_tree.svg').default, description: ( <> Docusaurus lets you focus on your docs, and we'll do the chores. Go @@ -32,7 +31,7 @@ const FeatureList: FeatureItem[] = [ }, { title: 'Powered by React', - image: '/img/undraw_docusaurus_react.svg', + Svg: require('@site/static/img/undraw_docusaurus_react.svg').default, description: ( <> Extend or customize your website layout by reusing React. Docusaurus can @@ -42,15 +41,11 @@ const FeatureList: FeatureItem[] = [ }, ]; -function Feature({title, image, description}: FeatureItem) { +function Feature({title, Svg, description}: FeatureItem) { return (
- {title} +

{title}

diff --git a/examples/classic-typescript/src/components/HomepageFeatures.module.css b/examples/classic-typescript/src/components/HomepageFeatures/styles.module.css similarity index 100% rename from examples/classic-typescript/src/components/HomepageFeatures.module.css rename to examples/classic-typescript/src/components/HomepageFeatures/styles.module.css diff --git a/examples/classic-typescript/src/css/custom.css b/examples/classic-typescript/src/css/custom.css index 6abe14854412..311dc090d973 100644 --- a/examples/classic-typescript/src/css/custom.css +++ b/examples/classic-typescript/src/css/custom.css @@ -6,16 +6,27 @@ /* You can override the default Infima variables here. */ :root { - --ifm-color-primary: #25c2a0; - --ifm-color-primary-dark: rgb(33, 175, 144); - --ifm-color-primary-darker: rgb(31, 165, 136); - --ifm-color-primary-darkest: rgb(26, 136, 112); - --ifm-color-primary-light: rgb(70, 203, 174); - --ifm-color-primary-lighter: rgb(102, 212, 189); - --ifm-color-primary-lightest: rgb(146, 224, 208); + --ifm-color-primary: #2e8555; + --ifm-color-primary-dark: #29784c; + --ifm-color-primary-darker: #277148; + --ifm-color-primary-darkest: #205d3b; + --ifm-color-primary-light: #33925d; + --ifm-color-primary-lighter: #359962; + --ifm-color-primary-lightest: #3cad6e; --ifm-code-font-size: 95%; } +/* For readability concerns, you should choose a lighter palette in dark mode. */ +[data-theme='dark'] { + --ifm-color-primary: #25c2a0; + --ifm-color-primary-dark: #21af90; + --ifm-color-primary-darker: #1fa588; + --ifm-color-primary-darkest: #1a8870; + --ifm-color-primary-light: #29d5b0; + --ifm-color-primary-lighter: #32d8b4; + --ifm-color-primary-lightest: #4fddbf; +} + .docusaurus-highlight-code-line { background-color: rgba(0, 0, 0, 0.1); display: block; @@ -23,6 +34,6 @@ padding: 0 var(--ifm-pre-padding); } -html[data-theme='dark'] .docusaurus-highlight-code-line { +[data-theme='dark'] .docusaurus-highlight-code-line { background-color: rgba(0, 0, 0, 0.3); } diff --git a/examples/classic-typescript/src/pages/index.module.css b/examples/classic-typescript/src/pages/index.module.css index 666feb6a172a..9f71a5da775b 100644 --- a/examples/classic-typescript/src/pages/index.module.css +++ b/examples/classic-typescript/src/pages/index.module.css @@ -10,7 +10,7 @@ overflow: hidden; } -@media screen and (max-width: 966px) { +@media screen and (max-width: 996px) { .heroBanner { padding: 2rem; } diff --git a/examples/classic-typescript/src/pages/index.tsx b/examples/classic-typescript/src/pages/index.tsx index cc4f72112724..3408a41e7f23 100644 --- a/examples/classic-typescript/src/pages/index.tsx +++ b/examples/classic-typescript/src/pages/index.tsx @@ -4,7 +4,7 @@ import Layout from '@theme/Layout'; import Link from '@docusaurus/Link'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import styles from './index.module.css'; -import HomepageFeatures from '../components/HomepageFeatures'; +import HomepageFeatures from '@site/src/components/HomepageFeatures'; function HomepageHeader() { const {siteConfig} = useDocusaurusContext(); diff --git a/examples/classic-typescript/static/img/undraw_docusaurus_mountain.svg b/examples/classic-typescript/static/img/undraw_docusaurus_mountain.svg index 431cef2f7fec..af961c49a888 100644 --- a/examples/classic-typescript/static/img/undraw_docusaurus_mountain.svg +++ b/examples/classic-typescript/static/img/undraw_docusaurus_mountain.svg @@ -1,4 +1,5 @@ + Easy to Use diff --git a/examples/classic-typescript/static/img/undraw_docusaurus_react.svg b/examples/classic-typescript/static/img/undraw_docusaurus_react.svg index e41705043338..94b5cf08f88f 100644 --- a/examples/classic-typescript/static/img/undraw_docusaurus_react.svg +++ b/examples/classic-typescript/static/img/undraw_docusaurus_react.svg @@ -1,4 +1,5 @@ + Powered by React diff --git a/examples/classic-typescript/static/img/undraw_docusaurus_tree.svg b/examples/classic-typescript/static/img/undraw_docusaurus_tree.svg index a05cc03dda90..d9161d33920c 100644 --- a/examples/classic-typescript/static/img/undraw_docusaurus_tree.svg +++ b/examples/classic-typescript/static/img/undraw_docusaurus_tree.svg @@ -1 +1,40 @@ -docu_tree \ No newline at end of file + + Focus on What Matters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/classic-typescript/yarn.lock b/examples/classic-typescript/yarn.lock index 8afa85cafa9f..1fd0c7e58a8a 100644 --- a/examples/classic-typescript/yarn.lock +++ b/examples/classic-typescript/yarn.lock @@ -2,145 +2,152 @@ # yarn lockfile v1 -"@algolia/autocomplete-core@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-core/-/autocomplete-core-1.5.0.tgz#6c91c9de7748e9c103846828a58dfe92bd4d6689" - integrity sha512-E7+VJwcvwMM8vPeaVn7fNUgix8WHV8A1WUeHDi2KHemCaaGc8lvUnP3QnvhMxiDhTe7OpMEv4o2TBUMyDgThaw== +"@algolia/autocomplete-core@1.5.2": + version "1.5.2" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-core/-/autocomplete-core-1.5.2.tgz#ec0178e07b44fd74a057728ac157291b26cecf37" + integrity sha512-DY0bhyczFSS1b/CqJlTE/nQRtnTAHl6IemIkBy0nEWnhDzRDdtdx4p5Uuk3vwAFxwEEgi1WqKwgSSMx6DpNL4A== dependencies: - "@algolia/autocomplete-shared" "1.5.0" + "@algolia/autocomplete-shared" "1.5.2" -"@algolia/autocomplete-preset-algolia@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.5.0.tgz#61671f09c0c77133d9baf1356719f8378c48437a" - integrity sha512-iiFxKERGHkvkiupmrFJbvESpP/zv5jSgH714XRiP5LDvUHaYOo4GLAwZCFf2ef/L5tdtPBARvekn6k1Xf33gjA== +"@algolia/autocomplete-preset-algolia@1.5.2": + version "1.5.2" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.5.2.tgz#36c5638cc6dba6ea46a86e5a0314637ca40a77ca" + integrity sha512-3MRYnYQFJyovANzSX2CToS6/5cfVjbLLqFsZTKcvF3abhQzxbqwwaMBlJtt620uBUOeMzhdfasKhCc40+RHiZw== dependencies: - "@algolia/autocomplete-shared" "1.5.0" + "@algolia/autocomplete-shared" "1.5.2" -"@algolia/autocomplete-shared@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.5.0.tgz#09580bc89408a2ab5f29e312120dad68f58019bd" - integrity sha512-bRSkqHHHSwZYbFY3w9hgMyQRm86Wz27bRaGCbNldLfbk0zUjApmE4ajx+ZCVSLqxvcUEjMqZFJzDsder12eKsg== +"@algolia/autocomplete-shared@1.5.2": + version "1.5.2" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.5.2.tgz#e157f9ad624ab8fd940ff28bd2094cdf199cdd79" + integrity sha512-ylQAYv5H0YKMfHgVWX0j0NmL8XBcAeeeVQUmppnnMtzDbDnca6CzhKj3Q8eF9cHCgcdTDdb5K+3aKyGWA0obug== -"@algolia/cache-browser-local-storage@4.11.0": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.11.0.tgz#1c168add00b398a860db6c86039e33b2843a9425" - integrity sha512-4sr9vHIG1fVA9dONagdzhsI/6M5mjs/qOe2xUP0yBmwsTsuwiZq3+Xu6D3dsxsuFetcJgC6ydQoCW8b7fDJHYQ== +"@algolia/cache-browser-local-storage@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.13.0.tgz#f8aa4fe31104b19d616ea392f9ed5c2ea847d964" + integrity sha512-nj1vHRZauTqP/bluwkRIgEADEimqojJgoTRCel5f6q8WCa9Y8QeI4bpDQP28FoeKnDRYa3J5CauDlN466jqRhg== dependencies: - "@algolia/cache-common" "4.11.0" + "@algolia/cache-common" "4.13.0" -"@algolia/cache-common@4.11.0": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@algolia/cache-common/-/cache-common-4.11.0.tgz#066fe6d58b18e4b028dbef9bb8de07c5e22a3594" - integrity sha512-lODcJRuPXqf+6mp0h6bOxPMlbNoyn3VfjBVcQh70EDP0/xExZbkpecgHyyZK4kWg+evu+mmgvTK3GVHnet/xKw== +"@algolia/cache-common@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@algolia/cache-common/-/cache-common-4.13.0.tgz#27b83fd3939d08d72261b36a07eeafc4cb4d2113" + integrity sha512-f9mdZjskCui/dA/fA/5a+6hZ7xnHaaZI5tM/Rw9X8rRB39SUlF/+o3P47onZ33n/AwkpSbi5QOyhs16wHd55kA== -"@algolia/cache-in-memory@4.11.0": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@algolia/cache-in-memory/-/cache-in-memory-4.11.0.tgz#763c8cb655e6fd2261588e04214fca0959ac07c1" - integrity sha512-aBz+stMSTBOBaBEQ43zJXz2DnwS7fL6dR0e2myehAgtfAWlWwLDHruc/98VOy1ZAcBk1blE2LCU02bT5HekGxQ== +"@algolia/cache-in-memory@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@algolia/cache-in-memory/-/cache-in-memory-4.13.0.tgz#10801a74550cbabb64b59ff08c56bce9c278ff2d" + integrity sha512-hHdc+ahPiMM92CQMljmObE75laYzNFYLrNOu0Q3/eyvubZZRtY2SUsEEgyUEyzXruNdzrkcDxFYa7YpWBJYHAg== dependencies: - "@algolia/cache-common" "4.11.0" + "@algolia/cache-common" "4.13.0" -"@algolia/client-account@4.11.0": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@algolia/client-account/-/client-account-4.11.0.tgz#67fadd3b0802b013ebaaa4b47bb7babae892374e" - integrity sha512-jwmFBoUSzoMwMqgD3PmzFJV/d19p1RJXB6C1ADz4ju4mU7rkaQLtqyZroQpheLoU5s5Tilmn/T8/0U2XLoJCRQ== +"@algolia/client-account@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@algolia/client-account/-/client-account-4.13.0.tgz#f8646dd40d1e9e3353e10abbd5d6c293ea92a8e2" + integrity sha512-FzFqFt9b0g/LKszBDoEsW+dVBuUe1K3scp2Yf7q6pgHWM1WqyqUlARwVpLxqyc+LoyJkTxQftOKjyFUqddnPKA== dependencies: - "@algolia/client-common" "4.11.0" - "@algolia/client-search" "4.11.0" - "@algolia/transporter" "4.11.0" + "@algolia/client-common" "4.13.0" + "@algolia/client-search" "4.13.0" + "@algolia/transporter" "4.13.0" -"@algolia/client-analytics@4.11.0": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@algolia/client-analytics/-/client-analytics-4.11.0.tgz#cbdc8128205e2da749cafc79e54708d14c413974" - integrity sha512-v5U9585aeEdYml7JqggHAj3E5CQ+jPwGVztPVhakBk8H/cmLyPS2g8wvmIbaEZCHmWn4TqFj3EBHVYxAl36fSA== +"@algolia/client-analytics@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@algolia/client-analytics/-/client-analytics-4.13.0.tgz#a00bd02df45d71becb9dd4c5c993d805f2e1786d" + integrity sha512-klmnoq2FIiiMHImkzOm+cGxqRLLu9CMHqFhbgSy9wtXZrqb8BBUIUE2VyBe7azzv1wKcxZV2RUyNOMpFqmnRZA== dependencies: - "@algolia/client-common" "4.11.0" - "@algolia/client-search" "4.11.0" - "@algolia/requester-common" "4.11.0" - "@algolia/transporter" "4.11.0" + "@algolia/client-common" "4.13.0" + "@algolia/client-search" "4.13.0" + "@algolia/requester-common" "4.13.0" + "@algolia/transporter" "4.13.0" -"@algolia/client-common@4.11.0": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@algolia/client-common/-/client-common-4.11.0.tgz#9a2d1f6f8eaad25ba5d6d4ce307ba5bd84e6f999" - integrity sha512-Qy+F+TZq12kc7tgfC+FM3RvYH/Ati7sUiUv/LkvlxFwNwNPwWGoZO81AzVSareXT/ksDDrabD4mHbdTbBPTRmQ== +"@algolia/client-common@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@algolia/client-common/-/client-common-4.13.0.tgz#8bc373d164dbdcce38b4586912bbe162492bcb86" + integrity sha512-GoXfTp0kVcbgfSXOjfrxx+slSipMqGO9WnNWgeMmru5Ra09MDjrcdunsiiuzF0wua6INbIpBQFTC2Mi5lUNqGA== dependencies: - "@algolia/requester-common" "4.11.0" - "@algolia/transporter" "4.11.0" + "@algolia/requester-common" "4.13.0" + "@algolia/transporter" "4.13.0" -"@algolia/client-personalization@4.11.0": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@algolia/client-personalization/-/client-personalization-4.11.0.tgz#d3bf0e760f85df876b4baf5b81996f0aa3a59940" - integrity sha512-mI+X5IKiijHAzf9fy8VSl/GTT67dzFDnJ0QAM8D9cMPevnfX4U72HRln3Mjd0xEaYUOGve8TK/fMg7d3Z5yG6g== +"@algolia/client-personalization@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@algolia/client-personalization/-/client-personalization-4.13.0.tgz#10fb7af356422551f11a67222b39c52306f1512c" + integrity sha512-KneLz2WaehJmNfdr5yt2HQETpLaCYagRdWwIwkTqRVFCv4DxRQ2ChPVW9jeTj4YfAAhfzE6F8hn7wkQ/Jfj6ZA== dependencies: - "@algolia/client-common" "4.11.0" - "@algolia/requester-common" "4.11.0" - "@algolia/transporter" "4.11.0" + "@algolia/client-common" "4.13.0" + "@algolia/requester-common" "4.13.0" + "@algolia/transporter" "4.13.0" -"@algolia/client-search@4.11.0": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@algolia/client-search/-/client-search-4.11.0.tgz#c1105d715a2a04ba27231eca86f5d6620f68f4ae" - integrity sha512-iovPLc5YgiXBdw2qMhU65sINgo9umWbHFzInxoNErWnYoTQWfXsW6P54/NlKx5uscoLVjSf+5RUWwFu5BX+lpw== +"@algolia/client-search@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@algolia/client-search/-/client-search-4.13.0.tgz#2d8ff8e755c4a37ec89968f3f9b358eed005c7f0" + integrity sha512-blgCKYbZh1NgJWzeGf+caKE32mo3j54NprOf0LZVCubQb3Kx37tk1Hc8SDs9bCAE8hUvf3cazMPIg7wscSxspA== dependencies: - "@algolia/client-common" "4.11.0" - "@algolia/requester-common" "4.11.0" - "@algolia/transporter" "4.11.0" + "@algolia/client-common" "4.13.0" + "@algolia/requester-common" "4.13.0" + "@algolia/transporter" "4.13.0" "@algolia/events@^4.0.1": version "4.0.1" resolved "https://registry.yarnpkg.com/@algolia/events/-/events-4.0.1.tgz#fd39e7477e7bc703d7f893b556f676c032af3950" integrity sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ== -"@algolia/logger-common@4.11.0": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@algolia/logger-common/-/logger-common-4.11.0.tgz#bac1c2d59d29dee378b57412c8edd435b97de663" - integrity sha512-pRMJFeOY8hoWKIxWuGHIrqnEKN/kqKh7UilDffG/+PeEGxBuku+Wq5CfdTFG0C9ewUvn8mAJn5BhYA5k8y0Jqg== +"@algolia/logger-common@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@algolia/logger-common/-/logger-common-4.13.0.tgz#be2606e71aae618a1ff1ea9a1b5f5a74284b35a8" + integrity sha512-8yqXk7rMtmQJ9wZiHOt/6d4/JDEg5VCk83gJ39I+X/pwUPzIsbKy9QiK4uJ3aJELKyoIiDT1hpYVt+5ia+94IA== -"@algolia/logger-console@4.11.0": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@algolia/logger-console/-/logger-console-4.11.0.tgz#ced19e3abb22eb782ed5268d51efb5aa9ef109ef" - integrity sha512-wXztMk0a3VbNmYP8Kpc+F7ekuvaqZmozM2eTLok0XIshpAeZ/NJDHDffXK2Pw+NF0wmHqurptLYwKoikjBYvhQ== +"@algolia/logger-console@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@algolia/logger-console/-/logger-console-4.13.0.tgz#f28028a760e3d9191e28a10b12925e48f6c9afde" + integrity sha512-YepRg7w2/87L0vSXRfMND6VJ5d6699sFJBRWzZPOlek2p5fLxxK7O0VncYuc/IbVHEgeApvgXx0WgCEa38GVuQ== dependencies: - "@algolia/logger-common" "4.11.0" + "@algolia/logger-common" "4.13.0" -"@algolia/requester-browser-xhr@4.11.0": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.11.0.tgz#f9e1ad56f185432aa8dde8cad53ae271fd5d6181" - integrity sha512-Fp3SfDihAAFR8bllg8P5ouWi3+qpEVN5e7hrtVIYldKBOuI/qFv80Zv/3/AMKNJQRYglS4zWyPuqrXm58nz6KA== +"@algolia/requester-browser-xhr@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.13.0.tgz#e2483f4e8d7f09e27cd0daf6c77711d15c5a919f" + integrity sha512-Dj+bnoWR5MotrnjblzGKZ2kCdQi2cK/VzPURPnE616NU/il7Ypy6U6DLGZ/ZYz+tnwPa0yypNf21uqt84fOgrg== dependencies: - "@algolia/requester-common" "4.11.0" + "@algolia/requester-common" "4.13.0" -"@algolia/requester-common@4.11.0": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@algolia/requester-common/-/requester-common-4.11.0.tgz#d16de98d3ff72434bac39e4d915eab08035946a9" - integrity sha512-+cZGe/9fuYgGuxjaBC+xTGBkK7OIYdfapxhfvEf03dviLMPmhmVYFJtJlzAjQ2YmGDJpHrGgAYj3i/fbs8yhiA== +"@algolia/requester-common@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@algolia/requester-common/-/requester-common-4.13.0.tgz#47fb3464cfb26b55ba43676d13f295d812830596" + integrity sha512-BRTDj53ecK+gn7ugukDWOOcBRul59C4NblCHqj4Zm5msd5UnHFjd/sGX+RLOEoFMhetILAnmg6wMrRrQVac9vw== -"@algolia/requester-node-http@4.11.0": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@algolia/requester-node-http/-/requester-node-http-4.11.0.tgz#beb2b6b68d5f4ce15aec80ede623f0ac96991368" - integrity sha512-qJIk9SHRFkKDi6dMT9hba8X1J1z92T5AZIgl+tsApjTGIRQXJLTIm+0q4yOefokfu4CoxYwRZ9QAq+ouGwfeOg== +"@algolia/requester-node-http@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@algolia/requester-node-http/-/requester-node-http-4.13.0.tgz#7d981bbd31492f51dd11820a665f9d8906793c37" + integrity sha512-9b+3O4QFU4azLhGMrZAr/uZPydvzOR4aEZfSL8ZrpLZ7fbbqTO0S/5EVko+QIgglRAtVwxvf8UJ1wzTD2jvKxQ== dependencies: - "@algolia/requester-common" "4.11.0" + "@algolia/requester-common" "4.13.0" -"@algolia/transporter@4.11.0": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@algolia/transporter/-/transporter-4.11.0.tgz#a8de3c173093ceceb02b26b577395ce3b3d4b96f" - integrity sha512-k4dyxiaEfYpw4UqybK9q7lrFzehygo6KV3OCYJMMdX0IMWV0m4DXdU27c1zYRYtthaFYaBzGF4Kjcl8p8vxCKw== +"@algolia/transporter@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@algolia/transporter/-/transporter-4.13.0.tgz#f6379e5329efa2127da68c914d1141f5f21dbd07" + integrity sha512-8tSQYE+ykQENAdeZdofvtkOr5uJ9VcQSWgRhQ9h01AehtBIPAczk/b2CLrMsw5yQZziLs5cZ3pJ3478yI+urhA== dependencies: - "@algolia/cache-common" "4.11.0" - "@algolia/logger-common" "4.11.0" - "@algolia/requester-common" "4.11.0" + "@algolia/cache-common" "4.13.0" + "@algolia/logger-common" "4.13.0" + "@algolia/requester-common" "4.13.0" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.8.3": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.0.tgz#0dfc80309beec8411e65e706461c408b0bb9b431" - integrity sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA== +"@ampproject/remapping@^2.1.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.1.2.tgz#4edca94973ded9630d20101cd8559cedb8d8bd34" + integrity sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg== dependencies: - "@babel/highlight" "^7.16.0" + "@jridgewell/trace-mapping" "^0.3.0" -"@babel/compat-data@^7.13.11", "@babel/compat-data@^7.16.0", "@babel/compat-data@^7.16.4": - version "7.16.4" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.16.4.tgz#081d6bbc336ec5c2435c6346b2ae1fb98b5ac68e" - integrity sha512-1o/jo7D+kC9ZjHX5v+EHrdjl3PhxMrLSOTGsOdHJ+KL8HCaEK6ehrVL2RS6oHDZp+L7xLirLrPmQtEng769J/Q== +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.8.3": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789" + integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg== + dependencies: + "@babel/highlight" "^7.16.7" + +"@babel/compat-data@^7.13.11", "@babel/compat-data@^7.16.8", "@babel/compat-data@^7.17.0", "@babel/compat-data@^7.17.7": + version "7.17.7" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.17.7.tgz#078d8b833fbbcc95286613be8c716cef2b519fa2" + integrity sha512-p8pdE6j0a29TNGebNm7NzYZWB3xVZJBZ7XGs42uAKzQo8VQ3F0By/cQCtUEABwIqw5zo6WA4NbmxsfzADzMKnQ== "@babel/core@7.12.9": version "7.12.9" @@ -164,86 +171,86 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/core@^7.15.5", "@babel/core@^7.16.0": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.16.5.tgz#924aa9e1ae56e1e55f7184c8bf073a50d8677f5c" - integrity sha512-wUcenlLzuWMZ9Zt8S0KmFwGlH6QKRh3vsm/dhDA3CHkiTA45YuG1XkHRcNRl73EFPXDp/d5kVOU0/y7x2w6OaQ== - dependencies: - "@babel/code-frame" "^7.16.0" - "@babel/generator" "^7.16.5" - "@babel/helper-compilation-targets" "^7.16.3" - "@babel/helper-module-transforms" "^7.16.5" - "@babel/helpers" "^7.16.5" - "@babel/parser" "^7.16.5" - "@babel/template" "^7.16.0" - "@babel/traverse" "^7.16.5" - "@babel/types" "^7.16.0" +"@babel/core@^7.15.5", "@babel/core@^7.17.8": + version "7.17.8" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.17.8.tgz#3dac27c190ebc3a4381110d46c80e77efe172e1a" + integrity sha512-OdQDV/7cRBtJHLSOBqqbYNkOcydOgnX59TZx4puf41fzcVtN3e/4yqY8lMQsK+5X2lJtAdmA+6OHqsj1hBJ4IQ== + dependencies: + "@ampproject/remapping" "^2.1.0" + "@babel/code-frame" "^7.16.7" + "@babel/generator" "^7.17.7" + "@babel/helper-compilation-targets" "^7.17.7" + "@babel/helper-module-transforms" "^7.17.7" + "@babel/helpers" "^7.17.8" + "@babel/parser" "^7.17.8" + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.17.3" + "@babel/types" "^7.17.0" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" json5 "^2.1.2" semver "^6.3.0" - source-map "^0.5.0" -"@babel/generator@^7.12.5", "@babel/generator@^7.16.0", "@babel/generator@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.16.5.tgz#26e1192eb8f78e0a3acaf3eede3c6fc96d22bedf" - integrity sha512-kIvCdjZqcdKqoDbVVdt5R99icaRtrtYhYK/xux5qiWCBmfdvEYMFZ68QCrpE5cbFM1JsuArUNs1ZkuKtTtUcZA== +"@babel/generator@^7.12.5", "@babel/generator@^7.17.3", "@babel/generator@^7.17.7": + version "7.17.7" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.17.7.tgz#8da2599beb4a86194a3b24df6c085931d9ee45ad" + integrity sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w== dependencies: - "@babel/types" "^7.16.0" + "@babel/types" "^7.17.0" jsesc "^2.5.1" source-map "^0.5.0" -"@babel/helper-annotate-as-pure@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.0.tgz#9a1f0ebcda53d9a2d00108c4ceace6a5d5f1f08d" - integrity sha512-ItmYF9vR4zA8cByDocY05o0LGUkp1zhbTQOH1NFyl5xXEqlTJQCEJjieriw+aFpxo16swMxUnUiKS7a/r4vtHg== +"@babel/helper-annotate-as-pure@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz#bb2339a7534a9c128e3102024c60760a3a7f3862" + integrity sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw== dependencies: - "@babel/types" "^7.16.0" + "@babel/types" "^7.16.7" -"@babel/helper-builder-binary-assignment-operator-visitor@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.5.tgz#a8429d064dce8207194b8bf05a70a9ea828746af" - integrity sha512-3JEA9G5dmmnIWdzaT9d0NmFRgYnWUThLsDaL7982H0XqqWr56lRrsmwheXFMjR+TMl7QMBb6mzy9kvgr1lRLUA== +"@babel/helper-builder-binary-assignment-operator-visitor@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.7.tgz#38d138561ea207f0f69eb1626a418e4f7e6a580b" + integrity sha512-C6FdbRaxYjwVu/geKW4ZeQ0Q31AftgRcdSnZ5/jsH6BzCJbtvXvhpfkbkThYSuutZA7nCXpPR6AD9zd1dprMkA== dependencies: - "@babel/helper-explode-assignable-expression" "^7.16.0" - "@babel/types" "^7.16.0" + "@babel/helper-explode-assignable-expression" "^7.16.7" + "@babel/types" "^7.16.7" -"@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.16.3": - version "7.16.3" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.3.tgz#5b480cd13f68363df6ec4dc8ac8e2da11363cbf0" - integrity sha512-vKsoSQAyBmxS35JUOOt+07cLc6Nk/2ljLIHwmq2/NM6hdioUaqEXq/S+nXvbvXbZkNDlWOymPanJGOc4CBjSJA== +"@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.16.7", "@babel/helper-compilation-targets@^7.17.7": + version "7.17.7" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.7.tgz#a3c2924f5e5f0379b356d4cfb313d1414dc30e46" + integrity sha512-UFzlz2jjd8kroj0hmCFV5zr+tQPi1dpC2cRsDV/3IEW8bJfCPrPpmcSN6ZS8RqIq4LXcmpipCQFPddyFA5Yc7w== dependencies: - "@babel/compat-data" "^7.16.0" - "@babel/helper-validator-option" "^7.14.5" + "@babel/compat-data" "^7.17.7" + "@babel/helper-validator-option" "^7.16.7" browserslist "^4.17.5" semver "^6.3.0" -"@babel/helper-create-class-features-plugin@^7.16.0", "@babel/helper-create-class-features-plugin@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.16.5.tgz#5d1bcd096792c1ebec6249eebc6358eec55d0cad" - integrity sha512-NEohnYA7mkB8L5JhU7BLwcBdU3j83IziR9aseMueWGeAjblbul3zzb8UvJ3a1zuBiqCMObzCJHFqKIQE6hTVmg== +"@babel/helper-create-class-features-plugin@^7.16.10", "@babel/helper-create-class-features-plugin@^7.16.7", "@babel/helper-create-class-features-plugin@^7.17.6": + version "7.17.6" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.6.tgz#3778c1ed09a7f3e65e6d6e0f6fbfcc53809d92c9" + integrity sha512-SogLLSxXm2OkBbSsHZMM4tUi8fUzjs63AT/d0YQIzr6GSd8Hxsbk2KYDX0k0DweAzGMj/YWeiCsorIdtdcW8Eg== dependencies: - "@babel/helper-annotate-as-pure" "^7.16.0" - "@babel/helper-environment-visitor" "^7.16.5" - "@babel/helper-function-name" "^7.16.0" - "@babel/helper-member-expression-to-functions" "^7.16.5" - "@babel/helper-optimise-call-expression" "^7.16.0" - "@babel/helper-replace-supers" "^7.16.5" - "@babel/helper-split-export-declaration" "^7.16.0" + "@babel/helper-annotate-as-pure" "^7.16.7" + "@babel/helper-environment-visitor" "^7.16.7" + "@babel/helper-function-name" "^7.16.7" + "@babel/helper-member-expression-to-functions" "^7.16.7" + "@babel/helper-optimise-call-expression" "^7.16.7" + "@babel/helper-replace-supers" "^7.16.7" + "@babel/helper-split-export-declaration" "^7.16.7" -"@babel/helper-create-regexp-features-plugin@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.16.0.tgz#06b2348ce37fccc4f5e18dcd8d75053f2a7c44ff" - integrity sha512-3DyG0zAFAZKcOp7aVr33ddwkxJ0Z0Jr5V99y3I690eYLpukJsJvAbzTy1ewoCqsML8SbIrjH14Jc/nSQ4TvNPA== +"@babel/helper-create-regexp-features-plugin@^7.16.7": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.17.0.tgz#1dcc7d40ba0c6b6b25618997c5dbfd310f186fe1" + integrity sha512-awO2So99wG6KnlE+TPs6rn83gCz5WlEePJDTnLEqbchMVrBeAujURVphRdigsk094VhvZehFoNOihSlcBjwsXA== dependencies: - "@babel/helper-annotate-as-pure" "^7.16.0" - regexpu-core "^4.7.1" + "@babel/helper-annotate-as-pure" "^7.16.7" + regexpu-core "^5.0.1" -"@babel/helper-define-polyfill-provider@^0.3.0": - version "0.3.0" - resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.0.tgz#c5b10cf4b324ff840140bb07e05b8564af2ae971" - integrity sha512-7hfT8lUljl/tM3h+izTX/pO3W3frz2ok6Pk+gzys8iJqDfZrZy2pXjRTZAvG2YmfHun1X4q8/UZRLatMfqc5Tg== +"@babel/helper-define-polyfill-provider@^0.3.1": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.1.tgz#52411b445bdb2e676869e5a74960d2d3826d2665" + integrity sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA== dependencies: "@babel/helper-compilation-targets" "^7.13.0" "@babel/helper-module-imports" "^7.12.13" @@ -254,114 +261,114 @@ resolve "^1.14.2" semver "^6.1.2" -"@babel/helper-environment-visitor@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.5.tgz#f6a7f38b3c6d8b07c88faea083c46c09ef5451b8" - integrity sha512-ODQyc5AnxmZWm/R2W7fzhamOk1ey8gSguo5SGvF0zcB3uUzRpTRmM/jmLSm9bDMyPlvbyJ+PwPEK0BWIoZ9wjg== +"@babel/helper-environment-visitor@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz#ff484094a839bde9d89cd63cba017d7aae80ecd7" + integrity sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag== dependencies: - "@babel/types" "^7.16.0" + "@babel/types" "^7.16.7" -"@babel/helper-explode-assignable-expression@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.0.tgz#753017337a15f46f9c09f674cff10cee9b9d7778" - integrity sha512-Hk2SLxC9ZbcOhLpg/yMznzJ11W++lg5GMbxt1ev6TXUiJB0N42KPC+7w8a+eWGuqDnUYuwStJoZHM7RgmIOaGQ== +"@babel/helper-explode-assignable-expression@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.7.tgz#12a6d8522fdd834f194e868af6354e8650242b7a" + integrity sha512-KyUenhWMC8VrxzkGP0Jizjo4/Zx+1nNZhgocs+gLzyZyB8SHidhoq9KK/8Ato4anhwsivfkBLftky7gvzbZMtQ== dependencies: - "@babel/types" "^7.16.0" + "@babel/types" "^7.16.7" -"@babel/helper-function-name@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.16.0.tgz#b7dd0797d00bbfee4f07e9c4ea5b0e30c8bb1481" - integrity sha512-BZh4mEk1xi2h4HFjWUXRQX5AEx4rvaZxHgax9gcjdLWdkjsY7MKt5p0otjsg5noXw+pB+clMCjw+aEVYADMjog== +"@babel/helper-function-name@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz#f1ec51551fb1c8956bc8dd95f38523b6cf375f8f" + integrity sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA== dependencies: - "@babel/helper-get-function-arity" "^7.16.0" - "@babel/template" "^7.16.0" - "@babel/types" "^7.16.0" + "@babel/helper-get-function-arity" "^7.16.7" + "@babel/template" "^7.16.7" + "@babel/types" "^7.16.7" -"@babel/helper-get-function-arity@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.0.tgz#0088c7486b29a9cb5d948b1a1de46db66e089cfa" - integrity sha512-ASCquNcywC1NkYh/z7Cgp3w31YW8aojjYIlNg4VeJiHkqyP4AzIvr4qx7pYDb4/s8YcsZWqqOSxgkvjUz1kpDQ== +"@babel/helper-get-function-arity@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz#ea08ac753117a669f1508ba06ebcc49156387419" + integrity sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw== dependencies: - "@babel/types" "^7.16.0" + "@babel/types" "^7.16.7" -"@babel/helper-hoist-variables@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.0.tgz#4c9023c2f1def7e28ff46fc1dbcd36a39beaa81a" - integrity sha512-1AZlpazjUR0EQZQv3sgRNfM9mEVWPK3M6vlalczA+EECcPz3XPh6VplbErL5UoMpChhSck5wAJHthlj1bYpcmg== +"@babel/helper-hoist-variables@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz#86bcb19a77a509c7b77d0e22323ef588fa58c246" + integrity sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg== dependencies: - "@babel/types" "^7.16.0" + "@babel/types" "^7.16.7" -"@babel/helper-member-expression-to-functions@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.5.tgz#1bc9f7e87354e86f8879c67b316cb03d3dc2caab" - integrity sha512-7fecSXq7ZrLE+TWshbGT+HyCLkxloWNhTbU2QM1NTI/tDqyf0oZiMcEfYtDuUDCo528EOlt39G1rftea4bRZIw== +"@babel/helper-member-expression-to-functions@^7.16.7": + version "7.17.7" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.17.7.tgz#a34013b57d8542a8c4ff8ba3f747c02452a4d8c4" + integrity sha512-thxXgnQ8qQ11W2wVUObIqDL4p148VMxkt5T/qpN5k2fboRyzFGFmKsTGViquyM5QHKUy48OZoca8kw4ajaDPyw== dependencies: - "@babel/types" "^7.16.0" + "@babel/types" "^7.17.0" -"@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.0.tgz#90538e60b672ecf1b448f5f4f5433d37e79a3ec3" - integrity sha512-kkH7sWzKPq0xt3H1n+ghb4xEMP8k0U7XV3kkB+ZGy69kDk2ySFW1qPi06sjKzFY3t1j6XbJSqr4mF9L7CYVyhg== +"@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz#25612a8091a999704461c8a222d0efec5d091437" + integrity sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg== dependencies: - "@babel/types" "^7.16.0" + "@babel/types" "^7.16.7" -"@babel/helper-module-transforms@^7.12.1", "@babel/helper-module-transforms@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.16.5.tgz#530ebf6ea87b500f60840578515adda2af470a29" - integrity sha512-CkvMxgV4ZyyioElFwcuWnDCcNIeyqTkCm9BxXZi73RR1ozqlpboqsbGUNvRTflgZtFbbJ1v5Emvm+lkjMYY/LQ== - dependencies: - "@babel/helper-environment-visitor" "^7.16.5" - "@babel/helper-module-imports" "^7.16.0" - "@babel/helper-simple-access" "^7.16.0" - "@babel/helper-split-export-declaration" "^7.16.0" - "@babel/helper-validator-identifier" "^7.15.7" - "@babel/template" "^7.16.0" - "@babel/traverse" "^7.16.5" - "@babel/types" "^7.16.0" +"@babel/helper-module-transforms@^7.12.1", "@babel/helper-module-transforms@^7.16.7", "@babel/helper-module-transforms@^7.17.7": + version "7.17.7" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.17.7.tgz#3943c7f777139e7954a5355c815263741a9c1cbd" + integrity sha512-VmZD99F3gNTYB7fJRDTi+u6l/zxY0BE6OIxPSU7a50s6ZUQkHwSDmV92FfM+oCG0pZRVojGYhkR8I0OGeCVREw== + dependencies: + "@babel/helper-environment-visitor" "^7.16.7" + "@babel/helper-module-imports" "^7.16.7" + "@babel/helper-simple-access" "^7.17.7" + "@babel/helper-split-export-declaration" "^7.16.7" + "@babel/helper-validator-identifier" "^7.16.7" + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.17.3" + "@babel/types" "^7.17.0" -"@babel/helper-optimise-call-expression@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.0.tgz#cecdb145d70c54096b1564f8e9f10cd7d193b338" - integrity sha512-SuI467Gi2V8fkofm2JPnZzB/SUuXoJA5zXe/xzyPP2M04686RzFKFHPK6HDVN6JvWBIEW8tt9hPR7fXdn2Lgpw== +"@babel/helper-optimise-call-expression@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz#a34e3560605abbd31a18546bd2aad3e6d9a174f2" + integrity sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w== dependencies: - "@babel/types" "^7.16.0" + "@babel/types" "^7.16.7" "@babel/helper-plugin-utils@7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz#2f75a831269d4f677de49986dff59927533cf375" integrity sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg== -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.13.0", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.5", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.5.tgz#afe37a45f39fce44a3d50a7958129ea5b1a5c074" - integrity sha512-59KHWHXxVA9K4HNF4sbHCf+eJeFe0Te/ZFGqBT4OjXhrwvA04sGfaEGsVTdsjoszq0YTP49RC9UKe5g8uN2RwQ== +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.13.0", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz#aa3a8ab4c3cceff8e65eb9e73d87dc4ff320b2f5" + integrity sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA== -"@babel/helper-remap-async-to-generator@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.5.tgz#e706646dc4018942acb4b29f7e185bc246d65ac3" - integrity sha512-X+aAJldyxrOmN9v3FKp+Hu1NO69VWgYgDGq6YDykwRPzxs5f2N+X988CBXS7EQahDU+Vpet5QYMqLk+nsp+Qxw== +"@babel/helper-remap-async-to-generator@^7.16.8": + version "7.16.8" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.8.tgz#29ffaade68a367e2ed09c90901986918d25e57e3" + integrity sha512-fm0gH7Flb8H51LqJHy3HJ3wnE1+qtYR2A99K06ahwrawLdOFsCEWjZOrYricXJHoPSudNKxrMBUPEIPxiIIvBw== dependencies: - "@babel/helper-annotate-as-pure" "^7.16.0" - "@babel/helper-wrap-function" "^7.16.5" - "@babel/types" "^7.16.0" + "@babel/helper-annotate-as-pure" "^7.16.7" + "@babel/helper-wrap-function" "^7.16.8" + "@babel/types" "^7.16.8" -"@babel/helper-replace-supers@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.16.5.tgz#96d3988bd0ab0a2d22c88c6198c3d3234ca25326" - integrity sha512-ao3seGVa/FZCMCCNDuBcqnBFSbdr8N2EW35mzojx3TwfIbdPmNK+JV6+2d5bR0Z71W5ocLnQp9en/cTF7pBJiQ== +"@babel/helper-replace-supers@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.16.7.tgz#e9f5f5f32ac90429c1a4bdec0f231ef0c2838ab1" + integrity sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw== dependencies: - "@babel/helper-environment-visitor" "^7.16.5" - "@babel/helper-member-expression-to-functions" "^7.16.5" - "@babel/helper-optimise-call-expression" "^7.16.0" - "@babel/traverse" "^7.16.5" - "@babel/types" "^7.16.0" + "@babel/helper-environment-visitor" "^7.16.7" + "@babel/helper-member-expression-to-functions" "^7.16.7" + "@babel/helper-optimise-call-expression" "^7.16.7" + "@babel/traverse" "^7.16.7" + "@babel/types" "^7.16.7" -"@babel/helper-simple-access@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.16.0.tgz#21d6a27620e383e37534cf6c10bba019a6f90517" - integrity sha512-o1rjBT/gppAqKsYfUdfHq5Rk03lMQrkPHG1OWzHWpLgVXRH4HnMM9Et9CVdIqwkCQlobnGHEJMsgWP/jE1zUiw== +"@babel/helper-simple-access@^7.17.7": + version "7.17.7" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.17.7.tgz#aaa473de92b7987c6dfa7ce9a7d9674724823367" + integrity sha512-txyMCGroZ96i+Pxr3Je3lzEJjqwaRC9buMUgtomcrLe5Nd0+fk1h0LLA+ixUF5OW7AhHuQ7Es1WcQJZmZsz2XA== dependencies: - "@babel/types" "^7.16.0" + "@babel/types" "^7.17.0" "@babel/helper-skip-transparent-expression-wrappers@^7.16.0": version "7.16.0" @@ -370,144 +377,144 @@ dependencies: "@babel/types" "^7.16.0" -"@babel/helper-split-export-declaration@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.0.tgz#29672f43663e936df370aaeb22beddb3baec7438" - integrity sha512-0YMMRpuDFNGTHNRiiqJX19GjNXA4H0E8jZ2ibccfSxaCogbm3am5WN/2nQNj0YnQwGWM1J06GOcQ2qnh3+0paw== +"@babel/helper-split-export-declaration@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz#0b648c0c42da9d3920d85ad585f2778620b8726b" + integrity sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw== dependencies: - "@babel/types" "^7.16.0" + "@babel/types" "^7.16.7" -"@babel/helper-validator-identifier@^7.15.7": - version "7.15.7" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz#220df993bfe904a4a6b02ab4f3385a5ebf6e2389" - integrity sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w== +"@babel/helper-validator-identifier@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad" + integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw== -"@babel/helper-validator-option@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz#6e72a1fff18d5dfcb878e1e62f1a021c4b72d5a3" - integrity sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow== +"@babel/helper-validator-option@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz#b203ce62ce5fe153899b617c08957de860de4d23" + integrity sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ== -"@babel/helper-wrap-function@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.16.5.tgz#0158fca6f6d0889c3fee8a6ed6e5e07b9b54e41f" - integrity sha512-2J2pmLBqUqVdJw78U0KPNdeE2qeuIyKoG4mKV7wAq3mc4jJG282UgjZw4ZYDnqiWQuS3Y3IYdF/AQ6CpyBV3VA== +"@babel/helper-wrap-function@^7.16.8": + version "7.16.8" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.16.8.tgz#58afda087c4cd235de92f7ceedebca2c41274200" + integrity sha512-8RpyRVIAW1RcDDGTA+GpPAwV22wXCfKOoM9bet6TLkGIFTkRQSkH1nMQ5Yet4MpoXe1ZwHPVtNasc2w0uZMqnw== dependencies: - "@babel/helper-function-name" "^7.16.0" - "@babel/template" "^7.16.0" - "@babel/traverse" "^7.16.5" - "@babel/types" "^7.16.0" + "@babel/helper-function-name" "^7.16.7" + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.16.8" + "@babel/types" "^7.16.8" -"@babel/helpers@^7.12.5", "@babel/helpers@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.16.5.tgz#29a052d4b827846dd76ece16f565b9634c554ebd" - integrity sha512-TLgi6Lh71vvMZGEkFuIxzaPsyeYCHQ5jJOOX1f0xXn0uciFuE8cEk0wyBquMcCxBXZ5BJhE2aUB7pnWTD150Tw== +"@babel/helpers@^7.12.5", "@babel/helpers@^7.17.8": + version "7.17.8" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.17.8.tgz#288450be8c6ac7e4e44df37bcc53d345e07bc106" + integrity sha512-QcL86FGxpfSJwGtAvv4iG93UL6bmqBdmoVY0CMCU2g+oD2ezQse3PT5Pa+jiD6LJndBQi0EDlpzOWNlLuhz5gw== dependencies: - "@babel/template" "^7.16.0" - "@babel/traverse" "^7.16.5" - "@babel/types" "^7.16.0" + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.17.3" + "@babel/types" "^7.17.0" -"@babel/highlight@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.16.0.tgz#6ceb32b2ca4b8f5f361fb7fd821e3fddf4a1725a" - integrity sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g== +"@babel/highlight@^7.16.7": + version "7.16.10" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.16.10.tgz#744f2eb81579d6eea753c227b0f570ad785aba88" + integrity sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw== dependencies: - "@babel/helper-validator-identifier" "^7.15.7" + "@babel/helper-validator-identifier" "^7.16.7" chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.12.7", "@babel/parser@^7.16.0", "@babel/parser@^7.16.4", "@babel/parser@^7.16.5": - version "7.16.6" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.16.6.tgz#8f194828193e8fa79166f34a4b4e52f3e769a314" - integrity sha512-Gr86ujcNuPDnNOY8mi383Hvi8IYrJVJYuf3XcuBM/Dgd+bINn/7tHqsj+tKkoreMbmGsFLsltI/JJd8fOFWGDQ== +"@babel/parser@^7.12.7", "@babel/parser@^7.16.7", "@babel/parser@^7.17.3", "@babel/parser@^7.17.8": + version "7.17.8" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.8.tgz#2817fb9d885dd8132ea0f8eb615a6388cca1c240" + integrity sha512-BoHhDJrJXqcg+ZL16Xv39H9n+AqJ4pcDrQBGZN+wHxIysrLZ3/ECwCBUch/1zUNhnsXULcONU3Ei5Hmkfk6kiQ== -"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.16.2": - version "7.16.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.16.2.tgz#2977fca9b212db153c195674e57cfab807733183" - integrity sha512-h37CvpLSf8gb2lIJ2CgC3t+EjFbi0t8qS7LCS1xcJIlEXE4czlofwaW7W1HA8zpgOCzI9C1nmoqNR1zWkk0pQg== +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.16.7.tgz#4eda6d6c2a0aa79c70fa7b6da67763dfe2141050" + integrity sha512-anv/DObl7waiGEnC24O9zqL0pSuI9hljihqiDuFHC8d7/bjr/4RLGPWuc8rYOff/QPzbEPSkzG8wGG9aDuhHRg== dependencies: - "@babel/helper-plugin-utils" "^7.14.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.16.0.tgz#358972eaab006f5eb0826183b0c93cbcaf13e1e2" - integrity sha512-4tcFwwicpWTrpl9qjf7UsoosaArgImF85AxqCRZlgc3IQDvkUHjJpruXAL58Wmj+T6fypWTC/BakfEkwIL/pwA== +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.16.7.tgz#cc001234dfc139ac45f6bcf801866198c8c72ff9" + integrity sha512-di8vUHRdf+4aJ7ltXhaDbPoszdkh59AQtJM5soLsuHpQJdFQZOA4uGj0V2u/CZ8bJ/u8ULDL5yq6FO/bCXnKHw== dependencies: - "@babel/helper-plugin-utils" "^7.14.5" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/helper-skip-transparent-expression-wrappers" "^7.16.0" - "@babel/plugin-proposal-optional-chaining" "^7.16.0" + "@babel/plugin-proposal-optional-chaining" "^7.16.7" -"@babel/plugin-proposal-async-generator-functions@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.16.5.tgz#fd3bd7e0d98404a3d4cbca15a72d533f8c9a2f67" - integrity sha512-C/FX+3HNLV6sz7AqbTQqEo1L9/kfrKjxcVtgyBCmvIgOjvuBVUWooDoi7trsLxOzCEo5FccjRvKHkfDsJFZlfA== +"@babel/plugin-proposal-async-generator-functions@^7.16.8": + version "7.16.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.16.8.tgz#3bdd1ebbe620804ea9416706cd67d60787504bc8" + integrity sha512-71YHIvMuiuqWJQkebWJtdhQTfd4Q4mF76q2IX37uZPkG9+olBxsX+rH1vkhFto4UeJZ9dPY2s+mDvhDm1u2BGQ== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" - "@babel/helper-remap-async-to-generator" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-remap-async-to-generator" "^7.16.8" "@babel/plugin-syntax-async-generators" "^7.8.4" -"@babel/plugin-proposal-class-properties@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.5.tgz#3269f44b89122110f6339806e05d43d84106468a" - integrity sha512-pJD3HjgRv83s5dv1sTnDbZOaTjghKEz8KUn1Kbh2eAIRhGuyQ1XSeI4xVXU3UlIEVA3DAyIdxqT1eRn7Wcn55A== +"@babel/plugin-proposal-class-properties@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.7.tgz#925cad7b3b1a2fcea7e59ecc8eb5954f961f91b0" + integrity sha512-IobU0Xme31ewjYOShSIqd/ZGM/r/cuOz2z0MDbNrhF5FW+ZVgi0f2lyeoj9KFPDOAqsYxmLWZte1WOwlvY9aww== dependencies: - "@babel/helper-create-class-features-plugin" "^7.16.5" - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-create-class-features-plugin" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-proposal-class-static-block@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.16.5.tgz#df58ab015a7d3b0963aafc8f20792dcd834952a9" - integrity sha512-EEFzuLZcm/rNJ8Q5krK+FRKdVkd6FjfzT9tuSZql9sQn64K0hHA2KLJ0DqVot9/iV6+SsuadC5yI39zWnm+nmQ== +"@babel/plugin-proposal-class-static-block@^7.16.7": + version "7.17.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.17.6.tgz#164e8fd25f0d80fa48c5a4d1438a6629325ad83c" + integrity sha512-X/tididvL2zbs7jZCeeRJ8167U/+Ac135AM6jCAx6gYXDUviZV5Ku9UDvWS2NCuWlFjIRXklYhwo6HhAC7ETnA== dependencies: - "@babel/helper-create-class-features-plugin" "^7.16.5" - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-create-class-features-plugin" "^7.17.6" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-class-static-block" "^7.14.5" -"@babel/plugin-proposal-dynamic-import@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.5.tgz#2e0d19d5702db4dcb9bc846200ca02f2e9d60e9e" - integrity sha512-P05/SJZTTvHz79LNYTF8ff5xXge0kk5sIIWAypcWgX4BTRUgyHc8wRxJ/Hk+mU0KXldgOOslKaeqnhthcDJCJQ== +"@babel/plugin-proposal-dynamic-import@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.7.tgz#c19c897eaa46b27634a00fee9fb7d829158704b2" + integrity sha512-I8SW9Ho3/8DRSdmDdH3gORdyUuYnk1m4cMxUAdu5oy4n3OfN8flDEH+d60iG7dUfi0KkYwSvoalHzzdRzpWHTg== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-dynamic-import" "^7.8.3" -"@babel/plugin-proposal-export-namespace-from@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.16.5.tgz#3b4dd28378d1da2fea33e97b9f25d1c2f5bf1ac9" - integrity sha512-i+sltzEShH1vsVydvNaTRsgvq2vZsfyrd7K7vPLUU/KgS0D5yZMe6uipM0+izminnkKrEfdUnz7CxMRb6oHZWw== +"@babel/plugin-proposal-export-namespace-from@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.16.7.tgz#09de09df18445a5786a305681423ae63507a6163" + integrity sha512-ZxdtqDXLRGBL64ocZcs7ovt71L3jhC1RGSyR996svrCi3PYqHNkb3SwPJCs8RIzD86s+WPpt2S73+EHCGO+NUA== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-export-namespace-from" "^7.8.3" -"@babel/plugin-proposal-json-strings@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.16.5.tgz#1e726930fca139caab6b084d232a9270d9d16f9c" - integrity sha512-QQJueTFa0y9E4qHANqIvMsuxM/qcLQmKttBACtPCQzGUEizsXDACGonlPiSwynHfOa3vNw0FPMVvQzbuXwh4SQ== +"@babel/plugin-proposal-json-strings@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.16.7.tgz#9732cb1d17d9a2626a08c5be25186c195b6fa6e8" + integrity sha512-lNZ3EEggsGY78JavgbHsK9u5P3pQaW7k4axlgFLYkMd7UBsiNahCITShLjNQschPyjtO6dADrL24757IdhBrsQ== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-json-strings" "^7.8.3" -"@babel/plugin-proposal-logical-assignment-operators@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.16.5.tgz#df1f2e4b5a0ec07abf061d2c18e53abc237d3ef5" - integrity sha512-xqibl7ISO2vjuQM+MzR3rkd0zfNWltk7n9QhaD8ghMmMceVguYrNDt7MikRyj4J4v3QehpnrU8RYLnC7z/gZLA== +"@babel/plugin-proposal-logical-assignment-operators@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.16.7.tgz#be23c0ba74deec1922e639832904be0bea73cdea" + integrity sha512-K3XzyZJGQCr00+EtYtrDjmwX7o7PLK6U9bi1nCwkQioRFVUv6dJoxbQjtWVtP+bCPy82bONBKG8NPyQ4+i6yjg== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" -"@babel/plugin-proposal-nullish-coalescing-operator@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.5.tgz#652555bfeeeee2d2104058c6225dc6f75e2d0f07" - integrity sha512-YwMsTp/oOviSBhrjwi0vzCUycseCYwoXnLiXIL3YNjHSMBHicGTz7GjVU/IGgz4DtOEXBdCNG72pvCX22ehfqg== +"@babel/plugin-proposal-nullish-coalescing-operator@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.7.tgz#141fc20b6857e59459d430c850a0011e36561d99" + integrity sha512-aUOrYU3EVtjf62jQrCj63pYZ7k6vns2h/DQvHPWGmsJRYzWXZ6/AsfgpiRy6XiuIDADhJzP2Q9MwSMKauBQ+UQ== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" -"@babel/plugin-proposal-numeric-separator@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.5.tgz#edcb6379b6cf4570be64c45965d8da7a2debf039" - integrity sha512-DvB9l/TcsCRvsIV9v4jxR/jVP45cslTVC0PMVHvaJhhNuhn2Y1SOhCSFlPK777qLB5wb8rVDaNoqMTyOqtY5Iw== +"@babel/plugin-proposal-numeric-separator@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.7.tgz#d6b69f4af63fb38b6ca2558442a7fb191236eba9" + integrity sha512-vQgPMknOIgiuVqbokToyXbkY/OmmjAzr/0lhSIbG/KmnzXPGwW/AdhdKpi+O4X/VkWiWjnkKOBiqJrTaC98VKw== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-numeric-separator" "^7.10.4" "@babel/plugin-proposal-object-rest-spread@7.12.1": @@ -519,59 +526,59 @@ "@babel/plugin-syntax-object-rest-spread" "^7.8.0" "@babel/plugin-transform-parameters" "^7.12.1" -"@babel/plugin-proposal-object-rest-spread@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.16.5.tgz#f30f80dacf7bc1404bf67f99c8d9c01665e830ad" - integrity sha512-UEd6KpChoyPhCoE840KRHOlGhEZFutdPDMGj+0I56yuTTOaT51GzmnEl/0uT41fB/vD2nT+Pci2KjezyE3HmUw== +"@babel/plugin-proposal-object-rest-spread@^7.16.7": + version "7.17.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.17.3.tgz#d9eb649a54628a51701aef7e0ea3d17e2b9dd390" + integrity sha512-yuL5iQA/TbZn+RGAfxQXfi7CNLmKi1f8zInn4IgobuCWcAb7i+zj4TYzQ9l8cEzVyJ89PDGuqxK1xZpUDISesw== dependencies: - "@babel/compat-data" "^7.16.4" - "@babel/helper-compilation-targets" "^7.16.3" - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/compat-data" "^7.17.0" + "@babel/helper-compilation-targets" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.16.5" + "@babel/plugin-transform-parameters" "^7.16.7" -"@babel/plugin-proposal-optional-catch-binding@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.5.tgz#1a5405765cf589a11a33a1fd75b2baef7d48b74e" - integrity sha512-ihCMxY1Iljmx4bWy/PIMJGXN4NS4oUj1MKynwO07kiKms23pNvIn1DMB92DNB2R0EA882sw0VXIelYGdtF7xEQ== +"@babel/plugin-proposal-optional-catch-binding@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.7.tgz#c623a430674ffc4ab732fd0a0ae7722b67cb74cf" + integrity sha512-eMOH/L4OvWSZAE1VkHbr1vckLG1WUcHGJSLqqQwl2GaUqG6QjddvrOaTUMNYiv77H5IKPMZ9U9P7EaHwvAShfA== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" -"@babel/plugin-proposal-optional-chaining@^7.16.0", "@babel/plugin-proposal-optional-chaining@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.5.tgz#a5fa61056194d5059366c0009cb9a9e66ed75c1f" - integrity sha512-kzdHgnaXRonttiTfKYnSVafbWngPPr2qKw9BWYBESl91W54e+9R5pP70LtWxV56g0f05f/SQrwHYkfvbwcdQ/A== +"@babel/plugin-proposal-optional-chaining@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.7.tgz#7cd629564724816c0e8a969535551f943c64c39a" + integrity sha512-eC3xy+ZrUcBtP7x+sq62Q/HYd674pPTb/77XZMb5wbDPGWIdUbSr4Agr052+zaUPSb+gGRnjxXfKFvx5iMJ+DA== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/helper-skip-transparent-expression-wrappers" "^7.16.0" "@babel/plugin-syntax-optional-chaining" "^7.8.3" -"@babel/plugin-proposal-private-methods@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.16.5.tgz#2086f7d78c1b0c712d49b5c3fbc2d1ca21a7ee12" - integrity sha512-+yFMO4BGT3sgzXo+lrq7orX5mAZt57DwUK6seqII6AcJnJOIhBJ8pzKH47/ql/d426uQ7YhN8DpUFirQzqYSUA== +"@babel/plugin-proposal-private-methods@^7.16.11": + version "7.16.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.16.11.tgz#e8df108288555ff259f4527dbe84813aac3a1c50" + integrity sha512-F/2uAkPlXDr8+BHpZvo19w3hLFKge+k75XUprE6jaqKxjGkSYcK+4c+bup5PdW/7W/Rpjwql7FTVEDW+fRAQsw== dependencies: - "@babel/helper-create-class-features-plugin" "^7.16.5" - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-create-class-features-plugin" "^7.16.10" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-proposal-private-property-in-object@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.16.5.tgz#a42d4b56005db3d405b12841309dbca647e7a21b" - integrity sha512-+YGh5Wbw0NH3y/E5YMu6ci5qTDmAEVNoZ3I54aB6nVEOZ5BQ7QJlwKq5pYVucQilMByGn/bvX0af+uNaPRCabA== +"@babel/plugin-proposal-private-property-in-object@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.16.7.tgz#b0b8cef543c2c3d57e59e2c611994861d46a3fce" + integrity sha512-rMQkjcOFbm+ufe3bTZLyOfsOUOxyvLXZJCTARhJr+8UMSoZmqTe1K1BgkFcrW37rAchWg57yI69ORxiWvUINuQ== dependencies: - "@babel/helper-annotate-as-pure" "^7.16.0" - "@babel/helper-create-class-features-plugin" "^7.16.5" - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-annotate-as-pure" "^7.16.7" + "@babel/helper-create-class-features-plugin" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-private-property-in-object" "^7.14.5" -"@babel/plugin-proposal-unicode-property-regex@^7.16.5", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.16.5.tgz#35fe753afa7c572f322bd068ff3377bde0f37080" - integrity sha512-s5sKtlKQyFSatt781HQwv1hoM5BQ9qRH30r+dK56OLDsHmV74mzwJNX7R1yMuE7VZKG5O6q/gmOGSAO6ikTudg== +"@babel/plugin-proposal-unicode-property-regex@^7.16.7", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.16.7.tgz#635d18eb10c6214210ffc5ff4932552de08188a2" + integrity sha512-QRK0YI/40VLhNVGIjRNAAQkEHws0cswSdFFjpFyt943YmJIU1da9uW63Iu6NFV6CxTZW5eTDCrwZUstBWgp/Rg== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.16.0" - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-create-regexp-features-plugin" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" @@ -622,12 +629,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-syntax-jsx@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.5.tgz#bf255d252f78bc8b77a17cadc37d1aa5b8ed4394" - integrity sha512-42OGssv9NPk4QHKVgIHlzeLgPOW5rGgfV5jzG90AhcXXIv6hu/eqj63w4VgvRxdvZY3AlYeDgPiSJ3BqAd1Y6Q== +"@babel/plugin-syntax-jsx@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.7.tgz#50b6571d13f764266a113d77c82b4a6508bbe665" + integrity sha512-Esxmk7YjA8QysKeT3VhTXvF6y77f/a91SIs4pWb4H2eWGQkCKFgQaG6hdoEVZtGsrAcb2K5BW66XsOErD4WU3Q== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-logical-assignment-operators@^7.10.4": version "7.10.4" @@ -685,349 +692,350 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-syntax-typescript@^7.16.0": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.5.tgz#f47a33e4eee38554f00fb6b2f894fa1f5649b0b3" - integrity sha512-/d4//lZ1Vqb4mZ5xTep3dDK888j7BGM/iKqBmndBaoYAFPlPKrGU608VVBz5JeyAb6YQDjRu1UKqj86UhwWVgw== +"@babel/plugin-syntax-typescript@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.7.tgz#39c9b55ee153151990fb038651d58d3fd03f98f8" + integrity sha512-YhUIJHHGkqPgEcMYkPCKTyGUdoGKWtopIycQyjJH8OjvRgOYsXsaKehLVPScKJWAULPxMa4N1vCe6szREFlZ7A== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-arrow-functions@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.16.5.tgz#04c18944dd55397b521d9d7511e791acea7acf2d" - integrity sha512-8bTHiiZyMOyfZFULjsCnYOWG059FVMes0iljEHSfARhNgFfpsqE92OrCffv3veSw9rwMkYcFe9bj0ZoXU2IGtQ== +"@babel/plugin-transform-arrow-functions@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.16.7.tgz#44125e653d94b98db76369de9c396dc14bef4154" + integrity sha512-9ffkFFMbvzTvv+7dTp/66xvZAWASuPD5Tl9LK3Z9vhOmANo6j94rik+5YMBt4CwHVMWLWpMsriIc2zsa3WW3xQ== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-async-to-generator@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.16.5.tgz#89c9b501e65bb14c4579a6ce9563f859de9b34e4" - integrity sha512-TMXgfioJnkXU+XRoj7P2ED7rUm5jbnDWwlCuFVTpQboMfbSya5WrmubNBAMlk7KXvywpo8rd8WuYZkis1o2H8w== +"@babel/plugin-transform-async-to-generator@^7.16.8": + version "7.16.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.16.8.tgz#b83dff4b970cf41f1b819f8b49cc0cfbaa53a808" + integrity sha512-MtmUmTJQHCnyJVrScNzNlofQJ3dLFuobYn3mwOTKHnSCMtbNsqvF71GQmJfFjdrXSsAA7iysFmYWw4bXZ20hOg== dependencies: - "@babel/helper-module-imports" "^7.16.0" - "@babel/helper-plugin-utils" "^7.16.5" - "@babel/helper-remap-async-to-generator" "^7.16.5" + "@babel/helper-module-imports" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-remap-async-to-generator" "^7.16.8" -"@babel/plugin-transform-block-scoped-functions@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.5.tgz#af087494e1c387574260b7ee9b58cdb5a4e9b0b0" - integrity sha512-BxmIyKLjUGksJ99+hJyL/HIxLIGnLKtw772zYDER7UuycDZ+Xvzs98ZQw6NGgM2ss4/hlFAaGiZmMNKvValEjw== +"@babel/plugin-transform-block-scoped-functions@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.7.tgz#4d0d57d9632ef6062cdf354bb717102ee042a620" + integrity sha512-JUuzlzmF40Z9cXyytcbZEZKckgrQzChbQJw/5PuEHYeqzCsvebDx0K0jWnIIVcmmDOAVctCgnYs0pMcrYj2zJg== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-block-scoping@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.16.5.tgz#b91f254fe53e210eabe4dd0c40f71c0ed253c5e7" - integrity sha512-JxjSPNZSiOtmxjX7PBRBeRJTUKTyJ607YUYeT0QJCNdsedOe+/rXITjP08eG8xUpsLfPirgzdCFN+h0w6RI+pQ== +"@babel/plugin-transform-block-scoping@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.16.7.tgz#f50664ab99ddeaee5bc681b8f3a6ea9d72ab4f87" + integrity sha512-ObZev2nxVAYA4bhyusELdo9hb3H+A56bxH3FZMbEImZFiEDYVHXQSJ1hQKFlDnlt8G9bBrCZ5ZpURZUrV4G5qQ== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-classes@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.16.5.tgz#6acf2ec7adb50fb2f3194dcd2909dbd056dcf216" - integrity sha512-DzJ1vYf/7TaCYy57J3SJ9rV+JEuvmlnvvyvYKFbk5u46oQbBvuB9/0w+YsVsxkOv8zVWKpDmUoj4T5ILHoXevA== +"@babel/plugin-transform-classes@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.16.7.tgz#8f4b9562850cd973de3b498f1218796eb181ce00" + integrity sha512-WY7og38SFAGYRe64BrjKf8OrE6ulEHtr5jEYaZMwox9KebgqPi67Zqz8K53EKk1fFEJgm96r32rkKZ3qA2nCWQ== dependencies: - "@babel/helper-annotate-as-pure" "^7.16.0" - "@babel/helper-environment-visitor" "^7.16.5" - "@babel/helper-function-name" "^7.16.0" - "@babel/helper-optimise-call-expression" "^7.16.0" - "@babel/helper-plugin-utils" "^7.16.5" - "@babel/helper-replace-supers" "^7.16.5" - "@babel/helper-split-export-declaration" "^7.16.0" + "@babel/helper-annotate-as-pure" "^7.16.7" + "@babel/helper-environment-visitor" "^7.16.7" + "@babel/helper-function-name" "^7.16.7" + "@babel/helper-optimise-call-expression" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-replace-supers" "^7.16.7" + "@babel/helper-split-export-declaration" "^7.16.7" globals "^11.1.0" -"@babel/plugin-transform-computed-properties@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.16.5.tgz#2af91ebf0cceccfcc701281ada7cfba40a9b322a" - integrity sha512-n1+O7xtU5lSLraRzX88CNcpl7vtGdPakKzww74bVwpAIRgz9JVLJJpOLb0uYqcOaXVM0TL6X0RVeIJGD2CnCkg== +"@babel/plugin-transform-computed-properties@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.16.7.tgz#66dee12e46f61d2aae7a73710f591eb3df616470" + integrity sha512-gN72G9bcmenVILj//sv1zLNaPyYcOzUho2lIJBMh/iakJ9ygCo/hEF9cpGb61SCMEDxbbyBoVQxrt+bWKu5KGw== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-destructuring@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.16.5.tgz#89ebc87499ac4a81b897af53bb5d3eed261bd568" - integrity sha512-GuRVAsjq+c9YPK6NeTkRLWyQskDC099XkBSVO+6QzbnOnH2d/4mBVXYStaPrZD3dFRfg00I6BFJ9Atsjfs8mlg== +"@babel/plugin-transform-destructuring@^7.16.7": + version "7.17.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.17.7.tgz#49dc2675a7afa9a5e4c6bdee636061136c3408d1" + integrity sha512-XVh0r5yq9sLR4vZ6eVZe8FKfIcSgaTBxVBRSYokRj2qksf6QerYnTxz9/GTuKTH/n/HwLP7t6gtlybHetJ/6hQ== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-dotall-regex@^7.16.5", "@babel/plugin-transform-dotall-regex@^7.4.4": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.16.5.tgz#b40739c00b6686820653536d6d143e311de67936" - integrity sha512-iQiEMt8Q4/5aRGHpGVK2Zc7a6mx7qEAO7qehgSug3SDImnuMzgmm/wtJALXaz25zUj1PmnNHtShjFgk4PDx4nw== +"@babel/plugin-transform-dotall-regex@^7.16.7", "@babel/plugin-transform-dotall-regex@^7.4.4": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.16.7.tgz#6b2d67686fab15fb6a7fd4bd895d5982cfc81241" + integrity sha512-Lyttaao2SjZF6Pf4vk1dVKv8YypMpomAbygW+mU5cYP3S5cWTfCJjG8xV6CFdzGFlfWK81IjL9viiTvpb6G7gQ== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.16.0" - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-create-regexp-features-plugin" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-duplicate-keys@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.16.5.tgz#2450f2742325412b746d7d005227f5e8973b512a" - integrity sha512-81tijpDg2a6I1Yhj4aWY1l3O1J4Cg/Pd7LfvuaH2VVInAkXtzibz9+zSPdUM1WvuUi128ksstAP0hM5w48vQgg== +"@babel/plugin-transform-duplicate-keys@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.16.7.tgz#2207e9ca8f82a0d36a5a67b6536e7ef8b08823c9" + integrity sha512-03DvpbRfvWIXyK0/6QiR1KMTWeT6OcQ7tbhjrXyFS02kjuX/mu5Bvnh5SDSWHxyawit2g5aWhKwI86EE7GUnTw== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-exponentiation-operator@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.5.tgz#36e261fa1ab643cfaf30eeab38e00ed1a76081e2" - integrity sha512-12rba2HwemQPa7BLIKCzm1pT2/RuQHtSFHdNl41cFiC6oi4tcrp7gjB07pxQvFpcADojQywSjblQth6gJyE6CA== +"@babel/plugin-transform-exponentiation-operator@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.7.tgz#efa9862ef97e9e9e5f653f6ddc7b665e8536fe9b" + integrity sha512-8UYLSlyLgRixQvlYH3J2ekXFHDFLQutdy7FfFAMm3CPZ6q9wHCwnUyiXpQCe3gVVnQlHc5nsuiEVziteRNTXEA== dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor" "^7.16.5" - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-for-of@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.16.5.tgz#9b544059c6ca11d565457c0ff1f08e13ce225261" - integrity sha512-+DpCAJFPAvViR17PIMi9x2AE34dll5wNlXO43wagAX2YcRGgEVHCNFC4azG85b4YyyFarvkc/iD5NPrz4Oneqw== +"@babel/plugin-transform-for-of@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.16.7.tgz#649d639d4617dff502a9a158c479b3b556728d8c" + integrity sha512-/QZm9W92Ptpw7sjI9Nx1mbcsWz33+l8kuMIQnDwgQBG5s3fAfQvkRjQ7NqXhtNcKOnPkdICmUHyCaWW06HCsqg== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-function-name@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.16.5.tgz#6896ebb6a5538a75d6a4086a277752f655a7bd15" - integrity sha512-Fuec/KPSpVLbGo6z1RPw4EE1X+z9gZk1uQmnYy7v4xr4TO9p41v1AoUuXEtyqAI7H+xNJYSICzRqZBhDEkd3kQ== +"@babel/plugin-transform-function-name@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.16.7.tgz#5ab34375c64d61d083d7d2f05c38d90b97ec65cf" + integrity sha512-SU/C68YVwTRxqWj5kgsbKINakGag0KTgq9f2iZEXdStoAbOzLHEBRYzImmA6yFo8YZhJVflvXmIHUO7GWHmxxA== dependencies: - "@babel/helper-function-name" "^7.16.0" - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-compilation-targets" "^7.16.7" + "@babel/helper-function-name" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-literals@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.16.5.tgz#af392b90e3edb2bd6dc316844cbfd6b9e009d320" - integrity sha512-B1j9C/IfvshnPcklsc93AVLTrNVa69iSqztylZH6qnmiAsDDOmmjEYqOm3Ts2lGSgTSywnBNiqC949VdD0/gfw== +"@babel/plugin-transform-literals@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.16.7.tgz#254c9618c5ff749e87cb0c0cef1a0a050c0bdab1" + integrity sha512-6tH8RTpTWI0s2sV6uq3e/C9wPo4PTqqZps4uF0kzQ9/xPLFQtipynvmT1g/dOfEJ+0EQsHhkQ/zyRId8J2b8zQ== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-member-expression-literals@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.16.5.tgz#4bd6ecdc11932361631097b779ca5c7570146dd5" - integrity sha512-d57i3vPHWgIde/9Y8W/xSFUndhvhZN5Wu2TjRrN1MVz5KzdUihKnfDVlfP1U7mS5DNj/WHHhaE4/tTi4hIyHwQ== +"@babel/plugin-transform-member-expression-literals@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.16.7.tgz#6e5dcf906ef8a098e630149d14c867dd28f92384" + integrity sha512-mBruRMbktKQwbxaJof32LT9KLy2f3gH+27a5XSuXo6h7R3vqltl0PgZ80C8ZMKw98Bf8bqt6BEVi3svOh2PzMw== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-modules-amd@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.16.5.tgz#92c0a3e83f642cb7e75fada9ab497c12c2616527" - integrity sha512-oHI15S/hdJuSCfnwIz+4lm6wu/wBn7oJ8+QrkzPPwSFGXk8kgdI/AIKcbR/XnD1nQVMg/i6eNaXpszbGuwYDRQ== +"@babel/plugin-transform-modules-amd@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.16.7.tgz#b28d323016a7daaae8609781d1f8c9da42b13186" + integrity sha512-KaaEtgBL7FKYwjJ/teH63oAmE3lP34N3kshz8mm4VMAw7U3PxjVwwUmxEFksbgsNUaO3wId9R2AVQYSEGRa2+g== dependencies: - "@babel/helper-module-transforms" "^7.16.5" - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-module-transforms" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-commonjs@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.16.5.tgz#4ee03b089536f076b2773196529d27c32b9d7bde" - integrity sha512-ABhUkxvoQyqhCWyb8xXtfwqNMJD7tx+irIRnUh6lmyFud7Jln1WzONXKlax1fg/ey178EXbs4bSGNd6PngO+SQ== +"@babel/plugin-transform-modules-commonjs@^7.16.8": + version "7.17.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.17.7.tgz#d86b217c8e45bb5f2dbc11eefc8eab62cf980d19" + integrity sha512-ITPmR2V7MqioMJyrxUo2onHNC3e+MvfFiFIR0RP21d3PtlVb6sfzoxNKiphSZUOM9hEIdzCcZe83ieX3yoqjUA== dependencies: - "@babel/helper-module-transforms" "^7.16.5" - "@babel/helper-plugin-utils" "^7.16.5" - "@babel/helper-simple-access" "^7.16.0" + "@babel/helper-module-transforms" "^7.17.7" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-simple-access" "^7.17.7" babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-systemjs@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.16.5.tgz#07078ba2e3cc94fbdd06836e355c246e98ad006b" - integrity sha512-53gmLdScNN28XpjEVIm7LbWnD/b/TpbwKbLk6KV4KqC9WyU6rq1jnNmVG6UgAdQZVVGZVoik3DqHNxk4/EvrjA== +"@babel/plugin-transform-modules-systemjs@^7.16.7": + version "7.17.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.17.8.tgz#81fd834024fae14ea78fbe34168b042f38703859" + integrity sha512-39reIkMTUVagzgA5x88zDYXPCMT6lcaRKs1+S9K6NKBPErbgO/w/kP8GlNQTC87b412ZTlmNgr3k2JrWgHH+Bw== dependencies: - "@babel/helper-hoist-variables" "^7.16.0" - "@babel/helper-module-transforms" "^7.16.5" - "@babel/helper-plugin-utils" "^7.16.5" - "@babel/helper-validator-identifier" "^7.15.7" + "@babel/helper-hoist-variables" "^7.16.7" + "@babel/helper-module-transforms" "^7.17.7" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-validator-identifier" "^7.16.7" babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-umd@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.16.5.tgz#caa9c53d636fb4e3c99fd35a4c9ba5e5cd7e002e" - integrity sha512-qTFnpxHMoenNHkS3VoWRdwrcJ3FhX567GvDA3hRZKF0Dj8Fmg0UzySZp3AP2mShl/bzcywb/UWAMQIjA1bhXvw== +"@babel/plugin-transform-modules-umd@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.16.7.tgz#23dad479fa585283dbd22215bff12719171e7618" + integrity sha512-EMh7uolsC8O4xhudF2F6wedbSHm1HHZ0C6aJ7K67zcDNidMzVcxWdGr+htW9n21klm+bOn+Rx4CBsAntZd3rEQ== dependencies: - "@babel/helper-module-transforms" "^7.16.5" - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-module-transforms" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-named-capturing-groups-regex@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.16.5.tgz#4afd8cdee377ce3568f4e8a9ee67539b69886a3c" - integrity sha512-/wqGDgvFUeKELW6ex6QB7dLVRkd5ehjw34tpXu1nhKC0sFfmaLabIswnpf8JgDyV2NeDmZiwoOb0rAmxciNfjA== +"@babel/plugin-transform-named-capturing-groups-regex@^7.16.8": + version "7.16.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.16.8.tgz#7f860e0e40d844a02c9dcf9d84965e7dfd666252" + integrity sha512-j3Jw+n5PvpmhRR+mrgIh04puSANCk/T/UA3m3P1MjJkhlK906+ApHhDIqBQDdOgL/r1UYpz4GNclTXxyZrYGSw== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.16.0" + "@babel/helper-create-regexp-features-plugin" "^7.16.7" -"@babel/plugin-transform-new-target@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.16.5.tgz#759ea9d6fbbc20796056a5d89d13977626384416" - integrity sha512-ZaIrnXF08ZC8jnKR4/5g7YakGVL6go6V9ql6Jl3ecO8PQaQqFE74CuM384kezju7Z9nGCCA20BqZaR1tJ/WvHg== +"@babel/plugin-transform-new-target@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.16.7.tgz#9967d89a5c243818e0800fdad89db22c5f514244" + integrity sha512-xiLDzWNMfKoGOpc6t3U+etCE2yRnn3SM09BXqWPIZOBpL2gvVrBWUKnsJx0K/ADi5F5YC5f8APFfWrz25TdlGg== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-object-super@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.5.tgz#8ccd9a1bcd3e7732ff8aa1702d067d8cd70ce380" - integrity sha512-tded+yZEXuxt9Jdtkc1RraW1zMF/GalVxaVVxh41IYwirdRgyAxxxCKZ9XB7LxZqmsjfjALxupNE1MIz9KH+Zg== +"@babel/plugin-transform-object-super@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.7.tgz#ac359cf8d32cf4354d27a46867999490b6c32a94" + integrity sha512-14J1feiQVWaGvRxj2WjyMuXS2jsBkgB3MdSN5HuC2G5nRspa5RK9COcs82Pwy5BuGcjb+fYaUj94mYcOj7rCvw== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" - "@babel/helper-replace-supers" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-replace-supers" "^7.16.7" -"@babel/plugin-transform-parameters@^7.12.1", "@babel/plugin-transform-parameters@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.16.5.tgz#4fc74b18a89638bd90aeec44a11793ecbe031dde" - integrity sha512-B3O6AL5oPop1jAVg8CV+haeUte9oFuY85zu0jwnRNZZi3tVAbJriu5tag/oaO2kGaQM/7q7aGPBlTI5/sr9enA== +"@babel/plugin-transform-parameters@^7.12.1", "@babel/plugin-transform-parameters@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.16.7.tgz#a1721f55b99b736511cb7e0152f61f17688f331f" + integrity sha512-AT3MufQ7zZEhU2hwOA11axBnExW0Lszu4RL/tAlUJBuNoRak+wehQW8h6KcXOcgjY42fHtDxswuMhMjFEuv/aw== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-property-literals@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.5.tgz#58f1465a7202a2bb2e6b003905212dd7a79abe3f" - integrity sha512-+IRcVW71VdF9pEH/2R/Apab4a19LVvdVsr/gEeotH00vSDVlKD+XgfSIw+cgGWsjDB/ziqGv/pGoQZBIiQVXHg== +"@babel/plugin-transform-property-literals@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.7.tgz#2dadac85155436f22c696c4827730e0fe1057a55" + integrity sha512-z4FGr9NMGdoIl1RqavCqGG+ZuYjfZ/hkCIeuH6Do7tXmSm0ls11nYVSJqFEUOSJbDab5wC6lRE/w6YjVcr6Hqw== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-transform-react-constant-elements@^7.14.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.16.5.tgz#4b01ea6b14bd4e55ca92bb2d6c28dd9957118924" - integrity sha512-fdc1s5npHMZ9A+w9bYbrZu4499WyYPVaTTsRO8bU0GJcMuK4ejIX4lyjnpvi+YGLK/EhFQxWszqylO0vaMciFw== + version "7.17.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.17.6.tgz#6cc273c2f612a6a50cb657e63ee1303e5e68d10a" + integrity sha512-OBv9VkyyKtsHZiHLoSfCn+h6yU7YKX8nrs32xUmOa1SRSk+t03FosB6fBZ0Yz4BpD1WV7l73Nsad+2Tz7APpqw== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-react-display-name@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.16.5.tgz#d5e910327d7931fb9f8f9b6c6999473ceae5a286" - integrity sha512-dHYCOnzSsXFz8UcdNQIHGvg94qPL/teF7CCiCEMRxmA1G2p5Mq4JnKVowCDxYfiQ9D7RstaAp9kwaSI+sXbnhw== +"@babel/plugin-transform-react-display-name@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.16.7.tgz#7b6d40d232f4c0f550ea348593db3b21e2404340" + integrity sha512-qgIg8BcZgd0G/Cz916D5+9kqX0c7nPZyXaP8R2tLNN5tkyIZdG5fEwBrxwplzSnjC1jvQmyMNVwUCZPcbGY7Pg== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-react-jsx-development@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.16.5.tgz#87da9204c275ffb57f45d192a1120cf104bc1e86" - integrity sha512-uQSLacMZSGLCxOw20dzo1dmLlKkd+DsayoV54q3MHXhbqgPzoiGerZQgNPl/Ro8/OcXV2ugfnkx+rxdS0sN5Uw== +"@babel/plugin-transform-react-jsx-development@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.16.7.tgz#43a00724a3ed2557ed3f276a01a929e6686ac7b8" + integrity sha512-RMvQWvpla+xy6MlBpPlrKZCMRs2AGiHOGHY3xRwl0pEeim348dDyxeH4xBsMPbIMhujeq7ihE702eM2Ew0Wo+A== dependencies: - "@babel/plugin-transform-react-jsx" "^7.16.5" + "@babel/plugin-transform-react-jsx" "^7.16.7" -"@babel/plugin-transform-react-jsx@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.16.5.tgz#5298aedc5f81e02b1cb702e597e8d6a346675765" - integrity sha512-+arLIz1d7kmwX0fKxTxbnoeG85ONSnLpvdODa4P3pc1sS7CV1hfmtYWufkW/oYsPnkDrEeQFxhUWcFnrXW7jQQ== +"@babel/plugin-transform-react-jsx@^7.16.7": + version "7.17.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.17.3.tgz#eac1565da176ccb1a715dae0b4609858808008c1" + integrity sha512-9tjBm4O07f7mzKSIlEmPdiE6ub7kfIe6Cd+w+oQebpATfTQMAgW+YOuWxogbKVTulA+MEO7byMeIUtQ1z+z+ZQ== dependencies: - "@babel/helper-annotate-as-pure" "^7.16.0" - "@babel/helper-module-imports" "^7.16.0" - "@babel/helper-plugin-utils" "^7.16.5" - "@babel/plugin-syntax-jsx" "^7.16.5" - "@babel/types" "^7.16.0" + "@babel/helper-annotate-as-pure" "^7.16.7" + "@babel/helper-module-imports" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/plugin-syntax-jsx" "^7.16.7" + "@babel/types" "^7.17.0" -"@babel/plugin-transform-react-pure-annotations@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.16.5.tgz#6535d0fe67c7a3a26c5105f92c8cbcbe844cd94b" - integrity sha512-0nYU30hCxnCVCbRjSy9ahlhWZ2Sn6khbY4FqR91W+2RbSqkWEbVu2gXh45EqNy4Bq7sRU+H4i0/6YKwOSzh16A== +"@babel/plugin-transform-react-pure-annotations@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.16.7.tgz#232bfd2f12eb551d6d7d01d13fe3f86b45eb9c67" + integrity sha512-hs71ToC97k3QWxswh2ElzMFABXHvGiJ01IB1TbYQDGeWRKWz/MPUTh5jGExdHvosYKpnJW5Pm3S4+TA3FyX+GA== dependencies: - "@babel/helper-annotate-as-pure" "^7.16.0" - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-annotate-as-pure" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-regenerator@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.16.5.tgz#704cc6d8dd3dd4758267621ab7b36375238cef13" - integrity sha512-2z+it2eVWU8TtQQRauvGUqZwLy4+7rTfo6wO4npr+fvvN1SW30ZF3O/ZRCNmTuu4F5MIP8OJhXAhRV5QMJOuYg== +"@babel/plugin-transform-regenerator@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.16.7.tgz#9e7576dc476cb89ccc5096fff7af659243b4adeb" + integrity sha512-mF7jOgGYCkSJagJ6XCujSQg+6xC1M77/03K2oBmVJWoFGNUtnVJO4WHKJk3dnPC8HCcj4xBQP1Egm8DWh3Pb3Q== dependencies: regenerator-transform "^0.14.2" -"@babel/plugin-transform-reserved-words@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.16.5.tgz#db95e98799675e193dc2b47d3e72a7c0651d0c30" - integrity sha512-aIB16u8lNcf7drkhXJRoggOxSTUAuihTSTfAcpynowGJOZiGf+Yvi7RuTwFzVYSYPmWyARsPqUGoZWWWxLiknw== +"@babel/plugin-transform-reserved-words@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.16.7.tgz#1d798e078f7c5958eec952059c460b220a63f586" + integrity sha512-KQzzDnZ9hWQBjwi5lpY5v9shmm6IVG0U9pB18zvMu2i4H90xpT4gmqwPYsn8rObiadYe2M0gmgsiOIF5A/2rtg== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-runtime@^7.16.0": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.16.5.tgz#0cc3f01d69f299d5a42cd9ec43b92ea7a777b8db" - integrity sha512-gxpfS8XQWDbQ8oP5NcmpXxtEgCJkbO+W9VhZlOhr0xPyVaRjAQPOv7ZDj9fg0d5s9+NiVvMCE6gbkEkcsxwGRw== +"@babel/plugin-transform-runtime@^7.17.0": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.17.0.tgz#0a2e08b5e2b2d95c4b1d3b3371a2180617455b70" + integrity sha512-fr7zPWnKXNc1xoHfrIU9mN/4XKX4VLZ45Q+oMhfsYIaHvg7mHgmhfOy/ckRWqDK7XF3QDigRpkh5DKq6+clE8A== dependencies: - "@babel/helper-module-imports" "^7.16.0" - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-module-imports" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" babel-plugin-polyfill-corejs2 "^0.3.0" - babel-plugin-polyfill-corejs3 "^0.4.0" + babel-plugin-polyfill-corejs3 "^0.5.0" babel-plugin-polyfill-regenerator "^0.3.0" semver "^6.3.0" -"@babel/plugin-transform-shorthand-properties@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.5.tgz#ccb60b1a23b799f5b9a14d97c5bc81025ffd96d7" - integrity sha512-ZbuWVcY+MAXJuuW7qDoCwoxDUNClfZxoo7/4swVbOW1s/qYLOMHlm9YRWMsxMFuLs44eXsv4op1vAaBaBaDMVg== +"@babel/plugin-transform-shorthand-properties@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.7.tgz#e8549ae4afcf8382f711794c0c7b6b934c5fbd2a" + integrity sha512-hah2+FEnoRoATdIb05IOXf+4GzXYTq75TVhIn1PewihbpyrNWUt2JbudKQOETWw6QpLe+AIUpJ5MVLYTQbeeUg== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-spread@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.16.5.tgz#912b06cff482c233025d3e69cf56d3e8fa166c29" - integrity sha512-5d6l/cnG7Lw4tGHEoga4xSkYp1euP7LAtrah1h1PgJ3JY7yNsjybsxQAnVK4JbtReZ/8z6ASVmd3QhYYKLaKZw== +"@babel/plugin-transform-spread@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.16.7.tgz#a303e2122f9f12e0105daeedd0f30fb197d8ff44" + integrity sha512-+pjJpgAngb53L0iaA5gU/1MLXJIfXcYepLgXB3esVRf4fqmj8f2cxM3/FKaHsZms08hFQJkFccEWuIpm429TXg== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/helper-skip-transparent-expression-wrappers" "^7.16.0" -"@babel/plugin-transform-sticky-regex@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.5.tgz#593579bb2b5a8adfbe02cb43823275d9098f75f9" - integrity sha512-usYsuO1ID2LXxzuUxifgWtJemP7wL2uZtyrTVM4PKqsmJycdS4U4mGovL5xXkfUheds10Dd2PjoQLXw6zCsCbg== - dependencies: - "@babel/helper-plugin-utils" "^7.16.5" - -"@babel/plugin-transform-template-literals@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.16.5.tgz#343651385fd9923f5aa2275ca352c5d9183e1773" - integrity sha512-gnyKy9RyFhkovex4BjKWL3BVYzUDG6zC0gba7VMLbQoDuqMfJ1SDXs8k/XK41Mmt1Hyp4qNAvGFb9hKzdCqBRQ== - dependencies: - "@babel/helper-plugin-utils" "^7.16.5" - -"@babel/plugin-transform-typeof-symbol@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.16.5.tgz#a1d1bf2c71573fe30965d0e4cd6a3291202e20ed" - integrity sha512-ldxCkW180qbrvyCVDzAUZqB0TAeF8W/vGJoRcaf75awm6By+PxfJKvuqVAnq8N9wz5Xa6mSpM19OfVKKVmGHSQ== - dependencies: - "@babel/helper-plugin-utils" "^7.16.5" - -"@babel/plugin-transform-typescript@^7.16.1": - version "7.16.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.16.1.tgz#cc0670b2822b0338355bc1b3d2246a42b8166409" - integrity sha512-NO4XoryBng06jjw/qWEU2LhcLJr1tWkhpMam/H4eas/CDKMX/b2/Ylb6EI256Y7+FVPCawwSM1rrJNOpDiz+Lg== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.16.0" - "@babel/helper-plugin-utils" "^7.14.5" - "@babel/plugin-syntax-typescript" "^7.16.0" - -"@babel/plugin-transform-unicode-escapes@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.16.5.tgz#80507c225af49b4f4ee647e2a0ce53d2eeff9e85" - integrity sha512-shiCBHTIIChGLdyojsKQjoAyB8MBwat25lKM7MJjbe1hE0bgIppD+LX9afr41lLHOhqceqeWl4FkLp+Bgn9o1Q== - dependencies: - "@babel/helper-plugin-utils" "^7.16.5" - -"@babel/plugin-transform-unicode-regex@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.5.tgz#ac84d6a1def947d71ffb832426aa53b83d7ed49e" - integrity sha512-GTJ4IW012tiPEMMubd7sD07iU9O/LOo8Q/oU4xNhcaq0Xn8+6TcUQaHtC8YxySo1T+ErQ8RaWogIEeFhKGNPzw== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.16.0" - "@babel/helper-plugin-utils" "^7.16.5" - -"@babel/preset-env@^7.15.6", "@babel/preset-env@^7.16.4": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.16.5.tgz#2e94d922f4a890979af04ffeb6a6b4e44ba90847" - integrity sha512-MiJJW5pwsktG61NDxpZ4oJ1CKxM1ncam9bzRtx9g40/WkLRkxFP6mhpkYV0/DxcciqoiHicx291+eUQrXb/SfQ== - dependencies: - "@babel/compat-data" "^7.16.4" - "@babel/helper-compilation-targets" "^7.16.3" - "@babel/helper-plugin-utils" "^7.16.5" - "@babel/helper-validator-option" "^7.14.5" - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.16.2" - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.16.0" - "@babel/plugin-proposal-async-generator-functions" "^7.16.5" - "@babel/plugin-proposal-class-properties" "^7.16.5" - "@babel/plugin-proposal-class-static-block" "^7.16.5" - "@babel/plugin-proposal-dynamic-import" "^7.16.5" - "@babel/plugin-proposal-export-namespace-from" "^7.16.5" - "@babel/plugin-proposal-json-strings" "^7.16.5" - "@babel/plugin-proposal-logical-assignment-operators" "^7.16.5" - "@babel/plugin-proposal-nullish-coalescing-operator" "^7.16.5" - "@babel/plugin-proposal-numeric-separator" "^7.16.5" - "@babel/plugin-proposal-object-rest-spread" "^7.16.5" - "@babel/plugin-proposal-optional-catch-binding" "^7.16.5" - "@babel/plugin-proposal-optional-chaining" "^7.16.5" - "@babel/plugin-proposal-private-methods" "^7.16.5" - "@babel/plugin-proposal-private-property-in-object" "^7.16.5" - "@babel/plugin-proposal-unicode-property-regex" "^7.16.5" +"@babel/plugin-transform-sticky-regex@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.7.tgz#c84741d4f4a38072b9a1e2e3fd56d359552e8660" + integrity sha512-NJa0Bd/87QV5NZZzTuZG5BPJjLYadeSZ9fO6oOUoL4iQx+9EEuw/eEM92SrsT19Yc2jgB1u1hsjqDtH02c3Drw== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-template-literals@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.16.7.tgz#f3d1c45d28967c8e80f53666fc9c3e50618217ab" + integrity sha512-VwbkDDUeenlIjmfNeDX/V0aWrQH2QiVyJtwymVQSzItFDTpxfyJh3EVaQiS0rIN/CqbLGr0VcGmuwyTdZtdIsA== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-typeof-symbol@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.16.7.tgz#9cdbe622582c21368bd482b660ba87d5545d4f7e" + integrity sha512-p2rOixCKRJzpg9JB4gjnG4gjWkWa89ZoYUnl9snJ1cWIcTH/hvxZqfO+WjG6T8DRBpctEol5jw1O5rA8gkCokQ== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-typescript@^7.16.7": + version "7.16.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.16.8.tgz#591ce9b6b83504903fa9dd3652c357c2ba7a1ee0" + integrity sha512-bHdQ9k7YpBDO2d0NVfkj51DpQcvwIzIusJ7mEUaMlbZq3Kt/U47j24inXZHQ5MDiYpCs+oZiwnXyKedE8+q7AQ== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/plugin-syntax-typescript" "^7.16.7" + +"@babel/plugin-transform-unicode-escapes@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.16.7.tgz#da8717de7b3287a2c6d659750c964f302b31ece3" + integrity sha512-TAV5IGahIz3yZ9/Hfv35TV2xEm+kaBDaZQCn2S/hG9/CZ0DktxJv9eKfPc7yYCvOYR4JGx1h8C+jcSOvgaaI/Q== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-unicode-regex@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.7.tgz#0f7aa4a501198976e25e82702574c34cfebe9ef2" + integrity sha512-oC5tYYKw56HO75KZVLQ+R/Nl3Hro9kf8iG0hXoaHP7tjAyCpvqBiSNe6vGrZni1Z6MggmUOC6A7VP7AVmw225Q== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/preset-env@^7.15.6", "@babel/preset-env@^7.16.11": + version "7.16.11" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.16.11.tgz#5dd88fd885fae36f88fd7c8342475c9f0abe2982" + integrity sha512-qcmWG8R7ZW6WBRPZK//y+E3Cli151B20W1Rv7ln27vuPaXU/8TKms6jFdiJtF7UDTxcrb7mZd88tAeK9LjdT8g== + dependencies: + "@babel/compat-data" "^7.16.8" + "@babel/helper-compilation-targets" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-validator-option" "^7.16.7" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.16.7" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.16.7" + "@babel/plugin-proposal-async-generator-functions" "^7.16.8" + "@babel/plugin-proposal-class-properties" "^7.16.7" + "@babel/plugin-proposal-class-static-block" "^7.16.7" + "@babel/plugin-proposal-dynamic-import" "^7.16.7" + "@babel/plugin-proposal-export-namespace-from" "^7.16.7" + "@babel/plugin-proposal-json-strings" "^7.16.7" + "@babel/plugin-proposal-logical-assignment-operators" "^7.16.7" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.16.7" + "@babel/plugin-proposal-numeric-separator" "^7.16.7" + "@babel/plugin-proposal-object-rest-spread" "^7.16.7" + "@babel/plugin-proposal-optional-catch-binding" "^7.16.7" + "@babel/plugin-proposal-optional-chaining" "^7.16.7" + "@babel/plugin-proposal-private-methods" "^7.16.11" + "@babel/plugin-proposal-private-property-in-object" "^7.16.7" + "@babel/plugin-proposal-unicode-property-regex" "^7.16.7" "@babel/plugin-syntax-async-generators" "^7.8.4" "@babel/plugin-syntax-class-properties" "^7.12.13" "@babel/plugin-syntax-class-static-block" "^7.14.5" @@ -1042,44 +1050,44 @@ "@babel/plugin-syntax-optional-chaining" "^7.8.3" "@babel/plugin-syntax-private-property-in-object" "^7.14.5" "@babel/plugin-syntax-top-level-await" "^7.14.5" - "@babel/plugin-transform-arrow-functions" "^7.16.5" - "@babel/plugin-transform-async-to-generator" "^7.16.5" - "@babel/plugin-transform-block-scoped-functions" "^7.16.5" - "@babel/plugin-transform-block-scoping" "^7.16.5" - "@babel/plugin-transform-classes" "^7.16.5" - "@babel/plugin-transform-computed-properties" "^7.16.5" - "@babel/plugin-transform-destructuring" "^7.16.5" - "@babel/plugin-transform-dotall-regex" "^7.16.5" - "@babel/plugin-transform-duplicate-keys" "^7.16.5" - "@babel/plugin-transform-exponentiation-operator" "^7.16.5" - "@babel/plugin-transform-for-of" "^7.16.5" - "@babel/plugin-transform-function-name" "^7.16.5" - "@babel/plugin-transform-literals" "^7.16.5" - "@babel/plugin-transform-member-expression-literals" "^7.16.5" - "@babel/plugin-transform-modules-amd" "^7.16.5" - "@babel/plugin-transform-modules-commonjs" "^7.16.5" - "@babel/plugin-transform-modules-systemjs" "^7.16.5" - "@babel/plugin-transform-modules-umd" "^7.16.5" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.16.5" - "@babel/plugin-transform-new-target" "^7.16.5" - "@babel/plugin-transform-object-super" "^7.16.5" - "@babel/plugin-transform-parameters" "^7.16.5" - "@babel/plugin-transform-property-literals" "^7.16.5" - "@babel/plugin-transform-regenerator" "^7.16.5" - "@babel/plugin-transform-reserved-words" "^7.16.5" - "@babel/plugin-transform-shorthand-properties" "^7.16.5" - "@babel/plugin-transform-spread" "^7.16.5" - "@babel/plugin-transform-sticky-regex" "^7.16.5" - "@babel/plugin-transform-template-literals" "^7.16.5" - "@babel/plugin-transform-typeof-symbol" "^7.16.5" - "@babel/plugin-transform-unicode-escapes" "^7.16.5" - "@babel/plugin-transform-unicode-regex" "^7.16.5" + "@babel/plugin-transform-arrow-functions" "^7.16.7" + "@babel/plugin-transform-async-to-generator" "^7.16.8" + "@babel/plugin-transform-block-scoped-functions" "^7.16.7" + "@babel/plugin-transform-block-scoping" "^7.16.7" + "@babel/plugin-transform-classes" "^7.16.7" + "@babel/plugin-transform-computed-properties" "^7.16.7" + "@babel/plugin-transform-destructuring" "^7.16.7" + "@babel/plugin-transform-dotall-regex" "^7.16.7" + "@babel/plugin-transform-duplicate-keys" "^7.16.7" + "@babel/plugin-transform-exponentiation-operator" "^7.16.7" + "@babel/plugin-transform-for-of" "^7.16.7" + "@babel/plugin-transform-function-name" "^7.16.7" + "@babel/plugin-transform-literals" "^7.16.7" + "@babel/plugin-transform-member-expression-literals" "^7.16.7" + "@babel/plugin-transform-modules-amd" "^7.16.7" + "@babel/plugin-transform-modules-commonjs" "^7.16.8" + "@babel/plugin-transform-modules-systemjs" "^7.16.7" + "@babel/plugin-transform-modules-umd" "^7.16.7" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.16.8" + "@babel/plugin-transform-new-target" "^7.16.7" + "@babel/plugin-transform-object-super" "^7.16.7" + "@babel/plugin-transform-parameters" "^7.16.7" + "@babel/plugin-transform-property-literals" "^7.16.7" + "@babel/plugin-transform-regenerator" "^7.16.7" + "@babel/plugin-transform-reserved-words" "^7.16.7" + "@babel/plugin-transform-shorthand-properties" "^7.16.7" + "@babel/plugin-transform-spread" "^7.16.7" + "@babel/plugin-transform-sticky-regex" "^7.16.7" + "@babel/plugin-transform-template-literals" "^7.16.7" + "@babel/plugin-transform-typeof-symbol" "^7.16.7" + "@babel/plugin-transform-unicode-escapes" "^7.16.7" + "@babel/plugin-transform-unicode-regex" "^7.16.7" "@babel/preset-modules" "^0.1.5" - "@babel/types" "^7.16.0" + "@babel/types" "^7.16.8" babel-plugin-polyfill-corejs2 "^0.3.0" - babel-plugin-polyfill-corejs3 "^0.4.0" + babel-plugin-polyfill-corejs3 "^0.5.0" babel-plugin-polyfill-regenerator "^0.3.0" - core-js-compat "^3.19.1" + core-js-compat "^3.20.2" semver "^6.3.0" "@babel/preset-modules@^0.1.5": @@ -1093,340 +1101,332 @@ "@babel/types" "^7.4.4" esutils "^2.0.2" -"@babel/preset-react@^7.14.5", "@babel/preset-react@^7.16.0": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.16.5.tgz#09df3b7a6522cb3e6682dc89b4dfebb97d22031b" - integrity sha512-3kzUOQeaxY/2vhPDS7CX/KGEGu/1bOYGvdRDJ2U5yjEz5o5jmIeTPLoiQBPGjfhPascLuW5OlMiPzwOOuB6txg== - dependencies: - "@babel/helper-plugin-utils" "^7.16.5" - "@babel/helper-validator-option" "^7.14.5" - "@babel/plugin-transform-react-display-name" "^7.16.5" - "@babel/plugin-transform-react-jsx" "^7.16.5" - "@babel/plugin-transform-react-jsx-development" "^7.16.5" - "@babel/plugin-transform-react-pure-annotations" "^7.16.5" - -"@babel/preset-typescript@^7.15.0", "@babel/preset-typescript@^7.16.0": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.16.5.tgz#b86a5b0ae739ba741347d2f58c52f52e63cf1ba1" - integrity sha512-lmAWRoJ9iOSvs3DqOndQpj8XqXkzaiQs50VG/zESiI9D3eoZhGriU675xNCr0UwvsuXrhMAGvyk1w+EVWF3u8Q== - dependencies: - "@babel/helper-plugin-utils" "^7.16.5" - "@babel/helper-validator-option" "^7.14.5" - "@babel/plugin-transform-typescript" "^7.16.1" - -"@babel/runtime-corejs3@^7.16.3": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.16.5.tgz#9057d879720c136193f0440bc400088212a74894" - integrity sha512-F1pMwvTiUNSAM8mc45kccMQxj31x3y3P+tA/X8hKNWp3/hUsxdGxZ3D3H8JIkxtfA8qGkaBTKvcmvStaYseAFw== - dependencies: - core-js-pure "^3.19.0" +"@babel/preset-react@^7.14.5", "@babel/preset-react@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.16.7.tgz#4c18150491edc69c183ff818f9f2aecbe5d93852" + integrity sha512-fWpyI8UM/HE6DfPBzD8LnhQ/OcH8AgTaqcqP2nGOXEUV+VKBR5JRN9hCk9ai+zQQ57vtm9oWeXguBCPNUjytgA== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-validator-option" "^7.16.7" + "@babel/plugin-transform-react-display-name" "^7.16.7" + "@babel/plugin-transform-react-jsx" "^7.16.7" + "@babel/plugin-transform-react-jsx-development" "^7.16.7" + "@babel/plugin-transform-react-pure-annotations" "^7.16.7" + +"@babel/preset-typescript@^7.15.0", "@babel/preset-typescript@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.16.7.tgz#ab114d68bb2020afc069cd51b37ff98a046a70b9" + integrity sha512-WbVEmgXdIyvzB77AQjGBEyYPZx+8tTsO50XtfozQrkW8QB2rLJpH2lgx0TRw5EJrBxOZQ+wCcyPVQvS8tjEHpQ== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-validator-option" "^7.16.7" + "@babel/plugin-transform-typescript" "^7.16.7" + +"@babel/runtime-corejs3@^7.17.8": + version "7.17.8" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.17.8.tgz#d7dd49fb812f29c61c59126da3792d8740d4e284" + integrity sha512-ZbYSUvoSF6dXZmMl/CYTMOvzIFnbGfv4W3SEHYgMvNsFTeLaF2gkGAF4K2ddmtSK4Emej+0aYcnSC6N5dPCXUQ== + dependencies: + core-js-pure "^3.20.2" regenerator-runtime "^0.13.4" -"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.16.3", "@babel/runtime@^7.8.4": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.5.tgz#7f3e34bf8bdbbadf03fbb7b1ea0d929569c9487a" - integrity sha512-TXWihFIS3Pyv5hzR7j6ihmeLkZfrXGxAr5UfSl8CHf+6q/wpiYDkUau0czckpYG8QmnCIuPpdLtuA9VmuGGyMA== +"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.17.8", "@babel/runtime@^7.8.4": + version "7.17.8" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.8.tgz#3e56e4aff81befa55ac3ac6a0967349fd1c5bca2" + integrity sha512-dQpEpK0O9o6lj6oPu0gRDbbnk+4LeHlNcBpspf6Olzt3GIX4P1lWF1gS+pHLDFlaJvbR6q7jCfQ08zA4QJBnmA== dependencies: regenerator-runtime "^0.13.4" -"@babel/template@^7.12.7", "@babel/template@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.0.tgz#d16a35ebf4cd74e202083356fab21dd89363ddd6" - integrity sha512-MnZdpFD/ZdYhXwiunMqqgyZyucaYsbL0IrjoGjaVhGilz+x8YB++kRfygSOIj1yOtWKPlx7NBp+9I1RQSgsd5A== - dependencies: - "@babel/code-frame" "^7.16.0" - "@babel/parser" "^7.16.0" - "@babel/types" "^7.16.0" - -"@babel/traverse@^7.12.9", "@babel/traverse@^7.13.0", "@babel/traverse@^7.16.3", "@babel/traverse@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.16.5.tgz#d7d400a8229c714a59b87624fc67b0f1fbd4b2b3" - integrity sha512-FOCODAzqUMROikDYLYxl4nmwiLlu85rNqBML/A5hKRVXG2LV8d0iMqgPzdYTcIpjZEBB7D6UDU9vxRZiriASdQ== - dependencies: - "@babel/code-frame" "^7.16.0" - "@babel/generator" "^7.16.5" - "@babel/helper-environment-visitor" "^7.16.5" - "@babel/helper-function-name" "^7.16.0" - "@babel/helper-hoist-variables" "^7.16.0" - "@babel/helper-split-export-declaration" "^7.16.0" - "@babel/parser" "^7.16.5" - "@babel/types" "^7.16.0" +"@babel/template@^7.12.7", "@babel/template@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155" + integrity sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w== + dependencies: + "@babel/code-frame" "^7.16.7" + "@babel/parser" "^7.16.7" + "@babel/types" "^7.16.7" + +"@babel/traverse@^7.12.9", "@babel/traverse@^7.13.0", "@babel/traverse@^7.16.7", "@babel/traverse@^7.16.8", "@babel/traverse@^7.17.3": + version "7.17.3" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.17.3.tgz#0ae0f15b27d9a92ba1f2263358ea7c4e7db47b57" + integrity sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw== + dependencies: + "@babel/code-frame" "^7.16.7" + "@babel/generator" "^7.17.3" + "@babel/helper-environment-visitor" "^7.16.7" + "@babel/helper-function-name" "^7.16.7" + "@babel/helper-hoist-variables" "^7.16.7" + "@babel/helper-split-export-declaration" "^7.16.7" + "@babel/parser" "^7.17.3" + "@babel/types" "^7.17.0" debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.12.7", "@babel/types@^7.15.6", "@babel/types@^7.16.0", "@babel/types@^7.4.4": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.16.0.tgz#db3b313804f96aadd0b776c4823e127ad67289ba" - integrity sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg== +"@babel/types@^7.12.7", "@babel/types@^7.15.6", "@babel/types@^7.16.0", "@babel/types@^7.16.7", "@babel/types@^7.16.8", "@babel/types@^7.17.0", "@babel/types@^7.4.4": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.17.0.tgz#a826e368bccb6b3d84acd76acad5c0d87342390b" + integrity sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw== dependencies: - "@babel/helper-validator-identifier" "^7.15.7" + "@babel/helper-validator-identifier" "^7.16.7" to-fast-properties "^2.0.0" -"@docsearch/css@3.0.0-alpha.42": - version "3.0.0-alpha.42" - resolved "https://registry.yarnpkg.com/@docsearch/css/-/css-3.0.0-alpha.42.tgz#deb6049e999d6ca9451eba4793cb5b6da28c8773" - integrity sha512-AGwI2AXUacYhVOHmYnsXoYDJKO6Ued2W+QO80GERbMLhC7GH5tfvtW5REs/s7jSdcU3vzFoxT8iPDBCh/PkrlQ== +"@docsearch/css@3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@docsearch/css/-/css-3.0.0.tgz#fe57b474802ffd706d3246eab25d52fac8aa3698" + integrity sha512-1kkV7tkAsiuEd0shunYRByKJe3xQDG2q7wYg24SOw1nV9/2lwEd4WrUYRJC/ukGTl2/kHeFxsaUvtiOy0y6fFA== -"@docsearch/react@^3.0.0-alpha.39": - version "3.0.0-alpha.42" - resolved "https://registry.yarnpkg.com/@docsearch/react/-/react-3.0.0-alpha.42.tgz#1d22a2b05779f24d090ff8d7ff2699e4d50dff5c" - integrity sha512-1aOslZJDxwUUcm2QRNmlEePUgL8P5fOAeFdOLDMctHQkV2iTja9/rKVbkP8FZbIUnZxuuCCn8ErLrjD/oXWOag== +"@docsearch/react@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@docsearch/react/-/react-3.0.0.tgz#d02ebdc67573412185a6a4df13bc254c7c0da491" + integrity sha512-yhMacqS6TVQYoBh/o603zszIb5Bl8MIXuOc6Vy617I74pirisDzzcNh0NEaYQt50fVVR3khUbeEhUEWEWipESg== dependencies: - "@algolia/autocomplete-core" "1.5.0" - "@algolia/autocomplete-preset-algolia" "1.5.0" - "@docsearch/css" "3.0.0-alpha.42" + "@algolia/autocomplete-core" "1.5.2" + "@algolia/autocomplete-preset-algolia" "1.5.2" + "@docsearch/css" "3.0.0" algoliasearch "^4.0.0" -"@docusaurus/core@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/core/-/core-2.0.0-beta.14.tgz#9baf8fbfe29f444f985616013b5d80435ea5f29e" - integrity sha512-dW95WbD+WE+35Ee1RYIS1QDcBhvUxUWuDmrWr1X0uH5ZHIeOmOnsGVjjn4FA8VN2MkF0uuWknmRakQmJk0KMZw== +"@docusaurus/core@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/core/-/core-2.0.0-beta.18.tgz#44c6eefe29257462df630640a35f0c86bd80639a" + integrity sha512-puV7l+0/BPSi07Xmr8tVktfs1BzhC8P5pm6Bs2CfvysCJ4nefNCD1CosPc1PGBWy901KqeeEJ1aoGwj9tU3AUA== dependencies: - "@babel/core" "^7.16.0" - "@babel/generator" "^7.16.0" + "@babel/core" "^7.17.8" + "@babel/generator" "^7.17.7" "@babel/plugin-syntax-dynamic-import" "^7.8.3" - "@babel/plugin-transform-runtime" "^7.16.0" - "@babel/preset-env" "^7.16.4" - "@babel/preset-react" "^7.16.0" - "@babel/preset-typescript" "^7.16.0" - "@babel/runtime" "^7.16.3" - "@babel/runtime-corejs3" "^7.16.3" - "@babel/traverse" "^7.16.3" - "@docusaurus/cssnano-preset" "2.0.0-beta.14" - "@docusaurus/logger" "2.0.0-beta.14" - "@docusaurus/mdx-loader" "2.0.0-beta.14" + "@babel/plugin-transform-runtime" "^7.17.0" + "@babel/preset-env" "^7.16.11" + "@babel/preset-react" "^7.16.7" + "@babel/preset-typescript" "^7.16.7" + "@babel/runtime" "^7.17.8" + "@babel/runtime-corejs3" "^7.17.8" + "@babel/traverse" "^7.17.3" + "@docusaurus/cssnano-preset" "2.0.0-beta.18" + "@docusaurus/logger" "2.0.0-beta.18" + "@docusaurus/mdx-loader" "2.0.0-beta.18" "@docusaurus/react-loadable" "5.5.2" - "@docusaurus/utils" "2.0.0-beta.14" - "@docusaurus/utils-common" "2.0.0-beta.14" - "@docusaurus/utils-validation" "2.0.0-beta.14" - "@slorber/static-site-generator-webpack-plugin" "^4.0.0" - "@svgr/webpack" "^6.0.0" - autoprefixer "^10.3.5" - babel-loader "^8.2.2" + "@docusaurus/utils" "2.0.0-beta.18" + "@docusaurus/utils-common" "2.0.0-beta.18" + "@docusaurus/utils-validation" "2.0.0-beta.18" + "@slorber/static-site-generator-webpack-plugin" "^4.0.4" + "@svgr/webpack" "^6.2.1" + autoprefixer "^10.4.4" + babel-loader "^8.2.4" babel-plugin-dynamic-import-node "2.3.0" - boxen "^5.0.1" - chokidar "^3.5.2" - clean-css "^5.1.5" + boxen "^6.2.1" + chokidar "^3.5.3" + clean-css "^5.2.4" + cli-table3 "^0.6.1" + combine-promises "^1.1.0" commander "^5.1.0" - copy-webpack-plugin "^9.0.1" - core-js "^3.18.0" - css-loader "^5.1.1" - css-minimizer-webpack-plugin "^3.0.2" - cssnano "^5.0.8" + copy-webpack-plugin "^10.2.4" + core-js "^3.21.1" + css-loader "^6.7.1" + css-minimizer-webpack-plugin "^3.4.1" + cssnano "^5.1.5" del "^6.0.0" detect-port "^1.3.0" escape-html "^1.0.3" eta "^1.12.3" file-loader "^6.2.0" - fs-extra "^10.0.0" - globby "^11.0.2" - html-minifier-terser "^6.0.2" + fs-extra "^10.0.1" + html-minifier-terser "^6.1.0" html-tags "^3.1.0" - html-webpack-plugin "^5.4.0" + html-webpack-plugin "^5.5.0" import-fresh "^3.3.0" is-root "^2.1.0" leven "^3.1.0" - lodash "^4.17.20" - mini-css-extract-plugin "^1.6.0" + lodash "^4.17.21" + mini-css-extract-plugin "^2.6.0" nprogress "^0.2.0" - postcss "^8.3.7" - postcss-loader "^6.1.1" - prompts "^2.4.1" - react-dev-utils "12.0.0-next.47" - react-error-overlay "^6.0.9" - react-helmet "^6.1.0" + postcss "^8.4.12" + postcss-loader "^6.2.1" + prompts "^2.4.2" + react-dev-utils "^12.0.0" + react-helmet-async "^1.2.3" react-loadable "npm:@docusaurus/react-loadable@5.5.2" react-loadable-ssr-addon-v5-slorber "^1.0.1" react-router "^5.2.0" react-router-config "^5.1.1" react-router-dom "^5.2.0" remark-admonitions "^1.2.1" - resolve-pathname "^3.0.0" rtl-detect "^1.0.4" - semver "^7.3.4" + semver "^7.3.5" serve-handler "^6.1.3" - shelljs "^0.8.4" - strip-ansi "^6.0.0" - terser-webpack-plugin "^5.2.4" + shelljs "^0.8.5" + terser-webpack-plugin "^5.3.1" tslib "^2.3.1" update-notifier "^5.1.0" url-loader "^4.1.1" - wait-on "^6.0.0" - webpack "^5.61.0" - webpack-bundle-analyzer "^4.4.2" - webpack-dev-server "^4.5.0" + wait-on "^6.0.1" + webpack "^5.70.0" + webpack-bundle-analyzer "^4.5.0" + webpack-dev-server "^4.7.4" webpack-merge "^5.8.0" - webpackbar "^5.0.0-3" + webpackbar "^5.0.2" -"@docusaurus/cssnano-preset@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/cssnano-preset/-/cssnano-preset-2.0.0-beta.14.tgz#99bad713e3b58a89f63c25cec90b83437c3b3f2d" - integrity sha512-O5CebLXrytSQSpa0cgoMIUZ19gnLfCHhHPYqMfKxk0kvgR6g8b5AbsXxaMbgFNAqH690zPRsXmXb39BmXC7fMg== +"@docusaurus/cssnano-preset@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/cssnano-preset/-/cssnano-preset-2.0.0-beta.18.tgz#235ac9064fe8f8da618349ce5305be3ed3a44e29" + integrity sha512-VxhYmpyx16Wv00W9TUfLVv0NgEK/BwP7pOdWoaiELEIAMV7SO1+6iB8gsFUhtfKZ31I4uPVLMKrCyWWakoFeFA== dependencies: - cssnano-preset-advanced "^5.1.4" - postcss "^8.3.7" - postcss-sort-media-queries "^4.1.0" + cssnano-preset-advanced "^5.3.1" + postcss "^8.4.12" + postcss-sort-media-queries "^4.2.1" -"@docusaurus/logger@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/logger/-/logger-2.0.0-beta.14.tgz#d8c4e5f1c8b39149705587b98ca926549be51064" - integrity sha512-KNK8RgTGArXXlTUGhHUcYLJCI51gTMerSoebNXpTxAOBHFqjwJKv95LqVOy/uotoJZDUeEWR4vS/szGz4g7NaA== +"@docusaurus/logger@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/logger/-/logger-2.0.0-beta.18.tgz#12302f312a083eb018caa28505b63f5dd4ab6a91" + integrity sha512-frNe5vhH3mbPmH980Lvzaz45+n1PQl3TkslzWYXQeJOkFX17zUd3e3U7F9kR1+DocmAqHkgAoWuXVcvEoN29fg== dependencies: chalk "^4.1.2" tslib "^2.3.1" -"@docusaurus/mdx-loader@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/mdx-loader/-/mdx-loader-2.0.0-beta.14.tgz#f4750a02a8d178c843bc50f29f5a92d6cd0692cd" - integrity sha512-lusTVTHc4WbNQY8bDM9zPQWZBIo70SnEyWzCqtznxpV7L3kjSoWEpBCHaYWE/lY2VhvayRsZtrqLwNs3KQgqXw== - dependencies: - "@babel/parser" "^7.16.4" - "@babel/traverse" "^7.16.3" - "@docusaurus/logger" "2.0.0-beta.14" - "@docusaurus/utils" "2.0.0-beta.14" - "@mdx-js/mdx" "^1.6.21" - "@mdx-js/react" "^1.6.21" +"@docusaurus/mdx-loader@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/mdx-loader/-/mdx-loader-2.0.0-beta.18.tgz#4a9fc0607e0a210a7d7db3108415208dd36e33d3" + integrity sha512-pOmAQM4Y1jhuZTbEhjh4ilQa74Mh6Q0pMZn1xgIuyYDdqvIOrOlM/H0i34YBn3+WYuwsGim4/X0qynJMLDUA4A== + dependencies: + "@babel/parser" "^7.17.8" + "@babel/traverse" "^7.17.3" + "@docusaurus/logger" "2.0.0-beta.18" + "@docusaurus/utils" "2.0.0-beta.18" + "@mdx-js/mdx" "^1.6.22" escape-html "^1.0.3" file-loader "^6.2.0" - fs-extra "^10.0.0" - gray-matter "^4.0.3" + fs-extra "^10.0.1" + image-size "^1.0.1" mdast-util-to-string "^2.0.0" remark-emoji "^2.1.0" stringify-object "^3.3.0" tslib "^2.3.1" unist-util-visit "^2.0.2" url-loader "^4.1.1" - webpack "^5.61.0" + webpack "^5.70.0" -"@docusaurus/module-type-aliases@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/module-type-aliases/-/module-type-aliases-2.0.0-beta.14.tgz#8a11f9c4a408d8e8cc4cb59ba81a28ecc629256a" - integrity sha512-jlSwYoRVeNxvmjbVil35mRVSXZdOmEM95Sph7NxC6IE/ceT1a8s4tpzI2xUMsGgSfLBldqhkXe+WSOYqUL7x3w== +"@docusaurus/module-type-aliases@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/module-type-aliases/-/module-type-aliases-2.0.0-beta.18.tgz#001379229c58cbc3ed565e19437cbda86d5e8742" + integrity sha512-e6mples8FZRyT7QyqidGS6BgkROjM+gljJsdOqoctbtBp+SZ5YDjwRHOmoY7eqEfsQNOaFZvT2hK38ui87hCRA== dependencies: - "@docusaurus/types" "2.0.0-beta.14" + "@docusaurus/types" "2.0.0-beta.18" "@types/react" "*" - "@types/react-helmet" "*" "@types/react-router-config" "*" "@types/react-router-dom" "*" - -"@docusaurus/plugin-content-blog@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.0.0-beta.14.tgz#d390ab0ab3aceaeb0be7d49ccde0cf5a2e0b1566" - integrity sha512-MLDRNbQKxwBDsWADyBT/fES7F7xzEEGS8CsdTnm48l7yGSWL8GM3PT6YvjdqHxNxZw3RCRRPUAiJcjZwfOjd8w== - dependencies: - "@docusaurus/core" "2.0.0-beta.14" - "@docusaurus/logger" "2.0.0-beta.14" - "@docusaurus/mdx-loader" "2.0.0-beta.14" - "@docusaurus/utils" "2.0.0-beta.14" - "@docusaurus/utils-validation" "2.0.0-beta.14" - escape-string-regexp "^4.0.0" + react-helmet-async "*" + +"@docusaurus/plugin-content-blog@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.0.0-beta.18.tgz#95fe3dfc8bae9bf153c65a3a441234c450cbac0a" + integrity sha512-qzK83DgB+mxklk3PQC2nuTGPQD/8ogw1nXSmaQpyXAyhzcz4CXAZ9Swl/Ee9A/bvPwQGnSHSP3xqIYl8OkFtfw== + dependencies: + "@docusaurus/core" "2.0.0-beta.18" + "@docusaurus/logger" "2.0.0-beta.18" + "@docusaurus/mdx-loader" "2.0.0-beta.18" + "@docusaurus/utils" "2.0.0-beta.18" + "@docusaurus/utils-common" "2.0.0-beta.18" + "@docusaurus/utils-validation" "2.0.0-beta.18" + cheerio "^1.0.0-rc.10" feed "^4.2.2" - fs-extra "^10.0.0" - globby "^11.0.2" - js-yaml "^4.0.0" - loader-utils "^2.0.0" - lodash "^4.17.20" + fs-extra "^10.0.1" + lodash "^4.17.21" reading-time "^1.5.0" remark-admonitions "^1.2.1" tslib "^2.3.1" utility-types "^3.10.0" - webpack "^5.61.0" - -"@docusaurus/plugin-content-docs@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-docs/-/plugin-content-docs-2.0.0-beta.14.tgz#533ba6ba471b45ba7a7867207b251f281a6bed1e" - integrity sha512-pjAhfFevIkVl/t+6x9RVsE+6c+VN8Ru1uImTgXk5uVkp6yS1AxW7neEngsczZ1gSiENfTiYyhgWmTXK/uy03kw== - dependencies: - "@docusaurus/core" "2.0.0-beta.14" - "@docusaurus/logger" "2.0.0-beta.14" - "@docusaurus/mdx-loader" "2.0.0-beta.14" - "@docusaurus/utils" "2.0.0-beta.14" - "@docusaurus/utils-validation" "2.0.0-beta.14" + webpack "^5.70.0" + +"@docusaurus/plugin-content-docs@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-docs/-/plugin-content-docs-2.0.0-beta.18.tgz#fef52d945da2928e0f4f3f9a9384d9ee7f2d4288" + integrity sha512-z4LFGBJuzn4XQiUA7OEA2SZTqlp+IYVjd3NrCk/ZUfNi1tsTJS36ATkk9Y6d0Nsp7K2kRXqaXPsz4adDgeIU+Q== + dependencies: + "@docusaurus/core" "2.0.0-beta.18" + "@docusaurus/logger" "2.0.0-beta.18" + "@docusaurus/mdx-loader" "2.0.0-beta.18" + "@docusaurus/utils" "2.0.0-beta.18" + "@docusaurus/utils-validation" "2.0.0-beta.18" combine-promises "^1.1.0" - escape-string-regexp "^4.0.0" - fs-extra "^10.0.0" - globby "^11.0.2" - import-fresh "^3.2.2" - js-yaml "^4.0.0" - loader-utils "^2.0.0" - lodash "^4.17.20" + fs-extra "^10.0.1" + import-fresh "^3.3.0" + js-yaml "^4.1.0" + lodash "^4.17.21" remark-admonitions "^1.2.1" - shelljs "^0.8.4" tslib "^2.3.1" utility-types "^3.10.0" - webpack "^5.61.0" - -"@docusaurus/plugin-content-pages@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-pages/-/plugin-content-pages-2.0.0-beta.14.tgz#7f176d585994339cbe5c65332ed321eec82f53e3" - integrity sha512-gGcMPG4e+K57cbBPf7IfV5lFCBdraXcpJeDqXlD8ArTeZrAe8Lx3SGz2lco25DgdRGqjMivab3BoT6Hkmo7vVA== - dependencies: - "@docusaurus/core" "2.0.0-beta.14" - "@docusaurus/mdx-loader" "2.0.0-beta.14" - "@docusaurus/utils" "2.0.0-beta.14" - "@docusaurus/utils-validation" "2.0.0-beta.14" - globby "^11.0.2" + webpack "^5.70.0" + +"@docusaurus/plugin-content-pages@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-pages/-/plugin-content-pages-2.0.0-beta.18.tgz#0fef392be3fea3d85c212caf4eb744ead920c30b" + integrity sha512-CJ2Xeb9hQrMeF4DGywSDVX2TFKsQpc8ZA7czyeBAAbSFsoRyxXPYeSh8aWljqR4F1u/EKGSKy0Shk/D4wumaHw== + dependencies: + "@docusaurus/core" "2.0.0-beta.18" + "@docusaurus/mdx-loader" "2.0.0-beta.18" + "@docusaurus/utils" "2.0.0-beta.18" + "@docusaurus/utils-validation" "2.0.0-beta.18" + fs-extra "^10.0.1" remark-admonitions "^1.2.1" tslib "^2.3.1" - webpack "^5.61.0" + webpack "^5.70.0" -"@docusaurus/plugin-debug@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-debug/-/plugin-debug-2.0.0-beta.14.tgz#74d661a5cfefded7c9c281956ec2ec02260b576d" - integrity sha512-l0T26nZ9keyG2HrWwfwwHdqRzJg6cEJahyvKmnAOFfKieHPMxCJ9axBW+Ecy2PUMwJO7rILc6UObbhifNH7PnQ== +"@docusaurus/plugin-debug@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-debug/-/plugin-debug-2.0.0-beta.18.tgz#d4582532e59b538a23398f7c444b005367efa922" + integrity sha512-inLnLERgG7q0WlVmK6nYGHwVqREz13ivkynmNygEibJZToFRdgnIPW+OwD8QzgC5MpQTJw7+uYjcitpBumy1Gw== dependencies: - "@docusaurus/core" "2.0.0-beta.14" - "@docusaurus/utils" "2.0.0-beta.14" - fs-extra "^10.0.0" + "@docusaurus/core" "2.0.0-beta.18" + "@docusaurus/utils" "2.0.0-beta.18" + fs-extra "^10.0.1" react-json-view "^1.21.3" tslib "^2.3.1" -"@docusaurus/plugin-google-analytics@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-2.0.0-beta.14.tgz#16bfdd9245767e008be88cfeb47c7ceeef3884f6" - integrity sha512-fVtAwqK9iHjj32Dtg0j+T6ikD8yjTh5ruYru7rKYxld6LSSkU29Q0wp39qYxR390jn3rkrXLRCZ7qHT/Hs0zZg== +"@docusaurus/plugin-google-analytics@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-2.0.0-beta.18.tgz#a9b1659abb3f588e866aaa742ec4c82fe943eda3" + integrity sha512-s9dRBWDrZ1uu3wFXPCF7yVLo/+5LUFAeoxpXxzory8gn9GYDt8ZDj80h5DUyCLxiy72OG6bXWNOYS/Vc6cOPXQ== dependencies: - "@docusaurus/core" "2.0.0-beta.14" - "@docusaurus/utils-validation" "2.0.0-beta.14" + "@docusaurus/core" "2.0.0-beta.18" + "@docusaurus/utils-validation" "2.0.0-beta.18" tslib "^2.3.1" -"@docusaurus/plugin-google-gtag@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-2.0.0-beta.14.tgz#be950af01da784965a7fd7ba61d557055cceeb5e" - integrity sha512-DcaNRvu0VLS/C6qRAG0QNWjnuP8dAdzH0NOfl86AxdK6dWOP5NlGD9QoIFKTa19PB8iTzM2XZn/hOCub4hR6MQ== +"@docusaurus/plugin-google-gtag@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-2.0.0-beta.18.tgz#b51611ac01915523ddcfc9732f7862cf4996a0e1" + integrity sha512-h7vPuLVo/9pHmbFcvb4tCpjg4SxxX4k+nfVDyippR254FM++Z/nA5pRB0WvvIJ3ZTe0ioOb5Wlx2xdzJIBHUNg== dependencies: - "@docusaurus/core" "2.0.0-beta.14" - "@docusaurus/utils-validation" "2.0.0-beta.14" + "@docusaurus/core" "2.0.0-beta.18" + "@docusaurus/utils-validation" "2.0.0-beta.18" tslib "^2.3.1" -"@docusaurus/plugin-sitemap@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-sitemap/-/plugin-sitemap-2.0.0-beta.14.tgz#13042fee40ab2a66615c44d9ef440abb3df5c42a" - integrity sha512-ikSgz4VAttDB2uOrPa7fq/E/GKS5HAtKfD572kBj8RvppdlgFYwCLZ88ex5cnRFF//2ccaobYkU4QwDw2UKWMA== - dependencies: - "@docusaurus/core" "2.0.0-beta.14" - "@docusaurus/utils" "2.0.0-beta.14" - "@docusaurus/utils-common" "2.0.0-beta.14" - "@docusaurus/utils-validation" "2.0.0-beta.14" - fs-extra "^10.0.0" - sitemap "^7.0.0" +"@docusaurus/plugin-sitemap@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-sitemap/-/plugin-sitemap-2.0.0-beta.18.tgz#7e8217e95bede5719bd02265dcf7eb2fea76b675" + integrity sha512-Klonht0Ye3FivdBpS80hkVYNOH+8lL/1rbCPEV92rKhwYdwnIejqhdKct4tUTCl8TYwWiyeUFQqobC/5FNVZPQ== + dependencies: + "@docusaurus/core" "2.0.0-beta.18" + "@docusaurus/utils" "2.0.0-beta.18" + "@docusaurus/utils-common" "2.0.0-beta.18" + "@docusaurus/utils-validation" "2.0.0-beta.18" + fs-extra "^10.0.1" + sitemap "^7.1.1" tslib "^2.3.1" -"@docusaurus/preset-classic@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/preset-classic/-/preset-classic-2.0.0-beta.14.tgz#128026fb201fdc6271614587ca09187bc83d930a" - integrity sha512-43rHA6wM4FcbHLPiBpqY4VSUjUXOWvW/N4q0wvf1LMoPH25lUzIaldpjD3Unzq5+UCYCFES24ktl58QOh7PB2g== - dependencies: - "@docusaurus/core" "2.0.0-beta.14" - "@docusaurus/plugin-content-blog" "2.0.0-beta.14" - "@docusaurus/plugin-content-docs" "2.0.0-beta.14" - "@docusaurus/plugin-content-pages" "2.0.0-beta.14" - "@docusaurus/plugin-debug" "2.0.0-beta.14" - "@docusaurus/plugin-google-analytics" "2.0.0-beta.14" - "@docusaurus/plugin-google-gtag" "2.0.0-beta.14" - "@docusaurus/plugin-sitemap" "2.0.0-beta.14" - "@docusaurus/theme-classic" "2.0.0-beta.14" - "@docusaurus/theme-search-algolia" "2.0.0-beta.14" +"@docusaurus/preset-classic@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/preset-classic/-/preset-classic-2.0.0-beta.18.tgz#82f6905d34a13e46289ac4d2f1125e47033bd9d8" + integrity sha512-TfDulvFt/vLWr/Yy7O0yXgwHtJhdkZ739bTlFNwEkRMAy8ggi650e52I1I0T79s67llecb4JihgHPW+mwiVkCQ== + dependencies: + "@docusaurus/core" "2.0.0-beta.18" + "@docusaurus/plugin-content-blog" "2.0.0-beta.18" + "@docusaurus/plugin-content-docs" "2.0.0-beta.18" + "@docusaurus/plugin-content-pages" "2.0.0-beta.18" + "@docusaurus/plugin-debug" "2.0.0-beta.18" + "@docusaurus/plugin-google-analytics" "2.0.0-beta.18" + "@docusaurus/plugin-google-gtag" "2.0.0-beta.18" + "@docusaurus/plugin-sitemap" "2.0.0-beta.18" + "@docusaurus/theme-classic" "2.0.0-beta.18" + "@docusaurus/theme-common" "2.0.0-beta.18" + "@docusaurus/theme-search-algolia" "2.0.0-beta.18" "@docusaurus/react-loadable@5.5.2", "react-loadable@npm:@docusaurus/react-loadable@5.5.2": version "5.5.2" @@ -1436,123 +1436,125 @@ "@types/react" "*" prop-types "^15.6.2" -"@docusaurus/theme-classic@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-classic/-/theme-classic-2.0.0-beta.14.tgz#1e11f0e034bbb530ce38e669bc61a8eeea839132" - integrity sha512-gAatNruzgPh1NdCcIJPkhBpZE4jmbO+nYwpk/scatYQWBkhOs/fcI9tieIaGZIqi60N6lAUYQkPH+qXtLxX7Iw== - dependencies: - "@docusaurus/core" "2.0.0-beta.14" - "@docusaurus/plugin-content-blog" "2.0.0-beta.14" - "@docusaurus/plugin-content-docs" "2.0.0-beta.14" - "@docusaurus/plugin-content-pages" "2.0.0-beta.14" - "@docusaurus/theme-common" "2.0.0-beta.14" - "@docusaurus/theme-translations" "2.0.0-beta.14" - "@docusaurus/utils" "2.0.0-beta.14" - "@docusaurus/utils-validation" "2.0.0-beta.14" - "@mdx-js/mdx" "^1.6.21" - "@mdx-js/react" "^1.6.21" +"@docusaurus/theme-classic@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-classic/-/theme-classic-2.0.0-beta.18.tgz#a3632e83923ed4372f80999128375cd0b378d3f8" + integrity sha512-WJWofvSGKC4Luidk0lyUwkLnO3DDynBBHwmt4QrV+aAVWWSOHUjA2mPOF6GLGuzkZd3KfL9EvAfsU0aGE1Hh5g== + dependencies: + "@docusaurus/core" "2.0.0-beta.18" + "@docusaurus/plugin-content-blog" "2.0.0-beta.18" + "@docusaurus/plugin-content-docs" "2.0.0-beta.18" + "@docusaurus/plugin-content-pages" "2.0.0-beta.18" + "@docusaurus/theme-common" "2.0.0-beta.18" + "@docusaurus/theme-translations" "2.0.0-beta.18" + "@docusaurus/utils" "2.0.0-beta.18" + "@docusaurus/utils-common" "2.0.0-beta.18" + "@docusaurus/utils-validation" "2.0.0-beta.18" + "@mdx-js/react" "^1.6.22" clsx "^1.1.1" copy-text-to-clipboard "^3.0.1" - globby "^11.0.2" - infima "0.2.0-alpha.37" - lodash "^4.17.20" - postcss "^8.3.7" - prism-react-renderer "^1.2.1" - prismjs "^1.23.0" + infima "0.2.0-alpha.38" + lodash "^4.17.21" + postcss "^8.4.12" + prism-react-renderer "^1.3.1" + prismjs "^1.27.0" react-router-dom "^5.2.0" - rtlcss "^3.3.0" + rtlcss "^3.5.0" -"@docusaurus/theme-common@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-common/-/theme-common-2.0.0-beta.14.tgz#9795071a0df62b7700f6fbdea09946f3aae8183d" - integrity sha512-hr/+rx9mszjMEbrR329WFSj1jl/VxglSggLWhXqswiA3Lh5rbbeQv2ExwpBl4JBG5HxvtHUYmwYOuOTMuvRYTQ== +"@docusaurus/theme-common@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-common/-/theme-common-2.0.0-beta.18.tgz#abf74f82c37d2ce813f92447cb020831290059fb" + integrity sha512-3pI2Q6ttScDVTDbuUKAx+TdC8wmwZ2hfWk8cyXxksvC9bBHcyzXhSgcK8LTsszn2aANyZ3e3QY2eNSOikTFyng== dependencies: - "@docusaurus/plugin-content-blog" "2.0.0-beta.14" - "@docusaurus/plugin-content-docs" "2.0.0-beta.14" - "@docusaurus/plugin-content-pages" "2.0.0-beta.14" + "@docusaurus/module-type-aliases" "2.0.0-beta.18" + "@docusaurus/plugin-content-blog" "2.0.0-beta.18" + "@docusaurus/plugin-content-docs" "2.0.0-beta.18" + "@docusaurus/plugin-content-pages" "2.0.0-beta.18" clsx "^1.1.1" - fs-extra "^10.0.0" parse-numeric-range "^1.3.0" + prism-react-renderer "^1.3.1" tslib "^2.3.1" utility-types "^3.10.0" -"@docusaurus/theme-search-algolia@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-search-algolia/-/theme-search-algolia-2.0.0-beta.14.tgz#0238622324251c42098b2ccada4e19c3e92cd772" - integrity sha512-kTQl8vKXn8FAVVkCeN4XvU8PGWZTHToc+35F9GL06b4rv33zL9HaFIRX3nPM1NHC7I8qh+6gGeV0DRKGjO+j2g== - dependencies: - "@docsearch/react" "^3.0.0-alpha.39" - "@docusaurus/core" "2.0.0-beta.14" - "@docusaurus/logger" "2.0.0-beta.14" - "@docusaurus/theme-common" "2.0.0-beta.14" - "@docusaurus/theme-translations" "2.0.0-beta.14" - "@docusaurus/utils" "2.0.0-beta.14" - "@docusaurus/utils-validation" "2.0.0-beta.14" - algoliasearch "^4.10.5" - algoliasearch-helper "^3.5.5" +"@docusaurus/theme-search-algolia@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-search-algolia/-/theme-search-algolia-2.0.0-beta.18.tgz#cbdda8982deac4556848e04853b7f32d93886c02" + integrity sha512-2w97KO/gnjI49WVtYQqENpQ8iO1Sem0yaTxw7/qv/ndlmIAQD0syU4yx6GsA7bTQCOGwKOWWzZSetCgUmTnWgA== + dependencies: + "@docsearch/react" "^3.0.0" + "@docusaurus/core" "2.0.0-beta.18" + "@docusaurus/logger" "2.0.0-beta.18" + "@docusaurus/plugin-content-docs" "2.0.0-beta.18" + "@docusaurus/theme-common" "2.0.0-beta.18" + "@docusaurus/theme-translations" "2.0.0-beta.18" + "@docusaurus/utils" "2.0.0-beta.18" + "@docusaurus/utils-validation" "2.0.0-beta.18" + algoliasearch "^4.13.0" + algoliasearch-helper "^3.7.4" clsx "^1.1.1" eta "^1.12.3" - lodash "^4.17.20" + fs-extra "^10.0.1" + lodash "^4.17.21" tslib "^2.3.1" + utility-types "^3.10.0" -"@docusaurus/theme-translations@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-translations/-/theme-translations-2.0.0-beta.14.tgz#30f230c66aad3e085d680d49db484b663041be75" - integrity sha512-b67qJJIWc3A2tanYslDGpAUGfJ7oVAl+AdjGBYG3j3hYEUSyVUBzm8Y4iyCFEfW6BTx9pjqC/ECNO3iH2L3Ixg== +"@docusaurus/theme-translations@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-translations/-/theme-translations-2.0.0-beta.18.tgz#292699ce89b013262683faf7f4ee7b75a8745a79" + integrity sha512-1uTEUXlKC9nco1Lx9H5eOwzB+LP4yXJG5wfv1PMLE++kJEdZ40IVorlUi3nJnaa9/lJNq5vFvvUDrmeNWsxy/Q== dependencies: - fs-extra "^10.0.0" + fs-extra "^10.0.1" tslib "^2.3.1" -"@docusaurus/types@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/types/-/types-2.0.0-beta.14.tgz#d9e1bae14a16ad96c51caaea3c5049eedbaed869" - integrity sha512-1mVrVI0crwZrnjJJeUJUfBrwKdPJVsvh3E0I4pRG9Bo9dfeT8j3bj/GgjaKX4VYRFkDfsVPEpMfrVWujiWr4qQ== +"@docusaurus/types@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/types/-/types-2.0.0-beta.18.tgz#9446928a6b751eefde390420b39eac32ba26abb2" + integrity sha512-zkuSmPQYP3+z4IjGHlW0nGzSSpY7Sit0Nciu/66zSb5m07TK72t6T1MlpCAn/XijcB9Cq6nenC3kJh66nGsKYg== dependencies: commander "^5.1.0" - joi "^17.4.2" - querystring "0.2.0" + joi "^17.6.0" utility-types "^3.10.0" - webpack "^5.61.0" + webpack "^5.70.0" webpack-merge "^5.8.0" -"@docusaurus/utils-common@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/utils-common/-/utils-common-2.0.0-beta.14.tgz#4ee8a266366722b2c98e17c12b109236dd2b32fb" - integrity sha512-hNWyy083Bm+6jEzsm05gFmEfwumXph0E46s2HrWkSM8tClrOVmu/C1Rm7kWYn561gXHhrATtyXr/u8bKXByFcQ== +"@docusaurus/utils-common@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/utils-common/-/utils-common-2.0.0-beta.18.tgz#46cf0bed2a7c532b2b85eab5bb914ff118b2c4e9" + integrity sha512-pK83EcOIiKCLGhrTwukZMo5jqd1sqqqhQwOVyxyvg+x9SY/lsnNzScA96OEfm+qQLBwK1OABA7Xc1wfkgkUxvw== dependencies: tslib "^2.3.1" -"@docusaurus/utils-validation@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/utils-validation/-/utils-validation-2.0.0-beta.14.tgz#c5e54adbe6dd4b3d6f5525ae5138c0214e75a6c2" - integrity sha512-ttDp/fXjbM6rTfP8XCmBKtNygfPg8cncp+rPsWHdSFjGmE7HkinilFTtaw0Zos/096TtxsQx3DgGQyPOl6prnA== +"@docusaurus/utils-validation@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/utils-validation/-/utils-validation-2.0.0-beta.18.tgz#0dabf113d2c53ee685a715cd4caae6e219e9e41e" + integrity sha512-3aDrXjJJ8Cw2MAYEk5JMNnr8UHPxmVNbPU/PIHFWmWK09nJvs3IQ8nc9+8I30aIjRdIyc/BIOCxgvAcJ4hsxTA== dependencies: - "@docusaurus/logger" "2.0.0-beta.14" - "@docusaurus/utils" "2.0.0-beta.14" - joi "^17.4.2" + "@docusaurus/logger" "2.0.0-beta.18" + "@docusaurus/utils" "2.0.0-beta.18" + joi "^17.6.0" + js-yaml "^4.1.0" tslib "^2.3.1" -"@docusaurus/utils@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/utils/-/utils-2.0.0-beta.14.tgz#494d2181cc0fd264ebe12f2a08c6ae04878e5f90" - integrity sha512-7V+X70a+7UJHS7PeXS/BO2jz+zXaKhRlT7MUe5khu6i6n1oQA3Jqx1sfu78slemqEWe8u337jxal6uILcB0IWQ== +"@docusaurus/utils@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/utils/-/utils-2.0.0-beta.18.tgz#c3fe0e9fac30db4510962263993fd0ee2679eebb" + integrity sha512-v2vBmH7xSbPwx3+GB90HgLSQdj+Rh5ELtZWy7M20w907k0ROzDmPQ/8Ke2DK3o5r4pZPGnCrsB3SaYI83AEmAA== dependencies: - "@docusaurus/logger" "2.0.0-beta.14" - "@mdx-js/runtime" "^1.6.22" - "@svgr/webpack" "^6.0.0" - escape-string-regexp "^4.0.0" + "@docusaurus/logger" "2.0.0-beta.18" + "@svgr/webpack" "^6.2.1" file-loader "^6.2.0" - fs-extra "^10.0.0" + fs-extra "^10.0.1" github-slugger "^1.4.0" - globby "^11.0.4" + globby "^11.1.0" gray-matter "^4.0.3" - lodash "^4.17.20" - micromatch "^4.0.4" - remark-mdx-remove-exports "^1.6.22" - remark-mdx-remove-imports "^1.6.22" + js-yaml "^4.1.0" + lodash "^4.17.21" + micromatch "^4.0.5" resolve-pathname "^3.0.0" + shelljs "^0.8.5" tslib "^2.3.1" url-loader "^4.1.1" + webpack "^5.70.0" "@hapi/hoek@^9.0.0": version "9.2.1" @@ -1566,7 +1568,25 @@ dependencies: "@hapi/hoek" "^9.0.0" -"@mdx-js/mdx@1.6.22", "@mdx-js/mdx@^1.6.21": +"@jridgewell/resolve-uri@^3.0.3": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz#68eb521368db76d040a6315cdb24bf2483037b9c" + integrity sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew== + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.11" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz#771a1d8d744eeb71b6adb35808e1a6c7b9b8c8ec" + integrity sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg== + +"@jridgewell/trace-mapping@^0.3.0": + version "0.3.4" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.4.tgz#f6a0832dffd5b8a6aaa633b7d9f8e8e94c83a0c3" + integrity sha512-vFv9ttIedivx0ux3QSjhgtCVjPZd5l46ZOMDSCwnH1yUO2e964gO8LZGyv2QkqcgR6TnBU1v+1IFqmeoG+0UJQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@mdx-js/mdx@^1.6.22": version "1.6.22" resolved "https://registry.yarnpkg.com/@mdx-js/mdx/-/mdx-1.6.22.tgz#8a723157bf90e78f17dc0f27995398e6c731f1ba" integrity sha512-AMxuLxPz2j5/6TpF/XSdKpQP1NlG0z11dFOlq+2IP/lSgl11GY8ji6S/rgsViN/L0BDvHvUMruRb7ub+24LUYA== @@ -1591,20 +1611,11 @@ unist-builder "2.0.3" unist-util-visit "2.0.3" -"@mdx-js/react@1.6.22", "@mdx-js/react@^1.6.21": +"@mdx-js/react@^1.6.22": version "1.6.22" resolved "https://registry.yarnpkg.com/@mdx-js/react/-/react-1.6.22.tgz#ae09b4744fddc74714ee9f9d6f17a66e77c43573" integrity sha512-TDoPum4SHdfPiGSAaRBw7ECyI8VaHpK8GJugbJIJuqyh6kzw9ZLJZW3HGL3NNrJGxcAixUvqROm+YuQOo5eXtg== -"@mdx-js/runtime@^1.6.22": - version "1.6.22" - resolved "https://registry.yarnpkg.com/@mdx-js/runtime/-/runtime-1.6.22.tgz#3edd388bf68a519ffa1aaf9c446b548165102345" - integrity sha512-p17spaO2+55VLCuxXA3LVHC4phRx60NR2XMdZ+qgVU1lKvEX4y88dmFNOzGDCPLJ03IZyKrJ/rPWWRiBrd9JrQ== - dependencies: - "@mdx-js/mdx" "1.6.22" - "@mdx-js/react" "1.6.22" - buble-jsx-only "^0.19.8" - "@mdx-js/util@1.6.22": version "1.6.22" resolved "https://registry.yarnpkg.com/@mdx-js/util/-/util-1.6.22.tgz#219dfd89ae5b97a8801f015323ffa4b62f45718b" @@ -1637,9 +1648,9 @@ integrity sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g== "@sideway/address@^4.1.3": - version "4.1.3" - resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.3.tgz#d93cce5d45c5daec92ad76db492cc2ee3c64ab27" - integrity sha512-8ncEUtmnTsMmL7z1YPB47kPUq7LpKWJNFPsRzHiIajGC5uXlWGn+AmkYPcHNl8S4tcEGx+cnORnNYaw2wvL+LQ== + version "4.1.4" + resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.4.tgz#03dccebc6ea47fdc226f7d3d1ad512955d4783f0" + integrity sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw== dependencies: "@hapi/hoek" "^9.0.0" @@ -1658,15 +1669,14 @@ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== -"@slorber/static-site-generator-webpack-plugin@^4.0.0": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@slorber/static-site-generator-webpack-plugin/-/static-site-generator-webpack-plugin-4.0.1.tgz#0c8852146441aaa683693deaa5aee2f991d94841" - integrity sha512-PSv4RIVO1Y3kvHxjvqeVisk3E9XFoO04uwYBDWe217MFqKspplYswTuKLiJu0aLORQWzuQjfVsSlLPojwfYsLw== +"@slorber/static-site-generator-webpack-plugin@^4.0.4": + version "4.0.4" + resolved "https://registry.yarnpkg.com/@slorber/static-site-generator-webpack-plugin/-/static-site-generator-webpack-plugin-4.0.4.tgz#2bf4a2545e027830d2aa5eb950437c26a289b0f1" + integrity sha512-FvMavoWEIePps6/JwGCOLYKCRhuwIHhMtmbKpBFgzNkxwpa/569LfTkrbRk1m1I3n+ezJK4on9E1A6cjuZmD9g== dependencies: bluebird "^3.7.1" cheerio "^0.22.0" - eval "^0.1.4" - url "^0.11.0" + eval "^0.1.8" webpack-sources "^1.4.3" "@svgr/babel-plugin-add-jsx-attribute@^6.0.0": @@ -1704,15 +1714,15 @@ resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-6.0.0.tgz#eb688d0a5f539e34d268d8a516e81f5d7fede7c9" integrity sha512-VaphyHZ+xIKv5v0K0HCzyfAaLhPGJXSk2HkpYfXIOKb7DjLBv0soHDxNv6X0vr2titsxE7klb++u7iOf7TSrFQ== -"@svgr/babel-plugin-transform-svg-component@^6.1.0": - version "6.1.0" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-6.1.0.tgz#39f92954f7611c269a4ca6906d19e66cdc12babe" - integrity sha512-1zacrn08K5RyV2NtXahOZ5Im/+aB1Y0LVh6QpzwgQV05sY7H5Npq+OcW/UqXbfB2Ua/WnHsFossFQqigCjarYg== +"@svgr/babel-plugin-transform-svg-component@^6.2.0": + version "6.2.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-6.2.0.tgz#7ba61d9fc1fb42b0ba1a04e4630019fa7e993c4f" + integrity sha512-bhYIpsORb++wpsp91fymbFkf09Z/YEKR0DnFjxvN+8JHeCUD2unnh18jIMKnDJTWtvpTaGYPXELVe4OOzFI0xg== -"@svgr/babel-preset@^6.1.0": - version "6.1.0" - resolved "https://registry.yarnpkg.com/@svgr/babel-preset/-/babel-preset-6.1.0.tgz#b8a6b0019537bcd75b3e23fd33c180476c1ef446" - integrity sha512-f9XrTqcwhHLVkjvXBw6QJVxuIfmW22z8iTdGqGvUGGxWoeRV2EzSHstWMBgIVd7t+TmkerqowRvBYiT0OEx3cw== +"@svgr/babel-preset@^6.2.0": + version "6.2.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-preset/-/babel-preset-6.2.0.tgz#1d3ad8c7664253a4be8e4a0f0e6872f30d8af627" + integrity sha512-4WQNY0J71JIaL03DRn0vLiz87JXx0b9dYm2aA8XHlQJQoixMl4r/soYHm8dsaJZ3jWtkCiOYy48dp9izvXhDkQ== dependencies: "@svgr/babel-plugin-add-jsx-attribute" "^6.0.0" "@svgr/babel-plugin-remove-jsx-attribute" "^6.0.0" @@ -1721,57 +1731,57 @@ "@svgr/babel-plugin-svg-dynamic-title" "^6.0.0" "@svgr/babel-plugin-svg-em-dimensions" "^6.0.0" "@svgr/babel-plugin-transform-react-native-svg" "^6.0.0" - "@svgr/babel-plugin-transform-svg-component" "^6.1.0" + "@svgr/babel-plugin-transform-svg-component" "^6.2.0" -"@svgr/core@^6.1.2": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@svgr/core/-/core-6.1.2.tgz#17db14b8d559cb9dc4afa459aa487c00bf6cab80" - integrity sha512-G1UVZcPS5R+HfBG5QC7n2ibkax8RXki2sbKHySTTnajeNXbzriBJcpF4GpYzWptfvD2gmqTDY9XaX+x08TUyGQ== +"@svgr/core@^6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@svgr/core/-/core-6.2.1.tgz#195de807a9f27f9e0e0d678e01084b05c54fdf61" + integrity sha512-NWufjGI2WUyrg46mKuySfviEJ6IxHUOm/8a3Ph38VCWSp+83HBraCQrpEM3F3dB6LBs5x8OElS8h3C0oOJaJAA== dependencies: - "@svgr/plugin-jsx" "^6.1.2" + "@svgr/plugin-jsx" "^6.2.1" camelcase "^6.2.0" cosmiconfig "^7.0.1" -"@svgr/hast-util-to-babel-ast@^6.0.0": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-6.0.0.tgz#423329ad866b6c169009cc82b5e28ffee80c857c" - integrity sha512-S+TxtCdDyRGafH1VG1t/uPZ87aOYOHzWL8kqz4FoSZcIbzWA6rnOmjNViNiDzqmEpzp2PW5o5mZfvC9DiVZhTQ== +"@svgr/hast-util-to-babel-ast@^6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-6.2.1.tgz#ae065567b74cbe745afae617053adf9a764bea25" + integrity sha512-pt7MMkQFDlWJVy9ULJ1h+hZBDGFfSCwlBNW1HkLnVi7jUhyEXUaGYWi1x6bM2IXuAR9l265khBT4Av4lPmaNLQ== dependencies: "@babel/types" "^7.15.6" entities "^3.0.1" -"@svgr/plugin-jsx@^6.1.2": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@svgr/plugin-jsx/-/plugin-jsx-6.1.2.tgz#8a2815aaa46cc3d5cffa963e92b06bd0c33e7748" - integrity sha512-K/w16g3BznTjVjLyUyV0fE7LLl1HSq5KJjvczFVVvx9QG0+3xtU7RX6gvoVnTvYlrNo8QxxqLWVAU3HQm68Eew== +"@svgr/plugin-jsx@^6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@svgr/plugin-jsx/-/plugin-jsx-6.2.1.tgz#5668f1d2aa18c2f1bb7a1fc9f682d3f9aed263bd" + integrity sha512-u+MpjTsLaKo6r3pHeeSVsh9hmGRag2L7VzApWIaS8imNguqoUwDq/u6U/NDmYs/KAsrmtBjOEaAAPbwNGXXp1g== dependencies: "@babel/core" "^7.15.5" - "@svgr/babel-preset" "^6.1.0" - "@svgr/hast-util-to-babel-ast" "^6.0.0" + "@svgr/babel-preset" "^6.2.0" + "@svgr/hast-util-to-babel-ast" "^6.2.1" svg-parser "^2.0.2" -"@svgr/plugin-svgo@^6.1.2": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@svgr/plugin-svgo/-/plugin-svgo-6.1.2.tgz#4fe7a2defe237f0493dee947dde6fa5cea57e6c1" - integrity sha512-UHVSRZV3RdaggDT60OMIEmhskN736DOF6PuBcCaql6jBDA9+SZkA5ZMEw73ZLAlwdOAmw+0Gi4vx/xvAfnmerw== +"@svgr/plugin-svgo@^6.2.0": + version "6.2.0" + resolved "https://registry.yarnpkg.com/@svgr/plugin-svgo/-/plugin-svgo-6.2.0.tgz#4cbe6a33ccccdcae4e3b63ded64cc1cbe1faf48c" + integrity sha512-oDdMQONKOJEbuKwuy4Np6VdV6qoaLLvoY86hjvQEgU82Vx1MSWRyYms6Sl0f+NtqxLI/rDVufATbP/ev996k3Q== dependencies: cosmiconfig "^7.0.1" deepmerge "^4.2.2" svgo "^2.5.0" -"@svgr/webpack@^6.0.0": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@svgr/webpack/-/webpack-6.1.2.tgz#23fd605e9163deb7ef3feef52545ff11dc9989bf" - integrity sha512-5RzzWxFquywENwvnsiGjZ7IED+0l2lnICR3OKQ6OUyGgxlu+ac73NmDSXp6EPBz/ZTArpMZtug7jiPMUkXxnlg== +"@svgr/webpack@^6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@svgr/webpack/-/webpack-6.2.1.tgz#ef5d51c1b6be4e7537fb9f76b3f2b2e22b63c58d" + integrity sha512-h09ngMNd13hnePwgXa+Y5CgOjzlCvfWLHg+MBnydEedAnuLRzUHUJmGS3o2OsrhxTOOqEsPOFt5v/f6C5Qulcw== dependencies: "@babel/core" "^7.15.5" "@babel/plugin-transform-react-constant-elements" "^7.14.5" "@babel/preset-env" "^7.15.6" "@babel/preset-react" "^7.14.5" "@babel/preset-typescript" "^7.15.0" - "@svgr/core" "^6.1.2" - "@svgr/plugin-jsx" "^6.1.2" - "@svgr/plugin-svgo" "^6.1.2" + "@svgr/core" "^6.2.1" + "@svgr/plugin-jsx" "^6.2.1" + "@svgr/plugin-svgo" "^6.2.0" "@szmarczak/http-timer@^1.1.2": version "1.1.2" @@ -1785,38 +1795,80 @@ resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== -"@tsconfig/docusaurus@^1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@tsconfig/docusaurus/-/docusaurus-1.0.4.tgz#fc40f87a672568678d83533dd4031a09d75877ca" - integrity sha512-I6sziQAzLrrqj9r6S26c7aOAjfGVXIE7gWdNONPwnpDcHiMRMQut1s1YCi/APem3dOy23tAb2rvHfNtGCaWuUQ== +"@tsconfig/docusaurus@^1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@tsconfig/docusaurus/-/docusaurus-1.0.5.tgz#5298c5b0333c6263f06c3149b38ebccc9f169a4e" + integrity sha512-KM/TuJa9fugo67dTGx+ktIqf3fVc077J6jwHu845Hex4EQf7LABlNonP/mohDKT0cmncdtlYVHHF74xR/YpThg== -"@types/cssnano@^4.0.1": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@types/cssnano/-/cssnano-4.0.1.tgz#67fa912753d80973a016e7684a47fedf338aacff" - integrity sha512-hGOroxRTBkYl5gSBRJOffhV4+io+Y2bFX1VP7LgKEVHJt/LPPJaWUIuDAz74Vlp7l7hCDZfaDi7iPxwNwuVA4Q== +"@types/body-parser@*": + version "1.19.2" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" + integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/bonjour@^3.5.9": + version "3.5.10" + resolved "https://registry.yarnpkg.com/@types/bonjour/-/bonjour-3.5.10.tgz#0f6aadfe00ea414edc86f5d106357cda9701e275" + integrity sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw== dependencies: - postcss "5 - 7" + "@types/node" "*" -"@types/eslint-scope@^3.7.0": - version "3.7.2" - resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.2.tgz#11e96a868c67acf65bf6f11d10bb89ea71d5e473" - integrity sha512-TzgYCWoPiTeRg6RQYgtuW7iODtVoKu3RVL72k3WohqhjfaOLK5Mg2T4Tg1o2bSfu0vPkoI48wdQFv5b/Xe04wQ== +"@types/connect-history-api-fallback@^1.3.5": + version "1.3.5" + resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz#d1f7a8a09d0ed5a57aee5ae9c18ab9b803205dae" + integrity sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw== + dependencies: + "@types/express-serve-static-core" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.35" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" + integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== + dependencies: + "@types/node" "*" + +"@types/eslint-scope@^3.7.3": + version "3.7.3" + resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.3.tgz#125b88504b61e3c8bc6f870882003253005c3224" + integrity sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g== dependencies: "@types/eslint" "*" "@types/estree" "*" "@types/eslint@*": - version "8.2.1" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.2.1.tgz#13f3d69bac93c2ae008019c28783868d0a1d6605" - integrity sha512-UP9rzNn/XyGwb5RQ2fok+DzcIRIYwc16qTXse5+Smsy8MOIccCChT15KAwnsgQx4PzJkaMq4myFyZ4CL5TjhIQ== + version "8.4.1" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.4.1.tgz#c48251553e8759db9e656de3efc846954ac32304" + integrity sha512-GE44+DNEyxxh2Kc6ro/VkIj+9ma0pO0bwv9+uHSyBrikYOHr8zYcdPvnBOp1aw8s+CjRvuSx7CyWqRrNFQ59mA== dependencies: "@types/estree" "*" "@types/json-schema" "*" -"@types/estree@*", "@types/estree@^0.0.50": - version "0.0.50" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.50.tgz#1e0caa9364d3fccd2931c3ed96fdbeaa5d4cca83" - integrity sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw== +"@types/estree@*", "@types/estree@^0.0.51": + version "0.0.51" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40" + integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ== + +"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.18": + version "4.17.28" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz#c47def9f34ec81dc6328d0b1b5303d1ec98d86b8" + integrity sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + +"@types/express@*", "@types/express@^4.17.13": + version "4.17.13" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034" + integrity sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.18" + "@types/qs" "*" + "@types/serve-static" "*" "@types/hast@^2.0.0": version "2.3.4" @@ -1825,17 +1877,17 @@ dependencies: "@types/unist" "*" -"@types/history@*": - version "4.7.9" - resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.9.tgz#1cfb6d60ef3822c589f18e70f8b12f9a28ce8724" - integrity sha512-MUc6zSmU3tEVnkQ78q0peeEjKWPUADMlC/t++2bI8WnAG2tvYRPIgHG8lWkXwqc8MsUF6Z2MOf+Mh5sazOmhiQ== +"@types/history@^4.7.11": + version "4.7.11" + resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.11.tgz#56588b17ae8f50c53983a524fc3cc47437969d64" + integrity sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA== "@types/html-minifier-terser@^6.0.0": version "6.1.0" resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#4fc33a00c1d0c16987b1a20cf92d20614c55ac35" integrity sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg== -"@types/http-proxy@^1.17.5": +"@types/http-proxy@^1.17.8": version "1.17.8" resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.8.tgz#968c66903e7e42b483608030ee85800f22d03f55" integrity sha512-5kPLG5BKpWYkw/LVOGWpiq3nEVqxiN32rTgI53Sk12/xHFQ2rG3ehI9IO+O3W2QoKeyB92dJkoka8SUm6BX1pA== @@ -1843,9 +1895,9 @@ "@types/node" "*" "@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": - version "7.0.9" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" - integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== + version "7.0.11" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" + integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== "@types/mdast@^3.0.0": version "3.0.10" @@ -1854,15 +1906,15 @@ dependencies: "@types/unist" "*" -"@types/node@*": - version "17.0.2" - resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.2.tgz#a4c07d47ff737e8ee7e586fe636ff0e1ddff070a" - integrity sha512-JepeIUPFDARgIs0zD/SKPgFsJEAF0X5/qO80llx59gOxFTboS9Amv3S+QfB7lqBId5sFXJ99BN0J6zFRvL9dDA== +"@types/mime@^1": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" + integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== -"@types/node@^15.0.1": - version "15.14.9" - resolved "https://registry.yarnpkg.com/@types/node/-/node-15.14.9.tgz#bc43c990c3c9be7281868bbc7b8fdd6e2b57adfa" - integrity sha512-qjd88DrCxupx/kJD5yQgZdcYKZKSIGBVDIBE1/LTGcNm3d2Np/jxojkdePDdfnBHJc5W7vSMpbJ1aB7p/Py69A== +"@types/node@*", "@types/node@^17.0.5": + version "17.0.23" + resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.23.tgz#3b41a6e643589ac6442bdbd7a4a3ded62f33f7da" + integrity sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw== "@types/parse-json@^4.0.0": version "4.0.0" @@ -1879,43 +1931,46 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11" integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ== -"@types/react-helmet@*": - version "6.1.4" - resolved "https://registry.yarnpkg.com/@types/react-helmet/-/react-helmet-6.1.4.tgz#3e54a3eb37ba7fb34ffafc64f425be4e68df03b9" - integrity sha512-jyx50RNZXVaTGHY3MsoRPNpeiVk8b0XTPgD/O6KHF6COTDnG/+lRjPYvTK5nfWtR3xDOux0w6bHLAsaHo2ZLTA== - dependencies: - "@types/react" "*" +"@types/qs@*": + version "6.9.7" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" + integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== + +"@types/range-parser@*": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" + integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== "@types/react-router-config@*": - version "5.0.3" - resolved "https://registry.yarnpkg.com/@types/react-router-config/-/react-router-config-5.0.3.tgz#72a53561bcb5e13b82c576527b818fa2579591c7" - integrity sha512-38vpjXic0+E2sIBEKUe+RrCmbc8RqcQhNV8OmU3KUcwgy/yzTeo67MhllP+0zjZWNr7Lhw+RnUkL0hzkf63nUQ== + version "5.0.6" + resolved "https://registry.yarnpkg.com/@types/react-router-config/-/react-router-config-5.0.6.tgz#87c5c57e72d241db900d9734512c50ccec062451" + integrity sha512-db1mx37a1EJDf1XeX8jJN7R3PZABmJQXR8r28yUjVMFSjkmnQo6X6pOEEmNl+Tp2gYQOGPdYbFIipBtdElZ3Yg== dependencies: - "@types/history" "*" + "@types/history" "^4.7.11" "@types/react" "*" "@types/react-router" "*" "@types/react-router-dom@*": - version "5.3.2" - resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.3.2.tgz#ebd8e145cf056db5c66eb1dac63c72f52e8542ee" - integrity sha512-ELEYRUie2czuJzaZ5+ziIp9Hhw+juEw8b7C11YNA4QdLCVbQ3qLi2l4aq8XnlqM7V31LZX8dxUuFUCrzHm6sqQ== + version "5.3.3" + resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.3.3.tgz#e9d6b4a66fcdbd651a5f106c2656a30088cc1e83" + integrity sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw== dependencies: - "@types/history" "*" + "@types/history" "^4.7.11" "@types/react" "*" "@types/react-router" "*" "@types/react-router@*": - version "5.1.17" - resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.17.tgz#087091006213b11042f39570e5cd414863693968" - integrity sha512-RNSXOyb3VyRs/EOGmjBhhGKTbnN6fHWvy5FNLzWfOWOGjgVUKqJZXfpKzLmgoU8h6Hj8mpALj/mbXQASOb92wQ== + version "5.1.18" + resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.18.tgz#c8851884b60bc23733500d86c1266e1cfbbd9ef3" + integrity sha512-YYknwy0D0iOwKQgz9v8nOzt2J6l4gouBmDnWqUUznltOTaon+r8US8ky8HvN0tXvc38U9m6z/t2RsVsnd1zM0g== dependencies: - "@types/history" "*" + "@types/history" "^4.7.11" "@types/react" "*" "@types/react@*": - version "17.0.37" - resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.37.tgz#6884d0aa402605935c397ae689deed115caad959" - integrity sha512-2FS1oTqBGcH/s0E+CjrCCR9+JMpsu9b69RTFO+40ua43ZqP5MmQ4iUde/dMjWR909KxZwmOQIFq6AV6NjEG5xg== + version "17.0.43" + resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.43.tgz#4adc142887dd4a2601ce730bc56c3436fdb07a55" + integrity sha512-8Q+LNpdxf057brvPu1lMtC5Vn7J119xrP1aq4qiaefNioQUYANF/CYeK4NsKorSZyUGJ66g0IM+4bbjwx45o2A== dependencies: "@types/prop-types" "*" "@types/scheduler" "*" @@ -1938,11 +1993,40 @@ resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== +"@types/serve-index@^1.9.1": + version "1.9.1" + resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.1.tgz#1b5e85370a192c01ec6cec4735cf2917337a6278" + integrity sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg== + dependencies: + "@types/express" "*" + +"@types/serve-static@*": + version "1.13.10" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.10.tgz#f5e0ce8797d2d7cc5ebeda48a52c96c4fa47a8d9" + integrity sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ== + dependencies: + "@types/mime" "^1" + "@types/node" "*" + +"@types/sockjs@^0.3.33": + version "0.3.33" + resolved "https://registry.yarnpkg.com/@types/sockjs/-/sockjs-0.3.33.tgz#570d3a0b99ac995360e3136fd6045113b1bd236f" + integrity sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw== + dependencies: + "@types/node" "*" + "@types/unist@*", "@types/unist@^2.0.0", "@types/unist@^2.0.2", "@types/unist@^2.0.3": version "2.0.6" resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d" integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ== +"@types/ws@^8.2.2": + version "8.5.3" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d" + integrity sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w== + dependencies: + "@types/node" "*" + "@webassemblyjs/ast@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7" @@ -2074,43 +2158,28 @@ resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== -accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: - version "1.3.7" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" - integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== +accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: + version "1.3.8" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== dependencies: - mime-types "~2.1.24" - negotiator "0.6.2" - -acorn-dynamic-import@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz#482210140582a36b83c3e342e1cfebcaa9240948" - integrity sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw== + mime-types "~2.1.34" + negotiator "0.6.3" acorn-import-assertions@^1.7.6: version "1.8.0" resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz#ba2b5939ce62c238db6d93d81c9b111b29b855e9" integrity sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw== -acorn-jsx@^5.0.1: - version "5.3.2" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" - integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== - acorn-walk@^8.0.0: version "8.2.0" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== -acorn@^6.1.1: - version "6.4.2" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6" - integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ== - -acorn@^8.0.4, acorn@^8.4.1: - version "8.6.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.6.0.tgz#e3692ba0eb1a0c83eaa4f37f5fa7368dd7142895" - integrity sha512-U1riIR+lBSNi3IbxtaHOIKdH8sLFv3NYfNv8sg7ZsNhcfl4HF2++BfqqrNAxoCLQW1iiylOj76ecnaUxz+z9yw== +acorn@^8.0.4, acorn@^8.4.1, acorn@^8.5.0: + version "8.7.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" + integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ== address@^1.0.1, address@^1.1.2: version "1.1.2" @@ -2155,48 +2224,43 @@ ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5: uri-js "^4.2.2" ajv@^8.0.0, ajv@^8.8.0: - version "8.8.2" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.8.2.tgz#01b4fef2007a28bf75f0b7fc009f62679de4abbb" - integrity sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw== + version "8.11.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.11.0.tgz#977e91dd96ca669f54a11e23e378e33b884a565f" + integrity sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg== dependencies: fast-deep-equal "^3.1.1" json-schema-traverse "^1.0.0" require-from-string "^2.0.2" uri-js "^4.2.2" -algoliasearch-helper@^3.5.5: - version "3.7.0" - resolved "https://registry.yarnpkg.com/algoliasearch-helper/-/algoliasearch-helper-3.7.0.tgz#c0a0493df84d850360f664ad7a9d4fc78a94fd78" - integrity sha512-XJ3QfERBLfeVCyTVx80gon7r3/rgm/CE8Ha1H7cbablRe/X7SfYQ14g/eO+MhjVKIQp+gy9oC6G5ilmLwS1k6w== +algoliasearch-helper@^3.7.4: + version "3.7.4" + resolved "https://registry.yarnpkg.com/algoliasearch-helper/-/algoliasearch-helper-3.7.4.tgz#3812ea161da52463ec88da52612c9a363c1b181d" + integrity sha512-KmJrsHVm5TmxZ9Oj53XdXuM4CQeu7eVFnB15tpSFt+7is1d1yVCv3hxCLMqYSw/rH42ccv013miQpRr268P8vw== dependencies: "@algolia/events" "^4.0.1" -algoliasearch@^4.0.0, algoliasearch@^4.10.5: - version "4.11.0" - resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-4.11.0.tgz#234befb3ac355c094077f0edf3777240b1ee013c" - integrity sha512-IXRj8kAP2WrMmj+eoPqPc6P7Ncq1yZkFiyDrjTBObV1ADNL8Z/KdZ+dWC5MmYcBLAbcB/mMCpak5N/D1UIZvsA== - dependencies: - "@algolia/cache-browser-local-storage" "4.11.0" - "@algolia/cache-common" "4.11.0" - "@algolia/cache-in-memory" "4.11.0" - "@algolia/client-account" "4.11.0" - "@algolia/client-analytics" "4.11.0" - "@algolia/client-common" "4.11.0" - "@algolia/client-personalization" "4.11.0" - "@algolia/client-search" "4.11.0" - "@algolia/logger-common" "4.11.0" - "@algolia/logger-console" "4.11.0" - "@algolia/requester-browser-xhr" "4.11.0" - "@algolia/requester-common" "4.11.0" - "@algolia/requester-node-http" "4.11.0" - "@algolia/transporter" "4.11.0" - -alphanum-sort@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" - integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM= - -ansi-align@^3.0.0: +algoliasearch@^4.0.0, algoliasearch@^4.13.0: + version "4.13.0" + resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-4.13.0.tgz#e36611fda82b1fc548c156ae7929a7f486e4b663" + integrity sha512-oHv4faI1Vl2s+YC0YquwkK/TsaJs79g2JFg5FDm2rKN12VItPTAeQ7hyJMHarOPPYuCnNC5kixbtcqvb21wchw== + dependencies: + "@algolia/cache-browser-local-storage" "4.13.0" + "@algolia/cache-common" "4.13.0" + "@algolia/cache-in-memory" "4.13.0" + "@algolia/client-account" "4.13.0" + "@algolia/client-analytics" "4.13.0" + "@algolia/client-common" "4.13.0" + "@algolia/client-personalization" "4.13.0" + "@algolia/client-search" "4.13.0" + "@algolia/logger-common" "4.13.0" + "@algolia/logger-console" "4.13.0" + "@algolia/requester-browser-xhr" "4.13.0" + "@algolia/requester-common" "4.13.0" + "@algolia/requester-node-http" "4.13.0" + "@algolia/transporter" "4.13.0" + +ansi-align@^3.0.0, ansi-align@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.1.tgz#0cdf12e111ace773a86e9a1fad1225c43cb19a59" integrity sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w== @@ -2232,6 +2296,11 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" +ansi-styles@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.1.0.tgz#87313c102b8118abd57371afab34618bf7350ed3" + integrity sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ== + anymatch@~3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" @@ -2272,6 +2341,11 @@ array-union@^2.1.0: resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== +array-union@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-3.0.1.tgz#da52630d327f8b88cfbfb57728e2af5cd9b6b975" + integrity sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw== + asap@~2.0.3: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" @@ -2289,32 +2363,32 @@ at-least-node@^1.0.0: resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== -autoprefixer@^10.3.5, autoprefixer@^10.3.7: - version "10.4.0" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.0.tgz#c3577eb32a1079a440ec253e404eaf1eb21388c8" - integrity sha512-7FdJ1ONtwzV1G43GDD0kpVMn/qbiNqyOPMFTX5nRffI+7vgWoFEc6DcXOxHJxrWNDXrZh18eDsZjvZGUljSRGA== +autoprefixer@^10.3.7, autoprefixer@^10.4.4: + version "10.4.4" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.4.tgz#3e85a245b32da876a893d3ac2ea19f01e7ea5a1e" + integrity sha512-Tm8JxsB286VweiZ5F0anmbyGiNI3v3wGv3mz9W+cxEDYB/6jbnj6GM9H9mK3wIL8ftgl+C07Lcwb8PG5PCCPzA== dependencies: - browserslist "^4.17.5" - caniuse-lite "^1.0.30001272" - fraction.js "^4.1.1" + browserslist "^4.20.2" + caniuse-lite "^1.0.30001317" + fraction.js "^4.2.0" normalize-range "^0.1.2" picocolors "^1.0.0" - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -axios@^0.21.1: - version "0.21.4" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" - integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== +axios@^0.25.0: + version "0.25.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.25.0.tgz#349cfbb31331a9b4453190791760a8d35b093e0a" + integrity sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g== dependencies: - follow-redirects "^1.14.0" + follow-redirects "^1.14.7" -babel-loader@^8.2.2: - version "8.2.3" - resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.2.3.tgz#8986b40f1a64cacfcb4b8429320085ef68b1342d" - integrity sha512-n4Zeta8NC3QAsuyiizu0GkmRcQ6clkV9WFUnUf1iXP//IeSKbWjofW3UHyZVwlOB4y039YQKefawyTn64Zwbuw== +babel-loader@^8.2.4: + version "8.2.4" + resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.2.4.tgz#95f5023c791b2e9e2ca6f67b0984f39c82ff384b" + integrity sha512-8dytA3gcvPPPv4Grjhnt8b5IIiTcq/zeXOPk4iTYI0SVXcsmuGg7JtBRDp8S9X+gJfhQ8ektjXZlDu1Bb33U8A== dependencies: find-cache-dir "^3.3.1" - loader-utils "^1.4.0" + loader-utils "^2.0.0" make-dir "^3.1.0" schema-utils "^2.6.5" @@ -2348,28 +2422,28 @@ babel-plugin-extract-import-names@1.6.22: "@babel/helper-plugin-utils" "7.10.4" babel-plugin-polyfill-corejs2@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.0.tgz#407082d0d355ba565af24126fb6cb8e9115251fd" - integrity sha512-wMDoBJ6uG4u4PNFh72Ty6t3EgfA91puCuAwKIazbQlci+ENb/UU9A3xG5lutjUIiXCIn1CY5L15r9LimiJyrSA== + version "0.3.1" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.1.tgz#440f1b70ccfaabc6b676d196239b138f8a2cfba5" + integrity sha512-v7/T6EQcNfVLfcN2X8Lulb7DjprieyLWJK/zOWH5DUYcAgex9sP3h25Q+DLsX9TloXe3y1O8l2q2Jv9q8UVB9w== dependencies: "@babel/compat-data" "^7.13.11" - "@babel/helper-define-polyfill-provider" "^0.3.0" + "@babel/helper-define-polyfill-provider" "^0.3.1" semver "^6.1.1" -babel-plugin-polyfill-corejs3@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.4.0.tgz#0b571f4cf3d67f911512f5c04842a7b8e8263087" - integrity sha512-YxFreYwUfglYKdLUGvIF2nJEsGwj+RhWSX/ije3D2vQPOXuyMLMtg/cCGMDpOA7Nd+MwlNdnGODbd2EwUZPlsw== +babel-plugin-polyfill-corejs3@^0.5.0: + version "0.5.2" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.2.tgz#aabe4b2fa04a6e038b688c5e55d44e78cd3a5f72" + integrity sha512-G3uJih0XWiID451fpeFaYGVuxHEjzKTHtc9uGFEjR6hHrvNzeS/PX+LLLcetJcytsB5m4j+K3o/EpXJNb/5IEQ== dependencies: - "@babel/helper-define-polyfill-provider" "^0.3.0" - core-js-compat "^3.18.0" + "@babel/helper-define-polyfill-provider" "^0.3.1" + core-js-compat "^3.21.0" babel-plugin-polyfill-regenerator@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.0.tgz#9ebbcd7186e1a33e21c5e20cae4e7983949533be" - integrity sha512-dhAPTDLGoMW5/84wkgwiLRwMnio2i1fUe53EuvtKMv0pn2p3S8OCoV1xAzfJPl0KOX7IB89s2ib85vbYiea3jg== + version "0.3.1" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz#2c0678ea47c75c8cc2fbb1852278d8fb68233990" + integrity sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A== dependencies: - "@babel/helper-define-polyfill-provider" "^0.3.0" + "@babel/helper-define-polyfill-provider" "^0.3.1" bail@^1.0.0: version "1.0.5" @@ -2406,20 +2480,20 @@ bluebird@^3.7.1: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== -body-parser@1.19.1: - version "1.19.1" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.1.tgz#1499abbaa9274af3ecc9f6f10396c995943e31d4" - integrity sha512-8ljfQi5eBk8EJfECMrgqNGWPEY5jWP+1IzkzkGdFFEwFQZZyaZ21UqdaHktgiMlH0xLHqIFtE/u2OYE5dOtViA== +body-parser@1.19.2: + version "1.19.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.2.tgz#4714ccd9c157d44797b8b5607d72c0b89952f26e" + integrity sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw== dependencies: - bytes "3.1.1" + bytes "3.1.2" content-type "~1.0.4" debug "2.6.9" depd "~1.1.2" http-errors "1.8.1" iconv-lite "0.4.24" on-finished "~2.3.0" - qs "6.9.6" - raw-body "2.4.2" + qs "6.9.7" + raw-body "2.4.3" type-is "~1.6.18" bonjour@^3.5.0: @@ -2439,7 +2513,7 @@ boolbase@^1.0.0, boolbase@~1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= -boxen@^5.0.0, boxen@^5.0.1: +boxen@^5.0.0: version "5.1.2" resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.1.2.tgz#788cb686fc83c1f486dfa8a40c68fc2b831d2b50" integrity sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ== @@ -2453,6 +2527,20 @@ boxen@^5.0.0, boxen@^5.0.1: widest-line "^3.1.0" wrap-ansi "^7.0.0" +boxen@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-6.2.1.tgz#b098a2278b2cd2845deef2dff2efc38d329b434d" + integrity sha512-H4PEsJXfFI/Pt8sjDWbHlQPx4zL/bvSQjcilJmaulGt5mLDorHOHpmdXAJcBcmru7PhYSp/cDMWRko4ZUMFkSw== + dependencies: + ansi-align "^3.0.1" + camelcase "^6.2.0" + chalk "^4.1.2" + cli-boxes "^3.0.0" + string-width "^5.0.1" + type-fest "^2.5.0" + widest-line "^4.0.1" + wrap-ansi "^8.0.1" + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -2461,37 +2549,24 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^3.0.1, braces@~3.0.2: +braces@^3.0.2, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== dependencies: fill-range "^7.0.1" -browserslist@^4.0.0, browserslist@^4.14.5, browserslist@^4.16.0, browserslist@^4.16.5, browserslist@^4.16.6, browserslist@^4.17.5, browserslist@^4.19.1: - version "4.19.1" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.19.1.tgz#4ac0435b35ab655896c31d53018b6dd5e9e4c9a3" - integrity sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A== +browserslist@^4.0.0, browserslist@^4.14.5, browserslist@^4.16.6, browserslist@^4.17.5, browserslist@^4.18.1, browserslist@^4.19.1, browserslist@^4.20.2: + version "4.20.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.20.2.tgz#567b41508757ecd904dab4d1c646c612cd3d4f88" + integrity sha512-CQOBCqp/9pDvDbx3xfMi+86pr4KXIf2FDkTTdeuYw8OxS9t898LA1Khq57gtufFILXpfgsSx5woNgsBgvGjpsA== dependencies: - caniuse-lite "^1.0.30001286" - electron-to-chromium "^1.4.17" + caniuse-lite "^1.0.30001317" + electron-to-chromium "^1.4.84" escalade "^3.1.1" - node-releases "^2.0.1" + node-releases "^2.0.2" picocolors "^1.0.0" -buble-jsx-only@^0.19.8: - version "0.19.8" - resolved "https://registry.yarnpkg.com/buble-jsx-only/-/buble-jsx-only-0.19.8.tgz#6e3524aa0f1c523de32496ac9aceb9cc2b493867" - integrity sha512-7AW19pf7PrKFnGTEDzs6u9+JZqQwM1VnLS19OlqYDhXomtFFknnoQJAPHeg84RMFWAvOhYrG7harizJNwUKJsA== - dependencies: - acorn "^6.1.1" - acorn-dynamic-import "^4.0.0" - acorn-jsx "^5.0.1" - chalk "^2.4.2" - magic-string "^0.25.3" - minimist "^1.2.0" - regexpu-core "^4.5.4" - buffer-from@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" @@ -2507,10 +2582,10 @@ bytes@3.0.0: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= -bytes@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.1.tgz#3f018291cb4cbad9accb6e6970bca9c8889e879a" - integrity sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg== +bytes@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== cacheable-request@^6.0.0: version "6.1.0" @@ -2552,9 +2627,9 @@ camelcase-css@2.0.1: integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== camelcase@^6.2.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.1.tgz#250fd350cfd555d0d2160b1d51510eaf8326e86e" - integrity sha512-tVI4q5jjFV5CavAU8DXfza/TJcZutVKo/5Foskmsqcm0MsL91moHvwiGNnqaa2o6PF/7yT5ikDRcVcl8Rj6LCA== + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== caniuse-api@^3.0.0: version "3.0.0" @@ -2566,17 +2641,17 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001272, caniuse-lite@^1.0.30001286: - version "1.0.30001291" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001291.tgz#08a8d2cfea0b2cf2e1d94dd795942d0daef6108c" - integrity sha512-roMV5V0HNGgJ88s42eE70sstqGW/gwFndosYrikHthw98N5tLnOTxFqMLQjZVRxTWFlJ4rn+MsgXrR7MDPY4jA== +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001317: + version "1.0.30001320" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001320.tgz#8397391bec389b8ccce328636499b7284ee13285" + integrity sha512-MWPzG54AGdo3nWx7zHZTefseM5Y1ccM7hlQKHRqJkPozUaw3hNbBTMmLn16GG2FUzjR13Cr3NPfhIieX5PzXDA== ccount@^1.0.0, ccount@^1.0.3: version "1.1.0" resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.1.0.tgz#246687debb6014735131be8abab2d93898f8d043" integrity sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg== -chalk@^2.0.0, chalk@^2.4.2: +chalk@^2.0.0: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -2608,6 +2683,17 @@ character-reference-invalid@^1.0.0: resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz#083329cda0eae272ab3dbbf37e9a382c13af1560" integrity sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg== +cheerio-select@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/cheerio-select/-/cheerio-select-1.5.0.tgz#faf3daeb31b17c5e1a9dabcee288aaf8aafa5823" + integrity sha512-qocaHPv5ypefh6YNxvnbABM07KMxExbtbfuJoIie3iZXX1ERwYmJcIiRrr9H05ucQP1k28dav8rpdDgjQd8drg== + dependencies: + css-select "^4.1.3" + css-what "^5.0.1" + domelementtype "^2.2.0" + domhandler "^4.2.0" + domutils "^2.7.0" + cheerio@^0.22.0: version "0.22.0" resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-0.22.0.tgz#a9baa860a3f9b595a6b81b1a86873121ed3a269e" @@ -2630,10 +2716,23 @@ cheerio@^0.22.0: lodash.reject "^4.4.0" lodash.some "^4.4.0" -chokidar@^3.4.2, chokidar@^3.5.2: - version "3.5.2" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75" - integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ== +cheerio@^1.0.0-rc.10: + version "1.0.0-rc.10" + resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.10.tgz#2ba3dcdfcc26e7956fc1f440e61d51c643379f3e" + integrity sha512-g0J0q/O6mW8z5zxQ3A8E8J1hUgp4SMOvEoW/x84OwyHKe/Zccz83PVT4y5Crcr530FV6NgmKI1qvGTKVl9XXVw== + dependencies: + cheerio-select "^1.5.0" + dom-serializer "^1.3.2" + domhandler "^4.2.0" + htmlparser2 "^6.1.0" + parse5 "^6.0.1" + parse5-htmlparser2-tree-adapter "^6.0.1" + tslib "^2.2.0" + +chokidar@^3.4.2, chokidar@^3.5.3: + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== dependencies: anymatch "~3.1.2" braces "~3.0.2" @@ -2655,10 +2754,10 @@ ci-info@^2.0.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== -clean-css@^5.1.5, clean-css@^5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.2.2.tgz#d3a7c6ee2511011e051719838bdcf8314dc4548d" - integrity sha512-/eR8ru5zyxKzpBLv9YZvMXgTSSQn7AdkMItMYynsFgGwTveCRVam9IUPFloE85B4vAIj05IuKmmEoV7/AQjT0w== +clean-css@^5.2.2, clean-css@^5.2.4: + version "5.2.4" + resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.2.4.tgz#982b058f8581adb2ae062520808fb2429bd487a4" + integrity sha512-nKseG8wCzEuji/4yrgM/5cthL9oTDc5UOQyFMvW/Q53oP6gLH690o1NbuTh6Y18nujr7BxlsFuS7gXLnLzKJGg== dependencies: source-map "~0.6.0" @@ -2672,6 +2771,20 @@ cli-boxes@^2.2.1: resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== +cli-boxes@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-3.0.0.tgz#71a10c716feeba005e4504f36329ef0b17cf3145" + integrity sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g== + +cli-table3@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.1.tgz#36ce9b7af4847f288d3cdd081fbd09bf7bd237b8" + integrity sha512-w0q/enDHhPLq44ovMGdQeeDLvwxwavsJX7oQGYt/LrBlYsyaxyDnp6z3QzFut/6kLLKnlcUVJLrpB7KBfgG/RA== + dependencies: + string-width "^4.2.0" + optionalDependencies: + colors "1.4.0" + clone-deep@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" @@ -2732,6 +2845,11 @@ colorette@^2.0.10: resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.16.tgz#713b9af84fdb000139f04546bd4a93f62a5085da" integrity sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g== +colors@1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" + integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== + combine-promises@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/combine-promises/-/combine-promises-1.1.0.tgz#72db90743c0ca7aab7d0d8d2052fd7b0f674de71" @@ -2843,45 +2961,45 @@ cookie-signature@1.0.6: resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= -cookie@0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" - integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== +cookie@0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" + integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== copy-text-to-clipboard@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/copy-text-to-clipboard/-/copy-text-to-clipboard-3.0.1.tgz#8cbf8f90e0a47f12e4a24743736265d157bce69c" integrity sha512-rvVsHrpFcL4F2P8ihsoLdFHmd404+CMg71S756oRSeQgqk51U3kicGdnvfkrxva0xXH92SjGS62B0XIJsbh+9Q== -copy-webpack-plugin@^9.0.1: - version "9.1.0" - resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-9.1.0.tgz#2d2c460c4c4695ec0a58afb2801a1205256c4e6b" - integrity sha512-rxnR7PaGigJzhqETHGmAcxKnLZSR5u1Y3/bcIv/1FnqXedcL/E2ewK7ZCNrArJKCiSv8yVXhTqetJh8inDvfsA== +copy-webpack-plugin@^10.2.4: + version "10.2.4" + resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-10.2.4.tgz#6c854be3fdaae22025da34b9112ccf81c63308fe" + integrity sha512-xFVltahqlsRcyyJqQbDY6EYTtyQZF9rf+JPjwHObLdPFMEISqkFkr7mFoVOC6BfYS/dNThyoQKvziugm+OnwBg== dependencies: fast-glob "^3.2.7" glob-parent "^6.0.1" - globby "^11.0.3" + globby "^12.0.2" normalize-path "^3.0.0" - schema-utils "^3.1.1" + schema-utils "^4.0.0" serialize-javascript "^6.0.0" -core-js-compat@^3.18.0, core-js-compat@^3.19.1: - version "3.20.0" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.20.0.tgz#fd704640c5a213816b6d10ec0192756111e2c9d1" - integrity sha512-relrah5h+sslXssTTOkvqcC/6RURifB0W5yhYBdBkaPYa5/2KBMiog3XiD+s3TwEHWxInWVv4Jx2/Lw0vng+IQ== +core-js-compat@^3.20.2, core-js-compat@^3.21.0: + version "3.21.1" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.21.1.tgz#cac369f67c8d134ff8f9bd1623e3bc2c42068c82" + integrity sha512-gbgX5AUvMb8gwxC7FLVWYT7Kkgu/y7+h/h1X43yJkNqhlK2fuYyQimqvKGNZFAY6CKii/GFKJ2cp/1/42TN36g== dependencies: browserslist "^4.19.1" semver "7.0.0" -core-js-pure@^3.19.0: - version "3.20.0" - resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.20.0.tgz#7253feccf8bb05b72c153ddccdbe391ddbffbe03" - integrity sha512-qsrbIwWSEEYOM7z616jAVgwhuDDtPLwZSpUsU3vyUkHYqKTf/uwOJBZg2V7lMurYWkpVlaVOxBrfX0Q3ppvjfg== +core-js-pure@^3.20.2: + version "3.21.1" + resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.21.1.tgz#8c4d1e78839f5f46208de7230cebfb72bc3bdb51" + integrity sha512-12VZfFIu+wyVbBebyHmRTuEE/tZrB4tJToWcwAMcsp3h4+sHR+fMJWbKpYiCRWlhFBq+KNyO8rIV9rTkeVmznQ== -core-js@^3.18.0: - version "3.20.0" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.20.0.tgz#1c5ac07986b8d15473ab192e45a2e115a4a95b79" - integrity sha512-KjbKU7UEfg4YPpskMtMXPhUKn7m/1OdTHTVjy09ScR2LVaoUXe8Jh0UdvN2EKUR6iKTJph52SJP95mAB0MnVLQ== +core-js@^3.21.1: + version "3.21.1" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.21.1.tgz#f2e0ddc1fc43da6f904706e8e955bc19d06a0d94" + integrity sha512-FRq5b/VMrWlrmCzwRrpDYNxyHP9BcAZC+xHJaqTgIE5091ZV1NTmyh0sGOg5XqpnHvR0svdy0sv1gWA1zmhxig== core-util-is@~1.0.0: version "1.0.3" @@ -2910,12 +3028,12 @@ cosmiconfig@^7.0.0, cosmiconfig@^7.0.1: path-type "^4.0.0" yaml "^1.10.0" -cross-fetch@^3.0.4: - version "3.1.4" - resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.4.tgz#9723f3a3a247bf8b89039f3a380a9244e8fa2f39" - integrity sha512-1eAtFWdIubi6T4XPy6ei9iUFoKpUkIF971QLN8lIvvvwueI65+Nw5haMNKUwfJxabqlIIDODJKGrQ66gxC0PbQ== +cross-fetch@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f" + integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw== dependencies: - node-fetch "2.6.1" + node-fetch "2.6.7" cross-spawn@^7.0.3: version "7.0.3" @@ -2932,34 +3050,31 @@ crypto-random-string@^2.0.0: integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== css-declaration-sorter@^6.0.3: - version "6.1.3" - resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.1.3.tgz#e9852e4cf940ba79f509d9425b137d1f94438dc2" - integrity sha512-SvjQjNRZgh4ULK1LDJ2AduPKUKxIqmtU7ZAyi47BTV+M90Qvxr9AB6lKlLbDUfXqI9IQeYA8LbAsCZPpJEV3aA== + version "6.1.4" + resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.1.4.tgz#b9bfb4ed9a41f8dcca9bf7184d849ea94a8294b4" + integrity sha512-lpfkqS0fctcmZotJGhnxkIyJWvBXgpyi2wsFd4J8VB7wzyrT6Ch/3Q+FMNJpjK4gu1+GN5khOnpU2ZVKrLbhCw== dependencies: timsort "^0.3.0" -css-loader@^5.1.1: - version "5.2.7" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.2.7.tgz#9b9f111edf6fb2be5dc62525644cbc9c232064ae" - integrity sha512-Q7mOvpBNBG7YrVGMxRxcBJZFL75o+cH2abNASdibkj/fffYD8qWbInZrD0S9ccI6vZclF3DsHE7njGlLtaHbhg== +css-loader@^6.7.1: + version "6.7.1" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.7.1.tgz#e98106f154f6e1baf3fc3bc455cb9981c1d5fd2e" + integrity sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw== dependencies: icss-utils "^5.1.0" - loader-utils "^2.0.0" - postcss "^8.2.15" + postcss "^8.4.7" postcss-modules-extract-imports "^3.0.0" postcss-modules-local-by-default "^4.0.0" postcss-modules-scope "^3.0.0" postcss-modules-values "^4.0.0" - postcss-value-parser "^4.1.0" - schema-utils "^3.0.0" + postcss-value-parser "^4.2.0" semver "^7.3.5" -css-minimizer-webpack-plugin@^3.0.2: - version "3.3.0" - resolved "https://registry.yarnpkg.com/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-3.3.0.tgz#e61515072e788c4134b9ca395adc56243cf4d3e1" - integrity sha512-+SU5aHgGZkk2kxKsq/BZXnYee2cjHIiFARF2gGaG6gIFtLJ87330GeafqhxAemwi/WgQ40v0OQ7pBVljKAMoXg== +css-minimizer-webpack-plugin@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-3.4.1.tgz#ab78f781ced9181992fe7b6e4f3422e76429878f" + integrity sha512-1u6D71zeIfgngN2XNRJefc/hY7Ybsxd74Jm4qngIXyUEk7fss3VUzuHxLAq/R8NAba4QU9OUSaMZlbpRc7bM4Q== dependencies: - "@types/cssnano" "^4.0.1" cssnano "^5.0.6" jest-worker "^27.0.2" postcss "^8.3.5" @@ -2968,9 +3083,9 @@ css-minimizer-webpack-plugin@^3.0.2: source-map "^0.6.1" css-select@^4.1.3: - version "4.2.0" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.2.0.tgz#ab28276d3afb00cc05e818bd33eb030f14f57895" - integrity sha512-6YVG6hsH9yIb/si3Th/is8Pex7qnVHO6t7q7U6TIUnkQASGbS8tnUDBftnPynLNnuUl/r2+PTd0ekiiq7R0zJw== + version "4.2.1" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.2.1.tgz#9e665d6ae4c7f9d65dbe69d0316e3221fb274cdd" + integrity sha512-/aUslKhzkTNCQUB2qTX84lVmfia9NyjP3WpDGtj/WxhwBzWBYUV3DgUpurHTme8UTPcPlAD1DJ+b0nN/t50zDQ== dependencies: boolbase "^1.0.0" css-what "^5.1.0" @@ -3001,7 +3116,7 @@ css-what@2.1: resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg== -css-what@^5.1.0: +css-what@^5.0.1, css-what@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/css-what/-/css-what-5.1.0.tgz#3f7b707aadf633baf62c2ceb8579b545bb40f7fe" integrity sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw== @@ -3011,64 +3126,64 @@ cssesc@^3.0.0: resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== -cssnano-preset-advanced@^5.1.4: - version "5.1.9" - resolved "https://registry.yarnpkg.com/cssnano-preset-advanced/-/cssnano-preset-advanced-5.1.9.tgz#7f392122a5b26368cb05d30750d7a50d6ede7f8b" - integrity sha512-lWyaSP22ixL8pi9k+yz7VQwa1OHDCZ3SIZeq5K40NIRDII42ua2pO9HRtWQ9N+xh/AQTTHZR4ZOSxouB7VjCIQ== +cssnano-preset-advanced@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/cssnano-preset-advanced/-/cssnano-preset-advanced-5.3.1.tgz#f4fa7006aab67e354289b3efd512c93a272b3874" + integrity sha512-kfCknalY5VX/JKJ3Iri5/5rhZmQIqkbqgXsA6oaTnfA4flY/tt+w0hMxbExr0/fVuJL8w56j211op+pkQoNzoQ== dependencies: autoprefixer "^10.3.7" - cssnano-preset-default "^5.1.9" - postcss-discard-unused "^5.0.1" - postcss-merge-idents "^5.0.1" - postcss-reduce-idents "^5.0.1" - postcss-zindex "^5.0.1" + cssnano-preset-default "^5.2.5" + postcss-discard-unused "^5.1.0" + postcss-merge-idents "^5.1.1" + postcss-reduce-idents "^5.2.0" + postcss-zindex "^5.1.0" -cssnano-preset-default@^5.1.9: - version "5.1.9" - resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.1.9.tgz#79628ac48eccbdad570f70b4018cc38d43d1b7df" - integrity sha512-RhkEucqlQ+OxEi14K1p8gdXcMQy1mSpo7P1oC44oRls7BYIj8p+cht4IFBFV3W4iOjTP8EUB33XV1fX9KhDzyA== +cssnano-preset-default@^5.2.5: + version "5.2.5" + resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.2.5.tgz#267ded811a3e1664d78707f5355fcd89feeb38ac" + integrity sha512-WopL7PzN7sos3X8B54/QGl+CZUh1f0qN4ds+y2d5EPwRSSc3jsitVw81O+Uyop0pXyOfPfZxnc+LmA8w/Ki/WQ== dependencies: css-declaration-sorter "^6.0.3" - cssnano-utils "^2.0.1" - postcss-calc "^8.0.0" - postcss-colormin "^5.2.2" - postcss-convert-values "^5.0.2" - postcss-discard-comments "^5.0.1" - postcss-discard-duplicates "^5.0.1" - postcss-discard-empty "^5.0.1" - postcss-discard-overridden "^5.0.1" - postcss-merge-longhand "^5.0.4" - postcss-merge-rules "^5.0.3" - postcss-minify-font-values "^5.0.1" - postcss-minify-gradients "^5.0.3" - postcss-minify-params "^5.0.2" - postcss-minify-selectors "^5.1.0" - postcss-normalize-charset "^5.0.1" - postcss-normalize-display-values "^5.0.1" - postcss-normalize-positions "^5.0.1" - postcss-normalize-repeat-style "^5.0.1" - postcss-normalize-string "^5.0.1" - postcss-normalize-timing-functions "^5.0.1" - postcss-normalize-unicode "^5.0.1" - postcss-normalize-url "^5.0.4" - postcss-normalize-whitespace "^5.0.1" - postcss-ordered-values "^5.0.2" - postcss-reduce-initial "^5.0.2" - postcss-reduce-transforms "^5.0.1" - postcss-svgo "^5.0.3" - postcss-unique-selectors "^5.0.2" - -cssnano-utils@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-2.0.1.tgz#8660aa2b37ed869d2e2f22918196a9a8b6498ce2" - integrity sha512-i8vLRZTnEH9ubIyfdZCAdIdgnHAUeQeByEeQ2I7oTilvP9oHO6RScpeq3GsFUVqeB8uZgOQ9pw8utofNn32hhQ== + cssnano-utils "^3.1.0" + postcss-calc "^8.2.3" + postcss-colormin "^5.3.0" + postcss-convert-values "^5.1.0" + postcss-discard-comments "^5.1.1" + postcss-discard-duplicates "^5.1.0" + postcss-discard-empty "^5.1.1" + postcss-discard-overridden "^5.1.0" + postcss-merge-longhand "^5.1.3" + postcss-merge-rules "^5.1.1" + postcss-minify-font-values "^5.1.0" + postcss-minify-gradients "^5.1.1" + postcss-minify-params "^5.1.2" + postcss-minify-selectors "^5.2.0" + postcss-normalize-charset "^5.1.0" + postcss-normalize-display-values "^5.1.0" + postcss-normalize-positions "^5.1.0" + postcss-normalize-repeat-style "^5.1.0" + postcss-normalize-string "^5.1.0" + postcss-normalize-timing-functions "^5.1.0" + postcss-normalize-unicode "^5.1.0" + postcss-normalize-url "^5.1.0" + postcss-normalize-whitespace "^5.1.1" + postcss-ordered-values "^5.1.1" + postcss-reduce-initial "^5.1.0" + postcss-reduce-transforms "^5.1.0" + postcss-svgo "^5.1.0" + postcss-unique-selectors "^5.1.1" + +cssnano-utils@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-3.1.0.tgz#95684d08c91511edfc70d2636338ca37ef3a6861" + integrity sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA== -cssnano@^5.0.6, cssnano@^5.0.8: - version "5.0.14" - resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.0.14.tgz#99bc550f663b48c38e9b8e0ae795697c9de84b47" - integrity sha512-qzhRkFvBhv08tbyKCIfWbxBXmkIpLl1uNblt8SpTHkgLfON5OCPX/CCnkdNmEosvo8bANQYmTTMEgcVBlisHaw== +cssnano@^5.0.6, cssnano@^5.1.5: + version "5.1.5" + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.1.5.tgz#5f3f519538c7f1c182c527096892243db3e17397" + integrity sha512-VZO1e+bRRVixMeia1zKagrv0lLN1B/r/u12STGNNUFxnp97LIFgZHQa0JxqlwEkvzUyA9Oz/WnCTAFkdEbONmg== dependencies: - cssnano-preset-default "^5.1.9" + cssnano-preset-default "^5.2.5" lilconfig "^2.0.3" yaml "^1.10.2" @@ -3080,9 +3195,9 @@ csso@^4.2.0: css-tree "^1.1.2" csstype@^3.0.2: - version "3.0.10" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.10.tgz#2ad3a7bed70f35b965707c092e5f30b327c290e5" - integrity sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA== + version "3.0.11" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.11.tgz#d66700c5eacfac1940deb4e3ee5642792d85cd33" + integrity sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw== debug@2.6.9, debug@^2.6.0: version "2.6.9" @@ -3099,9 +3214,9 @@ debug@^3.1.1: ms "^2.1.1" debug@^4.1.0, debug@^4.1.1: - version "4.3.3" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" - integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: ms "2.1.2" @@ -3252,7 +3367,7 @@ dom-serializer@0: domelementtype "^2.0.1" entities "^2.0.0" -dom-serializer@^1.0.1: +dom-serializer@^1.0.1, dom-serializer@^1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.3.2.tgz#6206437d32ceefaec7161803230c7a20bc1b4d91" integrity sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig== @@ -3287,9 +3402,9 @@ domhandler@^2.3.0: domelementtype "1" domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.0.tgz#16c658c626cf966967e306f966b431f77d4a5626" - integrity sha512-fC0aXNQXqKSFTr2wDNZDhsEYjCiYsDWl3D01kwt25hm1YIPyDGHvvi3rw+PLqHAl/m71MaiF7d5zvBr0p5UB2g== + version "4.3.1" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c" + integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ== dependencies: domelementtype "^2.2.0" @@ -3309,7 +3424,7 @@ domutils@^1.5.1: dom-serializer "0" domelementtype "1" -domutils@^2.5.2, domutils@^2.8.0: +domutils@^2.5.2, domutils@^2.7.0, domutils@^2.8.0: version "2.8.0" resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== @@ -3338,26 +3453,36 @@ duplexer3@^0.1.4: resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= -duplexer@^0.1.1, duplexer@^0.1.2: +duplexer@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= -electron-to-chromium@^1.4.17: - version "1.4.25" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.25.tgz#ce95e6678f8c6893ae892c7e95a5000e83f1957f" - integrity sha512-bTwub9Y/76EiNmfaiJih+hAy6xn7Ns95S4KvI2NuKNOz8TEEKKQUu44xuy0PYMudjM9zdjKRS1bitsUvHTfuUg== +electron-to-chromium@^1.4.84: + version "1.4.93" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.93.tgz#2e87ac28721cb31d472ec2bd04f7daf9f2e13de2" + integrity sha512-ywq9Pc5Gwwpv7NG767CtoU8xF3aAUQJjH9//Wy3MBCg4w5JSLbJUq2L8IsCdzPMjvSgxuue9WcVaTOyyxCL0aQ== emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + emojis-list@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" @@ -3380,10 +3505,10 @@ end-of-stream@^1.1.0: dependencies: once "^1.4.0" -enhanced-resolve@^5.8.3: - version "5.8.3" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.8.3.tgz#6d552d465cce0423f5b3d718511ea53826a7b2f0" - integrity sha512-EGAbGvH7j7Xt2nc0E7D99La1OiEs8LnyimkRgwExpUMScN6O+3x9tIWs7PLQZVNx4YD+00skHXPXi1yQHpAmZA== +enhanced-resolve@^5.9.2: + version "5.9.2" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.9.2.tgz#0224dcd6a43389ebfb2d55efee517e5466772dd9" + integrity sha512-GIm3fQfwLJ8YZx2smuHpBKkXC1yOk+OBEmKckVyL0i/ea8mqDEykK3ld5dgH1QYPNyT/lIllxV2LULnxCHaHkA== dependencies: graceful-fs "^4.2.4" tapable "^2.2.0" @@ -3435,11 +3560,6 @@ escape-string-regexp@^1.0.5: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= -escape-string-regexp@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" - integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== - escape-string-regexp@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" @@ -3490,11 +3610,12 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= -eval@^0.1.4: - version "0.1.6" - resolved "https://registry.yarnpkg.com/eval/-/eval-0.1.6.tgz#9620d7d8c85515e97e6b47c5814f46ae381cb3cc" - integrity sha512-o0XUw+5OGkXw4pJZzQoXUk+H87DHuC+7ZE//oSrRGtatTmr12oTnLfg6QOq9DyTt0c/p4TwzgmkKrBzWTSizyQ== +eval@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/eval/-/eval-0.1.8.tgz#2b903473b8cc1d1989b83a1e7923f883eb357f85" + integrity sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw== dependencies: + "@types/node" "*" require-like ">= 0.1.1" eventemitter3@^4.0.0: @@ -3523,16 +3644,16 @@ execa@^5.0.0: strip-final-newline "^2.0.0" express@^4.17.1: - version "4.17.2" - resolved "https://registry.yarnpkg.com/express/-/express-4.17.2.tgz#c18369f265297319beed4e5558753cc8c1364cb3" - integrity sha512-oxlxJxcQlYwqPWKVJJtvQiwHgosH/LrLSPA+H4UxpyvSS6jC5aH+5MoHFM+KABgTOt0APue4w66Ha8jCUo9QGg== + version "4.17.3" + resolved "https://registry.yarnpkg.com/express/-/express-4.17.3.tgz#f6c7302194a4fb54271b73a1fe7a06478c8f85a1" + integrity sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg== dependencies: - accepts "~1.3.7" + accepts "~1.3.8" array-flatten "1.1.1" - body-parser "1.19.1" + body-parser "1.19.2" content-disposition "0.5.4" content-type "~1.0.4" - cookie "0.4.1" + cookie "0.4.2" cookie-signature "1.0.6" debug "2.6.9" depd "~1.1.2" @@ -3547,7 +3668,7 @@ express@^4.17.1: parseurl "~1.3.3" path-to-regexp "0.1.7" proxy-addr "~2.0.7" - qs "6.9.6" + qs "6.9.7" range-parser "~1.2.1" safe-buffer "5.2.1" send "0.17.2" @@ -3575,10 +3696,10 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-glob@^3.1.1, fast-glob@^3.2.7: - version "3.2.7" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1" - integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q== +fast-glob@^3.2.7, fast-glob@^3.2.9: + version "3.2.11" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" + integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" @@ -3625,11 +3746,11 @@ fbjs-css-vars@^1.0.0: integrity sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ== fbjs@^3.0.0, fbjs@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-3.0.2.tgz#dfae08a85c66a58372993ce2caf30863f569ff94" - integrity sha512-qv+boqYndjElAJHNN3NoM8XuwQZ1j2m3kEvTgdle8IDjr6oUbkEpvABWtj/rQl3vq4ew7dnElBxL4YJAwTVqQQ== + version "3.0.4" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-3.0.4.tgz#e1871c6bd3083bac71ff2da868ad5067d37716c6" + integrity sha512-ucV0tDODnGV3JCnnkmoszb5lf4bNpzjv80K41wd4k798Etq+UYD0y0TIfalLjZoKgjive6/adkRnszwapiDgBQ== dependencies: - cross-fetch "^3.0.4" + cross-fetch "^3.1.5" fbjs-css-vars "^1.0.0" loose-envify "^1.0.0" object-assign "^4.1.0" @@ -3652,10 +3773,10 @@ file-loader@^6.2.0: loader-utils "^2.0.0" schema-utils "^3.0.0" -filesize@^6.1.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/filesize/-/filesize-6.4.0.tgz#914f50471dd66fdca3cefe628bd0cde4ef769bcd" - integrity sha512-mjFIpOHC4jbfcTfoh4rkWpI31mF7viw9ikj/JyLoKzqlwG/YsefKfvYlYhdYdg/9mtK2z1AzgN/0LvVQ3zdlSQ== +filesize@^8.0.6: + version "8.0.7" + resolved "https://registry.yarnpkg.com/filesize/-/filesize-8.0.7.tgz#695e70d80f4e47012c132d57a059e80c6b580bd8" + integrity sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ== fill-range@^7.0.1: version "7.0.1" @@ -3693,7 +3814,7 @@ find-up@^3.0.0: dependencies: locate-path "^3.0.0" -find-up@^4.0.0, find-up@^4.1.0: +find-up@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== @@ -3717,12 +3838,12 @@ flux@^4.0.1: fbemitter "^3.0.0" fbjs "^3.0.1" -follow-redirects@^1.0.0, follow-redirects@^1.14.0: - version "1.14.6" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.6.tgz#8cfb281bbc035b3c067d6cd975b0f6ade6e855cd" - integrity sha512-fhUl5EwSJbbl8AR+uYL2KQDxLkdSjZGR36xy46AO7cOMTrCMON6Sa28FmAnC2tRTDbd/Uuzz3aJBv7EBN7JH8A== +follow-redirects@^1.0.0, follow-redirects@^1.14.7: + version "1.14.9" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7" + integrity sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w== -fork-ts-checker-webpack-plugin@^6.0.5: +fork-ts-checker-webpack-plugin@^6.5.0: version "6.5.0" resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.0.tgz#0282b335fa495a97e167f69018f566ea7d2a2b5e" integrity sha512-cS178Y+xxtIjEUorcHddKS7yCMlrDPV31mt47blKKRfMd70Kxu5xruAFE2o9sDY6wVC5deuob/u/alD04YYHnw== @@ -3746,20 +3867,20 @@ forwarded@0.2.0: resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== -fraction.js@^4.1.1: - version "4.1.2" - resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.1.2.tgz#13e420a92422b6cf244dff8690ed89401029fbe8" - integrity sha512-o2RiJQ6DZaR/5+Si0qJUIy637QMRudSi9kU/FFzx9EZazrIdnBgpU+3sEWCxAVhH2RtxW2Oz+T4p2o8uOPVcgA== +fraction.js@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950" + integrity sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA== fresh@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= -fs-extra@^10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.0.0.tgz#9ff61b655dde53fb34a82df84bb214ce802e17c1" - integrity sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ== +fs-extra@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.0.1.tgz#27de43b4320e833f6867cc044bfce29fdf0ef3b8" + integrity sha512-NbdoVMZso2Lsrn/QwLXOy6rm0ufY2zEOKCDzJR/0kBsb0E6qed0P3iYK+Ath3BfvXEeu4JhEtXLgILx5psUfag== dependencies: graceful-fs "^4.2.0" jsonfile "^6.0.1" @@ -3897,18 +4018,30 @@ globals@^11.1.0: resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== -globby@^11.0.1, globby@^11.0.2, globby@^11.0.3, globby@^11.0.4: - version "11.0.4" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5" - integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg== +globby@^11.0.1, globby@^11.0.4, globby@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== dependencies: array-union "^2.1.0" dir-glob "^3.0.1" - fast-glob "^3.1.1" - ignore "^5.1.4" - merge2 "^1.3.0" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" slash "^3.0.0" +globby@^12.0.2: + version "12.2.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-12.2.0.tgz#2ab8046b4fba4ff6eede835b29f678f90e3d3c22" + integrity sha512-wiSuFQLZ+urS9x2gGPl1H5drc5twabmm4m2gTR27XDFyjUHJUNsS8o/2aKyIF6IoBaR630atdher0XJ5g6OMmA== + dependencies: + array-union "^3.0.1" + dir-glob "^3.0.1" + fast-glob "^3.2.7" + ignore "^5.1.9" + merge2 "^1.4.1" + slash "^4.0.0" + got@^9.6.0: version "9.6.0" resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" @@ -3926,10 +4059,10 @@ got@^9.6.0: to-readable-stream "^1.0.0" url-parse-lax "^3.0.0" -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6: - version "4.2.8" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" - integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: + version "4.2.9" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" + integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== gray-matter@^4.0.3: version "4.0.3" @@ -3941,14 +4074,6 @@ gray-matter@^4.0.3: section-matter "^1.0.0" strip-bom-string "^1.0.0" -gzip-size@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.1.1.tgz#cb9bee692f87c0612b232840a873904e4c135274" - integrity sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA== - dependencies: - duplexer "^0.1.1" - pify "^4.0.1" - gzip-size@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462" @@ -3972,9 +4097,9 @@ has-flag@^4.0.0: integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== has-symbols@^1.0.1, has-symbols@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" - integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== has-tostringtag@^1.0.0: version "1.0.0" @@ -4123,7 +4248,7 @@ html-entities@^2.3.2: resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.3.2.tgz#760b404685cb1d794e4f4b744332e3b00dcfe488" integrity sha512-c3Ab/url5ksaT0WyleslpBEthOzWhrjQbg75y7XUsfSzi3Dgzt0l8w5e7DylRn15MTlMMD58dTfzddNS2kcAjQ== -html-minifier-terser@^6.0.2: +html-minifier-terser@^6.0.2, html-minifier-terser@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#bfc818934cc07918f6b3669f5774ecdfd48f32ab" integrity sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw== @@ -4146,7 +4271,7 @@ html-void-elements@^1.0.0: resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-1.0.5.tgz#ce9159494e86d95e45795b166c2021c2cfca4483" integrity sha512-uE/TxKuyNIcx44cIWnjr/rfIATDH7ZaOMmstu0CwhFG1Dunhlp4OC6/NMbhiwoq5BpW0ubi303qnEk/PZj614w== -html-webpack-plugin@^5.4.0: +html-webpack-plugin@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz#c3911936f57681c1f9f4d8b68c158cd9dfe52f50" integrity sha512-sy88PC2cRTVxvETRgUHFrL4No3UxvcH8G1NepGhqaTT+GXN2kTamqasot0inS5hXeg1cMbFDt27zzo9p35lZVw== @@ -4211,16 +4336,16 @@ http-errors@~1.6.2: statuses ">= 1.4.0 < 2" http-parser-js@>=0.5.1: - version "0.5.5" - resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.5.tgz#d7c30d5d3c90d865b4a2e870181f9d6f22ac7ac5" - integrity sha512-x+JVEkO2PoM8qqpbPbOL3cqHPwerep7OwzK7Ay+sMQjKzaKCqWvjoXm5tqMP9tXWWTnTzAjIhXg+J99XYuPhPA== + version "0.5.6" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.6.tgz#2e02406ab2df8af8a7abfba62e0da01c62b95afd" + integrity sha512-vDlkRPDJn93swjcjqMSaGSPABbIarsr1TLAui/gLDXzV5VsJNdXNzMYDyNBLQkjWQCJ1uizu8T2oDMhmGt0PRA== http-proxy-middleware@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.1.tgz#7ef3417a479fb7666a571e09966c66a39bd2c15f" - integrity sha512-cfaXRVoZxSed/BmkA7SwBVNI9Kj7HFltaE5rqYOub5kWzWZ+gofV2koVN1j2rMW7pEfSSlCHGJ31xmuyFyfLOg== + version "2.0.4" + resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.4.tgz#03af0f4676d172ae775cb5c33f592f40e1a4e07a" + integrity sha512-m/4FxX17SUvz4lJ5WPXOHDUuCwIqXLfLHs1s0uZ3oYjhoXlx9csYxaOa0ElDEJ+h8Q4iJ1s+lTMbiCa4EXIJqg== dependencies: - "@types/http-proxy" "^1.17.5" + "@types/http-proxy" "^1.17.8" http-proxy "^1.18.1" is-glob "^4.0.1" is-plain-obj "^3.0.0" @@ -4252,17 +4377,24 @@ icss-utils@^5.0.0, icss-utils@^5.1.0: resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== -ignore@^5.1.4: +ignore@^5.1.9, ignore@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== -immer@^9.0.6: - version "9.0.7" - resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.7.tgz#b6156bd7db55db7abc73fd2fdadf4e579a701075" - integrity sha512-KGllzpbamZDvOIxnmJ0jI840g7Oikx58lBPWV0hUh7dtAyZpFqqrBZdKka5GlTwMTZ1Tjc/bKKW4VSFAt6BqMA== +image-size@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/image-size/-/image-size-1.0.1.tgz#86d6cfc2b1d19eab5d2b368d4b9194d9e48541c5" + integrity sha512-VAwkvNSNGClRw9mDHhc5Efax8PLlsOGcUTh0T/LIriC8vPA3U5PdqXWqkz406MoYHMKW8Uf9gWr05T/rYB44kQ== + dependencies: + queue "6.0.2" + +immer@^9.0.7: + version "9.0.12" + resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.12.tgz#2d33ddf3ee1d247deab9d707ca472c8c942a0f20" + integrity sha512-lk7UNmSbAukB5B6dh9fnh5D0bJTOFKxVg2cyJWTYrWRfhLrLMBquONcUs3aFq507hNoIZEDDh8lb8UtOizSMhA== -import-fresh@^3.1.0, import-fresh@^3.2.1, import-fresh@^3.2.2, import-fresh@^3.3.0: +import-fresh@^3.1.0, import-fresh@^3.2.1, import-fresh@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== @@ -4285,10 +4417,10 @@ indent-string@^4.0.0: resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== -infima@0.2.0-alpha.37: - version "0.2.0-alpha.37" - resolved "https://registry.yarnpkg.com/infima/-/infima-0.2.0-alpha.37.tgz#b87ff42d528d6d050098a560f0294fbdd12adb78" - integrity sha512-4GX7Baw+/lwS4PPW/UJNY89tWSvYG1DL6baKVdpK6mC593iRgMssxNtORMTFArLPJ/A/lzsGhRmx+z6MaMxj0Q== +infima@0.2.0-alpha.38: + version "0.2.0-alpha.38" + resolved "https://registry.yarnpkg.com/infima/-/infima-0.2.0-alpha.38.tgz#e41d95c7cd82756549b17df12f613fed4af3d528" + integrity sha512-1WsmqSMI5IqzrUx3goq+miJznHBonbE3aoqZ1AR/i/oHhroxNeSV6Awv5VoVfXBhfTzLSnxkHaRI2qpAMYcCzw== inflight@^1.0.4: version "1.0.6" @@ -4328,6 +4460,13 @@ interpret@^1.0.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== +invariant@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + dependencies: + loose-envify "^1.0.0" + ip@^1.1.0: version "1.1.5" resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" @@ -4388,10 +4527,10 @@ is-ci@^2.0.0: dependencies: ci-info "^2.0.0" -is-core-module@^2.2.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.0.tgz#0321336c3d0925e497fd97f5d95cb114a5ccd548" - integrity sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw== +is-core-module@^2.8.1: + version "2.8.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.1.tgz#f59fdfca701d5879d0a6b100a40aa1560ce27211" + integrity sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA== dependencies: has "^1.0.3" @@ -4532,7 +4671,7 @@ is-word-character@^1.0.0: resolved "https://registry.yarnpkg.com/is-word-character/-/is-word-character-1.0.4.tgz#ce0e73216f98599060592f62ff31354ddbeb0230" integrity sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA== -is-wsl@^2.1.1, is-wsl@^2.2.0: +is-wsl@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== @@ -4564,19 +4703,19 @@ isobject@^3.0.1: resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= -jest-worker@^27.0.2, jest-worker@^27.4.1: - version "27.4.5" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.4.5.tgz#d696e3e46ae0f24cff3fa7195ffba22889262242" - integrity sha512-f2s8kEdy15cv9r7q4KkzGXvlY0JTcmCbMHZBfSQDwW77REr45IDWwd0lksDFeVHH2jJ5pqb90T77XscrjeGzzg== +jest-worker@^27.0.2, jest-worker@^27.4.5: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" + integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== dependencies: "@types/node" "*" merge-stream "^2.0.0" supports-color "^8.0.0" -joi@^17.4.0, joi@^17.4.2: - version "17.5.0" - resolved "https://registry.yarnpkg.com/joi/-/joi-17.5.0.tgz#7e66d0004b5045d971cf416a55fb61d33ac6e011" - integrity sha512-R7hR50COp7StzLnDi4ywOXHrBrgNXuUUfJWIR5lPY5Bm/pOD3jZaTwpluUXVLRWcoWZxkrHBBJ5hLxgnlehbdw== +joi@^17.6.0: + version "17.6.0" + resolved "https://registry.yarnpkg.com/joi/-/joi-17.6.0.tgz#0bb54f2f006c09a96e75ce687957bd04290054b2" + integrity sha512-OX5dG6DTbcr/kbMFj0KGYxuew69HPcAE3K/sZpEV2nP6e/j/C0HV+HNiBPCASxdx5T7DMoa0s8UeHWMnb6n2zw== dependencies: "@hapi/hoek" "^9.0.0" "@hapi/topo" "^5.0.0" @@ -4597,7 +4736,7 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@^4.0.0: +js-yaml@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== @@ -4639,19 +4778,10 @@ json-schema-traverse@^1.0.0: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== -json5@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" - integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== - dependencies: - minimist "^1.2.0" - json5@^2.1.2: - version "2.2.0" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" - integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== - dependencies: - minimist "^1.2.5" + version "2.2.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" + integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== jsonfile@^6.0.1: version "6.1.0" @@ -4697,9 +4827,9 @@ leven@^3.1.0: integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== lilconfig@^2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.4.tgz#f4507d043d7058b380b6a8f5cb7bcd4b34cee082" - integrity sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA== + version "2.0.5" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.5.tgz#19e57fd06ccc3848fd1891655b5a447092225b25" + integrity sha512-xaYmXZtTHPAw5m+xLN8ab9C+3a8YmV3asNSPOATITbtwrfbwaLJj8h66H1WMIpALCkqsIzK3h7oQ+PdX+LQ9Eg== lines-and-columns@^1.1.6: version "1.2.4" @@ -4711,15 +4841,6 @@ loader-runner@^4.2.0: resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.2.0.tgz#d7022380d66d14c5fb1d496b89864ebcfd478384" integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw== -loader-utils@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" - integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== - dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^1.0.1" - loader-utils@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.2.tgz#d6e3b4fb81870721ae4e0868ab11dd638368c129" @@ -4729,6 +4850,11 @@ loader-utils@^2.0.0: emojis-list "^3.0.0" json5 "^2.1.2" +loader-utils@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-3.2.0.tgz#bcecc51a7898bee7473d4bc6b845b23af8304d4f" + integrity sha512-HVl9ZqccQihZ7JM85dco1MvO9G+ONvxoGa9rkhzFsneGLKSUg1gJf9bWzhRhcvm2qChhWpebQhP44qxjKIUCaQ== + locate-path@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" @@ -4872,13 +4998,6 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" -magic-string@^0.25.3: - version "0.25.7" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" - integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA== - dependencies: - sourcemap-codec "^1.4.4" - make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" @@ -4939,10 +5058,10 @@ media-typer@0.3.0: resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= -memfs@^3.1.2, memfs@^3.2.2: - version "3.4.0" - resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.4.0.tgz#8bc12062b973be6b295d4340595736a656f0a257" - integrity sha512-o/RfP0J1d03YwsAxyHxAYs2kyJp55AFkMazlFAZFR2I2IXkxiUTXRabJ6RmNNCQ83LAD2jy52Khj0m3OffpNdA== +memfs@^3.1.2, memfs@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.4.1.tgz#b78092f466a0dce054d63d39275b24c71d3f1305" + integrity sha512-1c9VPVvW5P7I85c35zAdEr1TD5+F11IToIHIlrVIcflfnzPkJa0ZoYEoEdYDP8KgPFoSZ/opDrUsAoZWym3mtw== dependencies: fs-monkey "1.0.3" @@ -4956,7 +5075,7 @@ merge-stream@^2.0.0: resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== -merge2@^1.3.0: +merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== @@ -4966,18 +5085,18 @@ methods@~1.1.2: resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= -micromatch@^4.0.2, micromatch@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" - integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== +micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== dependencies: - braces "^3.0.1" - picomatch "^2.2.3" + braces "^3.0.2" + picomatch "^2.3.1" -mime-db@1.51.0, "mime-db@>= 1.43.0 < 2": - version "1.51.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.51.0.tgz#d9ff62451859b18342d960850dc3cfb77e63fb0c" - integrity sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g== +mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== mime-db@~1.33.0: version "1.33.0" @@ -4991,12 +5110,12 @@ mime-types@2.1.18: dependencies: mime-db "~1.33.0" -mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24: - version "2.1.34" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.34.tgz#5a712f9ec1503511a945803640fafe09d3793c24" - integrity sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A== +mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== dependencies: - mime-db "1.51.0" + mime-db "1.52.0" mime@1.6.0: version "1.6.0" @@ -5021,38 +5140,43 @@ mini-create-react-context@^0.4.0: "@babel/runtime" "^7.12.1" tiny-warning "^1.0.3" -mini-css-extract-plugin@^1.6.0: - version "1.6.2" - resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-1.6.2.tgz#83172b4fd812f8fc4a09d6f6d16f924f53990ca8" - integrity sha512-WhDvO3SjGm40oV5y26GjMJYjd2UMqrLAGKy5YS2/3QKJy2F7jgynuHTir/tgUUOiNQu5saXHdc8reo7YuhhT4Q== +mini-css-extract-plugin@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.6.0.tgz#578aebc7fc14d32c0ad304c2c34f08af44673f5e" + integrity sha512-ndG8nxCEnAemsg4FSgS+yNyHKgkTB4nPKqCOgh65j3/30qqC5RaSQQXMm++Y6sb6E1zRSxPkztj9fqxhS1Eo6w== dependencies: - loader-utils "^2.0.0" - schema-utils "^3.0.0" - webpack-sources "^1.1.0" + schema-utils "^4.0.0" minimalistic-assert@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== -minimatch@3.0.4, minimatch@^3.0.4: +minimatch@3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== dependencies: brace-expansion "^1.1.7" -minimist@^1.2.0, minimist@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" - integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== +minimatch@^3.0.4: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: + version "1.2.6" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" + integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== mkdirp@^0.5.5: - version "0.5.5" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" - integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== + version "0.5.6" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" + integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== dependencies: - minimist "^1.2.5" + minimist "^1.2.6" mrmime@^1.0.0: version "1.0.0" @@ -5087,15 +5211,15 @@ multicast-dns@^6.0.1: dns-packet "^1.3.1" thunky "^1.0.2" -nanoid@^3.1.30: - version "3.1.30" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.30.tgz#63f93cc548d2a113dc5dfbc63bfa09e2b9b64362" - integrity sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ== +nanoid@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35" + integrity sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw== -negotiator@0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" - integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== +negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== neo-async@^2.6.2: version "2.6.2" @@ -5117,20 +5241,22 @@ node-emoji@^1.10.0: dependencies: lodash "^4.17.21" -node-fetch@2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" - integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== +node-fetch@2.6.7: + version "2.6.7" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" + integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== + dependencies: + whatwg-url "^5.0.0" -node-forge@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" - integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA== +node-forge@^1.2.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.0.tgz#37a874ea723855f37db091e6c186e5b67a01d4b2" + integrity sha512-08ARB91bUi6zNKzVmaj3QO7cr397uiDT2nJ63cHjyNtCTWIgvS47j3eT0WfzUwS9+6Z5YshRaoasFkXCKrIYbA== -node-releases@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.1.tgz#3d1d395f204f1f2f29a54358b9fb678765ad2fc5" - integrity sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA== +node-releases@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.2.tgz#7139fe71e2f4f11b47d4d2986aaf8c48699e0c01" + integrity sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg== normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" @@ -5237,15 +5363,7 @@ onetime@^5.1.2: dependencies: mimic-fn "^2.1.0" -open@^7.0.2: - version "7.4.2" - resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" - integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q== - dependencies: - is-docker "^2.0.0" - is-wsl "^2.1.1" - -open@^8.0.9: +open@^8.0.9, open@^8.4.0: version "8.4.0" resolved "https://registry.yarnpkg.com/open/-/open-8.4.0.tgz#345321ae18f8138f82565a910fdc6b39e8c244f8" integrity sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q== @@ -5371,12 +5489,19 @@ parse-numeric-range@^1.3.0: resolved "https://registry.yarnpkg.com/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz#7c63b61190d61e4d53a1197f0c83c47bb670ffa3" integrity sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ== +parse5-htmlparser2-tree-adapter@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6" + integrity sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA== + dependencies: + parse5 "^6.0.1" + parse5@^5.0.0: version "5.1.1" resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178" integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug== -parse5@^6.0.0: +parse5@^6.0.0, parse5@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== @@ -5419,7 +5544,7 @@ path-key@^3.0.0, path-key@^3.1.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== -path-parse@^1.0.6: +path-parse@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== @@ -5446,25 +5571,15 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -picocolors@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-0.2.1.tgz#570670f793646851d1ba135996962abad587859f" - integrity sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA== - picocolors@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== -picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3: - version "2.3.0" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" - integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== - -pify@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" - integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== pkg-dir@^4.1.0: version "4.2.0" @@ -5489,59 +5604,59 @@ portfinder@^1.0.28: debug "^3.1.1" mkdirp "^0.5.5" -postcss-calc@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-8.0.0.tgz#a05b87aacd132740a5db09462a3612453e5df90a" - integrity sha512-5NglwDrcbiy8XXfPM11F3HeC6hoT9W7GUH/Zi5U/p7u3Irv4rHhdDcIZwG0llHXV4ftsBjpfWMXAnXNl4lnt8g== +postcss-calc@^8.2.3: + version "8.2.4" + resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-8.2.4.tgz#77b9c29bfcbe8a07ff6693dc87050828889739a5" + integrity sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q== dependencies: - postcss-selector-parser "^6.0.2" - postcss-value-parser "^4.0.2" + postcss-selector-parser "^6.0.9" + postcss-value-parser "^4.2.0" -postcss-colormin@^5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-5.2.2.tgz#019cd6912bef9e7e0924462c5e4ffae241e2f437" - integrity sha512-tSEe3NpqWARUTidDlF0LntPkdlhXqfDFuA1yslqpvvGAfpZ7oBaw+/QXd935NKm2U9p4PED0HDZlzmMk7fVC6g== +postcss-colormin@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-5.3.0.tgz#3cee9e5ca62b2c27e84fce63affc0cfb5901956a" + integrity sha512-WdDO4gOFG2Z8n4P8TWBpshnL3JpmNmJwdnfP2gbk2qBA8PWwOYcmjmI/t3CmMeL72a7Hkd+x/Mg9O2/0rD54Pg== dependencies: browserslist "^4.16.6" caniuse-api "^3.0.0" colord "^2.9.1" postcss-value-parser "^4.2.0" -postcss-convert-values@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-5.0.2.tgz#879b849dc3677c7d6bc94b6a2c1a3f0808798059" - integrity sha512-KQ04E2yadmfa1LqXm7UIDwW1ftxU/QWZmz6NKnHnUvJ3LEYbbcX6i329f/ig+WnEByHegulocXrECaZGLpL8Zg== +postcss-convert-values@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-5.1.0.tgz#f8d3abe40b4ce4b1470702a0706343eac17e7c10" + integrity sha512-GkyPbZEYJiWtQB0KZ0X6qusqFHUepguBCNFi9t5JJc7I2OTXG7C0twbTLvCfaKOLl3rSXmpAwV7W5txd91V84g== dependencies: - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-discard-comments@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-5.0.1.tgz#9eae4b747cf760d31f2447c27f0619d5718901fe" - integrity sha512-lgZBPTDvWrbAYY1v5GYEv8fEO/WhKOu/hmZqmCYfrpD6eyDWWzAOsl2rF29lpvziKO02Gc5GJQtlpkTmakwOWg== +postcss-discard-comments@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-5.1.1.tgz#e90019e1a0e5b99de05f63516ce640bd0df3d369" + integrity sha512-5JscyFmvkUxz/5/+TB3QTTT9Gi9jHkcn8dcmmuN68JQcv3aQg4y88yEHHhwFB52l/NkaJ43O0dbksGMAo49nfQ== -postcss-discard-duplicates@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-5.0.1.tgz#68f7cc6458fe6bab2e46c9f55ae52869f680e66d" - integrity sha512-svx747PWHKOGpAXXQkCc4k/DsWo+6bc5LsVrAsw+OU+Ibi7klFZCyX54gjYzX4TH+f2uzXjRviLARxkMurA2bA== +postcss-discard-duplicates@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz#9eb4fe8456706a4eebd6d3b7b777d07bad03e848" + integrity sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw== -postcss-discard-empty@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-5.0.1.tgz#ee136c39e27d5d2ed4da0ee5ed02bc8a9f8bf6d8" - integrity sha512-vfU8CxAQ6YpMxV2SvMcMIyF2LX1ZzWpy0lqHDsOdaKKLQVQGVP1pzhrI9JlsO65s66uQTfkQBKBD/A5gp9STFw== +postcss-discard-empty@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz#e57762343ff7f503fe53fca553d18d7f0c369c6c" + integrity sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A== -postcss-discard-overridden@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-5.0.1.tgz#454b41f707300b98109a75005ca4ab0ff2743ac6" - integrity sha512-Y28H7y93L2BpJhrdUR2SR2fnSsT+3TVx1NmVQLbcnZWwIUpJ7mfcTC6Za9M2PG6w8j7UQRfzxqn8jU2VwFxo3Q== +postcss-discard-overridden@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz#7e8c5b53325747e9d90131bb88635282fb4a276e" + integrity sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw== -postcss-discard-unused@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-unused/-/postcss-discard-unused-5.0.1.tgz#63e35a74a154912f93d4e75a1e6ff3cc146f934b" - integrity sha512-tD6xR/xyZTwfhKYRw0ylfCY8wbfhrjpKAMnDKRTLMy2fNW5hl0hoV6ap5vo2JdCkuHkP3CHw72beO4Y8pzFdww== +postcss-discard-unused@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-discard-unused/-/postcss-discard-unused-5.1.0.tgz#8974e9b143d887677304e558c1166d3762501142" + integrity sha512-KwLWymI9hbwXmJa0dkrzpRbSJEh0vVUd7r8t0yOGPcfKzyJJxFM8kLyC5Ev9avji6nY95pOp1W6HqIrfT+0VGw== dependencies: postcss-selector-parser "^6.0.5" -postcss-loader@^6.1.1: +postcss-loader@^6.2.1: version "6.2.1" resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-6.2.1.tgz#0895f7346b1702103d30fdc66e4d494a93c008ef" integrity sha512-WbbYpmAaKcux/P66bZ40bpWsBucjx/TTgVVzRZ9yUO8yQfVBlameJ0ZGVaPfH64hNSBh63a+ICP5nqOpBA0w+Q== @@ -5550,64 +5665,62 @@ postcss-loader@^6.1.1: klona "^2.0.5" semver "^7.3.5" -postcss-merge-idents@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-merge-idents/-/postcss-merge-idents-5.0.1.tgz#6b5856fc28f2571f28ecce49effb9b0e64be9437" - integrity sha512-xu8ueVU0RszbI2gKkxR6mluupsOSSLvt8q4gA2fcKFkA+x6SlH3cb4cFHpDvcRCNFbUmCR/VUub+Y6zPOjPx+Q== +postcss-merge-idents@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-merge-idents/-/postcss-merge-idents-5.1.1.tgz#7753817c2e0b75d0853b56f78a89771e15ca04a1" + integrity sha512-pCijL1TREiCoog5nQp7wUe+TUonA2tC2sQ54UGeMmryK3UFGIYKqDyjnqd6RcuI4znFn9hWSLNN8xKE/vWcUQw== dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" + cssnano-utils "^3.1.0" + postcss-value-parser "^4.2.0" -postcss-merge-longhand@^5.0.4: - version "5.0.4" - resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-5.0.4.tgz#41f4f3270282ea1a145ece078b7679f0cef21c32" - integrity sha512-2lZrOVD+d81aoYkZDpWu6+3dTAAGkCKbV5DoRhnIR7KOULVrI/R7bcMjhrH9KTRy6iiHKqmtG+n/MMj1WmqHFw== +postcss-merge-longhand@^5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-5.1.3.tgz#a49e2be6237316e3b55e329e0a8da15d1f9f47ab" + integrity sha512-lX8GPGvZ0iGP/IboM7HXH5JwkXvXod1Rr8H8ixwiA372hArk0zP4ZcCy4z4Prg/bfNlbbTf0KCOjCF9kKnpP/w== dependencies: - postcss-value-parser "^4.1.0" - stylehacks "^5.0.1" + postcss-value-parser "^4.2.0" + stylehacks "^5.1.0" -postcss-merge-rules@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-5.0.3.tgz#b5cae31f53129812a77e3eb1eeee448f8cf1a1db" - integrity sha512-cEKTMEbWazVa5NXd8deLdCnXl+6cYG7m2am+1HzqH0EnTdy8fRysatkaXb2dEnR+fdaDxTvuZ5zoBdv6efF6hg== +postcss-merge-rules@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-5.1.1.tgz#d327b221cd07540bcc8d9ff84446d8b404d00162" + integrity sha512-8wv8q2cXjEuCcgpIB1Xx1pIy8/rhMPIQqYKNzEdyx37m6gpq83mQQdCxgIkFgliyEnKvdwJf/C61vN4tQDq4Ww== dependencies: browserslist "^4.16.6" caniuse-api "^3.0.0" - cssnano-utils "^2.0.1" + cssnano-utils "^3.1.0" postcss-selector-parser "^6.0.5" -postcss-minify-font-values@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-5.0.1.tgz#a90cefbfdaa075bd3dbaa1b33588bb4dc268addf" - integrity sha512-7JS4qIsnqaxk+FXY1E8dHBDmraYFWmuL6cgt0T1SWGRO5bzJf8sUoelwa4P88LEWJZweHevAiDKxHlofuvtIoA== +postcss-minify-font-values@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz#f1df0014a726083d260d3bd85d7385fb89d1f01b" + integrity sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA== dependencies: - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-minify-gradients@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-5.0.3.tgz#f970a11cc71e08e9095e78ec3a6b34b91c19550e" - integrity sha512-Z91Ol22nB6XJW+5oe31+YxRsYooxOdFKcbOqY/V8Fxse1Y3vqlNRpi1cxCqoACZTQEhl+xvt4hsbWiV5R+XI9Q== +postcss-minify-gradients@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz#f1fe1b4f498134a5068240c2f25d46fcd236ba2c" + integrity sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw== dependencies: colord "^2.9.1" - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" + cssnano-utils "^3.1.0" + postcss-value-parser "^4.2.0" -postcss-minify-params@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-5.0.2.tgz#1b644da903473fbbb18fbe07b8e239883684b85c" - integrity sha512-qJAPuBzxO1yhLad7h2Dzk/F7n1vPyfHfCCh5grjGfjhi1ttCnq4ZXGIW77GSrEbh9Hus9Lc/e/+tB4vh3/GpDg== +postcss-minify-params@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-5.1.2.tgz#77e250780c64198289c954884ebe3ee4481c3b1c" + integrity sha512-aEP+p71S/urY48HWaRHasyx4WHQJyOYaKpQ6eXl8k0kxg66Wt/30VR6/woh8THgcpRbonJD5IeD+CzNhPi1L8g== dependencies: - alphanum-sort "^1.0.2" browserslist "^4.16.6" - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" + cssnano-utils "^3.1.0" + postcss-value-parser "^4.2.0" -postcss-minify-selectors@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-5.1.0.tgz#4385c845d3979ff160291774523ffa54eafd5a54" - integrity sha512-NzGBXDa7aPsAcijXZeagnJBKBPMYLaJJzB8CQh6ncvyl2sIndLVWfbcDi0SBjRWk5VqEjXvf8tYwzoKf4Z07og== +postcss-minify-selectors@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-5.2.0.tgz#17c2be233e12b28ffa8a421a02fc8b839825536c" + integrity sha512-vYxvHkW+iULstA+ctVNx0VoRAR4THQQRkG77o0oa4/mBS0OzGvvzLIvHDv/nNEM0crzN2WIyFU5X7wZhaUK3RA== dependencies: - alphanum-sort "^1.0.2" postcss-selector-parser "^6.0.5" postcss-modules-extract-imports@^3.0.0: @@ -5638,160 +5751,147 @@ postcss-modules-values@^4.0.0: dependencies: icss-utils "^5.0.0" -postcss-normalize-charset@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-5.0.1.tgz#121559d1bebc55ac8d24af37f67bd4da9efd91d0" - integrity sha512-6J40l6LNYnBdPSk+BHZ8SF+HAkS4q2twe5jnocgd+xWpz/mx/5Sa32m3W1AA8uE8XaXN+eg8trIlfu8V9x61eg== +postcss-normalize-charset@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz#9302de0b29094b52c259e9b2cf8dc0879879f0ed" + integrity sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg== -postcss-normalize-display-values@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-5.0.1.tgz#62650b965981a955dffee83363453db82f6ad1fd" - integrity sha512-uupdvWk88kLDXi5HEyI9IaAJTE3/Djbcrqq8YgjvAVuzgVuqIk3SuJWUisT2gaJbZm1H9g5k2w1xXilM3x8DjQ== +postcss-normalize-display-values@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz#72abbae58081960e9edd7200fcf21ab8325c3da8" + integrity sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA== dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-normalize-positions@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-5.0.1.tgz#868f6af1795fdfa86fbbe960dceb47e5f9492fe5" - integrity sha512-rvzWAJai5xej9yWqlCb1OWLd9JjW2Ex2BCPzUJrbaXmtKtgfL8dBMOOMTX6TnvQMtjk3ei1Lswcs78qKO1Skrg== +postcss-normalize-positions@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-5.1.0.tgz#902a7cb97cf0b9e8b1b654d4a43d451e48966458" + integrity sha512-8gmItgA4H5xiUxgN/3TVvXRoJxkAWLW6f/KKhdsH03atg0cB8ilXnrB5PpSshwVu/dD2ZsRFQcR1OEmSBDAgcQ== dependencies: - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-normalize-repeat-style@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.0.1.tgz#cbc0de1383b57f5bb61ddd6a84653b5e8665b2b5" - integrity sha512-syZ2itq0HTQjj4QtXZOeefomckiV5TaUO6ReIEabCh3wgDs4Mr01pkif0MeVwKyU/LHEkPJnpwFKRxqWA/7O3w== +postcss-normalize-repeat-style@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.0.tgz#f6d6fd5a54f51a741cc84a37f7459e60ef7a6398" + integrity sha512-IR3uBjc+7mcWGL6CtniKNQ4Rr5fTxwkaDHwMBDGGs1x9IVRkYIT/M4NelZWkAOBdV6v3Z9S46zqaKGlyzHSchw== dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-normalize-string@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-5.0.1.tgz#d9eafaa4df78c7a3b973ae346ef0e47c554985b0" - integrity sha512-Ic8GaQ3jPMVl1OEn2U//2pm93AXUcF3wz+OriskdZ1AOuYV25OdgS7w9Xu2LO5cGyhHCgn8dMXh9bO7vi3i9pA== +postcss-normalize-string@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz#411961169e07308c82c1f8c55f3e8a337757e228" + integrity sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w== dependencies: - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-normalize-timing-functions@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.0.1.tgz#8ee41103b9130429c6cbba736932b75c5e2cb08c" - integrity sha512-cPcBdVN5OsWCNEo5hiXfLUnXfTGtSFiBU9SK8k7ii8UD7OLuznzgNRYkLZow11BkQiiqMcgPyh4ZqXEEUrtQ1Q== +postcss-normalize-timing-functions@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz#d5614410f8f0b2388e9f240aa6011ba6f52dafbb" + integrity sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg== dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-normalize-unicode@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-5.0.1.tgz#82d672d648a411814aa5bf3ae565379ccd9f5e37" - integrity sha512-kAtYD6V3pK0beqrU90gpCQB7g6AOfP/2KIPCVBKJM2EheVsBQmx/Iof+9zR9NFKLAx4Pr9mDhogB27pmn354nA== +postcss-normalize-unicode@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.0.tgz#3d23aede35e160089a285e27bf715de11dc9db75" + integrity sha512-J6M3MizAAZ2dOdSjy2caayJLQT8E8K9XjLce8AUQMwOrCvjCHv24aLC/Lps1R1ylOfol5VIDMaM/Lo9NGlk1SQ== dependencies: - browserslist "^4.16.0" - postcss-value-parser "^4.1.0" + browserslist "^4.16.6" + postcss-value-parser "^4.2.0" -postcss-normalize-url@^5.0.4: - version "5.0.4" - resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-5.0.4.tgz#3b0322c425e31dd275174d0d5db0e466f50810fb" - integrity sha512-cNj3RzK2pgQQyNp7dzq0dqpUpQ/wYtdDZM3DepPmFjCmYIfceuD9VIAcOdvrNetjIU65g1B4uwdP/Krf6AFdXg== +postcss-normalize-url@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz#ed9d88ca82e21abef99f743457d3729a042adcdc" + integrity sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew== dependencies: normalize-url "^6.0.1" postcss-value-parser "^4.2.0" -postcss-normalize-whitespace@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.0.1.tgz#b0b40b5bcac83585ff07ead2daf2dcfbeeef8e9a" - integrity sha512-iPklmI5SBnRvwceb/XH568yyzK0qRVuAG+a1HFUsFRf11lEJTiQQa03a4RSCQvLKdcpX7XsI1Gen9LuLoqwiqA== +postcss-normalize-whitespace@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz#08a1a0d1ffa17a7cc6efe1e6c9da969cc4493cfa" + integrity sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA== dependencies: - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-ordered-values@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-5.0.2.tgz#1f351426977be00e0f765b3164ad753dac8ed044" - integrity sha512-8AFYDSOYWebJYLyJi3fyjl6CqMEG/UVworjiyK1r573I56kb3e879sCJLGvR3merj+fAdPpVplXKQZv+ey6CgQ== +postcss-ordered-values@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-5.1.1.tgz#0b41b610ba02906a3341e92cab01ff8ebc598adb" + integrity sha512-7lxgXF0NaoMIgyihL/2boNAEZKiW0+HkMhdKMTD93CjW8TdCy2hSdj8lsAo+uwm7EDG16Da2Jdmtqpedl0cMfw== dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" + cssnano-utils "^3.1.0" + postcss-value-parser "^4.2.0" -postcss-reduce-idents@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-reduce-idents/-/postcss-reduce-idents-5.0.1.tgz#99b49ce8ee6f9c179447671cc9693e198e877bb7" - integrity sha512-6Rw8iIVFbqtaZExgWK1rpVgP7DPFRPh0DDFZxJ/ADNqPiH10sPCoq5tgo6kLiTyfh9sxjKYjXdc8udLEcPOezg== +postcss-reduce-idents@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/postcss-reduce-idents/-/postcss-reduce-idents-5.2.0.tgz#c89c11336c432ac4b28792f24778859a67dfba95" + integrity sha512-BTrLjICoSB6gxbc58D5mdBK8OhXRDqud/zodYfdSi52qvDHdMwk+9kB9xsM8yJThH/sZU5A6QVSmMmaN001gIg== dependencies: - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-reduce-initial@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-5.0.2.tgz#fa424ce8aa88a89bc0b6d0f94871b24abe94c048" - integrity sha512-v/kbAAQ+S1V5v9TJvbGkV98V2ERPdU6XvMcKMjqAlYiJ2NtsHGlKYLPjWWcXlaTKNxooId7BGxeraK8qXvzKtw== +postcss-reduce-initial@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-5.1.0.tgz#fc31659ea6e85c492fb2a7b545370c215822c5d6" + integrity sha512-5OgTUviz0aeH6MtBjHfbr57tml13PuedK/Ecg8szzd4XRMbYxH4572JFG067z+FqBIf6Zp/d+0581glkvvWMFw== dependencies: browserslist "^4.16.6" caniuse-api "^3.0.0" -postcss-reduce-transforms@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-5.0.1.tgz#93c12f6a159474aa711d5269923e2383cedcf640" - integrity sha512-a//FjoPeFkRuAguPscTVmRQUODP+f3ke2HqFNgGPwdYnpeC29RZdCBvGRGTsKpMURb/I3p6jdKoBQ2zI+9Q7kA== +postcss-reduce-transforms@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz#333b70e7758b802f3dd0ddfe98bb1ccfef96b6e9" + integrity sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ== dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5: - version "6.0.7" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.7.tgz#48404830a635113a71fd79397de8209ed05a66fc" - integrity sha512-U+b/Deoi4I/UmE6KOVPpnhS7I7AYdKbhGcat+qTQ27gycvaACvNEw11ba6RrkwVmDVRW7sigWgLj4/KbbJjeDA== +postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5, postcss-selector-parser@^6.0.9: + version "6.0.9" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.9.tgz#ee71c3b9ff63d9cd130838876c13a2ec1a992b2f" + integrity sha512-UO3SgnZOVTwu4kyLR22UQ1xZh086RyNZppb7lLAKBFK8a32ttG5i87Y/P3+2bRSjZNyJ1B7hfFNo273tKe9YxQ== dependencies: cssesc "^3.0.0" util-deprecate "^1.0.2" -postcss-sort-media-queries@^4.1.0: +postcss-sort-media-queries@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/postcss-sort-media-queries/-/postcss-sort-media-queries-4.2.1.tgz#a99bae69ef1098ee3b64a5fa94d258ec240d0355" integrity sha512-9VYekQalFZ3sdgcTjXMa0dDjsfBVHXlraYJEMiOJ/2iMmI2JGCMavP16z3kWOaRu8NSaJCTgVpB/IVpH5yT9YQ== dependencies: sort-css-media-queries "2.0.4" -postcss-svgo@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-5.0.3.tgz#d945185756e5dfaae07f9edb0d3cae7ff79f9b30" - integrity sha512-41XZUA1wNDAZrQ3XgWREL/M2zSw8LJPvb5ZWivljBsUQAGoEKMYm6okHsTjJxKYI4M75RQEH4KYlEM52VwdXVA== +postcss-svgo@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-5.1.0.tgz#0a317400ced789f233a28826e77523f15857d80d" + integrity sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA== dependencies: - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" svgo "^2.7.0" -postcss-unique-selectors@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-5.0.2.tgz#5d6893daf534ae52626708e0d62250890108c0c1" - integrity sha512-w3zBVlrtZm7loQWRPVC0yjUwwpty7OM6DnEHkxcSQXO1bMS3RJ+JUS5LFMSDZHJcvGsRwhZinCWVqn8Kej4EDA== +postcss-unique-selectors@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz#a9f273d1eacd09e9aa6088f4b0507b18b1b541b6" + integrity sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA== dependencies: - alphanum-sort "^1.0.2" postcss-selector-parser "^6.0.5" -postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: +postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss-zindex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-zindex/-/postcss-zindex-5.0.1.tgz#c585724beb69d356af8c7e68847b28d6298ece03" - integrity sha512-nwgtJJys+XmmSGoYCcgkf/VczP8Mp/0OfSv3v0+fw0uABY4yxw+eFs0Xp9nAZHIKnS5j+e9ywQ+RD+ONyvl5pA== - -"postcss@5 - 7": - version "7.0.39" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.39.tgz#9624375d965630e2e1f2c02a935c82a59cb48309" - integrity sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA== - dependencies: - picocolors "^0.2.1" - source-map "^0.6.1" +postcss-zindex@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-zindex/-/postcss-zindex-5.1.0.tgz#4a5c7e5ff1050bd4c01d95b1847dfdcc58a496ff" + integrity sha512-fgFMf0OtVSBR1va1JNHYgMxYk73yhn/qb4uQDq1DLGYolz8gHCyr/sesEuGUaYs58E3ZJRcpoGuPVoB7Meiq9A== -postcss@^8.2.15, postcss@^8.3.11, postcss@^8.3.5, postcss@^8.3.7: - version "8.4.5" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.5.tgz#bae665764dfd4c6fcc24dc0fdf7e7aa00cc77f95" - integrity sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg== +postcss@^8.3.11, postcss@^8.3.5, postcss@^8.4.12, postcss@^8.4.7: + version "8.4.12" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.12.tgz#1e7de78733b28970fa4743f7da6f3763648b1905" + integrity sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg== dependencies: - nanoid "^3.1.30" + nanoid "^3.3.1" picocolors "^1.0.0" - source-map-js "^1.0.1" + source-map-js "^1.0.2" prepend-http@^2.0.0: version "2.0.0" @@ -5811,15 +5911,15 @@ pretty-time@^1.1.0: resolved "https://registry.yarnpkg.com/pretty-time/-/pretty-time-1.1.0.tgz#ffb7429afabb8535c346a34e41873adf3d74dd0e" integrity sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA== -prism-react-renderer@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-1.2.1.tgz#392460acf63540960e5e3caa699d851264e99b89" - integrity sha512-w23ch4f75V1Tnz8DajsYKvY5lF7H1+WvzvLUcF0paFxkTHSp42RS0H5CttdN2Q8RR3DRGZ9v5xD/h3n8C8kGmg== +prism-react-renderer@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-1.3.1.tgz#88fc9d0df6bed06ca2b9097421349f8c2f24e30d" + integrity sha512-xUeDMEz074d0zc5y6rxiMp/dlC7C+5IDDlaEUlcBOFE2wddz7hz5PNupb087mPwTt7T9BrFmewObfCBuf/LKwQ== -prismjs@^1.23.0: - version "1.25.0" - resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.25.0.tgz#6f822df1bdad965734b310b315a23315cf999756" - integrity sha512-WCjJHl1KEWbnkQom1+SzftbtXMKQoezOCYs5rECqMN+jP+apI7ftoflyqigqzopSO3hMhTEb0mFClA8lkolgEg== +prismjs@^1.27.0: + version "1.27.0" + resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.27.0.tgz#bb6ee3138a0b438a3653dd4d6ce0cc6510a45057" + integrity sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA== process-nextick-args@~2.0.0: version "2.0.1" @@ -5833,7 +5933,7 @@ promise@^7.1.1: dependencies: asap "~2.0.3" -prompts@^2.4.0, prompts@^2.4.1: +prompts@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== @@ -5842,13 +5942,13 @@ prompts@^2.4.0, prompts@^2.4.1: sisteransi "^1.0.5" prop-types@^15.6.2, prop-types@^15.7.2: - version "15.7.2" - resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" - integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== + version "15.8.1" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" + integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== dependencies: loose-envify "^1.4.0" object-assign "^4.1.1" - react-is "^16.8.1" + react-is "^16.13.1" property-information@^5.0.0, property-information@^5.3.0: version "5.6.0" @@ -5873,11 +5973,6 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" -punycode@1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" - integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= - punycode@^1.3.2: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" @@ -5900,21 +5995,23 @@ pure-color@^1.2.0: resolved "https://registry.yarnpkg.com/pure-color/-/pure-color-1.3.0.tgz#1fe064fb0ac851f0de61320a8bf796836422f33e" integrity sha1-H+Bk+wrIUfDeYTIKi/eWg2Qi8z4= -qs@6.9.6: - version "6.9.6" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.6.tgz#26ed3c8243a431b2924aca84cc90471f35d5a0ee" - integrity sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ== - -querystring@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" - integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= +qs@6.9.7: + version "6.9.7" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.7.tgz#4610846871485e1e048f44ae3b94033f0e675afe" + integrity sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw== queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== +queue@6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/queue/-/queue-6.0.2.tgz#b91525283e2315c7553d2efa18d83e76432fed65" + integrity sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA== + dependencies: + inherits "~2.0.3" + randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" @@ -5932,12 +6029,12 @@ range-parser@^1.2.1, range-parser@~1.2.1: resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== -raw-body@2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.2.tgz#baf3e9c21eebced59dd6533ac872b71f7b61cb32" - integrity sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ== +raw-body@2.4.3: + version "2.4.3" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.3.tgz#8f80305d11c2a0a545c2d9d89d7a0286fcead43c" + integrity sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g== dependencies: - bytes "3.1.1" + bytes "3.1.2" http-errors "1.8.1" iconv-lite "0.4.24" unpipe "1.0.0" @@ -5962,37 +6059,37 @@ react-base16-styling@^0.6.0: lodash.flow "^3.3.0" pure-color "^1.2.0" -react-dev-utils@12.0.0-next.47: - version "12.0.0-next.47" - resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-12.0.0-next.47.tgz#e55c31a05eb30cfd69ca516e8b87d61724e880fb" - integrity sha512-PsE71vP15TZMmp/RZKOJC4fYD5Pvt0+wCoyG3QHclto0d4FyIJI78xGRICOOThZFROqgXYlZP6ddmeybm+jO4w== +react-dev-utils@^12.0.0: + version "12.0.0" + resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-12.0.0.tgz#4eab12cdb95692a077616770b5988f0adf806526" + integrity sha512-xBQkitdxozPxt1YZ9O1097EJiVpwHr9FoAuEVURCKV0Av8NBERovJauzP7bo1ThvuhZ4shsQ1AJiu4vQpoT1AQ== dependencies: - "@babel/code-frame" "^7.10.4" + "@babel/code-frame" "^7.16.0" address "^1.1.2" - browserslist "^4.16.5" - chalk "^2.4.2" + browserslist "^4.18.1" + chalk "^4.1.2" cross-spawn "^7.0.3" detect-port-alt "^1.1.6" - escape-string-regexp "^2.0.0" - filesize "^6.1.0" - find-up "^4.1.0" - fork-ts-checker-webpack-plugin "^6.0.5" + escape-string-regexp "^4.0.0" + filesize "^8.0.6" + find-up "^5.0.0" + fork-ts-checker-webpack-plugin "^6.5.0" global-modules "^2.0.0" - globby "^11.0.1" - gzip-size "^5.1.1" - immer "^9.0.6" + globby "^11.0.4" + gzip-size "^6.0.0" + immer "^9.0.7" is-root "^2.1.0" - loader-utils "^2.0.0" - open "^7.0.2" + loader-utils "^3.2.0" + open "^8.4.0" pkg-up "^3.1.0" - prompts "^2.4.0" - react-error-overlay "7.0.0-next.54+1465357b" + prompts "^2.4.2" + react-error-overlay "^6.0.10" recursive-readdir "^2.2.2" - shell-quote "^1.7.2" - strip-ansi "^6.0.0" + shell-quote "^1.7.3" + strip-ansi "^6.0.1" text-table "^0.2.0" -react-dom@^17.0.1: +react-dom@^17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA== @@ -6001,32 +6098,28 @@ react-dom@^17.0.1: object-assign "^4.1.1" scheduler "^0.20.2" -react-error-overlay@7.0.0-next.54+1465357b: - version "7.0.0-next.54" - resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-7.0.0-next.54.tgz#c1eb5ab86aee15e9552e6d97897b08f2bd06d140" - integrity sha512-b96CiTnZahXPDNH9MKplvt5+jD+BkxDw7q5R3jnkUXze/ux1pLv32BBZmlj0OfCUeMqyz4sAmF+0ccJGVMlpXw== - -react-error-overlay@^6.0.9: +react-error-overlay@^6.0.10: version "6.0.10" resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.10.tgz#0fe26db4fa85d9dbb8624729580e90e7159a59a6" integrity sha512-mKR90fX7Pm5seCOfz8q9F+66VCc1PGsWSBxKbITjfKVQHMNF2zudxHnMdJiB1fRCb+XsbQV9sO9DCkgsMQgBIA== -react-fast-compare@^3.1.1: +react-fast-compare@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb" integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA== -react-helmet@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/react-helmet/-/react-helmet-6.1.0.tgz#a750d5165cb13cf213e44747502652e794468726" - integrity sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw== +react-helmet-async@*, react-helmet-async@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/react-helmet-async/-/react-helmet-async-1.2.3.tgz#57326a69304ea3293036eafb49475e9ba454cb37" + integrity sha512-mCk2silF53Tq/YaYdkl2sB+/tDoPnaxN7dFS/6ZLJb/rhUY2EWGI5Xj2b4jHppScMqY45MbgPSwTxDchKpZ5Kw== dependencies: - object-assign "^4.1.1" + "@babel/runtime" "^7.12.5" + invariant "^2.2.4" prop-types "^15.7.2" - react-fast-compare "^3.1.1" - react-side-effect "^2.1.0" + react-fast-compare "^3.2.0" + shallowequal "^1.1.0" -react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1: +react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -6089,11 +6182,6 @@ react-router@5.2.1, react-router@^5.2.0: tiny-invariant "^1.0.2" tiny-warning "^1.0.0" -react-side-effect@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-2.1.1.tgz#66c5701c3e7560ab4822a4ee2742dee215d72eb3" - integrity sha512-2FoTQzRNTncBVtnzxFOk2mCpcfxQpenBMbk5kSVBg5UcPqV9fRbgY2zhb7GTWWOlpFmAxhClBDlIq8Rsubz1yQ== - react-textarea-autosize@^8.3.2: version "8.3.3" resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.3.3.tgz#f70913945369da453fd554c168f6baacd1fa04d8" @@ -6103,7 +6191,7 @@ react-textarea-autosize@^8.3.2: use-composed-ref "^1.0.0" use-latest "^1.0.0" -react@^17.0.1: +react@^17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA== @@ -6159,10 +6247,10 @@ recursive-readdir@^2.2.2: dependencies: minimatch "3.0.4" -regenerate-unicode-properties@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-9.0.0.tgz#54d09c7115e1f53dc2314a974b32c1c344efe326" - integrity sha512-3E12UeNSPfjrgwjkR81m5J7Aw/T55Tu7nUyZVQYCKEOs+2dkxEY+DpPtZzO4YruuiPb7NkYLVcyJC4+zCbk5pA== +regenerate-unicode-properties@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz#7f442732aa7934a3740c779bb9b3340dccc1fb56" + integrity sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw== dependencies: regenerate "^1.4.2" @@ -6184,22 +6272,22 @@ regenerator-transform@^0.14.2: "@babel/runtime" "^7.8.4" regexp.prototype.flags@^1.2.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz#7ef352ae8d159e758c0eadca6f8fcb4eef07be26" - integrity sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA== + version "1.4.1" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.1.tgz#b3f4c0059af9e47eca9f3f660e51d81307e72307" + integrity sha512-pMR7hBVUUGI7PMA37m2ofIdQCsomVnas+Jn5UPGAHQ+/LlwKm/aTLJHdasmHRzlfeZwHiAOaRSo2rbBDm3nNUQ== dependencies: call-bind "^1.0.2" define-properties "^1.1.3" -regexpu-core@^4.5.4, regexpu-core@^4.7.1: - version "4.8.0" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.8.0.tgz#e5605ba361b67b1718478501327502f4479a98f0" - integrity sha512-1F6bYsoYiz6is+oz70NWur2Vlh9KWtswuRuzJOfeYUrfPX2o8n74AnUVaOGDbUqVGO9fNHu48/pjJO4sNVwsOg== +regexpu-core@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.0.1.tgz#c531122a7840de743dcf9c83e923b5560323ced3" + integrity sha512-CriEZlrKK9VJw/xQGJpQM5rY88BtuL8DM+AEwvcThHilbxiTAy8vq4iJnd2tqq8wLmjbGZzP7ZcKFjbGkmEFrw== dependencies: regenerate "^1.4.2" - regenerate-unicode-properties "^9.0.0" - regjsgen "^0.5.2" - regjsparser "^0.7.0" + regenerate-unicode-properties "^10.0.1" + regjsgen "^0.6.0" + regjsparser "^0.8.2" unicode-match-property-ecmascript "^2.0.0" unicode-match-property-value-ecmascript "^2.0.0" @@ -6217,15 +6305,15 @@ registry-url@^5.0.0: dependencies: rc "^1.2.8" -regjsgen@^0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.2.tgz#92ff295fb1deecbf6ecdab2543d207e91aa33733" - integrity sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A== +regjsgen@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.6.0.tgz#83414c5354afd7d6627b16af5f10f41c4e71808d" + integrity sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA== -regjsparser@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.7.0.tgz#a6b667b54c885e18b52554cb4960ef71187e9968" - integrity sha512-A4pcaORqmNMDVwUjWoTzuhwMGpP+NykpfqAsEgI1FSH/EzC7lrN5TMd+kN8YCovX+jMpu8eaqXgXPCa0g8FQNQ== +regjsparser@^0.8.2: + version "0.8.4" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.8.4.tgz#8a14285ffcc5de78c5b95d62bbf413b6bc132d5f" + integrity sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA== dependencies: jsesc "~0.5.0" @@ -6266,20 +6354,6 @@ remark-footnotes@2.0.0: resolved "https://registry.yarnpkg.com/remark-footnotes/-/remark-footnotes-2.0.0.tgz#9001c4c2ffebba55695d2dd80ffb8b82f7e6303f" integrity sha512-3Clt8ZMH75Ayjp9q4CorNeyjwIxHFcTkaektplKGl2A1jNGEUey8cKL0ZC5vJwfcD5GFGsNLImLG/NGzWIzoMQ== -remark-mdx-remove-exports@^1.6.22: - version "1.6.22" - resolved "https://registry.yarnpkg.com/remark-mdx-remove-exports/-/remark-mdx-remove-exports-1.6.22.tgz#9e34f3d02c9c54b02ca0a1fde946449338d06ecb" - integrity sha512-7g2uiTmTGfz5QyVb+toeX25frbk1Y6yd03RXGPtqx0+DVh86Gb7MkNYbk7H2X27zdZ3CQv1W/JqlFO0Oo8IxVA== - dependencies: - unist-util-remove "2.0.0" - -remark-mdx-remove-imports@^1.6.22: - version "1.6.22" - resolved "https://registry.yarnpkg.com/remark-mdx-remove-imports/-/remark-mdx-remove-imports-1.6.22.tgz#79f711c95359cff437a120d1fbdc1326ec455826" - integrity sha512-lmjAXD8Ltw0TsvBzb45S+Dxx7LTJAtDaMneMAv8LAUIPEyYoKkmGbmVsiF0/pY6mhM1Q16swCmu1TN+ie/vn/A== - dependencies: - unist-util-remove "2.0.0" - remark-mdx@1.6.22: version "1.6.22" resolved "https://registry.yarnpkg.com/remark-mdx/-/remark-mdx-1.6.22.tgz#06a8dab07dcfdd57f3373af7f86bd0e992108bbd" @@ -6365,12 +6439,13 @@ resolve-pathname@^3.0.0: integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng== resolve@^1.1.6, resolve@^1.14.2, resolve@^1.3.2: - version "1.20.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" - integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== + version "1.22.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" + integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw== dependencies: - is-core-module "^2.2.0" - path-parse "^1.0.6" + is-core-module "^2.8.1" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" responselike@^1.0.2: version "1.0.2" @@ -6401,7 +6476,7 @@ rtl-detect@^1.0.4: resolved "https://registry.yarnpkg.com/rtl-detect/-/rtl-detect-1.0.4.tgz#40ae0ea7302a150b96bc75af7d749607392ecac6" integrity sha512-EBR4I2VDSSYr7PkBmFy04uhycIpDKp+21p/jARYXlCSjQksTBQcJ0HFUPOO79EPPH5JS6VAhiIQbycf0O3JAxQ== -rtlcss@^3.3.0: +rtlcss@^3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/rtlcss/-/rtlcss-3.5.0.tgz#c9eb91269827a102bac7ae3115dd5d049de636c3" integrity sha512-wzgMaMFHQTnyi9YOwsx9LjOxYXJPzS8sYnFaKm6R5ysvTkwzHiB0vxnbHwchHQT65PTdBjDG21/kQBWI7q9O7A== @@ -6418,12 +6493,12 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -rxjs@^7.1.0: - version "7.4.0" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.4.0.tgz#a12a44d7eebf016f5ff2441b87f28c9a51cebc68" - integrity sha512-7SQDi7xeTMCJpqViXh8gL/lebcwlp3d831F05+9B44A4B0WfsEwUQHR64gsH1kvJ+Ep/J9K2+n1hVl1CsGN23w== +rxjs@^7.5.4: + version "7.5.5" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.5.5.tgz#2ebad89af0f560f460ad5cc4213219e1f7dd4e9f" + integrity sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw== dependencies: - tslib "~2.1.0" + tslib "^2.1.0" safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" @@ -6503,12 +6578,12 @@ select-hose@^2.0.0: resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= -selfsigned@^1.10.11: - version "1.10.11" - resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.11.tgz#24929cd906fe0f44b6d01fb23999a739537acbe9" - integrity sha512-aVmbPOfViZqOZPgRBT0+3u4yZFHpmnIghLMlAcb5/xhp5ZtB/RVnKhz5vl2M32CLXAqR4kha9zfhNg0Lf/sxKA== +selfsigned@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.0.0.tgz#e927cd5377cbb0a1075302cff8df1042cc2bce5b" + integrity sha512-cUdFiCbKoa1mZ6osuJs2uDHrs0k0oprsKveFiiaBKCNq3SYyb5gs2HxhQyDNLCmL51ZZThqi4YNDpCK6GOP1iQ== dependencies: - node-forge "^0.10.0" + node-forge "^1.2.0" semver-diff@^3.1.1: version "3.1.1" @@ -6624,6 +6699,11 @@ shallow-clone@^3.0.0: dependencies: kind-of "^6.0.2" +shallowequal@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" + integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== + shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -6636,24 +6716,24 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -shell-quote@^1.7.2: +shell-quote@^1.7.3: version "1.7.3" resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.3.tgz#aa40edac170445b9a431e17bb62c0b881b9c4123" integrity sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw== -shelljs@^0.8.4: - version "0.8.4" - resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.4.tgz#de7684feeb767f8716b326078a8a00875890e3c2" - integrity sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ== +shelljs@^0.8.5: + version "0.8.5" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.5.tgz#de055408d8361bed66c669d2f000538ced8ee20c" + integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow== dependencies: glob "^7.0.0" interpret "^1.0.0" rechoir "^0.6.2" signal-exit@^3.0.2, signal-exit@^3.0.3: - version "3.0.6" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.6.tgz#24e630c4b0f03fea446a2bd299e62b4a6ca8d0af" - integrity sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ== + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== sirv@^1.0.7: version "1.0.19" @@ -6669,12 +6749,12 @@ sisteransi@^1.0.5: resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== -sitemap@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/sitemap/-/sitemap-7.0.0.tgz#022bef4df8cba42e38e1fe77039f234cab0372b6" - integrity sha512-Ud0jrRQO2k7fEtPAM+cQkBKoMvxQyPKNXKDLn8tRVHxRCsdDQ2JZvw+aZ5IRYYQVAV9iGxEar6boTwZzev+x3g== +sitemap@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/sitemap/-/sitemap-7.1.1.tgz#eeed9ad6d95499161a3eadc60f8c6dce4bea2bef" + integrity sha512-mK3aFtjz4VdJN0igpIJrinf3EO8U8mxOPsTBzSsy06UtjZQJ3YY3o3Xa7zSc5nMqcMrRwlChHZ18Kxg0caiPBg== dependencies: - "@types/node" "^15.0.1" + "@types/node" "^17.0.5" "@types/sax" "^1.2.1" arg "^5.0.0" sax "^1.2.4" @@ -6684,6 +6764,11 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== +slash@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7" + integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== + sockjs@^0.3.21: version "0.3.24" resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.24.tgz#c9bc8995f33a111bea0395ec30aa3206bdb5ccce" @@ -6703,10 +6788,10 @@ source-list-map@^2.0.0: resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== -source-map-js@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.1.tgz#a1741c131e3c77d048252adfa24e23b908670caf" - integrity sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA== +source-map-js@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" + integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== source-map-support@~0.5.20: version "0.5.21" @@ -6731,11 +6816,6 @@ source-map@~0.7.2: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== -sourcemap-codec@^1.4.4: - version "1.4.8" - resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" - integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== - space-separated-tokens@^1.0.0: version "1.1.5" resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz#85f32c3d10d9682007e917414ddc5c26d1aa6899" @@ -6789,7 +6869,7 @@ std-env@^3.0.1: resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.0.1.tgz#bc4cbc0e438610197e34c2d79c3df30b491f5182" integrity sha512-mC1Ps9l77/97qeOZc+HrOL7TIaOboHqMZ24dGVQrlxFcpPpfCHpH+qfUT7Dz+6mlG8+JPA1KfBQo19iC/+Ngcw== -string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.2: +string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -6798,6 +6878,15 @@ string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.2: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" +string-width@^5.0.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -6828,7 +6917,7 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1: dependencies: ansi-regex "^5.0.1" -strip-ansi@^7.0.0: +strip-ansi@^7.0.0, strip-ansi@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2" integrity sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw== @@ -6862,12 +6951,12 @@ style-to-object@0.3.0, style-to-object@^0.3.0: dependencies: inline-style-parser "0.1.1" -stylehacks@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-5.0.1.tgz#323ec554198520986806388c7fdaebc38d2c06fb" - integrity sha512-Es0rVnHIqbWzveU1b24kbw92HsebBepxfcqe5iix7t9j0PQqhs0IxXVXv0pY2Bxa08CgMkzD6OWql7kbGOuEdA== +stylehacks@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-5.1.0.tgz#a40066490ca0caca04e96c6b02153ddc39913520" + integrity sha512-SzLmvHQTrIWfSgljkQCw2++C9+Ne91d/6Sp92I8c5uHTcy/PgeHamwITIbBW9wnFTY/3ZfSXR9HIL6Ikqmcu6Q== dependencies: - browserslist "^4.16.0" + browserslist "^4.16.6" postcss-selector-parser "^6.0.4" supports-color@^5.3.0: @@ -6891,6 +6980,11 @@ supports-color@^8.0.0: dependencies: has-flag "^4.0.0" +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + svg-parser@^2.0.2: version "2.0.4" resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5" @@ -6919,22 +7013,23 @@ tapable@^2.0.0, tapable@^2.1.1, tapable@^2.2.0: resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== -terser-webpack-plugin@^5.1.3, terser-webpack-plugin@^5.2.4: - version "5.3.0" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.0.tgz#21641326486ecf91d8054161c816e464435bae9f" - integrity sha512-LPIisi3Ol4chwAaPP8toUJ3L4qCM1G0wao7L3qNv57Drezxj6+VEyySpPw4B1HSO2Eg/hDY/MNF5XihCAoqnsQ== +terser-webpack-plugin@^5.1.3, terser-webpack-plugin@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.1.tgz#0320dcc270ad5372c1e8993fabbd927929773e54" + integrity sha512-GvlZdT6wPQKbDNW/GDQzZFg/j4vKU96yl2q6mcUkzKOgW4gwf1Z8cZToUCrz31XHlPWH8MVb1r2tFtdDtTGJ7g== dependencies: - jest-worker "^27.4.1" + jest-worker "^27.4.5" schema-utils "^3.1.1" serialize-javascript "^6.0.0" source-map "^0.6.1" terser "^5.7.2" terser@^5.10.0, terser@^5.7.2: - version "5.10.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.10.0.tgz#b86390809c0389105eb0a0b62397563096ddafcc" - integrity sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA== + version "5.12.1" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.12.1.tgz#4cf2ebed1f5bceef5c83b9f60104ac4a78b49e9c" + integrity sha512-NXbs+7nisos5E+yXwAD+y7zrcTkMqb0dEJxIGtSKPdCBzopf7ni4odPul2aechpV7EXNvOudYOX2bb5tln1jbQ== dependencies: + acorn "^8.5.0" commander "^2.20.0" source-map "~0.7.2" source-map-support "~0.5.20" @@ -6991,6 +7086,11 @@ totalist@^1.0.0: resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df" integrity sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g== +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= + trim-trailing-lines@^1.0.0: version "1.1.4" resolved "https://registry.yarnpkg.com/trim-trailing-lines/-/trim-trailing-lines-1.1.4.tgz#bd4abbec7cc880462f10b2c8b5ce1d8d1ec7c2c0" @@ -7006,26 +7106,21 @@ trough@^1.0.0: resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406" integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA== -ts-essentials@^2.0.3: - version "2.0.12" - resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-2.0.12.tgz#c9303f3d74f75fa7528c3d49b80e089ab09d8745" - integrity sha512-3IVX4nI6B5cc31/GFFE+i8ey/N2eA0CZDbo6n0yrz0zDX8ZJ8djmU1p+XRz7G3is0F3bB3pu2pAroFdAWQKU3w== - -tslib@^2.0.3, tslib@^2.3.1: +tslib@^2.0.3, tslib@^2.1.0, tslib@^2.2.0, tslib@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== -tslib@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a" - integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A== - type-fest@^0.20.2: version "0.20.2" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== +type-fest@^2.5.0: + version "2.12.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.12.1.tgz#d2be8f50bf5f8f0a5fd916d29bf3e98c17e960be" + integrity sha512-AiknQSEqKVGDDjtZqeKrUoTlcj7FKhupmnVUgz6KoOKtvMwRGE6hUNJ/nVear+h7fnUPO1q/htSkYKb1pyntkQ== + type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" @@ -7041,10 +7136,10 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" -typescript@^4.5.2: - version "4.5.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.4.tgz#a17d3a0263bf5c8723b9c52f43c5084edf13c2e8" - integrity sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg== +typescript@^4.6.3: + version "4.6.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.3.tgz#eefeafa6afdd31d725584c67a0eaba80f6fc6c6c" + integrity sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw== ua-parser-js@^0.7.30: version "0.7.31" @@ -7139,13 +7234,6 @@ unist-util-remove-position@^2.0.0: dependencies: unist-util-visit "^2.0.0" -unist-util-remove@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unist-util-remove/-/unist-util-remove-2.0.0.tgz#32c2ad5578802f2ca62ab808173d505b2c898488" - integrity sha512-HwwWyNHKkeg/eXRnE11IpzY8JT55JNM1YCwwU9YNCnfzk6s8GhPXrVBBZWiwLeATJbI7euvoGSzcy9M29UeW3g== - dependencies: - unist-util-is "^4.0.0" - unist-util-remove@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/unist-util-remove/-/unist-util-remove-2.1.0.tgz#b0b4738aa7ee445c402fda9328d604a02d010588" @@ -7230,20 +7318,10 @@ url-parse-lax@^3.0.0: dependencies: prepend-http "^2.0.0" -url@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" - integrity sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE= - dependencies: - punycode "1.3.2" - querystring "0.2.0" - use-composed-ref@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/use-composed-ref/-/use-composed-ref-1.1.0.tgz#9220e4e94a97b7b02d7d27eaeab0b37034438bbc" - integrity sha512-my1lNHGWsSDAhhVAT4MKs6IjBUtG6ZG11uUqexPH9PptiIZDQOzaF4f5tEbJ2+7qvNbtXNBbU3SfmN+fXlWDhg== - dependencies: - ts-essentials "^2.0.3" + version "1.2.1" + resolved "https://registry.yarnpkg.com/use-composed-ref/-/use-composed-ref-1.2.1.tgz#9bdcb5ccd894289105da2325e1210079f56bf849" + integrity sha512-6+X1FLlIcjvFMAeAD/hcxDT8tmyrWnbSPMU0EnxQuDLIxokuFzWliXBiYZuGIx+mrAMLBw0WFfCkaPw8ebzAhw== use-isomorphic-layout-effect@^1.0.0: version "1.1.1" @@ -7315,16 +7393,16 @@ vfile@^4.0.0: unist-util-stringify-position "^2.0.0" vfile-message "^2.0.0" -wait-on@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-6.0.0.tgz#7e9bf8e3d7fe2daecbb7a570ac8ca41e9311c7e7" - integrity sha512-tnUJr9p5r+bEYXPUdRseolmz5XqJTTj98JgOsfBn7Oz2dxfE2g3zw1jE+Mo8lopM3j3et/Mq1yW7kKX6qw7RVw== +wait-on@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-6.0.1.tgz#16bbc4d1e4ebdd41c5b4e63a2e16dbd1f4e5601e" + integrity sha512-zht+KASY3usTY5u2LgaNqn/Cd8MukxLGjdcZxT2ns5QzDmTFc4XoWBgC+C/na+sMRZTuVygQoMYwdcVjHnYIVw== dependencies: - axios "^0.21.1" - joi "^17.4.0" + axios "^0.25.0" + joi "^17.6.0" lodash "^4.17.21" minimist "^1.2.5" - rxjs "^7.1.0" + rxjs "^7.5.4" watchpack@^2.3.1: version "2.3.1" @@ -7346,7 +7424,12 @@ web-namespaces@^1.0.0, web-namespaces@^1.1.2: resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-1.1.4.tgz#bc98a3de60dadd7faefc403d1076d529f5e030ec" integrity sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw== -webpack-bundle-analyzer@^4.4.2: +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= + +webpack-bundle-analyzer@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.5.0.tgz#1b0eea2947e73528754a6f9af3e91b2b6e0f79d5" integrity sha512-GUMZlM3SKwS8Z+CKeIFx7CVoHn3dXFcUAjT/dcZQQmfSZGvitPfMob2ipjai7ovFFqPvTqkEZ/leL4O0YOdAYQ== @@ -7361,25 +7444,31 @@ webpack-bundle-analyzer@^4.4.2: sirv "^1.0.7" ws "^7.3.1" -webpack-dev-middleware@^5.2.1: - version "5.3.0" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.0.tgz#8fc02dba6e72e1d373eca361623d84610f27be7c" - integrity sha512-MouJz+rXAm9B1OTOYaJnn6rtD/lWZPy2ufQCH3BPs8Rloh/Du6Jze4p7AeLYHkVi0giJnYLaSGDC7S+GM9arhg== +webpack-dev-middleware@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.1.tgz#aa079a8dedd7e58bfeab358a9af7dab304cee57f" + integrity sha512-81EujCKkyles2wphtdrnPg/QqegC/AtqNH//mQkBYSMqwFVCQrxM6ktB2O/SPlZy7LqeEfTbV3cZARGQz6umhg== dependencies: colorette "^2.0.10" - memfs "^3.2.2" + memfs "^3.4.1" mime-types "^2.1.31" range-parser "^1.2.1" schema-utils "^4.0.0" -webpack-dev-server@^4.5.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.6.0.tgz#e8648601c440172d9b6f248d28db98bed335315a" - integrity sha512-oojcBIKvx3Ya7qs1/AVWHDgmP1Xml8rGsEBnSobxU/UJSX1xP1GPM3MwsAnDzvqcVmVki8tV7lbcsjEjk0PtYg== - dependencies: +webpack-dev-server@^4.7.4: + version "4.7.4" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.7.4.tgz#d0ef7da78224578384e795ac228d8efb63d5f945" + integrity sha512-nfdsb02Zi2qzkNmgtZjkrMOcXnYZ6FLKcQwpxT7MvmHKc+oTtDsBju8j+NMyAygZ9GW1jMEUpy3itHtqgEhe1A== + dependencies: + "@types/bonjour" "^3.5.9" + "@types/connect-history-api-fallback" "^1.3.5" + "@types/express" "^4.17.13" + "@types/serve-index" "^1.9.1" + "@types/sockjs" "^0.3.33" + "@types/ws" "^8.2.2" ansi-html-community "^0.0.8" bonjour "^3.5.0" - chokidar "^3.5.2" + chokidar "^3.5.3" colorette "^2.0.10" compression "^1.7.4" connect-history-api-fallback "^1.6.0" @@ -7394,14 +7483,13 @@ webpack-dev-server@^4.5.0: p-retry "^4.5.0" portfinder "^1.0.28" schema-utils "^4.0.0" - selfsigned "^1.10.11" + selfsigned "^2.0.0" serve-index "^1.9.1" sockjs "^0.3.21" spdy "^4.0.2" strip-ansi "^7.0.0" - url "^0.11.0" - webpack-dev-middleware "^5.2.1" - ws "^8.1.0" + webpack-dev-middleware "^5.3.1" + ws "^8.4.2" webpack-merge@^5.8.0: version "5.8.0" @@ -7411,7 +7499,7 @@ webpack-merge@^5.8.0: clone-deep "^4.0.1" wildcard "^2.0.0" -webpack-sources@^1.1.0, webpack-sources@^1.4.3: +webpack-sources@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== @@ -7419,18 +7507,18 @@ webpack-sources@^1.1.0, webpack-sources@^1.4.3: source-list-map "^2.0.0" source-map "~0.6.1" -webpack-sources@^3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.2.tgz#d88e3741833efec57c4c789b6010db9977545260" - integrity sha512-cp5qdmHnu5T8wRg2G3vZZHoJPN14aqQ89SyQ11NpGH5zEMDCclt49rzo+MaRazk7/UeILhAI+/sEtcM+7Fr0nw== +webpack-sources@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" + integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== -webpack@^5.61.0: - version "5.65.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.65.0.tgz#ed2891d9145ba1f0d318e4ea4f89c3fa18e6f9be" - integrity sha512-Q5or2o6EKs7+oKmJo7LaqZaMOlDWQse9Tm5l1WAfU/ujLGN5Pb0SqGeVkN/4bpPmEqEP5RnVhiqsOtWtUVwGRw== +webpack@^5.70.0: + version "5.70.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.70.0.tgz#3461e6287a72b5e6e2f4872700bc8de0d7500e6d" + integrity sha512-ZMWWy8CeuTTjCxbeaQI21xSswseF2oNOwc70QSKNePvmxE7XW36i7vpBMYZFAUHPwQiEbNGCEYIOOlyRbdGmxw== dependencies: - "@types/eslint-scope" "^3.7.0" - "@types/estree" "^0.0.50" + "@types/eslint-scope" "^3.7.3" + "@types/estree" "^0.0.51" "@webassemblyjs/ast" "1.11.1" "@webassemblyjs/wasm-edit" "1.11.1" "@webassemblyjs/wasm-parser" "1.11.1" @@ -7438,12 +7526,12 @@ webpack@^5.61.0: acorn-import-assertions "^1.7.6" browserslist "^4.14.5" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.8.3" + enhanced-resolve "^5.9.2" es-module-lexer "^0.9.0" eslint-scope "5.1.1" events "^3.2.0" glob-to-regexp "^0.4.1" - graceful-fs "^4.2.4" + graceful-fs "^4.2.9" json-parse-better-errors "^1.0.2" loader-runner "^4.2.0" mime-types "^2.1.27" @@ -7452,9 +7540,9 @@ webpack@^5.61.0: tapable "^2.1.1" terser-webpack-plugin "^5.1.3" watchpack "^2.3.1" - webpack-sources "^3.2.2" + webpack-sources "^3.2.3" -webpackbar@^5.0.0-3: +webpackbar@^5.0.2: version "5.0.2" resolved "https://registry.yarnpkg.com/webpackbar/-/webpackbar-5.0.2.tgz#d3dd466211c73852741dfc842b7556dcbc2b0570" integrity sha512-BmFJo7veBDgQzfWXl/wwYXr/VFus0614qZ8i9znqcl9fnEdiVkdbi0TedLQ6xAK92HZHDJ0QmyQ0fmuZPAgCYQ== @@ -7478,6 +7566,14 @@ websocket-extensions@>=0.1.1: resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0= + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + which@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" @@ -7499,6 +7595,13 @@ widest-line@^3.1.0: dependencies: string-width "^4.0.0" +widest-line@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-4.0.1.tgz#a0fc673aaba1ea6f0a0d35b3c2795c9a9cc2ebf2" + integrity sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig== + dependencies: + string-width "^5.0.1" + wildcard@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" @@ -7513,6 +7616,15 @@ wrap-ansi@^7.0.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.0.1.tgz#2101e861777fec527d0ea90c57c6b03aac56a5b3" + integrity sha512-QFF+ufAqhoYHvoHdajT/Po7KoXVBPXS2bgjIam5isfWJPfIOnQZ50JtUiVvCv/sjgacf3yRrt2ZKUZ/V4itN4g== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" @@ -7529,14 +7641,14 @@ write-file-atomic@^3.0.0: typedarray-to-buffer "^3.1.5" ws@^7.3.1: - version "7.5.6" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.6.tgz#e59fc509fb15ddfb65487ee9765c5a51dec5fe7b" - integrity sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA== - -ws@^8.1.0: - version "8.4.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.4.0.tgz#f05e982a0a88c604080e8581576e2a063802bed6" - integrity sha512-IHVsKe2pjajSUIl4KYMQOdlyliovpEPquKkqbwswulszzI7r0SfQrxnXdWAEqOlDCLrVSJzo+O1hAwdog2sKSQ== + version "7.5.7" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.7.tgz#9e0ac77ee50af70d58326ecff7e85eb3fa375e67" + integrity sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A== + +ws@^8.4.2: + version "8.5.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f" + integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg== xdg-basedir@^4.0.0: version "4.0.0" diff --git a/examples/classic/.stackblitzrc b/examples/classic/.stackblitzrc index a8c490e81a63..5490eb1ecc12 100644 --- a/examples/classic/.stackblitzrc +++ b/examples/classic/.stackblitzrc @@ -1,4 +1,4 @@ { "installDependencies": true, "startCommand": "npm start" -} \ No newline at end of file +} diff --git a/examples/classic/docs/intro.md b/examples/classic/docs/intro.md index 440ad3373eb7..500260230bfc 100644 --- a/examples/classic/docs/intro.md +++ b/examples/classic/docs/intro.md @@ -12,24 +12,36 @@ Get started by **creating a new site**. Or **try Docusaurus immediately** with **[docusaurus.new](https://docusaurus.new)**. +### What you'll need + +- [Node.js](https://nodejs.org/en/download/) version 14 or above: + - When installing Node.js, you are recommended to check all checkboxes related to dependencies. + ## Generate a new site -Generate a new Docusaurus site using the **classic template**: +Generate a new Docusaurus site using the **classic template**. + +The classic template will automatically be added to your project after you run the command: ```bash npm init docusaurus@latest my-website classic ``` +You can type this command into Command Prompt, Powershell, Terminal, or any other integrated terminal of your code editor. + +The command also installs all necessary dependencies you need to run Docusaurus. + ## Start your site Run the development server: ```bash cd my-website - -npx docusaurus start +npm run start ``` -Your site starts at `http://localhost:3000`. +The `cd` command changes the directory you're working with. In order to work with your newly created Docusaurus site, you'll need to navigate the terminal there. + +The `npm run start` command builds your website locally and serves it through a development server, ready for you to view at http://localhost:3000/. -Open `docs/intro.md` and edit some lines: the site **reloads automatically** and displays your changes. +Open `docs/intro.md` (this page) and edit some lines: the site **reloads automatically** and displays your changes. diff --git a/examples/classic/docs/tutorial-basics/create-a-document.md b/examples/classic/docs/tutorial-basics/create-a-document.md index feaced79d0a3..a9bb9a4140b1 100644 --- a/examples/classic/docs/tutorial-basics/create-a-document.md +++ b/examples/classic/docs/tutorial-basics/create-a-document.md @@ -41,14 +41,14 @@ This is my **first Docusaurus document**! It is also possible to create your sidebar explicitly in `sidebars.js`: -```diff title="sidebars.js" +```js title="sidebars.js" module.exports = { tutorialSidebar: [ { type: 'category', label: 'Tutorial', -- items: [...], -+ items: ['hello'], + // highlight-next-line + items: ['hello'], }, ], }; diff --git a/examples/classic/package.json b/examples/classic/package.json index 1daebc8b3a85..8c69f696a97e 100644 --- a/examples/classic/package.json +++ b/examples/classic/package.json @@ -15,13 +15,13 @@ "dev": "docusaurus start" }, "dependencies": { - "@docusaurus/core": "2.0.0-beta.14", - "@docusaurus/preset-classic": "2.0.0-beta.14", - "@mdx-js/react": "^1.6.21", + "@docusaurus/core": "2.0.0-beta.18", + "@docusaurus/preset-classic": "2.0.0-beta.18", + "@mdx-js/react": "^1.6.22", "clsx": "^1.1.1", - "prism-react-renderer": "^1.2.1", - "react": "^17.0.1", - "react-dom": "^17.0.1" + "prism-react-renderer": "^1.3.1", + "react": "^17.0.2", + "react-dom": "^17.0.2" }, "browserslist": { "production": [ @@ -36,4 +36,4 @@ ] }, "description": "Docusaurus example project" -} \ No newline at end of file +} diff --git a/examples/classic/sandbox.config.json b/examples/classic/sandbox.config.json index 069492640e53..95df40889ad5 100644 --- a/examples/classic/sandbox.config.json +++ b/examples/classic/sandbox.config.json @@ -7,4 +7,4 @@ "container": { "node": "14" } -} \ No newline at end of file +} diff --git a/packages/create-docusaurus/templates/classic/src/components/HomepageFeatures.js b/examples/classic/src/components/HomepageFeatures/index.js similarity index 82% rename from packages/create-docusaurus/templates/classic/src/components/HomepageFeatures.js rename to examples/classic/src/components/HomepageFeatures/index.js index 16f820b10355..78f410ba6888 100644 --- a/packages/create-docusaurus/templates/classic/src/components/HomepageFeatures.js +++ b/examples/classic/src/components/HomepageFeatures/index.js @@ -1,11 +1,11 @@ import React from 'react'; import clsx from 'clsx'; -import styles from './HomepageFeatures.module.css'; +import styles from './styles.module.css'; const FeatureList = [ { title: 'Easy to Use', - Svg: require('../../static/img/undraw_docusaurus_mountain.svg').default, + Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default, description: ( <> Docusaurus was designed from the ground up to be easily installed and @@ -15,7 +15,7 @@ const FeatureList = [ }, { title: 'Focus on What Matters', - Svg: require('../../static/img/undraw_docusaurus_tree.svg').default, + Svg: require('@site/static/img/undraw_docusaurus_tree.svg').default, description: ( <> Docusaurus lets you focus on your docs, and we'll do the chores. Go @@ -25,7 +25,7 @@ const FeatureList = [ }, { title: 'Powered by React', - Svg: require('../../static/img/undraw_docusaurus_react.svg').default, + Svg: require('@site/static/img/undraw_docusaurus_react.svg').default, description: ( <> Extend or customize your website layout by reusing React. Docusaurus can @@ -39,7 +39,7 @@ function Feature({Svg, title, description}) { return (
- +

{title}

diff --git a/examples/classic/src/components/HomepageFeatures.module.css b/examples/classic/src/components/HomepageFeatures/styles.module.css similarity index 100% rename from examples/classic/src/components/HomepageFeatures.module.css rename to examples/classic/src/components/HomepageFeatures/styles.module.css diff --git a/examples/classic/src/css/custom.css b/examples/classic/src/css/custom.css index 6abe14854412..311dc090d973 100644 --- a/examples/classic/src/css/custom.css +++ b/examples/classic/src/css/custom.css @@ -6,16 +6,27 @@ /* You can override the default Infima variables here. */ :root { - --ifm-color-primary: #25c2a0; - --ifm-color-primary-dark: rgb(33, 175, 144); - --ifm-color-primary-darker: rgb(31, 165, 136); - --ifm-color-primary-darkest: rgb(26, 136, 112); - --ifm-color-primary-light: rgb(70, 203, 174); - --ifm-color-primary-lighter: rgb(102, 212, 189); - --ifm-color-primary-lightest: rgb(146, 224, 208); + --ifm-color-primary: #2e8555; + --ifm-color-primary-dark: #29784c; + --ifm-color-primary-darker: #277148; + --ifm-color-primary-darkest: #205d3b; + --ifm-color-primary-light: #33925d; + --ifm-color-primary-lighter: #359962; + --ifm-color-primary-lightest: #3cad6e; --ifm-code-font-size: 95%; } +/* For readability concerns, you should choose a lighter palette in dark mode. */ +[data-theme='dark'] { + --ifm-color-primary: #25c2a0; + --ifm-color-primary-dark: #21af90; + --ifm-color-primary-darker: #1fa588; + --ifm-color-primary-darkest: #1a8870; + --ifm-color-primary-light: #29d5b0; + --ifm-color-primary-lighter: #32d8b4; + --ifm-color-primary-lightest: #4fddbf; +} + .docusaurus-highlight-code-line { background-color: rgba(0, 0, 0, 0.1); display: block; @@ -23,6 +34,6 @@ padding: 0 var(--ifm-pre-padding); } -html[data-theme='dark'] .docusaurus-highlight-code-line { +[data-theme='dark'] .docusaurus-highlight-code-line { background-color: rgba(0, 0, 0, 0.3); } diff --git a/examples/classic/src/pages/index.js b/examples/classic/src/pages/index.js index 27c21e8f99c4..a4fc2d3f03e6 100644 --- a/examples/classic/src/pages/index.js +++ b/examples/classic/src/pages/index.js @@ -4,7 +4,7 @@ import Layout from '@theme/Layout'; import Link from '@docusaurus/Link'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import styles from './index.module.css'; -import HomepageFeatures from '../components/HomepageFeatures'; +import HomepageFeatures from '@site/src/components/HomepageFeatures'; function HomepageHeader() { const {siteConfig} = useDocusaurusContext(); diff --git a/examples/classic/src/pages/index.module.css b/examples/classic/src/pages/index.module.css index 666feb6a172a..9f71a5da775b 100644 --- a/examples/classic/src/pages/index.module.css +++ b/examples/classic/src/pages/index.module.css @@ -10,7 +10,7 @@ overflow: hidden; } -@media screen and (max-width: 966px) { +@media screen and (max-width: 996px) { .heroBanner { padding: 2rem; } diff --git a/examples/classic/static/img/undraw_docusaurus_mountain.svg b/examples/classic/static/img/undraw_docusaurus_mountain.svg index 431cef2f7fec..af961c49a888 100644 --- a/examples/classic/static/img/undraw_docusaurus_mountain.svg +++ b/examples/classic/static/img/undraw_docusaurus_mountain.svg @@ -1,4 +1,5 @@ + Easy to Use diff --git a/examples/classic/static/img/undraw_docusaurus_react.svg b/examples/classic/static/img/undraw_docusaurus_react.svg index e41705043338..94b5cf08f88f 100644 --- a/examples/classic/static/img/undraw_docusaurus_react.svg +++ b/examples/classic/static/img/undraw_docusaurus_react.svg @@ -1,4 +1,5 @@ + Powered by React diff --git a/examples/classic/static/img/undraw_docusaurus_tree.svg b/examples/classic/static/img/undraw_docusaurus_tree.svg index a05cc03dda90..d9161d33920c 100644 --- a/examples/classic/static/img/undraw_docusaurus_tree.svg +++ b/examples/classic/static/img/undraw_docusaurus_tree.svg @@ -1 +1,40 @@ -docu_tree \ No newline at end of file + + Focus on What Matters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/classic/yarn.lock b/examples/classic/yarn.lock index 435baca3f9d3..c0468a24b5cf 100644 --- a/examples/classic/yarn.lock +++ b/examples/classic/yarn.lock @@ -2,145 +2,152 @@ # yarn lockfile v1 -"@algolia/autocomplete-core@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-core/-/autocomplete-core-1.5.0.tgz#6c91c9de7748e9c103846828a58dfe92bd4d6689" - integrity sha512-E7+VJwcvwMM8vPeaVn7fNUgix8WHV8A1WUeHDi2KHemCaaGc8lvUnP3QnvhMxiDhTe7OpMEv4o2TBUMyDgThaw== +"@algolia/autocomplete-core@1.5.2": + version "1.5.2" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-core/-/autocomplete-core-1.5.2.tgz#ec0178e07b44fd74a057728ac157291b26cecf37" + integrity sha512-DY0bhyczFSS1b/CqJlTE/nQRtnTAHl6IemIkBy0nEWnhDzRDdtdx4p5Uuk3vwAFxwEEgi1WqKwgSSMx6DpNL4A== dependencies: - "@algolia/autocomplete-shared" "1.5.0" + "@algolia/autocomplete-shared" "1.5.2" -"@algolia/autocomplete-preset-algolia@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.5.0.tgz#61671f09c0c77133d9baf1356719f8378c48437a" - integrity sha512-iiFxKERGHkvkiupmrFJbvESpP/zv5jSgH714XRiP5LDvUHaYOo4GLAwZCFf2ef/L5tdtPBARvekn6k1Xf33gjA== +"@algolia/autocomplete-preset-algolia@1.5.2": + version "1.5.2" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.5.2.tgz#36c5638cc6dba6ea46a86e5a0314637ca40a77ca" + integrity sha512-3MRYnYQFJyovANzSX2CToS6/5cfVjbLLqFsZTKcvF3abhQzxbqwwaMBlJtt620uBUOeMzhdfasKhCc40+RHiZw== dependencies: - "@algolia/autocomplete-shared" "1.5.0" + "@algolia/autocomplete-shared" "1.5.2" -"@algolia/autocomplete-shared@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.5.0.tgz#09580bc89408a2ab5f29e312120dad68f58019bd" - integrity sha512-bRSkqHHHSwZYbFY3w9hgMyQRm86Wz27bRaGCbNldLfbk0zUjApmE4ajx+ZCVSLqxvcUEjMqZFJzDsder12eKsg== +"@algolia/autocomplete-shared@1.5.2": + version "1.5.2" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.5.2.tgz#e157f9ad624ab8fd940ff28bd2094cdf199cdd79" + integrity sha512-ylQAYv5H0YKMfHgVWX0j0NmL8XBcAeeeVQUmppnnMtzDbDnca6CzhKj3Q8eF9cHCgcdTDdb5K+3aKyGWA0obug== -"@algolia/cache-browser-local-storage@4.11.0": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.11.0.tgz#1c168add00b398a860db6c86039e33b2843a9425" - integrity sha512-4sr9vHIG1fVA9dONagdzhsI/6M5mjs/qOe2xUP0yBmwsTsuwiZq3+Xu6D3dsxsuFetcJgC6ydQoCW8b7fDJHYQ== +"@algolia/cache-browser-local-storage@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.13.0.tgz#f8aa4fe31104b19d616ea392f9ed5c2ea847d964" + integrity sha512-nj1vHRZauTqP/bluwkRIgEADEimqojJgoTRCel5f6q8WCa9Y8QeI4bpDQP28FoeKnDRYa3J5CauDlN466jqRhg== dependencies: - "@algolia/cache-common" "4.11.0" + "@algolia/cache-common" "4.13.0" -"@algolia/cache-common@4.11.0": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@algolia/cache-common/-/cache-common-4.11.0.tgz#066fe6d58b18e4b028dbef9bb8de07c5e22a3594" - integrity sha512-lODcJRuPXqf+6mp0h6bOxPMlbNoyn3VfjBVcQh70EDP0/xExZbkpecgHyyZK4kWg+evu+mmgvTK3GVHnet/xKw== +"@algolia/cache-common@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@algolia/cache-common/-/cache-common-4.13.0.tgz#27b83fd3939d08d72261b36a07eeafc4cb4d2113" + integrity sha512-f9mdZjskCui/dA/fA/5a+6hZ7xnHaaZI5tM/Rw9X8rRB39SUlF/+o3P47onZ33n/AwkpSbi5QOyhs16wHd55kA== -"@algolia/cache-in-memory@4.11.0": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@algolia/cache-in-memory/-/cache-in-memory-4.11.0.tgz#763c8cb655e6fd2261588e04214fca0959ac07c1" - integrity sha512-aBz+stMSTBOBaBEQ43zJXz2DnwS7fL6dR0e2myehAgtfAWlWwLDHruc/98VOy1ZAcBk1blE2LCU02bT5HekGxQ== +"@algolia/cache-in-memory@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@algolia/cache-in-memory/-/cache-in-memory-4.13.0.tgz#10801a74550cbabb64b59ff08c56bce9c278ff2d" + integrity sha512-hHdc+ahPiMM92CQMljmObE75laYzNFYLrNOu0Q3/eyvubZZRtY2SUsEEgyUEyzXruNdzrkcDxFYa7YpWBJYHAg== dependencies: - "@algolia/cache-common" "4.11.0" + "@algolia/cache-common" "4.13.0" -"@algolia/client-account@4.11.0": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@algolia/client-account/-/client-account-4.11.0.tgz#67fadd3b0802b013ebaaa4b47bb7babae892374e" - integrity sha512-jwmFBoUSzoMwMqgD3PmzFJV/d19p1RJXB6C1ADz4ju4mU7rkaQLtqyZroQpheLoU5s5Tilmn/T8/0U2XLoJCRQ== +"@algolia/client-account@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@algolia/client-account/-/client-account-4.13.0.tgz#f8646dd40d1e9e3353e10abbd5d6c293ea92a8e2" + integrity sha512-FzFqFt9b0g/LKszBDoEsW+dVBuUe1K3scp2Yf7q6pgHWM1WqyqUlARwVpLxqyc+LoyJkTxQftOKjyFUqddnPKA== dependencies: - "@algolia/client-common" "4.11.0" - "@algolia/client-search" "4.11.0" - "@algolia/transporter" "4.11.0" + "@algolia/client-common" "4.13.0" + "@algolia/client-search" "4.13.0" + "@algolia/transporter" "4.13.0" -"@algolia/client-analytics@4.11.0": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@algolia/client-analytics/-/client-analytics-4.11.0.tgz#cbdc8128205e2da749cafc79e54708d14c413974" - integrity sha512-v5U9585aeEdYml7JqggHAj3E5CQ+jPwGVztPVhakBk8H/cmLyPS2g8wvmIbaEZCHmWn4TqFj3EBHVYxAl36fSA== +"@algolia/client-analytics@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@algolia/client-analytics/-/client-analytics-4.13.0.tgz#a00bd02df45d71becb9dd4c5c993d805f2e1786d" + integrity sha512-klmnoq2FIiiMHImkzOm+cGxqRLLu9CMHqFhbgSy9wtXZrqb8BBUIUE2VyBe7azzv1wKcxZV2RUyNOMpFqmnRZA== dependencies: - "@algolia/client-common" "4.11.0" - "@algolia/client-search" "4.11.0" - "@algolia/requester-common" "4.11.0" - "@algolia/transporter" "4.11.0" + "@algolia/client-common" "4.13.0" + "@algolia/client-search" "4.13.0" + "@algolia/requester-common" "4.13.0" + "@algolia/transporter" "4.13.0" -"@algolia/client-common@4.11.0": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@algolia/client-common/-/client-common-4.11.0.tgz#9a2d1f6f8eaad25ba5d6d4ce307ba5bd84e6f999" - integrity sha512-Qy+F+TZq12kc7tgfC+FM3RvYH/Ati7sUiUv/LkvlxFwNwNPwWGoZO81AzVSareXT/ksDDrabD4mHbdTbBPTRmQ== +"@algolia/client-common@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@algolia/client-common/-/client-common-4.13.0.tgz#8bc373d164dbdcce38b4586912bbe162492bcb86" + integrity sha512-GoXfTp0kVcbgfSXOjfrxx+slSipMqGO9WnNWgeMmru5Ra09MDjrcdunsiiuzF0wua6INbIpBQFTC2Mi5lUNqGA== dependencies: - "@algolia/requester-common" "4.11.0" - "@algolia/transporter" "4.11.0" + "@algolia/requester-common" "4.13.0" + "@algolia/transporter" "4.13.0" -"@algolia/client-personalization@4.11.0": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@algolia/client-personalization/-/client-personalization-4.11.0.tgz#d3bf0e760f85df876b4baf5b81996f0aa3a59940" - integrity sha512-mI+X5IKiijHAzf9fy8VSl/GTT67dzFDnJ0QAM8D9cMPevnfX4U72HRln3Mjd0xEaYUOGve8TK/fMg7d3Z5yG6g== +"@algolia/client-personalization@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@algolia/client-personalization/-/client-personalization-4.13.0.tgz#10fb7af356422551f11a67222b39c52306f1512c" + integrity sha512-KneLz2WaehJmNfdr5yt2HQETpLaCYagRdWwIwkTqRVFCv4DxRQ2ChPVW9jeTj4YfAAhfzE6F8hn7wkQ/Jfj6ZA== dependencies: - "@algolia/client-common" "4.11.0" - "@algolia/requester-common" "4.11.0" - "@algolia/transporter" "4.11.0" + "@algolia/client-common" "4.13.0" + "@algolia/requester-common" "4.13.0" + "@algolia/transporter" "4.13.0" -"@algolia/client-search@4.11.0": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@algolia/client-search/-/client-search-4.11.0.tgz#c1105d715a2a04ba27231eca86f5d6620f68f4ae" - integrity sha512-iovPLc5YgiXBdw2qMhU65sINgo9umWbHFzInxoNErWnYoTQWfXsW6P54/NlKx5uscoLVjSf+5RUWwFu5BX+lpw== +"@algolia/client-search@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@algolia/client-search/-/client-search-4.13.0.tgz#2d8ff8e755c4a37ec89968f3f9b358eed005c7f0" + integrity sha512-blgCKYbZh1NgJWzeGf+caKE32mo3j54NprOf0LZVCubQb3Kx37tk1Hc8SDs9bCAE8hUvf3cazMPIg7wscSxspA== dependencies: - "@algolia/client-common" "4.11.0" - "@algolia/requester-common" "4.11.0" - "@algolia/transporter" "4.11.0" + "@algolia/client-common" "4.13.0" + "@algolia/requester-common" "4.13.0" + "@algolia/transporter" "4.13.0" "@algolia/events@^4.0.1": version "4.0.1" resolved "https://registry.yarnpkg.com/@algolia/events/-/events-4.0.1.tgz#fd39e7477e7bc703d7f893b556f676c032af3950" integrity sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ== -"@algolia/logger-common@4.11.0": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@algolia/logger-common/-/logger-common-4.11.0.tgz#bac1c2d59d29dee378b57412c8edd435b97de663" - integrity sha512-pRMJFeOY8hoWKIxWuGHIrqnEKN/kqKh7UilDffG/+PeEGxBuku+Wq5CfdTFG0C9ewUvn8mAJn5BhYA5k8y0Jqg== +"@algolia/logger-common@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@algolia/logger-common/-/logger-common-4.13.0.tgz#be2606e71aae618a1ff1ea9a1b5f5a74284b35a8" + integrity sha512-8yqXk7rMtmQJ9wZiHOt/6d4/JDEg5VCk83gJ39I+X/pwUPzIsbKy9QiK4uJ3aJELKyoIiDT1hpYVt+5ia+94IA== -"@algolia/logger-console@4.11.0": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@algolia/logger-console/-/logger-console-4.11.0.tgz#ced19e3abb22eb782ed5268d51efb5aa9ef109ef" - integrity sha512-wXztMk0a3VbNmYP8Kpc+F7ekuvaqZmozM2eTLok0XIshpAeZ/NJDHDffXK2Pw+NF0wmHqurptLYwKoikjBYvhQ== +"@algolia/logger-console@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@algolia/logger-console/-/logger-console-4.13.0.tgz#f28028a760e3d9191e28a10b12925e48f6c9afde" + integrity sha512-YepRg7w2/87L0vSXRfMND6VJ5d6699sFJBRWzZPOlek2p5fLxxK7O0VncYuc/IbVHEgeApvgXx0WgCEa38GVuQ== dependencies: - "@algolia/logger-common" "4.11.0" + "@algolia/logger-common" "4.13.0" -"@algolia/requester-browser-xhr@4.11.0": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.11.0.tgz#f9e1ad56f185432aa8dde8cad53ae271fd5d6181" - integrity sha512-Fp3SfDihAAFR8bllg8P5ouWi3+qpEVN5e7hrtVIYldKBOuI/qFv80Zv/3/AMKNJQRYglS4zWyPuqrXm58nz6KA== +"@algolia/requester-browser-xhr@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.13.0.tgz#e2483f4e8d7f09e27cd0daf6c77711d15c5a919f" + integrity sha512-Dj+bnoWR5MotrnjblzGKZ2kCdQi2cK/VzPURPnE616NU/il7Ypy6U6DLGZ/ZYz+tnwPa0yypNf21uqt84fOgrg== dependencies: - "@algolia/requester-common" "4.11.0" + "@algolia/requester-common" "4.13.0" -"@algolia/requester-common@4.11.0": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@algolia/requester-common/-/requester-common-4.11.0.tgz#d16de98d3ff72434bac39e4d915eab08035946a9" - integrity sha512-+cZGe/9fuYgGuxjaBC+xTGBkK7OIYdfapxhfvEf03dviLMPmhmVYFJtJlzAjQ2YmGDJpHrGgAYj3i/fbs8yhiA== +"@algolia/requester-common@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@algolia/requester-common/-/requester-common-4.13.0.tgz#47fb3464cfb26b55ba43676d13f295d812830596" + integrity sha512-BRTDj53ecK+gn7ugukDWOOcBRul59C4NblCHqj4Zm5msd5UnHFjd/sGX+RLOEoFMhetILAnmg6wMrRrQVac9vw== -"@algolia/requester-node-http@4.11.0": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@algolia/requester-node-http/-/requester-node-http-4.11.0.tgz#beb2b6b68d5f4ce15aec80ede623f0ac96991368" - integrity sha512-qJIk9SHRFkKDi6dMT9hba8X1J1z92T5AZIgl+tsApjTGIRQXJLTIm+0q4yOefokfu4CoxYwRZ9QAq+ouGwfeOg== +"@algolia/requester-node-http@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@algolia/requester-node-http/-/requester-node-http-4.13.0.tgz#7d981bbd31492f51dd11820a665f9d8906793c37" + integrity sha512-9b+3O4QFU4azLhGMrZAr/uZPydvzOR4aEZfSL8ZrpLZ7fbbqTO0S/5EVko+QIgglRAtVwxvf8UJ1wzTD2jvKxQ== dependencies: - "@algolia/requester-common" "4.11.0" + "@algolia/requester-common" "4.13.0" -"@algolia/transporter@4.11.0": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@algolia/transporter/-/transporter-4.11.0.tgz#a8de3c173093ceceb02b26b577395ce3b3d4b96f" - integrity sha512-k4dyxiaEfYpw4UqybK9q7lrFzehygo6KV3OCYJMMdX0IMWV0m4DXdU27c1zYRYtthaFYaBzGF4Kjcl8p8vxCKw== +"@algolia/transporter@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@algolia/transporter/-/transporter-4.13.0.tgz#f6379e5329efa2127da68c914d1141f5f21dbd07" + integrity sha512-8tSQYE+ykQENAdeZdofvtkOr5uJ9VcQSWgRhQ9h01AehtBIPAczk/b2CLrMsw5yQZziLs5cZ3pJ3478yI+urhA== dependencies: - "@algolia/cache-common" "4.11.0" - "@algolia/logger-common" "4.11.0" - "@algolia/requester-common" "4.11.0" + "@algolia/cache-common" "4.13.0" + "@algolia/logger-common" "4.13.0" + "@algolia/requester-common" "4.13.0" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.8.3": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.0.tgz#0dfc80309beec8411e65e706461c408b0bb9b431" - integrity sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA== +"@ampproject/remapping@^2.1.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.1.2.tgz#4edca94973ded9630d20101cd8559cedb8d8bd34" + integrity sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg== dependencies: - "@babel/highlight" "^7.16.0" + "@jridgewell/trace-mapping" "^0.3.0" -"@babel/compat-data@^7.13.11", "@babel/compat-data@^7.16.0", "@babel/compat-data@^7.16.4": - version "7.16.4" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.16.4.tgz#081d6bbc336ec5c2435c6346b2ae1fb98b5ac68e" - integrity sha512-1o/jo7D+kC9ZjHX5v+EHrdjl3PhxMrLSOTGsOdHJ+KL8HCaEK6ehrVL2RS6oHDZp+L7xLirLrPmQtEng769J/Q== +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.8.3": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789" + integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg== + dependencies: + "@babel/highlight" "^7.16.7" + +"@babel/compat-data@^7.13.11", "@babel/compat-data@^7.16.8", "@babel/compat-data@^7.17.0", "@babel/compat-data@^7.17.7": + version "7.17.7" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.17.7.tgz#078d8b833fbbcc95286613be8c716cef2b519fa2" + integrity sha512-p8pdE6j0a29TNGebNm7NzYZWB3xVZJBZ7XGs42uAKzQo8VQ3F0By/cQCtUEABwIqw5zo6WA4NbmxsfzADzMKnQ== "@babel/core@7.12.9": version "7.12.9" @@ -164,86 +171,86 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/core@^7.15.5", "@babel/core@^7.16.0": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.16.5.tgz#924aa9e1ae56e1e55f7184c8bf073a50d8677f5c" - integrity sha512-wUcenlLzuWMZ9Zt8S0KmFwGlH6QKRh3vsm/dhDA3CHkiTA45YuG1XkHRcNRl73EFPXDp/d5kVOU0/y7x2w6OaQ== - dependencies: - "@babel/code-frame" "^7.16.0" - "@babel/generator" "^7.16.5" - "@babel/helper-compilation-targets" "^7.16.3" - "@babel/helper-module-transforms" "^7.16.5" - "@babel/helpers" "^7.16.5" - "@babel/parser" "^7.16.5" - "@babel/template" "^7.16.0" - "@babel/traverse" "^7.16.5" - "@babel/types" "^7.16.0" +"@babel/core@^7.15.5", "@babel/core@^7.17.8": + version "7.17.8" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.17.8.tgz#3dac27c190ebc3a4381110d46c80e77efe172e1a" + integrity sha512-OdQDV/7cRBtJHLSOBqqbYNkOcydOgnX59TZx4puf41fzcVtN3e/4yqY8lMQsK+5X2lJtAdmA+6OHqsj1hBJ4IQ== + dependencies: + "@ampproject/remapping" "^2.1.0" + "@babel/code-frame" "^7.16.7" + "@babel/generator" "^7.17.7" + "@babel/helper-compilation-targets" "^7.17.7" + "@babel/helper-module-transforms" "^7.17.7" + "@babel/helpers" "^7.17.8" + "@babel/parser" "^7.17.8" + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.17.3" + "@babel/types" "^7.17.0" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" json5 "^2.1.2" semver "^6.3.0" - source-map "^0.5.0" -"@babel/generator@^7.12.5", "@babel/generator@^7.16.0", "@babel/generator@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.16.5.tgz#26e1192eb8f78e0a3acaf3eede3c6fc96d22bedf" - integrity sha512-kIvCdjZqcdKqoDbVVdt5R99icaRtrtYhYK/xux5qiWCBmfdvEYMFZ68QCrpE5cbFM1JsuArUNs1ZkuKtTtUcZA== +"@babel/generator@^7.12.5", "@babel/generator@^7.17.3", "@babel/generator@^7.17.7": + version "7.17.7" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.17.7.tgz#8da2599beb4a86194a3b24df6c085931d9ee45ad" + integrity sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w== dependencies: - "@babel/types" "^7.16.0" + "@babel/types" "^7.17.0" jsesc "^2.5.1" source-map "^0.5.0" -"@babel/helper-annotate-as-pure@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.0.tgz#9a1f0ebcda53d9a2d00108c4ceace6a5d5f1f08d" - integrity sha512-ItmYF9vR4zA8cByDocY05o0LGUkp1zhbTQOH1NFyl5xXEqlTJQCEJjieriw+aFpxo16swMxUnUiKS7a/r4vtHg== +"@babel/helper-annotate-as-pure@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz#bb2339a7534a9c128e3102024c60760a3a7f3862" + integrity sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw== dependencies: - "@babel/types" "^7.16.0" + "@babel/types" "^7.16.7" -"@babel/helper-builder-binary-assignment-operator-visitor@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.5.tgz#a8429d064dce8207194b8bf05a70a9ea828746af" - integrity sha512-3JEA9G5dmmnIWdzaT9d0NmFRgYnWUThLsDaL7982H0XqqWr56lRrsmwheXFMjR+TMl7QMBb6mzy9kvgr1lRLUA== +"@babel/helper-builder-binary-assignment-operator-visitor@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.7.tgz#38d138561ea207f0f69eb1626a418e4f7e6a580b" + integrity sha512-C6FdbRaxYjwVu/geKW4ZeQ0Q31AftgRcdSnZ5/jsH6BzCJbtvXvhpfkbkThYSuutZA7nCXpPR6AD9zd1dprMkA== dependencies: - "@babel/helper-explode-assignable-expression" "^7.16.0" - "@babel/types" "^7.16.0" + "@babel/helper-explode-assignable-expression" "^7.16.7" + "@babel/types" "^7.16.7" -"@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.16.3": - version "7.16.3" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.3.tgz#5b480cd13f68363df6ec4dc8ac8e2da11363cbf0" - integrity sha512-vKsoSQAyBmxS35JUOOt+07cLc6Nk/2ljLIHwmq2/NM6hdioUaqEXq/S+nXvbvXbZkNDlWOymPanJGOc4CBjSJA== +"@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.16.7", "@babel/helper-compilation-targets@^7.17.7": + version "7.17.7" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.7.tgz#a3c2924f5e5f0379b356d4cfb313d1414dc30e46" + integrity sha512-UFzlz2jjd8kroj0hmCFV5zr+tQPi1dpC2cRsDV/3IEW8bJfCPrPpmcSN6ZS8RqIq4LXcmpipCQFPddyFA5Yc7w== dependencies: - "@babel/compat-data" "^7.16.0" - "@babel/helper-validator-option" "^7.14.5" + "@babel/compat-data" "^7.17.7" + "@babel/helper-validator-option" "^7.16.7" browserslist "^4.17.5" semver "^6.3.0" -"@babel/helper-create-class-features-plugin@^7.16.0", "@babel/helper-create-class-features-plugin@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.16.5.tgz#5d1bcd096792c1ebec6249eebc6358eec55d0cad" - integrity sha512-NEohnYA7mkB8L5JhU7BLwcBdU3j83IziR9aseMueWGeAjblbul3zzb8UvJ3a1zuBiqCMObzCJHFqKIQE6hTVmg== +"@babel/helper-create-class-features-plugin@^7.16.10", "@babel/helper-create-class-features-plugin@^7.16.7", "@babel/helper-create-class-features-plugin@^7.17.6": + version "7.17.6" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.6.tgz#3778c1ed09a7f3e65e6d6e0f6fbfcc53809d92c9" + integrity sha512-SogLLSxXm2OkBbSsHZMM4tUi8fUzjs63AT/d0YQIzr6GSd8Hxsbk2KYDX0k0DweAzGMj/YWeiCsorIdtdcW8Eg== dependencies: - "@babel/helper-annotate-as-pure" "^7.16.0" - "@babel/helper-environment-visitor" "^7.16.5" - "@babel/helper-function-name" "^7.16.0" - "@babel/helper-member-expression-to-functions" "^7.16.5" - "@babel/helper-optimise-call-expression" "^7.16.0" - "@babel/helper-replace-supers" "^7.16.5" - "@babel/helper-split-export-declaration" "^7.16.0" + "@babel/helper-annotate-as-pure" "^7.16.7" + "@babel/helper-environment-visitor" "^7.16.7" + "@babel/helper-function-name" "^7.16.7" + "@babel/helper-member-expression-to-functions" "^7.16.7" + "@babel/helper-optimise-call-expression" "^7.16.7" + "@babel/helper-replace-supers" "^7.16.7" + "@babel/helper-split-export-declaration" "^7.16.7" -"@babel/helper-create-regexp-features-plugin@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.16.0.tgz#06b2348ce37fccc4f5e18dcd8d75053f2a7c44ff" - integrity sha512-3DyG0zAFAZKcOp7aVr33ddwkxJ0Z0Jr5V99y3I690eYLpukJsJvAbzTy1ewoCqsML8SbIrjH14Jc/nSQ4TvNPA== +"@babel/helper-create-regexp-features-plugin@^7.16.7": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.17.0.tgz#1dcc7d40ba0c6b6b25618997c5dbfd310f186fe1" + integrity sha512-awO2So99wG6KnlE+TPs6rn83gCz5WlEePJDTnLEqbchMVrBeAujURVphRdigsk094VhvZehFoNOihSlcBjwsXA== dependencies: - "@babel/helper-annotate-as-pure" "^7.16.0" - regexpu-core "^4.7.1" + "@babel/helper-annotate-as-pure" "^7.16.7" + regexpu-core "^5.0.1" -"@babel/helper-define-polyfill-provider@^0.3.0": - version "0.3.0" - resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.0.tgz#c5b10cf4b324ff840140bb07e05b8564af2ae971" - integrity sha512-7hfT8lUljl/tM3h+izTX/pO3W3frz2ok6Pk+gzys8iJqDfZrZy2pXjRTZAvG2YmfHun1X4q8/UZRLatMfqc5Tg== +"@babel/helper-define-polyfill-provider@^0.3.1": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.1.tgz#52411b445bdb2e676869e5a74960d2d3826d2665" + integrity sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA== dependencies: "@babel/helper-compilation-targets" "^7.13.0" "@babel/helper-module-imports" "^7.12.13" @@ -254,114 +261,114 @@ resolve "^1.14.2" semver "^6.1.2" -"@babel/helper-environment-visitor@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.5.tgz#f6a7f38b3c6d8b07c88faea083c46c09ef5451b8" - integrity sha512-ODQyc5AnxmZWm/R2W7fzhamOk1ey8gSguo5SGvF0zcB3uUzRpTRmM/jmLSm9bDMyPlvbyJ+PwPEK0BWIoZ9wjg== +"@babel/helper-environment-visitor@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz#ff484094a839bde9d89cd63cba017d7aae80ecd7" + integrity sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag== dependencies: - "@babel/types" "^7.16.0" + "@babel/types" "^7.16.7" -"@babel/helper-explode-assignable-expression@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.0.tgz#753017337a15f46f9c09f674cff10cee9b9d7778" - integrity sha512-Hk2SLxC9ZbcOhLpg/yMznzJ11W++lg5GMbxt1ev6TXUiJB0N42KPC+7w8a+eWGuqDnUYuwStJoZHM7RgmIOaGQ== +"@babel/helper-explode-assignable-expression@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.7.tgz#12a6d8522fdd834f194e868af6354e8650242b7a" + integrity sha512-KyUenhWMC8VrxzkGP0Jizjo4/Zx+1nNZhgocs+gLzyZyB8SHidhoq9KK/8Ato4anhwsivfkBLftky7gvzbZMtQ== dependencies: - "@babel/types" "^7.16.0" + "@babel/types" "^7.16.7" -"@babel/helper-function-name@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.16.0.tgz#b7dd0797d00bbfee4f07e9c4ea5b0e30c8bb1481" - integrity sha512-BZh4mEk1xi2h4HFjWUXRQX5AEx4rvaZxHgax9gcjdLWdkjsY7MKt5p0otjsg5noXw+pB+clMCjw+aEVYADMjog== +"@babel/helper-function-name@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz#f1ec51551fb1c8956bc8dd95f38523b6cf375f8f" + integrity sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA== dependencies: - "@babel/helper-get-function-arity" "^7.16.0" - "@babel/template" "^7.16.0" - "@babel/types" "^7.16.0" + "@babel/helper-get-function-arity" "^7.16.7" + "@babel/template" "^7.16.7" + "@babel/types" "^7.16.7" -"@babel/helper-get-function-arity@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.0.tgz#0088c7486b29a9cb5d948b1a1de46db66e089cfa" - integrity sha512-ASCquNcywC1NkYh/z7Cgp3w31YW8aojjYIlNg4VeJiHkqyP4AzIvr4qx7pYDb4/s8YcsZWqqOSxgkvjUz1kpDQ== +"@babel/helper-get-function-arity@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz#ea08ac753117a669f1508ba06ebcc49156387419" + integrity sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw== dependencies: - "@babel/types" "^7.16.0" + "@babel/types" "^7.16.7" -"@babel/helper-hoist-variables@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.0.tgz#4c9023c2f1def7e28ff46fc1dbcd36a39beaa81a" - integrity sha512-1AZlpazjUR0EQZQv3sgRNfM9mEVWPK3M6vlalczA+EECcPz3XPh6VplbErL5UoMpChhSck5wAJHthlj1bYpcmg== +"@babel/helper-hoist-variables@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz#86bcb19a77a509c7b77d0e22323ef588fa58c246" + integrity sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg== dependencies: - "@babel/types" "^7.16.0" + "@babel/types" "^7.16.7" -"@babel/helper-member-expression-to-functions@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.5.tgz#1bc9f7e87354e86f8879c67b316cb03d3dc2caab" - integrity sha512-7fecSXq7ZrLE+TWshbGT+HyCLkxloWNhTbU2QM1NTI/tDqyf0oZiMcEfYtDuUDCo528EOlt39G1rftea4bRZIw== +"@babel/helper-member-expression-to-functions@^7.16.7": + version "7.17.7" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.17.7.tgz#a34013b57d8542a8c4ff8ba3f747c02452a4d8c4" + integrity sha512-thxXgnQ8qQ11W2wVUObIqDL4p148VMxkt5T/qpN5k2fboRyzFGFmKsTGViquyM5QHKUy48OZoca8kw4ajaDPyw== dependencies: - "@babel/types" "^7.16.0" + "@babel/types" "^7.17.0" -"@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.0.tgz#90538e60b672ecf1b448f5f4f5433d37e79a3ec3" - integrity sha512-kkH7sWzKPq0xt3H1n+ghb4xEMP8k0U7XV3kkB+ZGy69kDk2ySFW1qPi06sjKzFY3t1j6XbJSqr4mF9L7CYVyhg== +"@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz#25612a8091a999704461c8a222d0efec5d091437" + integrity sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg== dependencies: - "@babel/types" "^7.16.0" + "@babel/types" "^7.16.7" -"@babel/helper-module-transforms@^7.12.1", "@babel/helper-module-transforms@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.16.5.tgz#530ebf6ea87b500f60840578515adda2af470a29" - integrity sha512-CkvMxgV4ZyyioElFwcuWnDCcNIeyqTkCm9BxXZi73RR1ozqlpboqsbGUNvRTflgZtFbbJ1v5Emvm+lkjMYY/LQ== - dependencies: - "@babel/helper-environment-visitor" "^7.16.5" - "@babel/helper-module-imports" "^7.16.0" - "@babel/helper-simple-access" "^7.16.0" - "@babel/helper-split-export-declaration" "^7.16.0" - "@babel/helper-validator-identifier" "^7.15.7" - "@babel/template" "^7.16.0" - "@babel/traverse" "^7.16.5" - "@babel/types" "^7.16.0" +"@babel/helper-module-transforms@^7.12.1", "@babel/helper-module-transforms@^7.16.7", "@babel/helper-module-transforms@^7.17.7": + version "7.17.7" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.17.7.tgz#3943c7f777139e7954a5355c815263741a9c1cbd" + integrity sha512-VmZD99F3gNTYB7fJRDTi+u6l/zxY0BE6OIxPSU7a50s6ZUQkHwSDmV92FfM+oCG0pZRVojGYhkR8I0OGeCVREw== + dependencies: + "@babel/helper-environment-visitor" "^7.16.7" + "@babel/helper-module-imports" "^7.16.7" + "@babel/helper-simple-access" "^7.17.7" + "@babel/helper-split-export-declaration" "^7.16.7" + "@babel/helper-validator-identifier" "^7.16.7" + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.17.3" + "@babel/types" "^7.17.0" -"@babel/helper-optimise-call-expression@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.0.tgz#cecdb145d70c54096b1564f8e9f10cd7d193b338" - integrity sha512-SuI467Gi2V8fkofm2JPnZzB/SUuXoJA5zXe/xzyPP2M04686RzFKFHPK6HDVN6JvWBIEW8tt9hPR7fXdn2Lgpw== +"@babel/helper-optimise-call-expression@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz#a34e3560605abbd31a18546bd2aad3e6d9a174f2" + integrity sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w== dependencies: - "@babel/types" "^7.16.0" + "@babel/types" "^7.16.7" "@babel/helper-plugin-utils@7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz#2f75a831269d4f677de49986dff59927533cf375" integrity sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg== -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.13.0", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.5", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.5.tgz#afe37a45f39fce44a3d50a7958129ea5b1a5c074" - integrity sha512-59KHWHXxVA9K4HNF4sbHCf+eJeFe0Te/ZFGqBT4OjXhrwvA04sGfaEGsVTdsjoszq0YTP49RC9UKe5g8uN2RwQ== +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.13.0", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz#aa3a8ab4c3cceff8e65eb9e73d87dc4ff320b2f5" + integrity sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA== -"@babel/helper-remap-async-to-generator@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.5.tgz#e706646dc4018942acb4b29f7e185bc246d65ac3" - integrity sha512-X+aAJldyxrOmN9v3FKp+Hu1NO69VWgYgDGq6YDykwRPzxs5f2N+X988CBXS7EQahDU+Vpet5QYMqLk+nsp+Qxw== +"@babel/helper-remap-async-to-generator@^7.16.8": + version "7.16.8" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.8.tgz#29ffaade68a367e2ed09c90901986918d25e57e3" + integrity sha512-fm0gH7Flb8H51LqJHy3HJ3wnE1+qtYR2A99K06ahwrawLdOFsCEWjZOrYricXJHoPSudNKxrMBUPEIPxiIIvBw== dependencies: - "@babel/helper-annotate-as-pure" "^7.16.0" - "@babel/helper-wrap-function" "^7.16.5" - "@babel/types" "^7.16.0" + "@babel/helper-annotate-as-pure" "^7.16.7" + "@babel/helper-wrap-function" "^7.16.8" + "@babel/types" "^7.16.8" -"@babel/helper-replace-supers@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.16.5.tgz#96d3988bd0ab0a2d22c88c6198c3d3234ca25326" - integrity sha512-ao3seGVa/FZCMCCNDuBcqnBFSbdr8N2EW35mzojx3TwfIbdPmNK+JV6+2d5bR0Z71W5ocLnQp9en/cTF7pBJiQ== +"@babel/helper-replace-supers@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.16.7.tgz#e9f5f5f32ac90429c1a4bdec0f231ef0c2838ab1" + integrity sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw== dependencies: - "@babel/helper-environment-visitor" "^7.16.5" - "@babel/helper-member-expression-to-functions" "^7.16.5" - "@babel/helper-optimise-call-expression" "^7.16.0" - "@babel/traverse" "^7.16.5" - "@babel/types" "^7.16.0" + "@babel/helper-environment-visitor" "^7.16.7" + "@babel/helper-member-expression-to-functions" "^7.16.7" + "@babel/helper-optimise-call-expression" "^7.16.7" + "@babel/traverse" "^7.16.7" + "@babel/types" "^7.16.7" -"@babel/helper-simple-access@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.16.0.tgz#21d6a27620e383e37534cf6c10bba019a6f90517" - integrity sha512-o1rjBT/gppAqKsYfUdfHq5Rk03lMQrkPHG1OWzHWpLgVXRH4HnMM9Et9CVdIqwkCQlobnGHEJMsgWP/jE1zUiw== +"@babel/helper-simple-access@^7.17.7": + version "7.17.7" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.17.7.tgz#aaa473de92b7987c6dfa7ce9a7d9674724823367" + integrity sha512-txyMCGroZ96i+Pxr3Je3lzEJjqwaRC9buMUgtomcrLe5Nd0+fk1h0LLA+ixUF5OW7AhHuQ7Es1WcQJZmZsz2XA== dependencies: - "@babel/types" "^7.16.0" + "@babel/types" "^7.17.0" "@babel/helper-skip-transparent-expression-wrappers@^7.16.0": version "7.16.0" @@ -370,144 +377,144 @@ dependencies: "@babel/types" "^7.16.0" -"@babel/helper-split-export-declaration@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.0.tgz#29672f43663e936df370aaeb22beddb3baec7438" - integrity sha512-0YMMRpuDFNGTHNRiiqJX19GjNXA4H0E8jZ2ibccfSxaCogbm3am5WN/2nQNj0YnQwGWM1J06GOcQ2qnh3+0paw== +"@babel/helper-split-export-declaration@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz#0b648c0c42da9d3920d85ad585f2778620b8726b" + integrity sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw== dependencies: - "@babel/types" "^7.16.0" + "@babel/types" "^7.16.7" -"@babel/helper-validator-identifier@^7.15.7": - version "7.15.7" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz#220df993bfe904a4a6b02ab4f3385a5ebf6e2389" - integrity sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w== +"@babel/helper-validator-identifier@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad" + integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw== -"@babel/helper-validator-option@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz#6e72a1fff18d5dfcb878e1e62f1a021c4b72d5a3" - integrity sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow== +"@babel/helper-validator-option@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz#b203ce62ce5fe153899b617c08957de860de4d23" + integrity sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ== -"@babel/helper-wrap-function@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.16.5.tgz#0158fca6f6d0889c3fee8a6ed6e5e07b9b54e41f" - integrity sha512-2J2pmLBqUqVdJw78U0KPNdeE2qeuIyKoG4mKV7wAq3mc4jJG282UgjZw4ZYDnqiWQuS3Y3IYdF/AQ6CpyBV3VA== +"@babel/helper-wrap-function@^7.16.8": + version "7.16.8" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.16.8.tgz#58afda087c4cd235de92f7ceedebca2c41274200" + integrity sha512-8RpyRVIAW1RcDDGTA+GpPAwV22wXCfKOoM9bet6TLkGIFTkRQSkH1nMQ5Yet4MpoXe1ZwHPVtNasc2w0uZMqnw== dependencies: - "@babel/helper-function-name" "^7.16.0" - "@babel/template" "^7.16.0" - "@babel/traverse" "^7.16.5" - "@babel/types" "^7.16.0" + "@babel/helper-function-name" "^7.16.7" + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.16.8" + "@babel/types" "^7.16.8" -"@babel/helpers@^7.12.5", "@babel/helpers@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.16.5.tgz#29a052d4b827846dd76ece16f565b9634c554ebd" - integrity sha512-TLgi6Lh71vvMZGEkFuIxzaPsyeYCHQ5jJOOX1f0xXn0uciFuE8cEk0wyBquMcCxBXZ5BJhE2aUB7pnWTD150Tw== +"@babel/helpers@^7.12.5", "@babel/helpers@^7.17.8": + version "7.17.8" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.17.8.tgz#288450be8c6ac7e4e44df37bcc53d345e07bc106" + integrity sha512-QcL86FGxpfSJwGtAvv4iG93UL6bmqBdmoVY0CMCU2g+oD2ezQse3PT5Pa+jiD6LJndBQi0EDlpzOWNlLuhz5gw== dependencies: - "@babel/template" "^7.16.0" - "@babel/traverse" "^7.16.5" - "@babel/types" "^7.16.0" + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.17.3" + "@babel/types" "^7.17.0" -"@babel/highlight@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.16.0.tgz#6ceb32b2ca4b8f5f361fb7fd821e3fddf4a1725a" - integrity sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g== +"@babel/highlight@^7.16.7": + version "7.16.10" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.16.10.tgz#744f2eb81579d6eea753c227b0f570ad785aba88" + integrity sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw== dependencies: - "@babel/helper-validator-identifier" "^7.15.7" + "@babel/helper-validator-identifier" "^7.16.7" chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.12.7", "@babel/parser@^7.16.0", "@babel/parser@^7.16.4", "@babel/parser@^7.16.5": - version "7.16.6" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.16.6.tgz#8f194828193e8fa79166f34a4b4e52f3e769a314" - integrity sha512-Gr86ujcNuPDnNOY8mi383Hvi8IYrJVJYuf3XcuBM/Dgd+bINn/7tHqsj+tKkoreMbmGsFLsltI/JJd8fOFWGDQ== +"@babel/parser@^7.12.7", "@babel/parser@^7.16.7", "@babel/parser@^7.17.3", "@babel/parser@^7.17.8": + version "7.17.8" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.8.tgz#2817fb9d885dd8132ea0f8eb615a6388cca1c240" + integrity sha512-BoHhDJrJXqcg+ZL16Xv39H9n+AqJ4pcDrQBGZN+wHxIysrLZ3/ECwCBUch/1zUNhnsXULcONU3Ei5Hmkfk6kiQ== -"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.16.2": - version "7.16.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.16.2.tgz#2977fca9b212db153c195674e57cfab807733183" - integrity sha512-h37CvpLSf8gb2lIJ2CgC3t+EjFbi0t8qS7LCS1xcJIlEXE4czlofwaW7W1HA8zpgOCzI9C1nmoqNR1zWkk0pQg== +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.16.7.tgz#4eda6d6c2a0aa79c70fa7b6da67763dfe2141050" + integrity sha512-anv/DObl7waiGEnC24O9zqL0pSuI9hljihqiDuFHC8d7/bjr/4RLGPWuc8rYOff/QPzbEPSkzG8wGG9aDuhHRg== dependencies: - "@babel/helper-plugin-utils" "^7.14.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.16.0.tgz#358972eaab006f5eb0826183b0c93cbcaf13e1e2" - integrity sha512-4tcFwwicpWTrpl9qjf7UsoosaArgImF85AxqCRZlgc3IQDvkUHjJpruXAL58Wmj+T6fypWTC/BakfEkwIL/pwA== +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.16.7.tgz#cc001234dfc139ac45f6bcf801866198c8c72ff9" + integrity sha512-di8vUHRdf+4aJ7ltXhaDbPoszdkh59AQtJM5soLsuHpQJdFQZOA4uGj0V2u/CZ8bJ/u8ULDL5yq6FO/bCXnKHw== dependencies: - "@babel/helper-plugin-utils" "^7.14.5" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/helper-skip-transparent-expression-wrappers" "^7.16.0" - "@babel/plugin-proposal-optional-chaining" "^7.16.0" + "@babel/plugin-proposal-optional-chaining" "^7.16.7" -"@babel/plugin-proposal-async-generator-functions@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.16.5.tgz#fd3bd7e0d98404a3d4cbca15a72d533f8c9a2f67" - integrity sha512-C/FX+3HNLV6sz7AqbTQqEo1L9/kfrKjxcVtgyBCmvIgOjvuBVUWooDoi7trsLxOzCEo5FccjRvKHkfDsJFZlfA== +"@babel/plugin-proposal-async-generator-functions@^7.16.8": + version "7.16.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.16.8.tgz#3bdd1ebbe620804ea9416706cd67d60787504bc8" + integrity sha512-71YHIvMuiuqWJQkebWJtdhQTfd4Q4mF76q2IX37uZPkG9+olBxsX+rH1vkhFto4UeJZ9dPY2s+mDvhDm1u2BGQ== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" - "@babel/helper-remap-async-to-generator" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-remap-async-to-generator" "^7.16.8" "@babel/plugin-syntax-async-generators" "^7.8.4" -"@babel/plugin-proposal-class-properties@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.5.tgz#3269f44b89122110f6339806e05d43d84106468a" - integrity sha512-pJD3HjgRv83s5dv1sTnDbZOaTjghKEz8KUn1Kbh2eAIRhGuyQ1XSeI4xVXU3UlIEVA3DAyIdxqT1eRn7Wcn55A== +"@babel/plugin-proposal-class-properties@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.7.tgz#925cad7b3b1a2fcea7e59ecc8eb5954f961f91b0" + integrity sha512-IobU0Xme31ewjYOShSIqd/ZGM/r/cuOz2z0MDbNrhF5FW+ZVgi0f2lyeoj9KFPDOAqsYxmLWZte1WOwlvY9aww== dependencies: - "@babel/helper-create-class-features-plugin" "^7.16.5" - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-create-class-features-plugin" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-proposal-class-static-block@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.16.5.tgz#df58ab015a7d3b0963aafc8f20792dcd834952a9" - integrity sha512-EEFzuLZcm/rNJ8Q5krK+FRKdVkd6FjfzT9tuSZql9sQn64K0hHA2KLJ0DqVot9/iV6+SsuadC5yI39zWnm+nmQ== +"@babel/plugin-proposal-class-static-block@^7.16.7": + version "7.17.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.17.6.tgz#164e8fd25f0d80fa48c5a4d1438a6629325ad83c" + integrity sha512-X/tididvL2zbs7jZCeeRJ8167U/+Ac135AM6jCAx6gYXDUviZV5Ku9UDvWS2NCuWlFjIRXklYhwo6HhAC7ETnA== dependencies: - "@babel/helper-create-class-features-plugin" "^7.16.5" - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-create-class-features-plugin" "^7.17.6" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-class-static-block" "^7.14.5" -"@babel/plugin-proposal-dynamic-import@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.5.tgz#2e0d19d5702db4dcb9bc846200ca02f2e9d60e9e" - integrity sha512-P05/SJZTTvHz79LNYTF8ff5xXge0kk5sIIWAypcWgX4BTRUgyHc8wRxJ/Hk+mU0KXldgOOslKaeqnhthcDJCJQ== +"@babel/plugin-proposal-dynamic-import@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.7.tgz#c19c897eaa46b27634a00fee9fb7d829158704b2" + integrity sha512-I8SW9Ho3/8DRSdmDdH3gORdyUuYnk1m4cMxUAdu5oy4n3OfN8flDEH+d60iG7dUfi0KkYwSvoalHzzdRzpWHTg== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-dynamic-import" "^7.8.3" -"@babel/plugin-proposal-export-namespace-from@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.16.5.tgz#3b4dd28378d1da2fea33e97b9f25d1c2f5bf1ac9" - integrity sha512-i+sltzEShH1vsVydvNaTRsgvq2vZsfyrd7K7vPLUU/KgS0D5yZMe6uipM0+izminnkKrEfdUnz7CxMRb6oHZWw== +"@babel/plugin-proposal-export-namespace-from@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.16.7.tgz#09de09df18445a5786a305681423ae63507a6163" + integrity sha512-ZxdtqDXLRGBL64ocZcs7ovt71L3jhC1RGSyR996svrCi3PYqHNkb3SwPJCs8RIzD86s+WPpt2S73+EHCGO+NUA== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-export-namespace-from" "^7.8.3" -"@babel/plugin-proposal-json-strings@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.16.5.tgz#1e726930fca139caab6b084d232a9270d9d16f9c" - integrity sha512-QQJueTFa0y9E4qHANqIvMsuxM/qcLQmKttBACtPCQzGUEizsXDACGonlPiSwynHfOa3vNw0FPMVvQzbuXwh4SQ== +"@babel/plugin-proposal-json-strings@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.16.7.tgz#9732cb1d17d9a2626a08c5be25186c195b6fa6e8" + integrity sha512-lNZ3EEggsGY78JavgbHsK9u5P3pQaW7k4axlgFLYkMd7UBsiNahCITShLjNQschPyjtO6dADrL24757IdhBrsQ== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-json-strings" "^7.8.3" -"@babel/plugin-proposal-logical-assignment-operators@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.16.5.tgz#df1f2e4b5a0ec07abf061d2c18e53abc237d3ef5" - integrity sha512-xqibl7ISO2vjuQM+MzR3rkd0zfNWltk7n9QhaD8ghMmMceVguYrNDt7MikRyj4J4v3QehpnrU8RYLnC7z/gZLA== +"@babel/plugin-proposal-logical-assignment-operators@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.16.7.tgz#be23c0ba74deec1922e639832904be0bea73cdea" + integrity sha512-K3XzyZJGQCr00+EtYtrDjmwX7o7PLK6U9bi1nCwkQioRFVUv6dJoxbQjtWVtP+bCPy82bONBKG8NPyQ4+i6yjg== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" -"@babel/plugin-proposal-nullish-coalescing-operator@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.5.tgz#652555bfeeeee2d2104058c6225dc6f75e2d0f07" - integrity sha512-YwMsTp/oOviSBhrjwi0vzCUycseCYwoXnLiXIL3YNjHSMBHicGTz7GjVU/IGgz4DtOEXBdCNG72pvCX22ehfqg== +"@babel/plugin-proposal-nullish-coalescing-operator@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.7.tgz#141fc20b6857e59459d430c850a0011e36561d99" + integrity sha512-aUOrYU3EVtjf62jQrCj63pYZ7k6vns2h/DQvHPWGmsJRYzWXZ6/AsfgpiRy6XiuIDADhJzP2Q9MwSMKauBQ+UQ== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" -"@babel/plugin-proposal-numeric-separator@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.5.tgz#edcb6379b6cf4570be64c45965d8da7a2debf039" - integrity sha512-DvB9l/TcsCRvsIV9v4jxR/jVP45cslTVC0PMVHvaJhhNuhn2Y1SOhCSFlPK777qLB5wb8rVDaNoqMTyOqtY5Iw== +"@babel/plugin-proposal-numeric-separator@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.7.tgz#d6b69f4af63fb38b6ca2558442a7fb191236eba9" + integrity sha512-vQgPMknOIgiuVqbokToyXbkY/OmmjAzr/0lhSIbG/KmnzXPGwW/AdhdKpi+O4X/VkWiWjnkKOBiqJrTaC98VKw== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-numeric-separator" "^7.10.4" "@babel/plugin-proposal-object-rest-spread@7.12.1": @@ -519,59 +526,59 @@ "@babel/plugin-syntax-object-rest-spread" "^7.8.0" "@babel/plugin-transform-parameters" "^7.12.1" -"@babel/plugin-proposal-object-rest-spread@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.16.5.tgz#f30f80dacf7bc1404bf67f99c8d9c01665e830ad" - integrity sha512-UEd6KpChoyPhCoE840KRHOlGhEZFutdPDMGj+0I56yuTTOaT51GzmnEl/0uT41fB/vD2nT+Pci2KjezyE3HmUw== +"@babel/plugin-proposal-object-rest-spread@^7.16.7": + version "7.17.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.17.3.tgz#d9eb649a54628a51701aef7e0ea3d17e2b9dd390" + integrity sha512-yuL5iQA/TbZn+RGAfxQXfi7CNLmKi1f8zInn4IgobuCWcAb7i+zj4TYzQ9l8cEzVyJ89PDGuqxK1xZpUDISesw== dependencies: - "@babel/compat-data" "^7.16.4" - "@babel/helper-compilation-targets" "^7.16.3" - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/compat-data" "^7.17.0" + "@babel/helper-compilation-targets" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.16.5" + "@babel/plugin-transform-parameters" "^7.16.7" -"@babel/plugin-proposal-optional-catch-binding@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.5.tgz#1a5405765cf589a11a33a1fd75b2baef7d48b74e" - integrity sha512-ihCMxY1Iljmx4bWy/PIMJGXN4NS4oUj1MKynwO07kiKms23pNvIn1DMB92DNB2R0EA882sw0VXIelYGdtF7xEQ== +"@babel/plugin-proposal-optional-catch-binding@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.7.tgz#c623a430674ffc4ab732fd0a0ae7722b67cb74cf" + integrity sha512-eMOH/L4OvWSZAE1VkHbr1vckLG1WUcHGJSLqqQwl2GaUqG6QjddvrOaTUMNYiv77H5IKPMZ9U9P7EaHwvAShfA== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" -"@babel/plugin-proposal-optional-chaining@^7.16.0", "@babel/plugin-proposal-optional-chaining@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.5.tgz#a5fa61056194d5059366c0009cb9a9e66ed75c1f" - integrity sha512-kzdHgnaXRonttiTfKYnSVafbWngPPr2qKw9BWYBESl91W54e+9R5pP70LtWxV56g0f05f/SQrwHYkfvbwcdQ/A== +"@babel/plugin-proposal-optional-chaining@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.7.tgz#7cd629564724816c0e8a969535551f943c64c39a" + integrity sha512-eC3xy+ZrUcBtP7x+sq62Q/HYd674pPTb/77XZMb5wbDPGWIdUbSr4Agr052+zaUPSb+gGRnjxXfKFvx5iMJ+DA== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/helper-skip-transparent-expression-wrappers" "^7.16.0" "@babel/plugin-syntax-optional-chaining" "^7.8.3" -"@babel/plugin-proposal-private-methods@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.16.5.tgz#2086f7d78c1b0c712d49b5c3fbc2d1ca21a7ee12" - integrity sha512-+yFMO4BGT3sgzXo+lrq7orX5mAZt57DwUK6seqII6AcJnJOIhBJ8pzKH47/ql/d426uQ7YhN8DpUFirQzqYSUA== +"@babel/plugin-proposal-private-methods@^7.16.11": + version "7.16.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.16.11.tgz#e8df108288555ff259f4527dbe84813aac3a1c50" + integrity sha512-F/2uAkPlXDr8+BHpZvo19w3hLFKge+k75XUprE6jaqKxjGkSYcK+4c+bup5PdW/7W/Rpjwql7FTVEDW+fRAQsw== dependencies: - "@babel/helper-create-class-features-plugin" "^7.16.5" - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-create-class-features-plugin" "^7.16.10" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-proposal-private-property-in-object@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.16.5.tgz#a42d4b56005db3d405b12841309dbca647e7a21b" - integrity sha512-+YGh5Wbw0NH3y/E5YMu6ci5qTDmAEVNoZ3I54aB6nVEOZ5BQ7QJlwKq5pYVucQilMByGn/bvX0af+uNaPRCabA== +"@babel/plugin-proposal-private-property-in-object@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.16.7.tgz#b0b8cef543c2c3d57e59e2c611994861d46a3fce" + integrity sha512-rMQkjcOFbm+ufe3bTZLyOfsOUOxyvLXZJCTARhJr+8UMSoZmqTe1K1BgkFcrW37rAchWg57yI69ORxiWvUINuQ== dependencies: - "@babel/helper-annotate-as-pure" "^7.16.0" - "@babel/helper-create-class-features-plugin" "^7.16.5" - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-annotate-as-pure" "^7.16.7" + "@babel/helper-create-class-features-plugin" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-private-property-in-object" "^7.14.5" -"@babel/plugin-proposal-unicode-property-regex@^7.16.5", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.16.5.tgz#35fe753afa7c572f322bd068ff3377bde0f37080" - integrity sha512-s5sKtlKQyFSatt781HQwv1hoM5BQ9qRH30r+dK56OLDsHmV74mzwJNX7R1yMuE7VZKG5O6q/gmOGSAO6ikTudg== +"@babel/plugin-proposal-unicode-property-regex@^7.16.7", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.16.7.tgz#635d18eb10c6214210ffc5ff4932552de08188a2" + integrity sha512-QRK0YI/40VLhNVGIjRNAAQkEHws0cswSdFFjpFyt943YmJIU1da9uW63Iu6NFV6CxTZW5eTDCrwZUstBWgp/Rg== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.16.0" - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-create-regexp-features-plugin" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" @@ -622,12 +629,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-syntax-jsx@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.5.tgz#bf255d252f78bc8b77a17cadc37d1aa5b8ed4394" - integrity sha512-42OGssv9NPk4QHKVgIHlzeLgPOW5rGgfV5jzG90AhcXXIv6hu/eqj63w4VgvRxdvZY3AlYeDgPiSJ3BqAd1Y6Q== +"@babel/plugin-syntax-jsx@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.7.tgz#50b6571d13f764266a113d77c82b4a6508bbe665" + integrity sha512-Esxmk7YjA8QysKeT3VhTXvF6y77f/a91SIs4pWb4H2eWGQkCKFgQaG6hdoEVZtGsrAcb2K5BW66XsOErD4WU3Q== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-logical-assignment-operators@^7.10.4": version "7.10.4" @@ -685,349 +692,350 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-syntax-typescript@^7.16.0": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.5.tgz#f47a33e4eee38554f00fb6b2f894fa1f5649b0b3" - integrity sha512-/d4//lZ1Vqb4mZ5xTep3dDK888j7BGM/iKqBmndBaoYAFPlPKrGU608VVBz5JeyAb6YQDjRu1UKqj86UhwWVgw== +"@babel/plugin-syntax-typescript@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.7.tgz#39c9b55ee153151990fb038651d58d3fd03f98f8" + integrity sha512-YhUIJHHGkqPgEcMYkPCKTyGUdoGKWtopIycQyjJH8OjvRgOYsXsaKehLVPScKJWAULPxMa4N1vCe6szREFlZ7A== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-arrow-functions@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.16.5.tgz#04c18944dd55397b521d9d7511e791acea7acf2d" - integrity sha512-8bTHiiZyMOyfZFULjsCnYOWG059FVMes0iljEHSfARhNgFfpsqE92OrCffv3veSw9rwMkYcFe9bj0ZoXU2IGtQ== +"@babel/plugin-transform-arrow-functions@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.16.7.tgz#44125e653d94b98db76369de9c396dc14bef4154" + integrity sha512-9ffkFFMbvzTvv+7dTp/66xvZAWASuPD5Tl9LK3Z9vhOmANo6j94rik+5YMBt4CwHVMWLWpMsriIc2zsa3WW3xQ== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-async-to-generator@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.16.5.tgz#89c9b501e65bb14c4579a6ce9563f859de9b34e4" - integrity sha512-TMXgfioJnkXU+XRoj7P2ED7rUm5jbnDWwlCuFVTpQboMfbSya5WrmubNBAMlk7KXvywpo8rd8WuYZkis1o2H8w== +"@babel/plugin-transform-async-to-generator@^7.16.8": + version "7.16.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.16.8.tgz#b83dff4b970cf41f1b819f8b49cc0cfbaa53a808" + integrity sha512-MtmUmTJQHCnyJVrScNzNlofQJ3dLFuobYn3mwOTKHnSCMtbNsqvF71GQmJfFjdrXSsAA7iysFmYWw4bXZ20hOg== dependencies: - "@babel/helper-module-imports" "^7.16.0" - "@babel/helper-plugin-utils" "^7.16.5" - "@babel/helper-remap-async-to-generator" "^7.16.5" + "@babel/helper-module-imports" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-remap-async-to-generator" "^7.16.8" -"@babel/plugin-transform-block-scoped-functions@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.5.tgz#af087494e1c387574260b7ee9b58cdb5a4e9b0b0" - integrity sha512-BxmIyKLjUGksJ99+hJyL/HIxLIGnLKtw772zYDER7UuycDZ+Xvzs98ZQw6NGgM2ss4/hlFAaGiZmMNKvValEjw== +"@babel/plugin-transform-block-scoped-functions@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.7.tgz#4d0d57d9632ef6062cdf354bb717102ee042a620" + integrity sha512-JUuzlzmF40Z9cXyytcbZEZKckgrQzChbQJw/5PuEHYeqzCsvebDx0K0jWnIIVcmmDOAVctCgnYs0pMcrYj2zJg== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-block-scoping@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.16.5.tgz#b91f254fe53e210eabe4dd0c40f71c0ed253c5e7" - integrity sha512-JxjSPNZSiOtmxjX7PBRBeRJTUKTyJ607YUYeT0QJCNdsedOe+/rXITjP08eG8xUpsLfPirgzdCFN+h0w6RI+pQ== +"@babel/plugin-transform-block-scoping@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.16.7.tgz#f50664ab99ddeaee5bc681b8f3a6ea9d72ab4f87" + integrity sha512-ObZev2nxVAYA4bhyusELdo9hb3H+A56bxH3FZMbEImZFiEDYVHXQSJ1hQKFlDnlt8G9bBrCZ5ZpURZUrV4G5qQ== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-classes@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.16.5.tgz#6acf2ec7adb50fb2f3194dcd2909dbd056dcf216" - integrity sha512-DzJ1vYf/7TaCYy57J3SJ9rV+JEuvmlnvvyvYKFbk5u46oQbBvuB9/0w+YsVsxkOv8zVWKpDmUoj4T5ILHoXevA== +"@babel/plugin-transform-classes@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.16.7.tgz#8f4b9562850cd973de3b498f1218796eb181ce00" + integrity sha512-WY7og38SFAGYRe64BrjKf8OrE6ulEHtr5jEYaZMwox9KebgqPi67Zqz8K53EKk1fFEJgm96r32rkKZ3qA2nCWQ== dependencies: - "@babel/helper-annotate-as-pure" "^7.16.0" - "@babel/helper-environment-visitor" "^7.16.5" - "@babel/helper-function-name" "^7.16.0" - "@babel/helper-optimise-call-expression" "^7.16.0" - "@babel/helper-plugin-utils" "^7.16.5" - "@babel/helper-replace-supers" "^7.16.5" - "@babel/helper-split-export-declaration" "^7.16.0" + "@babel/helper-annotate-as-pure" "^7.16.7" + "@babel/helper-environment-visitor" "^7.16.7" + "@babel/helper-function-name" "^7.16.7" + "@babel/helper-optimise-call-expression" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-replace-supers" "^7.16.7" + "@babel/helper-split-export-declaration" "^7.16.7" globals "^11.1.0" -"@babel/plugin-transform-computed-properties@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.16.5.tgz#2af91ebf0cceccfcc701281ada7cfba40a9b322a" - integrity sha512-n1+O7xtU5lSLraRzX88CNcpl7vtGdPakKzww74bVwpAIRgz9JVLJJpOLb0uYqcOaXVM0TL6X0RVeIJGD2CnCkg== +"@babel/plugin-transform-computed-properties@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.16.7.tgz#66dee12e46f61d2aae7a73710f591eb3df616470" + integrity sha512-gN72G9bcmenVILj//sv1zLNaPyYcOzUho2lIJBMh/iakJ9ygCo/hEF9cpGb61SCMEDxbbyBoVQxrt+bWKu5KGw== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-destructuring@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.16.5.tgz#89ebc87499ac4a81b897af53bb5d3eed261bd568" - integrity sha512-GuRVAsjq+c9YPK6NeTkRLWyQskDC099XkBSVO+6QzbnOnH2d/4mBVXYStaPrZD3dFRfg00I6BFJ9Atsjfs8mlg== +"@babel/plugin-transform-destructuring@^7.16.7": + version "7.17.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.17.7.tgz#49dc2675a7afa9a5e4c6bdee636061136c3408d1" + integrity sha512-XVh0r5yq9sLR4vZ6eVZe8FKfIcSgaTBxVBRSYokRj2qksf6QerYnTxz9/GTuKTH/n/HwLP7t6gtlybHetJ/6hQ== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-dotall-regex@^7.16.5", "@babel/plugin-transform-dotall-regex@^7.4.4": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.16.5.tgz#b40739c00b6686820653536d6d143e311de67936" - integrity sha512-iQiEMt8Q4/5aRGHpGVK2Zc7a6mx7qEAO7qehgSug3SDImnuMzgmm/wtJALXaz25zUj1PmnNHtShjFgk4PDx4nw== +"@babel/plugin-transform-dotall-regex@^7.16.7", "@babel/plugin-transform-dotall-regex@^7.4.4": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.16.7.tgz#6b2d67686fab15fb6a7fd4bd895d5982cfc81241" + integrity sha512-Lyttaao2SjZF6Pf4vk1dVKv8YypMpomAbygW+mU5cYP3S5cWTfCJjG8xV6CFdzGFlfWK81IjL9viiTvpb6G7gQ== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.16.0" - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-create-regexp-features-plugin" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-duplicate-keys@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.16.5.tgz#2450f2742325412b746d7d005227f5e8973b512a" - integrity sha512-81tijpDg2a6I1Yhj4aWY1l3O1J4Cg/Pd7LfvuaH2VVInAkXtzibz9+zSPdUM1WvuUi128ksstAP0hM5w48vQgg== +"@babel/plugin-transform-duplicate-keys@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.16.7.tgz#2207e9ca8f82a0d36a5a67b6536e7ef8b08823c9" + integrity sha512-03DvpbRfvWIXyK0/6QiR1KMTWeT6OcQ7tbhjrXyFS02kjuX/mu5Bvnh5SDSWHxyawit2g5aWhKwI86EE7GUnTw== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-exponentiation-operator@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.5.tgz#36e261fa1ab643cfaf30eeab38e00ed1a76081e2" - integrity sha512-12rba2HwemQPa7BLIKCzm1pT2/RuQHtSFHdNl41cFiC6oi4tcrp7gjB07pxQvFpcADojQywSjblQth6gJyE6CA== +"@babel/plugin-transform-exponentiation-operator@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.7.tgz#efa9862ef97e9e9e5f653f6ddc7b665e8536fe9b" + integrity sha512-8UYLSlyLgRixQvlYH3J2ekXFHDFLQutdy7FfFAMm3CPZ6q9wHCwnUyiXpQCe3gVVnQlHc5nsuiEVziteRNTXEA== dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor" "^7.16.5" - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-for-of@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.16.5.tgz#9b544059c6ca11d565457c0ff1f08e13ce225261" - integrity sha512-+DpCAJFPAvViR17PIMi9x2AE34dll5wNlXO43wagAX2YcRGgEVHCNFC4azG85b4YyyFarvkc/iD5NPrz4Oneqw== +"@babel/plugin-transform-for-of@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.16.7.tgz#649d639d4617dff502a9a158c479b3b556728d8c" + integrity sha512-/QZm9W92Ptpw7sjI9Nx1mbcsWz33+l8kuMIQnDwgQBG5s3fAfQvkRjQ7NqXhtNcKOnPkdICmUHyCaWW06HCsqg== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-function-name@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.16.5.tgz#6896ebb6a5538a75d6a4086a277752f655a7bd15" - integrity sha512-Fuec/KPSpVLbGo6z1RPw4EE1X+z9gZk1uQmnYy7v4xr4TO9p41v1AoUuXEtyqAI7H+xNJYSICzRqZBhDEkd3kQ== +"@babel/plugin-transform-function-name@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.16.7.tgz#5ab34375c64d61d083d7d2f05c38d90b97ec65cf" + integrity sha512-SU/C68YVwTRxqWj5kgsbKINakGag0KTgq9f2iZEXdStoAbOzLHEBRYzImmA6yFo8YZhJVflvXmIHUO7GWHmxxA== dependencies: - "@babel/helper-function-name" "^7.16.0" - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-compilation-targets" "^7.16.7" + "@babel/helper-function-name" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-literals@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.16.5.tgz#af392b90e3edb2bd6dc316844cbfd6b9e009d320" - integrity sha512-B1j9C/IfvshnPcklsc93AVLTrNVa69iSqztylZH6qnmiAsDDOmmjEYqOm3Ts2lGSgTSywnBNiqC949VdD0/gfw== +"@babel/plugin-transform-literals@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.16.7.tgz#254c9618c5ff749e87cb0c0cef1a0a050c0bdab1" + integrity sha512-6tH8RTpTWI0s2sV6uq3e/C9wPo4PTqqZps4uF0kzQ9/xPLFQtipynvmT1g/dOfEJ+0EQsHhkQ/zyRId8J2b8zQ== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-member-expression-literals@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.16.5.tgz#4bd6ecdc11932361631097b779ca5c7570146dd5" - integrity sha512-d57i3vPHWgIde/9Y8W/xSFUndhvhZN5Wu2TjRrN1MVz5KzdUihKnfDVlfP1U7mS5DNj/WHHhaE4/tTi4hIyHwQ== +"@babel/plugin-transform-member-expression-literals@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.16.7.tgz#6e5dcf906ef8a098e630149d14c867dd28f92384" + integrity sha512-mBruRMbktKQwbxaJof32LT9KLy2f3gH+27a5XSuXo6h7R3vqltl0PgZ80C8ZMKw98Bf8bqt6BEVi3svOh2PzMw== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-modules-amd@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.16.5.tgz#92c0a3e83f642cb7e75fada9ab497c12c2616527" - integrity sha512-oHI15S/hdJuSCfnwIz+4lm6wu/wBn7oJ8+QrkzPPwSFGXk8kgdI/AIKcbR/XnD1nQVMg/i6eNaXpszbGuwYDRQ== +"@babel/plugin-transform-modules-amd@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.16.7.tgz#b28d323016a7daaae8609781d1f8c9da42b13186" + integrity sha512-KaaEtgBL7FKYwjJ/teH63oAmE3lP34N3kshz8mm4VMAw7U3PxjVwwUmxEFksbgsNUaO3wId9R2AVQYSEGRa2+g== dependencies: - "@babel/helper-module-transforms" "^7.16.5" - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-module-transforms" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-commonjs@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.16.5.tgz#4ee03b089536f076b2773196529d27c32b9d7bde" - integrity sha512-ABhUkxvoQyqhCWyb8xXtfwqNMJD7tx+irIRnUh6lmyFud7Jln1WzONXKlax1fg/ey178EXbs4bSGNd6PngO+SQ== +"@babel/plugin-transform-modules-commonjs@^7.16.8": + version "7.17.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.17.7.tgz#d86b217c8e45bb5f2dbc11eefc8eab62cf980d19" + integrity sha512-ITPmR2V7MqioMJyrxUo2onHNC3e+MvfFiFIR0RP21d3PtlVb6sfzoxNKiphSZUOM9hEIdzCcZe83ieX3yoqjUA== dependencies: - "@babel/helper-module-transforms" "^7.16.5" - "@babel/helper-plugin-utils" "^7.16.5" - "@babel/helper-simple-access" "^7.16.0" + "@babel/helper-module-transforms" "^7.17.7" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-simple-access" "^7.17.7" babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-systemjs@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.16.5.tgz#07078ba2e3cc94fbdd06836e355c246e98ad006b" - integrity sha512-53gmLdScNN28XpjEVIm7LbWnD/b/TpbwKbLk6KV4KqC9WyU6rq1jnNmVG6UgAdQZVVGZVoik3DqHNxk4/EvrjA== +"@babel/plugin-transform-modules-systemjs@^7.16.7": + version "7.17.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.17.8.tgz#81fd834024fae14ea78fbe34168b042f38703859" + integrity sha512-39reIkMTUVagzgA5x88zDYXPCMT6lcaRKs1+S9K6NKBPErbgO/w/kP8GlNQTC87b412ZTlmNgr3k2JrWgHH+Bw== dependencies: - "@babel/helper-hoist-variables" "^7.16.0" - "@babel/helper-module-transforms" "^7.16.5" - "@babel/helper-plugin-utils" "^7.16.5" - "@babel/helper-validator-identifier" "^7.15.7" + "@babel/helper-hoist-variables" "^7.16.7" + "@babel/helper-module-transforms" "^7.17.7" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-validator-identifier" "^7.16.7" babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-umd@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.16.5.tgz#caa9c53d636fb4e3c99fd35a4c9ba5e5cd7e002e" - integrity sha512-qTFnpxHMoenNHkS3VoWRdwrcJ3FhX567GvDA3hRZKF0Dj8Fmg0UzySZp3AP2mShl/bzcywb/UWAMQIjA1bhXvw== +"@babel/plugin-transform-modules-umd@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.16.7.tgz#23dad479fa585283dbd22215bff12719171e7618" + integrity sha512-EMh7uolsC8O4xhudF2F6wedbSHm1HHZ0C6aJ7K67zcDNidMzVcxWdGr+htW9n21klm+bOn+Rx4CBsAntZd3rEQ== dependencies: - "@babel/helper-module-transforms" "^7.16.5" - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-module-transforms" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-named-capturing-groups-regex@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.16.5.tgz#4afd8cdee377ce3568f4e8a9ee67539b69886a3c" - integrity sha512-/wqGDgvFUeKELW6ex6QB7dLVRkd5ehjw34tpXu1nhKC0sFfmaLabIswnpf8JgDyV2NeDmZiwoOb0rAmxciNfjA== +"@babel/plugin-transform-named-capturing-groups-regex@^7.16.8": + version "7.16.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.16.8.tgz#7f860e0e40d844a02c9dcf9d84965e7dfd666252" + integrity sha512-j3Jw+n5PvpmhRR+mrgIh04puSANCk/T/UA3m3P1MjJkhlK906+ApHhDIqBQDdOgL/r1UYpz4GNclTXxyZrYGSw== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.16.0" + "@babel/helper-create-regexp-features-plugin" "^7.16.7" -"@babel/plugin-transform-new-target@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.16.5.tgz#759ea9d6fbbc20796056a5d89d13977626384416" - integrity sha512-ZaIrnXF08ZC8jnKR4/5g7YakGVL6go6V9ql6Jl3ecO8PQaQqFE74CuM384kezju7Z9nGCCA20BqZaR1tJ/WvHg== +"@babel/plugin-transform-new-target@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.16.7.tgz#9967d89a5c243818e0800fdad89db22c5f514244" + integrity sha512-xiLDzWNMfKoGOpc6t3U+etCE2yRnn3SM09BXqWPIZOBpL2gvVrBWUKnsJx0K/ADi5F5YC5f8APFfWrz25TdlGg== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-object-super@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.5.tgz#8ccd9a1bcd3e7732ff8aa1702d067d8cd70ce380" - integrity sha512-tded+yZEXuxt9Jdtkc1RraW1zMF/GalVxaVVxh41IYwirdRgyAxxxCKZ9XB7LxZqmsjfjALxupNE1MIz9KH+Zg== +"@babel/plugin-transform-object-super@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.7.tgz#ac359cf8d32cf4354d27a46867999490b6c32a94" + integrity sha512-14J1feiQVWaGvRxj2WjyMuXS2jsBkgB3MdSN5HuC2G5nRspa5RK9COcs82Pwy5BuGcjb+fYaUj94mYcOj7rCvw== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" - "@babel/helper-replace-supers" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-replace-supers" "^7.16.7" -"@babel/plugin-transform-parameters@^7.12.1", "@babel/plugin-transform-parameters@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.16.5.tgz#4fc74b18a89638bd90aeec44a11793ecbe031dde" - integrity sha512-B3O6AL5oPop1jAVg8CV+haeUte9oFuY85zu0jwnRNZZi3tVAbJriu5tag/oaO2kGaQM/7q7aGPBlTI5/sr9enA== +"@babel/plugin-transform-parameters@^7.12.1", "@babel/plugin-transform-parameters@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.16.7.tgz#a1721f55b99b736511cb7e0152f61f17688f331f" + integrity sha512-AT3MufQ7zZEhU2hwOA11axBnExW0Lszu4RL/tAlUJBuNoRak+wehQW8h6KcXOcgjY42fHtDxswuMhMjFEuv/aw== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-property-literals@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.5.tgz#58f1465a7202a2bb2e6b003905212dd7a79abe3f" - integrity sha512-+IRcVW71VdF9pEH/2R/Apab4a19LVvdVsr/gEeotH00vSDVlKD+XgfSIw+cgGWsjDB/ziqGv/pGoQZBIiQVXHg== +"@babel/plugin-transform-property-literals@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.7.tgz#2dadac85155436f22c696c4827730e0fe1057a55" + integrity sha512-z4FGr9NMGdoIl1RqavCqGG+ZuYjfZ/hkCIeuH6Do7tXmSm0ls11nYVSJqFEUOSJbDab5wC6lRE/w6YjVcr6Hqw== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-transform-react-constant-elements@^7.14.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.16.5.tgz#4b01ea6b14bd4e55ca92bb2d6c28dd9957118924" - integrity sha512-fdc1s5npHMZ9A+w9bYbrZu4499WyYPVaTTsRO8bU0GJcMuK4ejIX4lyjnpvi+YGLK/EhFQxWszqylO0vaMciFw== + version "7.17.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.17.6.tgz#6cc273c2f612a6a50cb657e63ee1303e5e68d10a" + integrity sha512-OBv9VkyyKtsHZiHLoSfCn+h6yU7YKX8nrs32xUmOa1SRSk+t03FosB6fBZ0Yz4BpD1WV7l73Nsad+2Tz7APpqw== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-react-display-name@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.16.5.tgz#d5e910327d7931fb9f8f9b6c6999473ceae5a286" - integrity sha512-dHYCOnzSsXFz8UcdNQIHGvg94qPL/teF7CCiCEMRxmA1G2p5Mq4JnKVowCDxYfiQ9D7RstaAp9kwaSI+sXbnhw== +"@babel/plugin-transform-react-display-name@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.16.7.tgz#7b6d40d232f4c0f550ea348593db3b21e2404340" + integrity sha512-qgIg8BcZgd0G/Cz916D5+9kqX0c7nPZyXaP8R2tLNN5tkyIZdG5fEwBrxwplzSnjC1jvQmyMNVwUCZPcbGY7Pg== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-react-jsx-development@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.16.5.tgz#87da9204c275ffb57f45d192a1120cf104bc1e86" - integrity sha512-uQSLacMZSGLCxOw20dzo1dmLlKkd+DsayoV54q3MHXhbqgPzoiGerZQgNPl/Ro8/OcXV2ugfnkx+rxdS0sN5Uw== +"@babel/plugin-transform-react-jsx-development@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.16.7.tgz#43a00724a3ed2557ed3f276a01a929e6686ac7b8" + integrity sha512-RMvQWvpla+xy6MlBpPlrKZCMRs2AGiHOGHY3xRwl0pEeim348dDyxeH4xBsMPbIMhujeq7ihE702eM2Ew0Wo+A== dependencies: - "@babel/plugin-transform-react-jsx" "^7.16.5" + "@babel/plugin-transform-react-jsx" "^7.16.7" -"@babel/plugin-transform-react-jsx@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.16.5.tgz#5298aedc5f81e02b1cb702e597e8d6a346675765" - integrity sha512-+arLIz1d7kmwX0fKxTxbnoeG85ONSnLpvdODa4P3pc1sS7CV1hfmtYWufkW/oYsPnkDrEeQFxhUWcFnrXW7jQQ== +"@babel/plugin-transform-react-jsx@^7.16.7": + version "7.17.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.17.3.tgz#eac1565da176ccb1a715dae0b4609858808008c1" + integrity sha512-9tjBm4O07f7mzKSIlEmPdiE6ub7kfIe6Cd+w+oQebpATfTQMAgW+YOuWxogbKVTulA+MEO7byMeIUtQ1z+z+ZQ== dependencies: - "@babel/helper-annotate-as-pure" "^7.16.0" - "@babel/helper-module-imports" "^7.16.0" - "@babel/helper-plugin-utils" "^7.16.5" - "@babel/plugin-syntax-jsx" "^7.16.5" - "@babel/types" "^7.16.0" + "@babel/helper-annotate-as-pure" "^7.16.7" + "@babel/helper-module-imports" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/plugin-syntax-jsx" "^7.16.7" + "@babel/types" "^7.17.0" -"@babel/plugin-transform-react-pure-annotations@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.16.5.tgz#6535d0fe67c7a3a26c5105f92c8cbcbe844cd94b" - integrity sha512-0nYU30hCxnCVCbRjSy9ahlhWZ2Sn6khbY4FqR91W+2RbSqkWEbVu2gXh45EqNy4Bq7sRU+H4i0/6YKwOSzh16A== +"@babel/plugin-transform-react-pure-annotations@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.16.7.tgz#232bfd2f12eb551d6d7d01d13fe3f86b45eb9c67" + integrity sha512-hs71ToC97k3QWxswh2ElzMFABXHvGiJ01IB1TbYQDGeWRKWz/MPUTh5jGExdHvosYKpnJW5Pm3S4+TA3FyX+GA== dependencies: - "@babel/helper-annotate-as-pure" "^7.16.0" - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-annotate-as-pure" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-regenerator@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.16.5.tgz#704cc6d8dd3dd4758267621ab7b36375238cef13" - integrity sha512-2z+it2eVWU8TtQQRauvGUqZwLy4+7rTfo6wO4npr+fvvN1SW30ZF3O/ZRCNmTuu4F5MIP8OJhXAhRV5QMJOuYg== +"@babel/plugin-transform-regenerator@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.16.7.tgz#9e7576dc476cb89ccc5096fff7af659243b4adeb" + integrity sha512-mF7jOgGYCkSJagJ6XCujSQg+6xC1M77/03K2oBmVJWoFGNUtnVJO4WHKJk3dnPC8HCcj4xBQP1Egm8DWh3Pb3Q== dependencies: regenerator-transform "^0.14.2" -"@babel/plugin-transform-reserved-words@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.16.5.tgz#db95e98799675e193dc2b47d3e72a7c0651d0c30" - integrity sha512-aIB16u8lNcf7drkhXJRoggOxSTUAuihTSTfAcpynowGJOZiGf+Yvi7RuTwFzVYSYPmWyARsPqUGoZWWWxLiknw== +"@babel/plugin-transform-reserved-words@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.16.7.tgz#1d798e078f7c5958eec952059c460b220a63f586" + integrity sha512-KQzzDnZ9hWQBjwi5lpY5v9shmm6IVG0U9pB18zvMu2i4H90xpT4gmqwPYsn8rObiadYe2M0gmgsiOIF5A/2rtg== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-runtime@^7.16.0": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.16.5.tgz#0cc3f01d69f299d5a42cd9ec43b92ea7a777b8db" - integrity sha512-gxpfS8XQWDbQ8oP5NcmpXxtEgCJkbO+W9VhZlOhr0xPyVaRjAQPOv7ZDj9fg0d5s9+NiVvMCE6gbkEkcsxwGRw== +"@babel/plugin-transform-runtime@^7.17.0": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.17.0.tgz#0a2e08b5e2b2d95c4b1d3b3371a2180617455b70" + integrity sha512-fr7zPWnKXNc1xoHfrIU9mN/4XKX4VLZ45Q+oMhfsYIaHvg7mHgmhfOy/ckRWqDK7XF3QDigRpkh5DKq6+clE8A== dependencies: - "@babel/helper-module-imports" "^7.16.0" - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-module-imports" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" babel-plugin-polyfill-corejs2 "^0.3.0" - babel-plugin-polyfill-corejs3 "^0.4.0" + babel-plugin-polyfill-corejs3 "^0.5.0" babel-plugin-polyfill-regenerator "^0.3.0" semver "^6.3.0" -"@babel/plugin-transform-shorthand-properties@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.5.tgz#ccb60b1a23b799f5b9a14d97c5bc81025ffd96d7" - integrity sha512-ZbuWVcY+MAXJuuW7qDoCwoxDUNClfZxoo7/4swVbOW1s/qYLOMHlm9YRWMsxMFuLs44eXsv4op1vAaBaBaDMVg== +"@babel/plugin-transform-shorthand-properties@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.7.tgz#e8549ae4afcf8382f711794c0c7b6b934c5fbd2a" + integrity sha512-hah2+FEnoRoATdIb05IOXf+4GzXYTq75TVhIn1PewihbpyrNWUt2JbudKQOETWw6QpLe+AIUpJ5MVLYTQbeeUg== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-spread@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.16.5.tgz#912b06cff482c233025d3e69cf56d3e8fa166c29" - integrity sha512-5d6l/cnG7Lw4tGHEoga4xSkYp1euP7LAtrah1h1PgJ3JY7yNsjybsxQAnVK4JbtReZ/8z6ASVmd3QhYYKLaKZw== +"@babel/plugin-transform-spread@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.16.7.tgz#a303e2122f9f12e0105daeedd0f30fb197d8ff44" + integrity sha512-+pjJpgAngb53L0iaA5gU/1MLXJIfXcYepLgXB3esVRf4fqmj8f2cxM3/FKaHsZms08hFQJkFccEWuIpm429TXg== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/helper-skip-transparent-expression-wrappers" "^7.16.0" -"@babel/plugin-transform-sticky-regex@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.5.tgz#593579bb2b5a8adfbe02cb43823275d9098f75f9" - integrity sha512-usYsuO1ID2LXxzuUxifgWtJemP7wL2uZtyrTVM4PKqsmJycdS4U4mGovL5xXkfUheds10Dd2PjoQLXw6zCsCbg== - dependencies: - "@babel/helper-plugin-utils" "^7.16.5" - -"@babel/plugin-transform-template-literals@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.16.5.tgz#343651385fd9923f5aa2275ca352c5d9183e1773" - integrity sha512-gnyKy9RyFhkovex4BjKWL3BVYzUDG6zC0gba7VMLbQoDuqMfJ1SDXs8k/XK41Mmt1Hyp4qNAvGFb9hKzdCqBRQ== - dependencies: - "@babel/helper-plugin-utils" "^7.16.5" - -"@babel/plugin-transform-typeof-symbol@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.16.5.tgz#a1d1bf2c71573fe30965d0e4cd6a3291202e20ed" - integrity sha512-ldxCkW180qbrvyCVDzAUZqB0TAeF8W/vGJoRcaf75awm6By+PxfJKvuqVAnq8N9wz5Xa6mSpM19OfVKKVmGHSQ== - dependencies: - "@babel/helper-plugin-utils" "^7.16.5" - -"@babel/plugin-transform-typescript@^7.16.1": - version "7.16.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.16.1.tgz#cc0670b2822b0338355bc1b3d2246a42b8166409" - integrity sha512-NO4XoryBng06jjw/qWEU2LhcLJr1tWkhpMam/H4eas/CDKMX/b2/Ylb6EI256Y7+FVPCawwSM1rrJNOpDiz+Lg== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.16.0" - "@babel/helper-plugin-utils" "^7.14.5" - "@babel/plugin-syntax-typescript" "^7.16.0" - -"@babel/plugin-transform-unicode-escapes@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.16.5.tgz#80507c225af49b4f4ee647e2a0ce53d2eeff9e85" - integrity sha512-shiCBHTIIChGLdyojsKQjoAyB8MBwat25lKM7MJjbe1hE0bgIppD+LX9afr41lLHOhqceqeWl4FkLp+Bgn9o1Q== - dependencies: - "@babel/helper-plugin-utils" "^7.16.5" - -"@babel/plugin-transform-unicode-regex@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.5.tgz#ac84d6a1def947d71ffb832426aa53b83d7ed49e" - integrity sha512-GTJ4IW012tiPEMMubd7sD07iU9O/LOo8Q/oU4xNhcaq0Xn8+6TcUQaHtC8YxySo1T+ErQ8RaWogIEeFhKGNPzw== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.16.0" - "@babel/helper-plugin-utils" "^7.16.5" - -"@babel/preset-env@^7.15.6", "@babel/preset-env@^7.16.4": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.16.5.tgz#2e94d922f4a890979af04ffeb6a6b4e44ba90847" - integrity sha512-MiJJW5pwsktG61NDxpZ4oJ1CKxM1ncam9bzRtx9g40/WkLRkxFP6mhpkYV0/DxcciqoiHicx291+eUQrXb/SfQ== - dependencies: - "@babel/compat-data" "^7.16.4" - "@babel/helper-compilation-targets" "^7.16.3" - "@babel/helper-plugin-utils" "^7.16.5" - "@babel/helper-validator-option" "^7.14.5" - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.16.2" - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.16.0" - "@babel/plugin-proposal-async-generator-functions" "^7.16.5" - "@babel/plugin-proposal-class-properties" "^7.16.5" - "@babel/plugin-proposal-class-static-block" "^7.16.5" - "@babel/plugin-proposal-dynamic-import" "^7.16.5" - "@babel/plugin-proposal-export-namespace-from" "^7.16.5" - "@babel/plugin-proposal-json-strings" "^7.16.5" - "@babel/plugin-proposal-logical-assignment-operators" "^7.16.5" - "@babel/plugin-proposal-nullish-coalescing-operator" "^7.16.5" - "@babel/plugin-proposal-numeric-separator" "^7.16.5" - "@babel/plugin-proposal-object-rest-spread" "^7.16.5" - "@babel/plugin-proposal-optional-catch-binding" "^7.16.5" - "@babel/plugin-proposal-optional-chaining" "^7.16.5" - "@babel/plugin-proposal-private-methods" "^7.16.5" - "@babel/plugin-proposal-private-property-in-object" "^7.16.5" - "@babel/plugin-proposal-unicode-property-regex" "^7.16.5" +"@babel/plugin-transform-sticky-regex@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.7.tgz#c84741d4f4a38072b9a1e2e3fd56d359552e8660" + integrity sha512-NJa0Bd/87QV5NZZzTuZG5BPJjLYadeSZ9fO6oOUoL4iQx+9EEuw/eEM92SrsT19Yc2jgB1u1hsjqDtH02c3Drw== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-template-literals@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.16.7.tgz#f3d1c45d28967c8e80f53666fc9c3e50618217ab" + integrity sha512-VwbkDDUeenlIjmfNeDX/V0aWrQH2QiVyJtwymVQSzItFDTpxfyJh3EVaQiS0rIN/CqbLGr0VcGmuwyTdZtdIsA== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-typeof-symbol@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.16.7.tgz#9cdbe622582c21368bd482b660ba87d5545d4f7e" + integrity sha512-p2rOixCKRJzpg9JB4gjnG4gjWkWa89ZoYUnl9snJ1cWIcTH/hvxZqfO+WjG6T8DRBpctEol5jw1O5rA8gkCokQ== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-typescript@^7.16.7": + version "7.16.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.16.8.tgz#591ce9b6b83504903fa9dd3652c357c2ba7a1ee0" + integrity sha512-bHdQ9k7YpBDO2d0NVfkj51DpQcvwIzIusJ7mEUaMlbZq3Kt/U47j24inXZHQ5MDiYpCs+oZiwnXyKedE8+q7AQ== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/plugin-syntax-typescript" "^7.16.7" + +"@babel/plugin-transform-unicode-escapes@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.16.7.tgz#da8717de7b3287a2c6d659750c964f302b31ece3" + integrity sha512-TAV5IGahIz3yZ9/Hfv35TV2xEm+kaBDaZQCn2S/hG9/CZ0DktxJv9eKfPc7yYCvOYR4JGx1h8C+jcSOvgaaI/Q== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-unicode-regex@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.7.tgz#0f7aa4a501198976e25e82702574c34cfebe9ef2" + integrity sha512-oC5tYYKw56HO75KZVLQ+R/Nl3Hro9kf8iG0hXoaHP7tjAyCpvqBiSNe6vGrZni1Z6MggmUOC6A7VP7AVmw225Q== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/preset-env@^7.15.6", "@babel/preset-env@^7.16.11": + version "7.16.11" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.16.11.tgz#5dd88fd885fae36f88fd7c8342475c9f0abe2982" + integrity sha512-qcmWG8R7ZW6WBRPZK//y+E3Cli151B20W1Rv7ln27vuPaXU/8TKms6jFdiJtF7UDTxcrb7mZd88tAeK9LjdT8g== + dependencies: + "@babel/compat-data" "^7.16.8" + "@babel/helper-compilation-targets" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-validator-option" "^7.16.7" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.16.7" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.16.7" + "@babel/plugin-proposal-async-generator-functions" "^7.16.8" + "@babel/plugin-proposal-class-properties" "^7.16.7" + "@babel/plugin-proposal-class-static-block" "^7.16.7" + "@babel/plugin-proposal-dynamic-import" "^7.16.7" + "@babel/plugin-proposal-export-namespace-from" "^7.16.7" + "@babel/plugin-proposal-json-strings" "^7.16.7" + "@babel/plugin-proposal-logical-assignment-operators" "^7.16.7" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.16.7" + "@babel/plugin-proposal-numeric-separator" "^7.16.7" + "@babel/plugin-proposal-object-rest-spread" "^7.16.7" + "@babel/plugin-proposal-optional-catch-binding" "^7.16.7" + "@babel/plugin-proposal-optional-chaining" "^7.16.7" + "@babel/plugin-proposal-private-methods" "^7.16.11" + "@babel/plugin-proposal-private-property-in-object" "^7.16.7" + "@babel/plugin-proposal-unicode-property-regex" "^7.16.7" "@babel/plugin-syntax-async-generators" "^7.8.4" "@babel/plugin-syntax-class-properties" "^7.12.13" "@babel/plugin-syntax-class-static-block" "^7.14.5" @@ -1042,44 +1050,44 @@ "@babel/plugin-syntax-optional-chaining" "^7.8.3" "@babel/plugin-syntax-private-property-in-object" "^7.14.5" "@babel/plugin-syntax-top-level-await" "^7.14.5" - "@babel/plugin-transform-arrow-functions" "^7.16.5" - "@babel/plugin-transform-async-to-generator" "^7.16.5" - "@babel/plugin-transform-block-scoped-functions" "^7.16.5" - "@babel/plugin-transform-block-scoping" "^7.16.5" - "@babel/plugin-transform-classes" "^7.16.5" - "@babel/plugin-transform-computed-properties" "^7.16.5" - "@babel/plugin-transform-destructuring" "^7.16.5" - "@babel/plugin-transform-dotall-regex" "^7.16.5" - "@babel/plugin-transform-duplicate-keys" "^7.16.5" - "@babel/plugin-transform-exponentiation-operator" "^7.16.5" - "@babel/plugin-transform-for-of" "^7.16.5" - "@babel/plugin-transform-function-name" "^7.16.5" - "@babel/plugin-transform-literals" "^7.16.5" - "@babel/plugin-transform-member-expression-literals" "^7.16.5" - "@babel/plugin-transform-modules-amd" "^7.16.5" - "@babel/plugin-transform-modules-commonjs" "^7.16.5" - "@babel/plugin-transform-modules-systemjs" "^7.16.5" - "@babel/plugin-transform-modules-umd" "^7.16.5" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.16.5" - "@babel/plugin-transform-new-target" "^7.16.5" - "@babel/plugin-transform-object-super" "^7.16.5" - "@babel/plugin-transform-parameters" "^7.16.5" - "@babel/plugin-transform-property-literals" "^7.16.5" - "@babel/plugin-transform-regenerator" "^7.16.5" - "@babel/plugin-transform-reserved-words" "^7.16.5" - "@babel/plugin-transform-shorthand-properties" "^7.16.5" - "@babel/plugin-transform-spread" "^7.16.5" - "@babel/plugin-transform-sticky-regex" "^7.16.5" - "@babel/plugin-transform-template-literals" "^7.16.5" - "@babel/plugin-transform-typeof-symbol" "^7.16.5" - "@babel/plugin-transform-unicode-escapes" "^7.16.5" - "@babel/plugin-transform-unicode-regex" "^7.16.5" + "@babel/plugin-transform-arrow-functions" "^7.16.7" + "@babel/plugin-transform-async-to-generator" "^7.16.8" + "@babel/plugin-transform-block-scoped-functions" "^7.16.7" + "@babel/plugin-transform-block-scoping" "^7.16.7" + "@babel/plugin-transform-classes" "^7.16.7" + "@babel/plugin-transform-computed-properties" "^7.16.7" + "@babel/plugin-transform-destructuring" "^7.16.7" + "@babel/plugin-transform-dotall-regex" "^7.16.7" + "@babel/plugin-transform-duplicate-keys" "^7.16.7" + "@babel/plugin-transform-exponentiation-operator" "^7.16.7" + "@babel/plugin-transform-for-of" "^7.16.7" + "@babel/plugin-transform-function-name" "^7.16.7" + "@babel/plugin-transform-literals" "^7.16.7" + "@babel/plugin-transform-member-expression-literals" "^7.16.7" + "@babel/plugin-transform-modules-amd" "^7.16.7" + "@babel/plugin-transform-modules-commonjs" "^7.16.8" + "@babel/plugin-transform-modules-systemjs" "^7.16.7" + "@babel/plugin-transform-modules-umd" "^7.16.7" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.16.8" + "@babel/plugin-transform-new-target" "^7.16.7" + "@babel/plugin-transform-object-super" "^7.16.7" + "@babel/plugin-transform-parameters" "^7.16.7" + "@babel/plugin-transform-property-literals" "^7.16.7" + "@babel/plugin-transform-regenerator" "^7.16.7" + "@babel/plugin-transform-reserved-words" "^7.16.7" + "@babel/plugin-transform-shorthand-properties" "^7.16.7" + "@babel/plugin-transform-spread" "^7.16.7" + "@babel/plugin-transform-sticky-regex" "^7.16.7" + "@babel/plugin-transform-template-literals" "^7.16.7" + "@babel/plugin-transform-typeof-symbol" "^7.16.7" + "@babel/plugin-transform-unicode-escapes" "^7.16.7" + "@babel/plugin-transform-unicode-regex" "^7.16.7" "@babel/preset-modules" "^0.1.5" - "@babel/types" "^7.16.0" + "@babel/types" "^7.16.8" babel-plugin-polyfill-corejs2 "^0.3.0" - babel-plugin-polyfill-corejs3 "^0.4.0" + babel-plugin-polyfill-corejs3 "^0.5.0" babel-plugin-polyfill-regenerator "^0.3.0" - core-js-compat "^3.19.1" + core-js-compat "^3.20.2" semver "^6.3.0" "@babel/preset-modules@^0.1.5": @@ -1093,329 +1101,332 @@ "@babel/types" "^7.4.4" esutils "^2.0.2" -"@babel/preset-react@^7.14.5", "@babel/preset-react@^7.16.0": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.16.5.tgz#09df3b7a6522cb3e6682dc89b4dfebb97d22031b" - integrity sha512-3kzUOQeaxY/2vhPDS7CX/KGEGu/1bOYGvdRDJ2U5yjEz5o5jmIeTPLoiQBPGjfhPascLuW5OlMiPzwOOuB6txg== - dependencies: - "@babel/helper-plugin-utils" "^7.16.5" - "@babel/helper-validator-option" "^7.14.5" - "@babel/plugin-transform-react-display-name" "^7.16.5" - "@babel/plugin-transform-react-jsx" "^7.16.5" - "@babel/plugin-transform-react-jsx-development" "^7.16.5" - "@babel/plugin-transform-react-pure-annotations" "^7.16.5" - -"@babel/preset-typescript@^7.15.0", "@babel/preset-typescript@^7.16.0": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.16.5.tgz#b86a5b0ae739ba741347d2f58c52f52e63cf1ba1" - integrity sha512-lmAWRoJ9iOSvs3DqOndQpj8XqXkzaiQs50VG/zESiI9D3eoZhGriU675xNCr0UwvsuXrhMAGvyk1w+EVWF3u8Q== - dependencies: - "@babel/helper-plugin-utils" "^7.16.5" - "@babel/helper-validator-option" "^7.14.5" - "@babel/plugin-transform-typescript" "^7.16.1" - -"@babel/runtime-corejs3@^7.16.3": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.16.5.tgz#9057d879720c136193f0440bc400088212a74894" - integrity sha512-F1pMwvTiUNSAM8mc45kccMQxj31x3y3P+tA/X8hKNWp3/hUsxdGxZ3D3H8JIkxtfA8qGkaBTKvcmvStaYseAFw== - dependencies: - core-js-pure "^3.19.0" +"@babel/preset-react@^7.14.5", "@babel/preset-react@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.16.7.tgz#4c18150491edc69c183ff818f9f2aecbe5d93852" + integrity sha512-fWpyI8UM/HE6DfPBzD8LnhQ/OcH8AgTaqcqP2nGOXEUV+VKBR5JRN9hCk9ai+zQQ57vtm9oWeXguBCPNUjytgA== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-validator-option" "^7.16.7" + "@babel/plugin-transform-react-display-name" "^7.16.7" + "@babel/plugin-transform-react-jsx" "^7.16.7" + "@babel/plugin-transform-react-jsx-development" "^7.16.7" + "@babel/plugin-transform-react-pure-annotations" "^7.16.7" + +"@babel/preset-typescript@^7.15.0", "@babel/preset-typescript@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.16.7.tgz#ab114d68bb2020afc069cd51b37ff98a046a70b9" + integrity sha512-WbVEmgXdIyvzB77AQjGBEyYPZx+8tTsO50XtfozQrkW8QB2rLJpH2lgx0TRw5EJrBxOZQ+wCcyPVQvS8tjEHpQ== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-validator-option" "^7.16.7" + "@babel/plugin-transform-typescript" "^7.16.7" + +"@babel/runtime-corejs3@^7.17.8": + version "7.17.8" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.17.8.tgz#d7dd49fb812f29c61c59126da3792d8740d4e284" + integrity sha512-ZbYSUvoSF6dXZmMl/CYTMOvzIFnbGfv4W3SEHYgMvNsFTeLaF2gkGAF4K2ddmtSK4Emej+0aYcnSC6N5dPCXUQ== + dependencies: + core-js-pure "^3.20.2" regenerator-runtime "^0.13.4" -"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.16.3", "@babel/runtime@^7.8.4": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.5.tgz#7f3e34bf8bdbbadf03fbb7b1ea0d929569c9487a" - integrity sha512-TXWihFIS3Pyv5hzR7j6ihmeLkZfrXGxAr5UfSl8CHf+6q/wpiYDkUau0czckpYG8QmnCIuPpdLtuA9VmuGGyMA== +"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.17.8", "@babel/runtime@^7.8.4": + version "7.17.8" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.8.tgz#3e56e4aff81befa55ac3ac6a0967349fd1c5bca2" + integrity sha512-dQpEpK0O9o6lj6oPu0gRDbbnk+4LeHlNcBpspf6Olzt3GIX4P1lWF1gS+pHLDFlaJvbR6q7jCfQ08zA4QJBnmA== dependencies: regenerator-runtime "^0.13.4" -"@babel/template@^7.12.7", "@babel/template@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.0.tgz#d16a35ebf4cd74e202083356fab21dd89363ddd6" - integrity sha512-MnZdpFD/ZdYhXwiunMqqgyZyucaYsbL0IrjoGjaVhGilz+x8YB++kRfygSOIj1yOtWKPlx7NBp+9I1RQSgsd5A== - dependencies: - "@babel/code-frame" "^7.16.0" - "@babel/parser" "^7.16.0" - "@babel/types" "^7.16.0" - -"@babel/traverse@^7.12.9", "@babel/traverse@^7.13.0", "@babel/traverse@^7.16.3", "@babel/traverse@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.16.5.tgz#d7d400a8229c714a59b87624fc67b0f1fbd4b2b3" - integrity sha512-FOCODAzqUMROikDYLYxl4nmwiLlu85rNqBML/A5hKRVXG2LV8d0iMqgPzdYTcIpjZEBB7D6UDU9vxRZiriASdQ== - dependencies: - "@babel/code-frame" "^7.16.0" - "@babel/generator" "^7.16.5" - "@babel/helper-environment-visitor" "^7.16.5" - "@babel/helper-function-name" "^7.16.0" - "@babel/helper-hoist-variables" "^7.16.0" - "@babel/helper-split-export-declaration" "^7.16.0" - "@babel/parser" "^7.16.5" - "@babel/types" "^7.16.0" +"@babel/template@^7.12.7", "@babel/template@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155" + integrity sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w== + dependencies: + "@babel/code-frame" "^7.16.7" + "@babel/parser" "^7.16.7" + "@babel/types" "^7.16.7" + +"@babel/traverse@^7.12.9", "@babel/traverse@^7.13.0", "@babel/traverse@^7.16.7", "@babel/traverse@^7.16.8", "@babel/traverse@^7.17.3": + version "7.17.3" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.17.3.tgz#0ae0f15b27d9a92ba1f2263358ea7c4e7db47b57" + integrity sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw== + dependencies: + "@babel/code-frame" "^7.16.7" + "@babel/generator" "^7.17.3" + "@babel/helper-environment-visitor" "^7.16.7" + "@babel/helper-function-name" "^7.16.7" + "@babel/helper-hoist-variables" "^7.16.7" + "@babel/helper-split-export-declaration" "^7.16.7" + "@babel/parser" "^7.17.3" + "@babel/types" "^7.17.0" debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.12.7", "@babel/types@^7.15.6", "@babel/types@^7.16.0", "@babel/types@^7.4.4": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.16.0.tgz#db3b313804f96aadd0b776c4823e127ad67289ba" - integrity sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg== +"@babel/types@^7.12.7", "@babel/types@^7.15.6", "@babel/types@^7.16.0", "@babel/types@^7.16.7", "@babel/types@^7.16.8", "@babel/types@^7.17.0", "@babel/types@^7.4.4": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.17.0.tgz#a826e368bccb6b3d84acd76acad5c0d87342390b" + integrity sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw== dependencies: - "@babel/helper-validator-identifier" "^7.15.7" + "@babel/helper-validator-identifier" "^7.16.7" to-fast-properties "^2.0.0" -"@docsearch/css@3.0.0-alpha.42": - version "3.0.0-alpha.42" - resolved "https://registry.yarnpkg.com/@docsearch/css/-/css-3.0.0-alpha.42.tgz#deb6049e999d6ca9451eba4793cb5b6da28c8773" - integrity sha512-AGwI2AXUacYhVOHmYnsXoYDJKO6Ued2W+QO80GERbMLhC7GH5tfvtW5REs/s7jSdcU3vzFoxT8iPDBCh/PkrlQ== +"@docsearch/css@3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@docsearch/css/-/css-3.0.0.tgz#fe57b474802ffd706d3246eab25d52fac8aa3698" + integrity sha512-1kkV7tkAsiuEd0shunYRByKJe3xQDG2q7wYg24SOw1nV9/2lwEd4WrUYRJC/ukGTl2/kHeFxsaUvtiOy0y6fFA== -"@docsearch/react@^3.0.0-alpha.39": - version "3.0.0-alpha.42" - resolved "https://registry.yarnpkg.com/@docsearch/react/-/react-3.0.0-alpha.42.tgz#1d22a2b05779f24d090ff8d7ff2699e4d50dff5c" - integrity sha512-1aOslZJDxwUUcm2QRNmlEePUgL8P5fOAeFdOLDMctHQkV2iTja9/rKVbkP8FZbIUnZxuuCCn8ErLrjD/oXWOag== +"@docsearch/react@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@docsearch/react/-/react-3.0.0.tgz#d02ebdc67573412185a6a4df13bc254c7c0da491" + integrity sha512-yhMacqS6TVQYoBh/o603zszIb5Bl8MIXuOc6Vy617I74pirisDzzcNh0NEaYQt50fVVR3khUbeEhUEWEWipESg== dependencies: - "@algolia/autocomplete-core" "1.5.0" - "@algolia/autocomplete-preset-algolia" "1.5.0" - "@docsearch/css" "3.0.0-alpha.42" + "@algolia/autocomplete-core" "1.5.2" + "@algolia/autocomplete-preset-algolia" "1.5.2" + "@docsearch/css" "3.0.0" algoliasearch "^4.0.0" -"@docusaurus/core@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/core/-/core-2.0.0-beta.14.tgz#9baf8fbfe29f444f985616013b5d80435ea5f29e" - integrity sha512-dW95WbD+WE+35Ee1RYIS1QDcBhvUxUWuDmrWr1X0uH5ZHIeOmOnsGVjjn4FA8VN2MkF0uuWknmRakQmJk0KMZw== +"@docusaurus/core@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/core/-/core-2.0.0-beta.18.tgz#44c6eefe29257462df630640a35f0c86bd80639a" + integrity sha512-puV7l+0/BPSi07Xmr8tVktfs1BzhC8P5pm6Bs2CfvysCJ4nefNCD1CosPc1PGBWy901KqeeEJ1aoGwj9tU3AUA== dependencies: - "@babel/core" "^7.16.0" - "@babel/generator" "^7.16.0" + "@babel/core" "^7.17.8" + "@babel/generator" "^7.17.7" "@babel/plugin-syntax-dynamic-import" "^7.8.3" - "@babel/plugin-transform-runtime" "^7.16.0" - "@babel/preset-env" "^7.16.4" - "@babel/preset-react" "^7.16.0" - "@babel/preset-typescript" "^7.16.0" - "@babel/runtime" "^7.16.3" - "@babel/runtime-corejs3" "^7.16.3" - "@babel/traverse" "^7.16.3" - "@docusaurus/cssnano-preset" "2.0.0-beta.14" - "@docusaurus/logger" "2.0.0-beta.14" - "@docusaurus/mdx-loader" "2.0.0-beta.14" + "@babel/plugin-transform-runtime" "^7.17.0" + "@babel/preset-env" "^7.16.11" + "@babel/preset-react" "^7.16.7" + "@babel/preset-typescript" "^7.16.7" + "@babel/runtime" "^7.17.8" + "@babel/runtime-corejs3" "^7.17.8" + "@babel/traverse" "^7.17.3" + "@docusaurus/cssnano-preset" "2.0.0-beta.18" + "@docusaurus/logger" "2.0.0-beta.18" + "@docusaurus/mdx-loader" "2.0.0-beta.18" "@docusaurus/react-loadable" "5.5.2" - "@docusaurus/utils" "2.0.0-beta.14" - "@docusaurus/utils-common" "2.0.0-beta.14" - "@docusaurus/utils-validation" "2.0.0-beta.14" - "@slorber/static-site-generator-webpack-plugin" "^4.0.0" - "@svgr/webpack" "^6.0.0" - autoprefixer "^10.3.5" - babel-loader "^8.2.2" + "@docusaurus/utils" "2.0.0-beta.18" + "@docusaurus/utils-common" "2.0.0-beta.18" + "@docusaurus/utils-validation" "2.0.0-beta.18" + "@slorber/static-site-generator-webpack-plugin" "^4.0.4" + "@svgr/webpack" "^6.2.1" + autoprefixer "^10.4.4" + babel-loader "^8.2.4" babel-plugin-dynamic-import-node "2.3.0" - boxen "^5.0.1" - chokidar "^3.5.2" - clean-css "^5.1.5" + boxen "^6.2.1" + chokidar "^3.5.3" + clean-css "^5.2.4" + cli-table3 "^0.6.1" + combine-promises "^1.1.0" commander "^5.1.0" - copy-webpack-plugin "^9.0.1" - core-js "^3.18.0" - css-loader "^5.1.1" - css-minimizer-webpack-plugin "^3.0.2" - cssnano "^5.0.8" + copy-webpack-plugin "^10.2.4" + core-js "^3.21.1" + css-loader "^6.7.1" + css-minimizer-webpack-plugin "^3.4.1" + cssnano "^5.1.5" del "^6.0.0" detect-port "^1.3.0" escape-html "^1.0.3" eta "^1.12.3" file-loader "^6.2.0" - fs-extra "^10.0.0" - globby "^11.0.2" - html-minifier-terser "^6.0.2" + fs-extra "^10.0.1" + html-minifier-terser "^6.1.0" html-tags "^3.1.0" - html-webpack-plugin "^5.4.0" + html-webpack-plugin "^5.5.0" import-fresh "^3.3.0" is-root "^2.1.0" leven "^3.1.0" - lodash "^4.17.20" - mini-css-extract-plugin "^1.6.0" + lodash "^4.17.21" + mini-css-extract-plugin "^2.6.0" nprogress "^0.2.0" - postcss "^8.3.7" - postcss-loader "^6.1.1" - prompts "^2.4.1" - react-dev-utils "12.0.0-next.47" - react-error-overlay "^6.0.9" - react-helmet "^6.1.0" + postcss "^8.4.12" + postcss-loader "^6.2.1" + prompts "^2.4.2" + react-dev-utils "^12.0.0" + react-helmet-async "^1.2.3" react-loadable "npm:@docusaurus/react-loadable@5.5.2" react-loadable-ssr-addon-v5-slorber "^1.0.1" react-router "^5.2.0" react-router-config "^5.1.1" react-router-dom "^5.2.0" remark-admonitions "^1.2.1" - resolve-pathname "^3.0.0" rtl-detect "^1.0.4" - semver "^7.3.4" + semver "^7.3.5" serve-handler "^6.1.3" - shelljs "^0.8.4" - strip-ansi "^6.0.0" - terser-webpack-plugin "^5.2.4" + shelljs "^0.8.5" + terser-webpack-plugin "^5.3.1" tslib "^2.3.1" update-notifier "^5.1.0" url-loader "^4.1.1" - wait-on "^6.0.0" - webpack "^5.61.0" - webpack-bundle-analyzer "^4.4.2" - webpack-dev-server "^4.5.0" + wait-on "^6.0.1" + webpack "^5.70.0" + webpack-bundle-analyzer "^4.5.0" + webpack-dev-server "^4.7.4" webpack-merge "^5.8.0" - webpackbar "^5.0.0-3" + webpackbar "^5.0.2" -"@docusaurus/cssnano-preset@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/cssnano-preset/-/cssnano-preset-2.0.0-beta.14.tgz#99bad713e3b58a89f63c25cec90b83437c3b3f2d" - integrity sha512-O5CebLXrytSQSpa0cgoMIUZ19gnLfCHhHPYqMfKxk0kvgR6g8b5AbsXxaMbgFNAqH690zPRsXmXb39BmXC7fMg== +"@docusaurus/cssnano-preset@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/cssnano-preset/-/cssnano-preset-2.0.0-beta.18.tgz#235ac9064fe8f8da618349ce5305be3ed3a44e29" + integrity sha512-VxhYmpyx16Wv00W9TUfLVv0NgEK/BwP7pOdWoaiELEIAMV7SO1+6iB8gsFUhtfKZ31I4uPVLMKrCyWWakoFeFA== dependencies: - cssnano-preset-advanced "^5.1.4" - postcss "^8.3.7" - postcss-sort-media-queries "^4.1.0" + cssnano-preset-advanced "^5.3.1" + postcss "^8.4.12" + postcss-sort-media-queries "^4.2.1" -"@docusaurus/logger@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/logger/-/logger-2.0.0-beta.14.tgz#d8c4e5f1c8b39149705587b98ca926549be51064" - integrity sha512-KNK8RgTGArXXlTUGhHUcYLJCI51gTMerSoebNXpTxAOBHFqjwJKv95LqVOy/uotoJZDUeEWR4vS/szGz4g7NaA== +"@docusaurus/logger@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/logger/-/logger-2.0.0-beta.18.tgz#12302f312a083eb018caa28505b63f5dd4ab6a91" + integrity sha512-frNe5vhH3mbPmH980Lvzaz45+n1PQl3TkslzWYXQeJOkFX17zUd3e3U7F9kR1+DocmAqHkgAoWuXVcvEoN29fg== dependencies: chalk "^4.1.2" tslib "^2.3.1" -"@docusaurus/mdx-loader@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/mdx-loader/-/mdx-loader-2.0.0-beta.14.tgz#f4750a02a8d178c843bc50f29f5a92d6cd0692cd" - integrity sha512-lusTVTHc4WbNQY8bDM9zPQWZBIo70SnEyWzCqtznxpV7L3kjSoWEpBCHaYWE/lY2VhvayRsZtrqLwNs3KQgqXw== - dependencies: - "@babel/parser" "^7.16.4" - "@babel/traverse" "^7.16.3" - "@docusaurus/logger" "2.0.0-beta.14" - "@docusaurus/utils" "2.0.0-beta.14" - "@mdx-js/mdx" "^1.6.21" - "@mdx-js/react" "^1.6.21" +"@docusaurus/mdx-loader@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/mdx-loader/-/mdx-loader-2.0.0-beta.18.tgz#4a9fc0607e0a210a7d7db3108415208dd36e33d3" + integrity sha512-pOmAQM4Y1jhuZTbEhjh4ilQa74Mh6Q0pMZn1xgIuyYDdqvIOrOlM/H0i34YBn3+WYuwsGim4/X0qynJMLDUA4A== + dependencies: + "@babel/parser" "^7.17.8" + "@babel/traverse" "^7.17.3" + "@docusaurus/logger" "2.0.0-beta.18" + "@docusaurus/utils" "2.0.0-beta.18" + "@mdx-js/mdx" "^1.6.22" escape-html "^1.0.3" file-loader "^6.2.0" - fs-extra "^10.0.0" - gray-matter "^4.0.3" + fs-extra "^10.0.1" + image-size "^1.0.1" mdast-util-to-string "^2.0.0" remark-emoji "^2.1.0" stringify-object "^3.3.0" tslib "^2.3.1" unist-util-visit "^2.0.2" url-loader "^4.1.1" - webpack "^5.61.0" - -"@docusaurus/plugin-content-blog@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.0.0-beta.14.tgz#d390ab0ab3aceaeb0be7d49ccde0cf5a2e0b1566" - integrity sha512-MLDRNbQKxwBDsWADyBT/fES7F7xzEEGS8CsdTnm48l7yGSWL8GM3PT6YvjdqHxNxZw3RCRRPUAiJcjZwfOjd8w== - dependencies: - "@docusaurus/core" "2.0.0-beta.14" - "@docusaurus/logger" "2.0.0-beta.14" - "@docusaurus/mdx-loader" "2.0.0-beta.14" - "@docusaurus/utils" "2.0.0-beta.14" - "@docusaurus/utils-validation" "2.0.0-beta.14" - escape-string-regexp "^4.0.0" + webpack "^5.70.0" + +"@docusaurus/module-type-aliases@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/module-type-aliases/-/module-type-aliases-2.0.0-beta.18.tgz#001379229c58cbc3ed565e19437cbda86d5e8742" + integrity sha512-e6mples8FZRyT7QyqidGS6BgkROjM+gljJsdOqoctbtBp+SZ5YDjwRHOmoY7eqEfsQNOaFZvT2hK38ui87hCRA== + dependencies: + "@docusaurus/types" "2.0.0-beta.18" + "@types/react" "*" + "@types/react-router-config" "*" + "@types/react-router-dom" "*" + react-helmet-async "*" + +"@docusaurus/plugin-content-blog@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.0.0-beta.18.tgz#95fe3dfc8bae9bf153c65a3a441234c450cbac0a" + integrity sha512-qzK83DgB+mxklk3PQC2nuTGPQD/8ogw1nXSmaQpyXAyhzcz4CXAZ9Swl/Ee9A/bvPwQGnSHSP3xqIYl8OkFtfw== + dependencies: + "@docusaurus/core" "2.0.0-beta.18" + "@docusaurus/logger" "2.0.0-beta.18" + "@docusaurus/mdx-loader" "2.0.0-beta.18" + "@docusaurus/utils" "2.0.0-beta.18" + "@docusaurus/utils-common" "2.0.0-beta.18" + "@docusaurus/utils-validation" "2.0.0-beta.18" + cheerio "^1.0.0-rc.10" feed "^4.2.2" - fs-extra "^10.0.0" - globby "^11.0.2" - js-yaml "^4.0.0" - loader-utils "^2.0.0" - lodash "^4.17.20" + fs-extra "^10.0.1" + lodash "^4.17.21" reading-time "^1.5.0" remark-admonitions "^1.2.1" tslib "^2.3.1" utility-types "^3.10.0" - webpack "^5.61.0" - -"@docusaurus/plugin-content-docs@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-docs/-/plugin-content-docs-2.0.0-beta.14.tgz#533ba6ba471b45ba7a7867207b251f281a6bed1e" - integrity sha512-pjAhfFevIkVl/t+6x9RVsE+6c+VN8Ru1uImTgXk5uVkp6yS1AxW7neEngsczZ1gSiENfTiYyhgWmTXK/uy03kw== - dependencies: - "@docusaurus/core" "2.0.0-beta.14" - "@docusaurus/logger" "2.0.0-beta.14" - "@docusaurus/mdx-loader" "2.0.0-beta.14" - "@docusaurus/utils" "2.0.0-beta.14" - "@docusaurus/utils-validation" "2.0.0-beta.14" + webpack "^5.70.0" + +"@docusaurus/plugin-content-docs@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-docs/-/plugin-content-docs-2.0.0-beta.18.tgz#fef52d945da2928e0f4f3f9a9384d9ee7f2d4288" + integrity sha512-z4LFGBJuzn4XQiUA7OEA2SZTqlp+IYVjd3NrCk/ZUfNi1tsTJS36ATkk9Y6d0Nsp7K2kRXqaXPsz4adDgeIU+Q== + dependencies: + "@docusaurus/core" "2.0.0-beta.18" + "@docusaurus/logger" "2.0.0-beta.18" + "@docusaurus/mdx-loader" "2.0.0-beta.18" + "@docusaurus/utils" "2.0.0-beta.18" + "@docusaurus/utils-validation" "2.0.0-beta.18" combine-promises "^1.1.0" - escape-string-regexp "^4.0.0" - fs-extra "^10.0.0" - globby "^11.0.2" - import-fresh "^3.2.2" - js-yaml "^4.0.0" - loader-utils "^2.0.0" - lodash "^4.17.20" + fs-extra "^10.0.1" + import-fresh "^3.3.0" + js-yaml "^4.1.0" + lodash "^4.17.21" remark-admonitions "^1.2.1" - shelljs "^0.8.4" tslib "^2.3.1" utility-types "^3.10.0" - webpack "^5.61.0" - -"@docusaurus/plugin-content-pages@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-pages/-/plugin-content-pages-2.0.0-beta.14.tgz#7f176d585994339cbe5c65332ed321eec82f53e3" - integrity sha512-gGcMPG4e+K57cbBPf7IfV5lFCBdraXcpJeDqXlD8ArTeZrAe8Lx3SGz2lco25DgdRGqjMivab3BoT6Hkmo7vVA== - dependencies: - "@docusaurus/core" "2.0.0-beta.14" - "@docusaurus/mdx-loader" "2.0.0-beta.14" - "@docusaurus/utils" "2.0.0-beta.14" - "@docusaurus/utils-validation" "2.0.0-beta.14" - globby "^11.0.2" + webpack "^5.70.0" + +"@docusaurus/plugin-content-pages@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-pages/-/plugin-content-pages-2.0.0-beta.18.tgz#0fef392be3fea3d85c212caf4eb744ead920c30b" + integrity sha512-CJ2Xeb9hQrMeF4DGywSDVX2TFKsQpc8ZA7czyeBAAbSFsoRyxXPYeSh8aWljqR4F1u/EKGSKy0Shk/D4wumaHw== + dependencies: + "@docusaurus/core" "2.0.0-beta.18" + "@docusaurus/mdx-loader" "2.0.0-beta.18" + "@docusaurus/utils" "2.0.0-beta.18" + "@docusaurus/utils-validation" "2.0.0-beta.18" + fs-extra "^10.0.1" remark-admonitions "^1.2.1" tslib "^2.3.1" - webpack "^5.61.0" + webpack "^5.70.0" -"@docusaurus/plugin-debug@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-debug/-/plugin-debug-2.0.0-beta.14.tgz#74d661a5cfefded7c9c281956ec2ec02260b576d" - integrity sha512-l0T26nZ9keyG2HrWwfwwHdqRzJg6cEJahyvKmnAOFfKieHPMxCJ9axBW+Ecy2PUMwJO7rILc6UObbhifNH7PnQ== +"@docusaurus/plugin-debug@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-debug/-/plugin-debug-2.0.0-beta.18.tgz#d4582532e59b538a23398f7c444b005367efa922" + integrity sha512-inLnLERgG7q0WlVmK6nYGHwVqREz13ivkynmNygEibJZToFRdgnIPW+OwD8QzgC5MpQTJw7+uYjcitpBumy1Gw== dependencies: - "@docusaurus/core" "2.0.0-beta.14" - "@docusaurus/utils" "2.0.0-beta.14" - fs-extra "^10.0.0" + "@docusaurus/core" "2.0.0-beta.18" + "@docusaurus/utils" "2.0.0-beta.18" + fs-extra "^10.0.1" react-json-view "^1.21.3" tslib "^2.3.1" -"@docusaurus/plugin-google-analytics@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-2.0.0-beta.14.tgz#16bfdd9245767e008be88cfeb47c7ceeef3884f6" - integrity sha512-fVtAwqK9iHjj32Dtg0j+T6ikD8yjTh5ruYru7rKYxld6LSSkU29Q0wp39qYxR390jn3rkrXLRCZ7qHT/Hs0zZg== +"@docusaurus/plugin-google-analytics@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-2.0.0-beta.18.tgz#a9b1659abb3f588e866aaa742ec4c82fe943eda3" + integrity sha512-s9dRBWDrZ1uu3wFXPCF7yVLo/+5LUFAeoxpXxzory8gn9GYDt8ZDj80h5DUyCLxiy72OG6bXWNOYS/Vc6cOPXQ== dependencies: - "@docusaurus/core" "2.0.0-beta.14" - "@docusaurus/utils-validation" "2.0.0-beta.14" + "@docusaurus/core" "2.0.0-beta.18" + "@docusaurus/utils-validation" "2.0.0-beta.18" tslib "^2.3.1" -"@docusaurus/plugin-google-gtag@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-2.0.0-beta.14.tgz#be950af01da784965a7fd7ba61d557055cceeb5e" - integrity sha512-DcaNRvu0VLS/C6qRAG0QNWjnuP8dAdzH0NOfl86AxdK6dWOP5NlGD9QoIFKTa19PB8iTzM2XZn/hOCub4hR6MQ== +"@docusaurus/plugin-google-gtag@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-2.0.0-beta.18.tgz#b51611ac01915523ddcfc9732f7862cf4996a0e1" + integrity sha512-h7vPuLVo/9pHmbFcvb4tCpjg4SxxX4k+nfVDyippR254FM++Z/nA5pRB0WvvIJ3ZTe0ioOb5Wlx2xdzJIBHUNg== dependencies: - "@docusaurus/core" "2.0.0-beta.14" - "@docusaurus/utils-validation" "2.0.0-beta.14" + "@docusaurus/core" "2.0.0-beta.18" + "@docusaurus/utils-validation" "2.0.0-beta.18" tslib "^2.3.1" -"@docusaurus/plugin-sitemap@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-sitemap/-/plugin-sitemap-2.0.0-beta.14.tgz#13042fee40ab2a66615c44d9ef440abb3df5c42a" - integrity sha512-ikSgz4VAttDB2uOrPa7fq/E/GKS5HAtKfD572kBj8RvppdlgFYwCLZ88ex5cnRFF//2ccaobYkU4QwDw2UKWMA== - dependencies: - "@docusaurus/core" "2.0.0-beta.14" - "@docusaurus/utils" "2.0.0-beta.14" - "@docusaurus/utils-common" "2.0.0-beta.14" - "@docusaurus/utils-validation" "2.0.0-beta.14" - fs-extra "^10.0.0" - sitemap "^7.0.0" +"@docusaurus/plugin-sitemap@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-sitemap/-/plugin-sitemap-2.0.0-beta.18.tgz#7e8217e95bede5719bd02265dcf7eb2fea76b675" + integrity sha512-Klonht0Ye3FivdBpS80hkVYNOH+8lL/1rbCPEV92rKhwYdwnIejqhdKct4tUTCl8TYwWiyeUFQqobC/5FNVZPQ== + dependencies: + "@docusaurus/core" "2.0.0-beta.18" + "@docusaurus/utils" "2.0.0-beta.18" + "@docusaurus/utils-common" "2.0.0-beta.18" + "@docusaurus/utils-validation" "2.0.0-beta.18" + fs-extra "^10.0.1" + sitemap "^7.1.1" tslib "^2.3.1" -"@docusaurus/preset-classic@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/preset-classic/-/preset-classic-2.0.0-beta.14.tgz#128026fb201fdc6271614587ca09187bc83d930a" - integrity sha512-43rHA6wM4FcbHLPiBpqY4VSUjUXOWvW/N4q0wvf1LMoPH25lUzIaldpjD3Unzq5+UCYCFES24ktl58QOh7PB2g== - dependencies: - "@docusaurus/core" "2.0.0-beta.14" - "@docusaurus/plugin-content-blog" "2.0.0-beta.14" - "@docusaurus/plugin-content-docs" "2.0.0-beta.14" - "@docusaurus/plugin-content-pages" "2.0.0-beta.14" - "@docusaurus/plugin-debug" "2.0.0-beta.14" - "@docusaurus/plugin-google-analytics" "2.0.0-beta.14" - "@docusaurus/plugin-google-gtag" "2.0.0-beta.14" - "@docusaurus/plugin-sitemap" "2.0.0-beta.14" - "@docusaurus/theme-classic" "2.0.0-beta.14" - "@docusaurus/theme-search-algolia" "2.0.0-beta.14" +"@docusaurus/preset-classic@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/preset-classic/-/preset-classic-2.0.0-beta.18.tgz#82f6905d34a13e46289ac4d2f1125e47033bd9d8" + integrity sha512-TfDulvFt/vLWr/Yy7O0yXgwHtJhdkZ739bTlFNwEkRMAy8ggi650e52I1I0T79s67llecb4JihgHPW+mwiVkCQ== + dependencies: + "@docusaurus/core" "2.0.0-beta.18" + "@docusaurus/plugin-content-blog" "2.0.0-beta.18" + "@docusaurus/plugin-content-docs" "2.0.0-beta.18" + "@docusaurus/plugin-content-pages" "2.0.0-beta.18" + "@docusaurus/plugin-debug" "2.0.0-beta.18" + "@docusaurus/plugin-google-analytics" "2.0.0-beta.18" + "@docusaurus/plugin-google-gtag" "2.0.0-beta.18" + "@docusaurus/plugin-sitemap" "2.0.0-beta.18" + "@docusaurus/theme-classic" "2.0.0-beta.18" + "@docusaurus/theme-common" "2.0.0-beta.18" + "@docusaurus/theme-search-algolia" "2.0.0-beta.18" "@docusaurus/react-loadable@5.5.2", "react-loadable@npm:@docusaurus/react-loadable@5.5.2": version "5.5.2" @@ -1425,111 +1436,125 @@ "@types/react" "*" prop-types "^15.6.2" -"@docusaurus/theme-classic@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-classic/-/theme-classic-2.0.0-beta.14.tgz#1e11f0e034bbb530ce38e669bc61a8eeea839132" - integrity sha512-gAatNruzgPh1NdCcIJPkhBpZE4jmbO+nYwpk/scatYQWBkhOs/fcI9tieIaGZIqi60N6lAUYQkPH+qXtLxX7Iw== - dependencies: - "@docusaurus/core" "2.0.0-beta.14" - "@docusaurus/plugin-content-blog" "2.0.0-beta.14" - "@docusaurus/plugin-content-docs" "2.0.0-beta.14" - "@docusaurus/plugin-content-pages" "2.0.0-beta.14" - "@docusaurus/theme-common" "2.0.0-beta.14" - "@docusaurus/theme-translations" "2.0.0-beta.14" - "@docusaurus/utils" "2.0.0-beta.14" - "@docusaurus/utils-validation" "2.0.0-beta.14" - "@mdx-js/mdx" "^1.6.21" - "@mdx-js/react" "^1.6.21" +"@docusaurus/theme-classic@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-classic/-/theme-classic-2.0.0-beta.18.tgz#a3632e83923ed4372f80999128375cd0b378d3f8" + integrity sha512-WJWofvSGKC4Luidk0lyUwkLnO3DDynBBHwmt4QrV+aAVWWSOHUjA2mPOF6GLGuzkZd3KfL9EvAfsU0aGE1Hh5g== + dependencies: + "@docusaurus/core" "2.0.0-beta.18" + "@docusaurus/plugin-content-blog" "2.0.0-beta.18" + "@docusaurus/plugin-content-docs" "2.0.0-beta.18" + "@docusaurus/plugin-content-pages" "2.0.0-beta.18" + "@docusaurus/theme-common" "2.0.0-beta.18" + "@docusaurus/theme-translations" "2.0.0-beta.18" + "@docusaurus/utils" "2.0.0-beta.18" + "@docusaurus/utils-common" "2.0.0-beta.18" + "@docusaurus/utils-validation" "2.0.0-beta.18" + "@mdx-js/react" "^1.6.22" clsx "^1.1.1" copy-text-to-clipboard "^3.0.1" - globby "^11.0.2" - infima "0.2.0-alpha.37" - lodash "^4.17.20" - postcss "^8.3.7" - prism-react-renderer "^1.2.1" - prismjs "^1.23.0" + infima "0.2.0-alpha.38" + lodash "^4.17.21" + postcss "^8.4.12" + prism-react-renderer "^1.3.1" + prismjs "^1.27.0" react-router-dom "^5.2.0" - rtlcss "^3.3.0" + rtlcss "^3.5.0" -"@docusaurus/theme-common@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-common/-/theme-common-2.0.0-beta.14.tgz#9795071a0df62b7700f6fbdea09946f3aae8183d" - integrity sha512-hr/+rx9mszjMEbrR329WFSj1jl/VxglSggLWhXqswiA3Lh5rbbeQv2ExwpBl4JBG5HxvtHUYmwYOuOTMuvRYTQ== +"@docusaurus/theme-common@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-common/-/theme-common-2.0.0-beta.18.tgz#abf74f82c37d2ce813f92447cb020831290059fb" + integrity sha512-3pI2Q6ttScDVTDbuUKAx+TdC8wmwZ2hfWk8cyXxksvC9bBHcyzXhSgcK8LTsszn2aANyZ3e3QY2eNSOikTFyng== dependencies: - "@docusaurus/plugin-content-blog" "2.0.0-beta.14" - "@docusaurus/plugin-content-docs" "2.0.0-beta.14" - "@docusaurus/plugin-content-pages" "2.0.0-beta.14" + "@docusaurus/module-type-aliases" "2.0.0-beta.18" + "@docusaurus/plugin-content-blog" "2.0.0-beta.18" + "@docusaurus/plugin-content-docs" "2.0.0-beta.18" + "@docusaurus/plugin-content-pages" "2.0.0-beta.18" clsx "^1.1.1" - fs-extra "^10.0.0" parse-numeric-range "^1.3.0" + prism-react-renderer "^1.3.1" tslib "^2.3.1" utility-types "^3.10.0" -"@docusaurus/theme-search-algolia@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-search-algolia/-/theme-search-algolia-2.0.0-beta.14.tgz#0238622324251c42098b2ccada4e19c3e92cd772" - integrity sha512-kTQl8vKXn8FAVVkCeN4XvU8PGWZTHToc+35F9GL06b4rv33zL9HaFIRX3nPM1NHC7I8qh+6gGeV0DRKGjO+j2g== - dependencies: - "@docsearch/react" "^3.0.0-alpha.39" - "@docusaurus/core" "2.0.0-beta.14" - "@docusaurus/logger" "2.0.0-beta.14" - "@docusaurus/theme-common" "2.0.0-beta.14" - "@docusaurus/theme-translations" "2.0.0-beta.14" - "@docusaurus/utils" "2.0.0-beta.14" - "@docusaurus/utils-validation" "2.0.0-beta.14" - algoliasearch "^4.10.5" - algoliasearch-helper "^3.5.5" +"@docusaurus/theme-search-algolia@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-search-algolia/-/theme-search-algolia-2.0.0-beta.18.tgz#cbdda8982deac4556848e04853b7f32d93886c02" + integrity sha512-2w97KO/gnjI49WVtYQqENpQ8iO1Sem0yaTxw7/qv/ndlmIAQD0syU4yx6GsA7bTQCOGwKOWWzZSetCgUmTnWgA== + dependencies: + "@docsearch/react" "^3.0.0" + "@docusaurus/core" "2.0.0-beta.18" + "@docusaurus/logger" "2.0.0-beta.18" + "@docusaurus/plugin-content-docs" "2.0.0-beta.18" + "@docusaurus/theme-common" "2.0.0-beta.18" + "@docusaurus/theme-translations" "2.0.0-beta.18" + "@docusaurus/utils" "2.0.0-beta.18" + "@docusaurus/utils-validation" "2.0.0-beta.18" + algoliasearch "^4.13.0" + algoliasearch-helper "^3.7.4" clsx "^1.1.1" eta "^1.12.3" - lodash "^4.17.20" + fs-extra "^10.0.1" + lodash "^4.17.21" tslib "^2.3.1" + utility-types "^3.10.0" -"@docusaurus/theme-translations@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-translations/-/theme-translations-2.0.0-beta.14.tgz#30f230c66aad3e085d680d49db484b663041be75" - integrity sha512-b67qJJIWc3A2tanYslDGpAUGfJ7oVAl+AdjGBYG3j3hYEUSyVUBzm8Y4iyCFEfW6BTx9pjqC/ECNO3iH2L3Ixg== +"@docusaurus/theme-translations@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-translations/-/theme-translations-2.0.0-beta.18.tgz#292699ce89b013262683faf7f4ee7b75a8745a79" + integrity sha512-1uTEUXlKC9nco1Lx9H5eOwzB+LP4yXJG5wfv1PMLE++kJEdZ40IVorlUi3nJnaa9/lJNq5vFvvUDrmeNWsxy/Q== dependencies: - fs-extra "^10.0.0" + fs-extra "^10.0.1" tslib "^2.3.1" -"@docusaurus/utils-common@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/utils-common/-/utils-common-2.0.0-beta.14.tgz#4ee8a266366722b2c98e17c12b109236dd2b32fb" - integrity sha512-hNWyy083Bm+6jEzsm05gFmEfwumXph0E46s2HrWkSM8tClrOVmu/C1Rm7kWYn561gXHhrATtyXr/u8bKXByFcQ== +"@docusaurus/types@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/types/-/types-2.0.0-beta.18.tgz#9446928a6b751eefde390420b39eac32ba26abb2" + integrity sha512-zkuSmPQYP3+z4IjGHlW0nGzSSpY7Sit0Nciu/66zSb5m07TK72t6T1MlpCAn/XijcB9Cq6nenC3kJh66nGsKYg== + dependencies: + commander "^5.1.0" + joi "^17.6.0" + utility-types "^3.10.0" + webpack "^5.70.0" + webpack-merge "^5.8.0" + +"@docusaurus/utils-common@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/utils-common/-/utils-common-2.0.0-beta.18.tgz#46cf0bed2a7c532b2b85eab5bb914ff118b2c4e9" + integrity sha512-pK83EcOIiKCLGhrTwukZMo5jqd1sqqqhQwOVyxyvg+x9SY/lsnNzScA96OEfm+qQLBwK1OABA7Xc1wfkgkUxvw== dependencies: tslib "^2.3.1" -"@docusaurus/utils-validation@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/utils-validation/-/utils-validation-2.0.0-beta.14.tgz#c5e54adbe6dd4b3d6f5525ae5138c0214e75a6c2" - integrity sha512-ttDp/fXjbM6rTfP8XCmBKtNygfPg8cncp+rPsWHdSFjGmE7HkinilFTtaw0Zos/096TtxsQx3DgGQyPOl6prnA== +"@docusaurus/utils-validation@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/utils-validation/-/utils-validation-2.0.0-beta.18.tgz#0dabf113d2c53ee685a715cd4caae6e219e9e41e" + integrity sha512-3aDrXjJJ8Cw2MAYEk5JMNnr8UHPxmVNbPU/PIHFWmWK09nJvs3IQ8nc9+8I30aIjRdIyc/BIOCxgvAcJ4hsxTA== dependencies: - "@docusaurus/logger" "2.0.0-beta.14" - "@docusaurus/utils" "2.0.0-beta.14" - joi "^17.4.2" + "@docusaurus/logger" "2.0.0-beta.18" + "@docusaurus/utils" "2.0.0-beta.18" + joi "^17.6.0" + js-yaml "^4.1.0" tslib "^2.3.1" -"@docusaurus/utils@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/utils/-/utils-2.0.0-beta.14.tgz#494d2181cc0fd264ebe12f2a08c6ae04878e5f90" - integrity sha512-7V+X70a+7UJHS7PeXS/BO2jz+zXaKhRlT7MUe5khu6i6n1oQA3Jqx1sfu78slemqEWe8u337jxal6uILcB0IWQ== +"@docusaurus/utils@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/utils/-/utils-2.0.0-beta.18.tgz#c3fe0e9fac30db4510962263993fd0ee2679eebb" + integrity sha512-v2vBmH7xSbPwx3+GB90HgLSQdj+Rh5ELtZWy7M20w907k0ROzDmPQ/8Ke2DK3o5r4pZPGnCrsB3SaYI83AEmAA== dependencies: - "@docusaurus/logger" "2.0.0-beta.14" - "@mdx-js/runtime" "^1.6.22" - "@svgr/webpack" "^6.0.0" - escape-string-regexp "^4.0.0" + "@docusaurus/logger" "2.0.0-beta.18" + "@svgr/webpack" "^6.2.1" file-loader "^6.2.0" - fs-extra "^10.0.0" + fs-extra "^10.0.1" github-slugger "^1.4.0" - globby "^11.0.4" + globby "^11.1.0" gray-matter "^4.0.3" - lodash "^4.17.20" - micromatch "^4.0.4" - remark-mdx-remove-exports "^1.6.22" - remark-mdx-remove-imports "^1.6.22" + js-yaml "^4.1.0" + lodash "^4.17.21" + micromatch "^4.0.5" resolve-pathname "^3.0.0" + shelljs "^0.8.5" tslib "^2.3.1" url-loader "^4.1.1" + webpack "^5.70.0" "@hapi/hoek@^9.0.0": version "9.2.1" @@ -1543,7 +1568,25 @@ dependencies: "@hapi/hoek" "^9.0.0" -"@mdx-js/mdx@1.6.22", "@mdx-js/mdx@^1.6.21": +"@jridgewell/resolve-uri@^3.0.3": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz#68eb521368db76d040a6315cdb24bf2483037b9c" + integrity sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew== + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.11" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz#771a1d8d744eeb71b6adb35808e1a6c7b9b8c8ec" + integrity sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg== + +"@jridgewell/trace-mapping@^0.3.0": + version "0.3.4" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.4.tgz#f6a0832dffd5b8a6aaa633b7d9f8e8e94c83a0c3" + integrity sha512-vFv9ttIedivx0ux3QSjhgtCVjPZd5l46ZOMDSCwnH1yUO2e964gO8LZGyv2QkqcgR6TnBU1v+1IFqmeoG+0UJQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@mdx-js/mdx@^1.6.22": version "1.6.22" resolved "https://registry.yarnpkg.com/@mdx-js/mdx/-/mdx-1.6.22.tgz#8a723157bf90e78f17dc0f27995398e6c731f1ba" integrity sha512-AMxuLxPz2j5/6TpF/XSdKpQP1NlG0z11dFOlq+2IP/lSgl11GY8ji6S/rgsViN/L0BDvHvUMruRb7ub+24LUYA== @@ -1568,20 +1611,11 @@ unist-builder "2.0.3" unist-util-visit "2.0.3" -"@mdx-js/react@1.6.22", "@mdx-js/react@^1.6.21": +"@mdx-js/react@^1.6.22": version "1.6.22" resolved "https://registry.yarnpkg.com/@mdx-js/react/-/react-1.6.22.tgz#ae09b4744fddc74714ee9f9d6f17a66e77c43573" integrity sha512-TDoPum4SHdfPiGSAaRBw7ECyI8VaHpK8GJugbJIJuqyh6kzw9ZLJZW3HGL3NNrJGxcAixUvqROm+YuQOo5eXtg== -"@mdx-js/runtime@^1.6.22": - version "1.6.22" - resolved "https://registry.yarnpkg.com/@mdx-js/runtime/-/runtime-1.6.22.tgz#3edd388bf68a519ffa1aaf9c446b548165102345" - integrity sha512-p17spaO2+55VLCuxXA3LVHC4phRx60NR2XMdZ+qgVU1lKvEX4y88dmFNOzGDCPLJ03IZyKrJ/rPWWRiBrd9JrQ== - dependencies: - "@mdx-js/mdx" "1.6.22" - "@mdx-js/react" "1.6.22" - buble-jsx-only "^0.19.8" - "@mdx-js/util@1.6.22": version "1.6.22" resolved "https://registry.yarnpkg.com/@mdx-js/util/-/util-1.6.22.tgz#219dfd89ae5b97a8801f015323ffa4b62f45718b" @@ -1614,9 +1648,9 @@ integrity sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g== "@sideway/address@^4.1.3": - version "4.1.3" - resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.3.tgz#d93cce5d45c5daec92ad76db492cc2ee3c64ab27" - integrity sha512-8ncEUtmnTsMmL7z1YPB47kPUq7LpKWJNFPsRzHiIajGC5uXlWGn+AmkYPcHNl8S4tcEGx+cnORnNYaw2wvL+LQ== + version "4.1.4" + resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.4.tgz#03dccebc6ea47fdc226f7d3d1ad512955d4783f0" + integrity sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw== dependencies: "@hapi/hoek" "^9.0.0" @@ -1635,15 +1669,14 @@ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== -"@slorber/static-site-generator-webpack-plugin@^4.0.0": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@slorber/static-site-generator-webpack-plugin/-/static-site-generator-webpack-plugin-4.0.1.tgz#0c8852146441aaa683693deaa5aee2f991d94841" - integrity sha512-PSv4RIVO1Y3kvHxjvqeVisk3E9XFoO04uwYBDWe217MFqKspplYswTuKLiJu0aLORQWzuQjfVsSlLPojwfYsLw== +"@slorber/static-site-generator-webpack-plugin@^4.0.4": + version "4.0.4" + resolved "https://registry.yarnpkg.com/@slorber/static-site-generator-webpack-plugin/-/static-site-generator-webpack-plugin-4.0.4.tgz#2bf4a2545e027830d2aa5eb950437c26a289b0f1" + integrity sha512-FvMavoWEIePps6/JwGCOLYKCRhuwIHhMtmbKpBFgzNkxwpa/569LfTkrbRk1m1I3n+ezJK4on9E1A6cjuZmD9g== dependencies: bluebird "^3.7.1" cheerio "^0.22.0" - eval "^0.1.4" - url "^0.11.0" + eval "^0.1.8" webpack-sources "^1.4.3" "@svgr/babel-plugin-add-jsx-attribute@^6.0.0": @@ -1681,15 +1714,15 @@ resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-6.0.0.tgz#eb688d0a5f539e34d268d8a516e81f5d7fede7c9" integrity sha512-VaphyHZ+xIKv5v0K0HCzyfAaLhPGJXSk2HkpYfXIOKb7DjLBv0soHDxNv6X0vr2titsxE7klb++u7iOf7TSrFQ== -"@svgr/babel-plugin-transform-svg-component@^6.1.0": - version "6.1.0" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-6.1.0.tgz#39f92954f7611c269a4ca6906d19e66cdc12babe" - integrity sha512-1zacrn08K5RyV2NtXahOZ5Im/+aB1Y0LVh6QpzwgQV05sY7H5Npq+OcW/UqXbfB2Ua/WnHsFossFQqigCjarYg== +"@svgr/babel-plugin-transform-svg-component@^6.2.0": + version "6.2.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-6.2.0.tgz#7ba61d9fc1fb42b0ba1a04e4630019fa7e993c4f" + integrity sha512-bhYIpsORb++wpsp91fymbFkf09Z/YEKR0DnFjxvN+8JHeCUD2unnh18jIMKnDJTWtvpTaGYPXELVe4OOzFI0xg== -"@svgr/babel-preset@^6.1.0": - version "6.1.0" - resolved "https://registry.yarnpkg.com/@svgr/babel-preset/-/babel-preset-6.1.0.tgz#b8a6b0019537bcd75b3e23fd33c180476c1ef446" - integrity sha512-f9XrTqcwhHLVkjvXBw6QJVxuIfmW22z8iTdGqGvUGGxWoeRV2EzSHstWMBgIVd7t+TmkerqowRvBYiT0OEx3cw== +"@svgr/babel-preset@^6.2.0": + version "6.2.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-preset/-/babel-preset-6.2.0.tgz#1d3ad8c7664253a4be8e4a0f0e6872f30d8af627" + integrity sha512-4WQNY0J71JIaL03DRn0vLiz87JXx0b9dYm2aA8XHlQJQoixMl4r/soYHm8dsaJZ3jWtkCiOYy48dp9izvXhDkQ== dependencies: "@svgr/babel-plugin-add-jsx-attribute" "^6.0.0" "@svgr/babel-plugin-remove-jsx-attribute" "^6.0.0" @@ -1698,57 +1731,57 @@ "@svgr/babel-plugin-svg-dynamic-title" "^6.0.0" "@svgr/babel-plugin-svg-em-dimensions" "^6.0.0" "@svgr/babel-plugin-transform-react-native-svg" "^6.0.0" - "@svgr/babel-plugin-transform-svg-component" "^6.1.0" + "@svgr/babel-plugin-transform-svg-component" "^6.2.0" -"@svgr/core@^6.1.2": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@svgr/core/-/core-6.1.2.tgz#17db14b8d559cb9dc4afa459aa487c00bf6cab80" - integrity sha512-G1UVZcPS5R+HfBG5QC7n2ibkax8RXki2sbKHySTTnajeNXbzriBJcpF4GpYzWptfvD2gmqTDY9XaX+x08TUyGQ== +"@svgr/core@^6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@svgr/core/-/core-6.2.1.tgz#195de807a9f27f9e0e0d678e01084b05c54fdf61" + integrity sha512-NWufjGI2WUyrg46mKuySfviEJ6IxHUOm/8a3Ph38VCWSp+83HBraCQrpEM3F3dB6LBs5x8OElS8h3C0oOJaJAA== dependencies: - "@svgr/plugin-jsx" "^6.1.2" + "@svgr/plugin-jsx" "^6.2.1" camelcase "^6.2.0" cosmiconfig "^7.0.1" -"@svgr/hast-util-to-babel-ast@^6.0.0": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-6.0.0.tgz#423329ad866b6c169009cc82b5e28ffee80c857c" - integrity sha512-S+TxtCdDyRGafH1VG1t/uPZ87aOYOHzWL8kqz4FoSZcIbzWA6rnOmjNViNiDzqmEpzp2PW5o5mZfvC9DiVZhTQ== +"@svgr/hast-util-to-babel-ast@^6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-6.2.1.tgz#ae065567b74cbe745afae617053adf9a764bea25" + integrity sha512-pt7MMkQFDlWJVy9ULJ1h+hZBDGFfSCwlBNW1HkLnVi7jUhyEXUaGYWi1x6bM2IXuAR9l265khBT4Av4lPmaNLQ== dependencies: "@babel/types" "^7.15.6" entities "^3.0.1" -"@svgr/plugin-jsx@^6.1.2": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@svgr/plugin-jsx/-/plugin-jsx-6.1.2.tgz#8a2815aaa46cc3d5cffa963e92b06bd0c33e7748" - integrity sha512-K/w16g3BznTjVjLyUyV0fE7LLl1HSq5KJjvczFVVvx9QG0+3xtU7RX6gvoVnTvYlrNo8QxxqLWVAU3HQm68Eew== +"@svgr/plugin-jsx@^6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@svgr/plugin-jsx/-/plugin-jsx-6.2.1.tgz#5668f1d2aa18c2f1bb7a1fc9f682d3f9aed263bd" + integrity sha512-u+MpjTsLaKo6r3pHeeSVsh9hmGRag2L7VzApWIaS8imNguqoUwDq/u6U/NDmYs/KAsrmtBjOEaAAPbwNGXXp1g== dependencies: "@babel/core" "^7.15.5" - "@svgr/babel-preset" "^6.1.0" - "@svgr/hast-util-to-babel-ast" "^6.0.0" + "@svgr/babel-preset" "^6.2.0" + "@svgr/hast-util-to-babel-ast" "^6.2.1" svg-parser "^2.0.2" -"@svgr/plugin-svgo@^6.1.2": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@svgr/plugin-svgo/-/plugin-svgo-6.1.2.tgz#4fe7a2defe237f0493dee947dde6fa5cea57e6c1" - integrity sha512-UHVSRZV3RdaggDT60OMIEmhskN736DOF6PuBcCaql6jBDA9+SZkA5ZMEw73ZLAlwdOAmw+0Gi4vx/xvAfnmerw== +"@svgr/plugin-svgo@^6.2.0": + version "6.2.0" + resolved "https://registry.yarnpkg.com/@svgr/plugin-svgo/-/plugin-svgo-6.2.0.tgz#4cbe6a33ccccdcae4e3b63ded64cc1cbe1faf48c" + integrity sha512-oDdMQONKOJEbuKwuy4Np6VdV6qoaLLvoY86hjvQEgU82Vx1MSWRyYms6Sl0f+NtqxLI/rDVufATbP/ev996k3Q== dependencies: cosmiconfig "^7.0.1" deepmerge "^4.2.2" svgo "^2.5.0" -"@svgr/webpack@^6.0.0": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@svgr/webpack/-/webpack-6.1.2.tgz#23fd605e9163deb7ef3feef52545ff11dc9989bf" - integrity sha512-5RzzWxFquywENwvnsiGjZ7IED+0l2lnICR3OKQ6OUyGgxlu+ac73NmDSXp6EPBz/ZTArpMZtug7jiPMUkXxnlg== +"@svgr/webpack@^6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@svgr/webpack/-/webpack-6.2.1.tgz#ef5d51c1b6be4e7537fb9f76b3f2b2e22b63c58d" + integrity sha512-h09ngMNd13hnePwgXa+Y5CgOjzlCvfWLHg+MBnydEedAnuLRzUHUJmGS3o2OsrhxTOOqEsPOFt5v/f6C5Qulcw== dependencies: "@babel/core" "^7.15.5" "@babel/plugin-transform-react-constant-elements" "^7.14.5" "@babel/preset-env" "^7.15.6" "@babel/preset-react" "^7.14.5" "@babel/preset-typescript" "^7.15.0" - "@svgr/core" "^6.1.2" - "@svgr/plugin-jsx" "^6.1.2" - "@svgr/plugin-svgo" "^6.1.2" + "@svgr/core" "^6.2.1" + "@svgr/plugin-jsx" "^6.2.1" + "@svgr/plugin-svgo" "^6.2.0" "@szmarczak/http-timer@^1.1.2": version "1.1.2" @@ -1762,33 +1795,75 @@ resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== -"@types/cssnano@^4.0.1": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@types/cssnano/-/cssnano-4.0.1.tgz#67fa912753d80973a016e7684a47fedf338aacff" - integrity sha512-hGOroxRTBkYl5gSBRJOffhV4+io+Y2bFX1VP7LgKEVHJt/LPPJaWUIuDAz74Vlp7l7hCDZfaDi7iPxwNwuVA4Q== +"@types/body-parser@*": + version "1.19.2" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" + integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== dependencies: - postcss "5 - 7" + "@types/connect" "*" + "@types/node" "*" -"@types/eslint-scope@^3.7.0": - version "3.7.2" - resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.2.tgz#11e96a868c67acf65bf6f11d10bb89ea71d5e473" - integrity sha512-TzgYCWoPiTeRg6RQYgtuW7iODtVoKu3RVL72k3WohqhjfaOLK5Mg2T4Tg1o2bSfu0vPkoI48wdQFv5b/Xe04wQ== +"@types/bonjour@^3.5.9": + version "3.5.10" + resolved "https://registry.yarnpkg.com/@types/bonjour/-/bonjour-3.5.10.tgz#0f6aadfe00ea414edc86f5d106357cda9701e275" + integrity sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw== + dependencies: + "@types/node" "*" + +"@types/connect-history-api-fallback@^1.3.5": + version "1.3.5" + resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz#d1f7a8a09d0ed5a57aee5ae9c18ab9b803205dae" + integrity sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw== + dependencies: + "@types/express-serve-static-core" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.35" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" + integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== + dependencies: + "@types/node" "*" + +"@types/eslint-scope@^3.7.3": + version "3.7.3" + resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.3.tgz#125b88504b61e3c8bc6f870882003253005c3224" + integrity sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g== dependencies: "@types/eslint" "*" "@types/estree" "*" "@types/eslint@*": - version "8.2.1" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.2.1.tgz#13f3d69bac93c2ae008019c28783868d0a1d6605" - integrity sha512-UP9rzNn/XyGwb5RQ2fok+DzcIRIYwc16qTXse5+Smsy8MOIccCChT15KAwnsgQx4PzJkaMq4myFyZ4CL5TjhIQ== + version "8.4.1" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.4.1.tgz#c48251553e8759db9e656de3efc846954ac32304" + integrity sha512-GE44+DNEyxxh2Kc6ro/VkIj+9ma0pO0bwv9+uHSyBrikYOHr8zYcdPvnBOp1aw8s+CjRvuSx7CyWqRrNFQ59mA== dependencies: "@types/estree" "*" "@types/json-schema" "*" -"@types/estree@*", "@types/estree@^0.0.50": - version "0.0.50" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.50.tgz#1e0caa9364d3fccd2931c3ed96fdbeaa5d4cca83" - integrity sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw== +"@types/estree@*", "@types/estree@^0.0.51": + version "0.0.51" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40" + integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ== + +"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.18": + version "4.17.28" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz#c47def9f34ec81dc6328d0b1b5303d1ec98d86b8" + integrity sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + +"@types/express@*", "@types/express@^4.17.13": + version "4.17.13" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034" + integrity sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.18" + "@types/qs" "*" + "@types/serve-static" "*" "@types/hast@^2.0.0": version "2.3.4" @@ -1797,12 +1872,17 @@ dependencies: "@types/unist" "*" +"@types/history@^4.7.11": + version "4.7.11" + resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.11.tgz#56588b17ae8f50c53983a524fc3cc47437969d64" + integrity sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA== + "@types/html-minifier-terser@^6.0.0": version "6.1.0" resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#4fc33a00c1d0c16987b1a20cf92d20614c55ac35" integrity sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg== -"@types/http-proxy@^1.17.5": +"@types/http-proxy@^1.17.8": version "1.17.8" resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.8.tgz#968c66903e7e42b483608030ee85800f22d03f55" integrity sha512-5kPLG5BKpWYkw/LVOGWpiq3nEVqxiN32rTgI53Sk12/xHFQ2rG3ehI9IO+O3W2QoKeyB92dJkoka8SUm6BX1pA== @@ -1810,9 +1890,9 @@ "@types/node" "*" "@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": - version "7.0.9" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" - integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== + version "7.0.11" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" + integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== "@types/mdast@^3.0.0": version "3.0.10" @@ -1821,15 +1901,15 @@ dependencies: "@types/unist" "*" -"@types/node@*": - version "17.0.2" - resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.2.tgz#a4c07d47ff737e8ee7e586fe636ff0e1ddff070a" - integrity sha512-JepeIUPFDARgIs0zD/SKPgFsJEAF0X5/qO80llx59gOxFTboS9Amv3S+QfB7lqBId5sFXJ99BN0J6zFRvL9dDA== +"@types/mime@^1": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" + integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== -"@types/node@^15.0.1": - version "15.14.9" - resolved "https://registry.yarnpkg.com/@types/node/-/node-15.14.9.tgz#bc43c990c3c9be7281868bbc7b8fdd6e2b57adfa" - integrity sha512-qjd88DrCxupx/kJD5yQgZdcYKZKSIGBVDIBE1/LTGcNm3d2Np/jxojkdePDdfnBHJc5W7vSMpbJ1aB7p/Py69A== +"@types/node@*", "@types/node@^17.0.5": + version "17.0.23" + resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.23.tgz#3b41a6e643589ac6442bdbd7a4a3ded62f33f7da" + integrity sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw== "@types/parse-json@^4.0.0": version "4.0.0" @@ -1846,10 +1926,46 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11" integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ== +"@types/qs@*": + version "6.9.7" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" + integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== + +"@types/range-parser@*": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" + integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== + +"@types/react-router-config@*": + version "5.0.6" + resolved "https://registry.yarnpkg.com/@types/react-router-config/-/react-router-config-5.0.6.tgz#87c5c57e72d241db900d9734512c50ccec062451" + integrity sha512-db1mx37a1EJDf1XeX8jJN7R3PZABmJQXR8r28yUjVMFSjkmnQo6X6pOEEmNl+Tp2gYQOGPdYbFIipBtdElZ3Yg== + dependencies: + "@types/history" "^4.7.11" + "@types/react" "*" + "@types/react-router" "*" + +"@types/react-router-dom@*": + version "5.3.3" + resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.3.3.tgz#e9d6b4a66fcdbd651a5f106c2656a30088cc1e83" + integrity sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw== + dependencies: + "@types/history" "^4.7.11" + "@types/react" "*" + "@types/react-router" "*" + +"@types/react-router@*": + version "5.1.18" + resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.18.tgz#c8851884b60bc23733500d86c1266e1cfbbd9ef3" + integrity sha512-YYknwy0D0iOwKQgz9v8nOzt2J6l4gouBmDnWqUUznltOTaon+r8US8ky8HvN0tXvc38U9m6z/t2RsVsnd1zM0g== + dependencies: + "@types/history" "^4.7.11" + "@types/react" "*" + "@types/react@*": - version "17.0.37" - resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.37.tgz#6884d0aa402605935c397ae689deed115caad959" - integrity sha512-2FS1oTqBGcH/s0E+CjrCCR9+JMpsu9b69RTFO+40ua43ZqP5MmQ4iUde/dMjWR909KxZwmOQIFq6AV6NjEG5xg== + version "17.0.43" + resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.43.tgz#4adc142887dd4a2601ce730bc56c3436fdb07a55" + integrity sha512-8Q+LNpdxf057brvPu1lMtC5Vn7J119xrP1aq4qiaefNioQUYANF/CYeK4NsKorSZyUGJ66g0IM+4bbjwx45o2A== dependencies: "@types/prop-types" "*" "@types/scheduler" "*" @@ -1872,11 +1988,40 @@ resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== +"@types/serve-index@^1.9.1": + version "1.9.1" + resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.1.tgz#1b5e85370a192c01ec6cec4735cf2917337a6278" + integrity sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg== + dependencies: + "@types/express" "*" + +"@types/serve-static@*": + version "1.13.10" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.10.tgz#f5e0ce8797d2d7cc5ebeda48a52c96c4fa47a8d9" + integrity sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ== + dependencies: + "@types/mime" "^1" + "@types/node" "*" + +"@types/sockjs@^0.3.33": + version "0.3.33" + resolved "https://registry.yarnpkg.com/@types/sockjs/-/sockjs-0.3.33.tgz#570d3a0b99ac995360e3136fd6045113b1bd236f" + integrity sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw== + dependencies: + "@types/node" "*" + "@types/unist@*", "@types/unist@^2.0.0", "@types/unist@^2.0.2", "@types/unist@^2.0.3": version "2.0.6" resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d" integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ== +"@types/ws@^8.2.2": + version "8.5.3" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d" + integrity sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w== + dependencies: + "@types/node" "*" + "@webassemblyjs/ast@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7" @@ -2008,43 +2153,28 @@ resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== -accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: - version "1.3.7" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" - integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== +accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: + version "1.3.8" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== dependencies: - mime-types "~2.1.24" - negotiator "0.6.2" - -acorn-dynamic-import@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz#482210140582a36b83c3e342e1cfebcaa9240948" - integrity sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw== + mime-types "~2.1.34" + negotiator "0.6.3" acorn-import-assertions@^1.7.6: version "1.8.0" resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz#ba2b5939ce62c238db6d93d81c9b111b29b855e9" integrity sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw== -acorn-jsx@^5.0.1: - version "5.3.2" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" - integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== - acorn-walk@^8.0.0: version "8.2.0" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== -acorn@^6.1.1: - version "6.4.2" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6" - integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ== - -acorn@^8.0.4, acorn@^8.4.1: - version "8.6.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.6.0.tgz#e3692ba0eb1a0c83eaa4f37f5fa7368dd7142895" - integrity sha512-U1riIR+lBSNi3IbxtaHOIKdH8sLFv3NYfNv8sg7ZsNhcfl4HF2++BfqqrNAxoCLQW1iiylOj76ecnaUxz+z9yw== +acorn@^8.0.4, acorn@^8.4.1, acorn@^8.5.0: + version "8.7.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" + integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ== address@^1.0.1, address@^1.1.2: version "1.1.2" @@ -2089,48 +2219,43 @@ ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5: uri-js "^4.2.2" ajv@^8.0.0, ajv@^8.8.0: - version "8.8.2" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.8.2.tgz#01b4fef2007a28bf75f0b7fc009f62679de4abbb" - integrity sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw== + version "8.11.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.11.0.tgz#977e91dd96ca669f54a11e23e378e33b884a565f" + integrity sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg== dependencies: fast-deep-equal "^3.1.1" json-schema-traverse "^1.0.0" require-from-string "^2.0.2" uri-js "^4.2.2" -algoliasearch-helper@^3.5.5: - version "3.7.0" - resolved "https://registry.yarnpkg.com/algoliasearch-helper/-/algoliasearch-helper-3.7.0.tgz#c0a0493df84d850360f664ad7a9d4fc78a94fd78" - integrity sha512-XJ3QfERBLfeVCyTVx80gon7r3/rgm/CE8Ha1H7cbablRe/X7SfYQ14g/eO+MhjVKIQp+gy9oC6G5ilmLwS1k6w== +algoliasearch-helper@^3.7.4: + version "3.7.4" + resolved "https://registry.yarnpkg.com/algoliasearch-helper/-/algoliasearch-helper-3.7.4.tgz#3812ea161da52463ec88da52612c9a363c1b181d" + integrity sha512-KmJrsHVm5TmxZ9Oj53XdXuM4CQeu7eVFnB15tpSFt+7is1d1yVCv3hxCLMqYSw/rH42ccv013miQpRr268P8vw== dependencies: "@algolia/events" "^4.0.1" -algoliasearch@^4.0.0, algoliasearch@^4.10.5: - version "4.11.0" - resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-4.11.0.tgz#234befb3ac355c094077f0edf3777240b1ee013c" - integrity sha512-IXRj8kAP2WrMmj+eoPqPc6P7Ncq1yZkFiyDrjTBObV1ADNL8Z/KdZ+dWC5MmYcBLAbcB/mMCpak5N/D1UIZvsA== - dependencies: - "@algolia/cache-browser-local-storage" "4.11.0" - "@algolia/cache-common" "4.11.0" - "@algolia/cache-in-memory" "4.11.0" - "@algolia/client-account" "4.11.0" - "@algolia/client-analytics" "4.11.0" - "@algolia/client-common" "4.11.0" - "@algolia/client-personalization" "4.11.0" - "@algolia/client-search" "4.11.0" - "@algolia/logger-common" "4.11.0" - "@algolia/logger-console" "4.11.0" - "@algolia/requester-browser-xhr" "4.11.0" - "@algolia/requester-common" "4.11.0" - "@algolia/requester-node-http" "4.11.0" - "@algolia/transporter" "4.11.0" - -alphanum-sort@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" - integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM= - -ansi-align@^3.0.0: +algoliasearch@^4.0.0, algoliasearch@^4.13.0: + version "4.13.0" + resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-4.13.0.tgz#e36611fda82b1fc548c156ae7929a7f486e4b663" + integrity sha512-oHv4faI1Vl2s+YC0YquwkK/TsaJs79g2JFg5FDm2rKN12VItPTAeQ7hyJMHarOPPYuCnNC5kixbtcqvb21wchw== + dependencies: + "@algolia/cache-browser-local-storage" "4.13.0" + "@algolia/cache-common" "4.13.0" + "@algolia/cache-in-memory" "4.13.0" + "@algolia/client-account" "4.13.0" + "@algolia/client-analytics" "4.13.0" + "@algolia/client-common" "4.13.0" + "@algolia/client-personalization" "4.13.0" + "@algolia/client-search" "4.13.0" + "@algolia/logger-common" "4.13.0" + "@algolia/logger-console" "4.13.0" + "@algolia/requester-browser-xhr" "4.13.0" + "@algolia/requester-common" "4.13.0" + "@algolia/requester-node-http" "4.13.0" + "@algolia/transporter" "4.13.0" + +ansi-align@^3.0.0, ansi-align@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.1.tgz#0cdf12e111ace773a86e9a1fad1225c43cb19a59" integrity sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w== @@ -2166,6 +2291,11 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" +ansi-styles@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.1.0.tgz#87313c102b8118abd57371afab34618bf7350ed3" + integrity sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ== + anymatch@~3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" @@ -2206,6 +2336,11 @@ array-union@^2.1.0: resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== +array-union@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-3.0.1.tgz#da52630d327f8b88cfbfb57728e2af5cd9b6b975" + integrity sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw== + asap@~2.0.3: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" @@ -2223,32 +2358,32 @@ at-least-node@^1.0.0: resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== -autoprefixer@^10.3.5, autoprefixer@^10.3.7: - version "10.4.0" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.0.tgz#c3577eb32a1079a440ec253e404eaf1eb21388c8" - integrity sha512-7FdJ1ONtwzV1G43GDD0kpVMn/qbiNqyOPMFTX5nRffI+7vgWoFEc6DcXOxHJxrWNDXrZh18eDsZjvZGUljSRGA== +autoprefixer@^10.3.7, autoprefixer@^10.4.4: + version "10.4.4" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.4.tgz#3e85a245b32da876a893d3ac2ea19f01e7ea5a1e" + integrity sha512-Tm8JxsB286VweiZ5F0anmbyGiNI3v3wGv3mz9W+cxEDYB/6jbnj6GM9H9mK3wIL8ftgl+C07Lcwb8PG5PCCPzA== dependencies: - browserslist "^4.17.5" - caniuse-lite "^1.0.30001272" - fraction.js "^4.1.1" + browserslist "^4.20.2" + caniuse-lite "^1.0.30001317" + fraction.js "^4.2.0" normalize-range "^0.1.2" picocolors "^1.0.0" - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -axios@^0.21.1: - version "0.21.4" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" - integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== +axios@^0.25.0: + version "0.25.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.25.0.tgz#349cfbb31331a9b4453190791760a8d35b093e0a" + integrity sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g== dependencies: - follow-redirects "^1.14.0" + follow-redirects "^1.14.7" -babel-loader@^8.2.2: - version "8.2.3" - resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.2.3.tgz#8986b40f1a64cacfcb4b8429320085ef68b1342d" - integrity sha512-n4Zeta8NC3QAsuyiizu0GkmRcQ6clkV9WFUnUf1iXP//IeSKbWjofW3UHyZVwlOB4y039YQKefawyTn64Zwbuw== +babel-loader@^8.2.4: + version "8.2.4" + resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.2.4.tgz#95f5023c791b2e9e2ca6f67b0984f39c82ff384b" + integrity sha512-8dytA3gcvPPPv4Grjhnt8b5IIiTcq/zeXOPk4iTYI0SVXcsmuGg7JtBRDp8S9X+gJfhQ8ektjXZlDu1Bb33U8A== dependencies: find-cache-dir "^3.3.1" - loader-utils "^1.4.0" + loader-utils "^2.0.0" make-dir "^3.1.0" schema-utils "^2.6.5" @@ -2282,28 +2417,28 @@ babel-plugin-extract-import-names@1.6.22: "@babel/helper-plugin-utils" "7.10.4" babel-plugin-polyfill-corejs2@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.0.tgz#407082d0d355ba565af24126fb6cb8e9115251fd" - integrity sha512-wMDoBJ6uG4u4PNFh72Ty6t3EgfA91puCuAwKIazbQlci+ENb/UU9A3xG5lutjUIiXCIn1CY5L15r9LimiJyrSA== + version "0.3.1" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.1.tgz#440f1b70ccfaabc6b676d196239b138f8a2cfba5" + integrity sha512-v7/T6EQcNfVLfcN2X8Lulb7DjprieyLWJK/zOWH5DUYcAgex9sP3h25Q+DLsX9TloXe3y1O8l2q2Jv9q8UVB9w== dependencies: "@babel/compat-data" "^7.13.11" - "@babel/helper-define-polyfill-provider" "^0.3.0" + "@babel/helper-define-polyfill-provider" "^0.3.1" semver "^6.1.1" -babel-plugin-polyfill-corejs3@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.4.0.tgz#0b571f4cf3d67f911512f5c04842a7b8e8263087" - integrity sha512-YxFreYwUfglYKdLUGvIF2nJEsGwj+RhWSX/ije3D2vQPOXuyMLMtg/cCGMDpOA7Nd+MwlNdnGODbd2EwUZPlsw== +babel-plugin-polyfill-corejs3@^0.5.0: + version "0.5.2" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.2.tgz#aabe4b2fa04a6e038b688c5e55d44e78cd3a5f72" + integrity sha512-G3uJih0XWiID451fpeFaYGVuxHEjzKTHtc9uGFEjR6hHrvNzeS/PX+LLLcetJcytsB5m4j+K3o/EpXJNb/5IEQ== dependencies: - "@babel/helper-define-polyfill-provider" "^0.3.0" - core-js-compat "^3.18.0" + "@babel/helper-define-polyfill-provider" "^0.3.1" + core-js-compat "^3.21.0" babel-plugin-polyfill-regenerator@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.0.tgz#9ebbcd7186e1a33e21c5e20cae4e7983949533be" - integrity sha512-dhAPTDLGoMW5/84wkgwiLRwMnio2i1fUe53EuvtKMv0pn2p3S8OCoV1xAzfJPl0KOX7IB89s2ib85vbYiea3jg== + version "0.3.1" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz#2c0678ea47c75c8cc2fbb1852278d8fb68233990" + integrity sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A== dependencies: - "@babel/helper-define-polyfill-provider" "^0.3.0" + "@babel/helper-define-polyfill-provider" "^0.3.1" bail@^1.0.0: version "1.0.5" @@ -2340,20 +2475,20 @@ bluebird@^3.7.1: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== -body-parser@1.19.1: - version "1.19.1" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.1.tgz#1499abbaa9274af3ecc9f6f10396c995943e31d4" - integrity sha512-8ljfQi5eBk8EJfECMrgqNGWPEY5jWP+1IzkzkGdFFEwFQZZyaZ21UqdaHktgiMlH0xLHqIFtE/u2OYE5dOtViA== +body-parser@1.19.2: + version "1.19.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.2.tgz#4714ccd9c157d44797b8b5607d72c0b89952f26e" + integrity sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw== dependencies: - bytes "3.1.1" + bytes "3.1.2" content-type "~1.0.4" debug "2.6.9" depd "~1.1.2" http-errors "1.8.1" iconv-lite "0.4.24" on-finished "~2.3.0" - qs "6.9.6" - raw-body "2.4.2" + qs "6.9.7" + raw-body "2.4.3" type-is "~1.6.18" bonjour@^3.5.0: @@ -2373,7 +2508,7 @@ boolbase@^1.0.0, boolbase@~1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= -boxen@^5.0.0, boxen@^5.0.1: +boxen@^5.0.0: version "5.1.2" resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.1.2.tgz#788cb686fc83c1f486dfa8a40c68fc2b831d2b50" integrity sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ== @@ -2387,6 +2522,20 @@ boxen@^5.0.0, boxen@^5.0.1: widest-line "^3.1.0" wrap-ansi "^7.0.0" +boxen@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-6.2.1.tgz#b098a2278b2cd2845deef2dff2efc38d329b434d" + integrity sha512-H4PEsJXfFI/Pt8sjDWbHlQPx4zL/bvSQjcilJmaulGt5mLDorHOHpmdXAJcBcmru7PhYSp/cDMWRko4ZUMFkSw== + dependencies: + ansi-align "^3.0.1" + camelcase "^6.2.0" + chalk "^4.1.2" + cli-boxes "^3.0.0" + string-width "^5.0.1" + type-fest "^2.5.0" + widest-line "^4.0.1" + wrap-ansi "^8.0.1" + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -2395,37 +2544,24 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^3.0.1, braces@~3.0.2: +braces@^3.0.2, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== dependencies: fill-range "^7.0.1" -browserslist@^4.0.0, browserslist@^4.14.5, browserslist@^4.16.0, browserslist@^4.16.5, browserslist@^4.16.6, browserslist@^4.17.5, browserslist@^4.19.1: - version "4.19.1" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.19.1.tgz#4ac0435b35ab655896c31d53018b6dd5e9e4c9a3" - integrity sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A== +browserslist@^4.0.0, browserslist@^4.14.5, browserslist@^4.16.6, browserslist@^4.17.5, browserslist@^4.18.1, browserslist@^4.19.1, browserslist@^4.20.2: + version "4.20.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.20.2.tgz#567b41508757ecd904dab4d1c646c612cd3d4f88" + integrity sha512-CQOBCqp/9pDvDbx3xfMi+86pr4KXIf2FDkTTdeuYw8OxS9t898LA1Khq57gtufFILXpfgsSx5woNgsBgvGjpsA== dependencies: - caniuse-lite "^1.0.30001286" - electron-to-chromium "^1.4.17" + caniuse-lite "^1.0.30001317" + electron-to-chromium "^1.4.84" escalade "^3.1.1" - node-releases "^2.0.1" + node-releases "^2.0.2" picocolors "^1.0.0" -buble-jsx-only@^0.19.8: - version "0.19.8" - resolved "https://registry.yarnpkg.com/buble-jsx-only/-/buble-jsx-only-0.19.8.tgz#6e3524aa0f1c523de32496ac9aceb9cc2b493867" - integrity sha512-7AW19pf7PrKFnGTEDzs6u9+JZqQwM1VnLS19OlqYDhXomtFFknnoQJAPHeg84RMFWAvOhYrG7harizJNwUKJsA== - dependencies: - acorn "^6.1.1" - acorn-dynamic-import "^4.0.0" - acorn-jsx "^5.0.1" - chalk "^2.4.2" - magic-string "^0.25.3" - minimist "^1.2.0" - regexpu-core "^4.5.4" - buffer-from@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" @@ -2441,10 +2577,10 @@ bytes@3.0.0: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= -bytes@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.1.tgz#3f018291cb4cbad9accb6e6970bca9c8889e879a" - integrity sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg== +bytes@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== cacheable-request@^6.0.0: version "6.1.0" @@ -2486,9 +2622,9 @@ camelcase-css@2.0.1: integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== camelcase@^6.2.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.1.tgz#250fd350cfd555d0d2160b1d51510eaf8326e86e" - integrity sha512-tVI4q5jjFV5CavAU8DXfza/TJcZutVKo/5Foskmsqcm0MsL91moHvwiGNnqaa2o6PF/7yT5ikDRcVcl8Rj6LCA== + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== caniuse-api@^3.0.0: version "3.0.0" @@ -2500,17 +2636,17 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001272, caniuse-lite@^1.0.30001286: - version "1.0.30001291" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001291.tgz#08a8d2cfea0b2cf2e1d94dd795942d0daef6108c" - integrity sha512-roMV5V0HNGgJ88s42eE70sstqGW/gwFndosYrikHthw98N5tLnOTxFqMLQjZVRxTWFlJ4rn+MsgXrR7MDPY4jA== +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001317: + version "1.0.30001320" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001320.tgz#8397391bec389b8ccce328636499b7284ee13285" + integrity sha512-MWPzG54AGdo3nWx7zHZTefseM5Y1ccM7hlQKHRqJkPozUaw3hNbBTMmLn16GG2FUzjR13Cr3NPfhIieX5PzXDA== ccount@^1.0.0, ccount@^1.0.3: version "1.1.0" resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.1.0.tgz#246687debb6014735131be8abab2d93898f8d043" integrity sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg== -chalk@^2.0.0, chalk@^2.4.2: +chalk@^2.0.0: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -2542,6 +2678,17 @@ character-reference-invalid@^1.0.0: resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz#083329cda0eae272ab3dbbf37e9a382c13af1560" integrity sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg== +cheerio-select@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/cheerio-select/-/cheerio-select-1.5.0.tgz#faf3daeb31b17c5e1a9dabcee288aaf8aafa5823" + integrity sha512-qocaHPv5ypefh6YNxvnbABM07KMxExbtbfuJoIie3iZXX1ERwYmJcIiRrr9H05ucQP1k28dav8rpdDgjQd8drg== + dependencies: + css-select "^4.1.3" + css-what "^5.0.1" + domelementtype "^2.2.0" + domhandler "^4.2.0" + domutils "^2.7.0" + cheerio@^0.22.0: version "0.22.0" resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-0.22.0.tgz#a9baa860a3f9b595a6b81b1a86873121ed3a269e" @@ -2564,10 +2711,23 @@ cheerio@^0.22.0: lodash.reject "^4.4.0" lodash.some "^4.4.0" -chokidar@^3.4.2, chokidar@^3.5.2: - version "3.5.2" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75" - integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ== +cheerio@^1.0.0-rc.10: + version "1.0.0-rc.10" + resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.10.tgz#2ba3dcdfcc26e7956fc1f440e61d51c643379f3e" + integrity sha512-g0J0q/O6mW8z5zxQ3A8E8J1hUgp4SMOvEoW/x84OwyHKe/Zccz83PVT4y5Crcr530FV6NgmKI1qvGTKVl9XXVw== + dependencies: + cheerio-select "^1.5.0" + dom-serializer "^1.3.2" + domhandler "^4.2.0" + htmlparser2 "^6.1.0" + parse5 "^6.0.1" + parse5-htmlparser2-tree-adapter "^6.0.1" + tslib "^2.2.0" + +chokidar@^3.4.2, chokidar@^3.5.3: + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== dependencies: anymatch "~3.1.2" braces "~3.0.2" @@ -2589,10 +2749,10 @@ ci-info@^2.0.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== -clean-css@^5.1.5, clean-css@^5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.2.2.tgz#d3a7c6ee2511011e051719838bdcf8314dc4548d" - integrity sha512-/eR8ru5zyxKzpBLv9YZvMXgTSSQn7AdkMItMYynsFgGwTveCRVam9IUPFloE85B4vAIj05IuKmmEoV7/AQjT0w== +clean-css@^5.2.2, clean-css@^5.2.4: + version "5.2.4" + resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.2.4.tgz#982b058f8581adb2ae062520808fb2429bd487a4" + integrity sha512-nKseG8wCzEuji/4yrgM/5cthL9oTDc5UOQyFMvW/Q53oP6gLH690o1NbuTh6Y18nujr7BxlsFuS7gXLnLzKJGg== dependencies: source-map "~0.6.0" @@ -2606,6 +2766,20 @@ cli-boxes@^2.2.1: resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== +cli-boxes@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-3.0.0.tgz#71a10c716feeba005e4504f36329ef0b17cf3145" + integrity sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g== + +cli-table3@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.1.tgz#36ce9b7af4847f288d3cdd081fbd09bf7bd237b8" + integrity sha512-w0q/enDHhPLq44ovMGdQeeDLvwxwavsJX7oQGYt/LrBlYsyaxyDnp6z3QzFut/6kLLKnlcUVJLrpB7KBfgG/RA== + dependencies: + string-width "^4.2.0" + optionalDependencies: + colors "1.4.0" + clone-deep@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" @@ -2666,6 +2840,11 @@ colorette@^2.0.10: resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.16.tgz#713b9af84fdb000139f04546bd4a93f62a5085da" integrity sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g== +colors@1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" + integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== + combine-promises@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/combine-promises/-/combine-promises-1.1.0.tgz#72db90743c0ca7aab7d0d8d2052fd7b0f674de71" @@ -2777,45 +2956,45 @@ cookie-signature@1.0.6: resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= -cookie@0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" - integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== +cookie@0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" + integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== copy-text-to-clipboard@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/copy-text-to-clipboard/-/copy-text-to-clipboard-3.0.1.tgz#8cbf8f90e0a47f12e4a24743736265d157bce69c" integrity sha512-rvVsHrpFcL4F2P8ihsoLdFHmd404+CMg71S756oRSeQgqk51U3kicGdnvfkrxva0xXH92SjGS62B0XIJsbh+9Q== -copy-webpack-plugin@^9.0.1: - version "9.1.0" - resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-9.1.0.tgz#2d2c460c4c4695ec0a58afb2801a1205256c4e6b" - integrity sha512-rxnR7PaGigJzhqETHGmAcxKnLZSR5u1Y3/bcIv/1FnqXedcL/E2ewK7ZCNrArJKCiSv8yVXhTqetJh8inDvfsA== +copy-webpack-plugin@^10.2.4: + version "10.2.4" + resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-10.2.4.tgz#6c854be3fdaae22025da34b9112ccf81c63308fe" + integrity sha512-xFVltahqlsRcyyJqQbDY6EYTtyQZF9rf+JPjwHObLdPFMEISqkFkr7mFoVOC6BfYS/dNThyoQKvziugm+OnwBg== dependencies: fast-glob "^3.2.7" glob-parent "^6.0.1" - globby "^11.0.3" + globby "^12.0.2" normalize-path "^3.0.0" - schema-utils "^3.1.1" + schema-utils "^4.0.0" serialize-javascript "^6.0.0" -core-js-compat@^3.18.0, core-js-compat@^3.19.1: - version "3.20.0" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.20.0.tgz#fd704640c5a213816b6d10ec0192756111e2c9d1" - integrity sha512-relrah5h+sslXssTTOkvqcC/6RURifB0W5yhYBdBkaPYa5/2KBMiog3XiD+s3TwEHWxInWVv4Jx2/Lw0vng+IQ== +core-js-compat@^3.20.2, core-js-compat@^3.21.0: + version "3.21.1" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.21.1.tgz#cac369f67c8d134ff8f9bd1623e3bc2c42068c82" + integrity sha512-gbgX5AUvMb8gwxC7FLVWYT7Kkgu/y7+h/h1X43yJkNqhlK2fuYyQimqvKGNZFAY6CKii/GFKJ2cp/1/42TN36g== dependencies: browserslist "^4.19.1" semver "7.0.0" -core-js-pure@^3.19.0: - version "3.20.0" - resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.20.0.tgz#7253feccf8bb05b72c153ddccdbe391ddbffbe03" - integrity sha512-qsrbIwWSEEYOM7z616jAVgwhuDDtPLwZSpUsU3vyUkHYqKTf/uwOJBZg2V7lMurYWkpVlaVOxBrfX0Q3ppvjfg== +core-js-pure@^3.20.2: + version "3.21.1" + resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.21.1.tgz#8c4d1e78839f5f46208de7230cebfb72bc3bdb51" + integrity sha512-12VZfFIu+wyVbBebyHmRTuEE/tZrB4tJToWcwAMcsp3h4+sHR+fMJWbKpYiCRWlhFBq+KNyO8rIV9rTkeVmznQ== -core-js@^3.18.0: - version "3.20.0" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.20.0.tgz#1c5ac07986b8d15473ab192e45a2e115a4a95b79" - integrity sha512-KjbKU7UEfg4YPpskMtMXPhUKn7m/1OdTHTVjy09ScR2LVaoUXe8Jh0UdvN2EKUR6iKTJph52SJP95mAB0MnVLQ== +core-js@^3.21.1: + version "3.21.1" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.21.1.tgz#f2e0ddc1fc43da6f904706e8e955bc19d06a0d94" + integrity sha512-FRq5b/VMrWlrmCzwRrpDYNxyHP9BcAZC+xHJaqTgIE5091ZV1NTmyh0sGOg5XqpnHvR0svdy0sv1gWA1zmhxig== core-util-is@~1.0.0: version "1.0.3" @@ -2844,12 +3023,12 @@ cosmiconfig@^7.0.0, cosmiconfig@^7.0.1: path-type "^4.0.0" yaml "^1.10.0" -cross-fetch@^3.0.4: - version "3.1.4" - resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.4.tgz#9723f3a3a247bf8b89039f3a380a9244e8fa2f39" - integrity sha512-1eAtFWdIubi6T4XPy6ei9iUFoKpUkIF971QLN8lIvvvwueI65+Nw5haMNKUwfJxabqlIIDODJKGrQ66gxC0PbQ== +cross-fetch@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f" + integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw== dependencies: - node-fetch "2.6.1" + node-fetch "2.6.7" cross-spawn@^7.0.3: version "7.0.3" @@ -2866,34 +3045,31 @@ crypto-random-string@^2.0.0: integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== css-declaration-sorter@^6.0.3: - version "6.1.3" - resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.1.3.tgz#e9852e4cf940ba79f509d9425b137d1f94438dc2" - integrity sha512-SvjQjNRZgh4ULK1LDJ2AduPKUKxIqmtU7ZAyi47BTV+M90Qvxr9AB6lKlLbDUfXqI9IQeYA8LbAsCZPpJEV3aA== + version "6.1.4" + resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.1.4.tgz#b9bfb4ed9a41f8dcca9bf7184d849ea94a8294b4" + integrity sha512-lpfkqS0fctcmZotJGhnxkIyJWvBXgpyi2wsFd4J8VB7wzyrT6Ch/3Q+FMNJpjK4gu1+GN5khOnpU2ZVKrLbhCw== dependencies: timsort "^0.3.0" -css-loader@^5.1.1: - version "5.2.7" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.2.7.tgz#9b9f111edf6fb2be5dc62525644cbc9c232064ae" - integrity sha512-Q7mOvpBNBG7YrVGMxRxcBJZFL75o+cH2abNASdibkj/fffYD8qWbInZrD0S9ccI6vZclF3DsHE7njGlLtaHbhg== +css-loader@^6.7.1: + version "6.7.1" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.7.1.tgz#e98106f154f6e1baf3fc3bc455cb9981c1d5fd2e" + integrity sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw== dependencies: icss-utils "^5.1.0" - loader-utils "^2.0.0" - postcss "^8.2.15" + postcss "^8.4.7" postcss-modules-extract-imports "^3.0.0" postcss-modules-local-by-default "^4.0.0" postcss-modules-scope "^3.0.0" postcss-modules-values "^4.0.0" - postcss-value-parser "^4.1.0" - schema-utils "^3.0.0" + postcss-value-parser "^4.2.0" semver "^7.3.5" -css-minimizer-webpack-plugin@^3.0.2: - version "3.3.0" - resolved "https://registry.yarnpkg.com/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-3.3.0.tgz#e61515072e788c4134b9ca395adc56243cf4d3e1" - integrity sha512-+SU5aHgGZkk2kxKsq/BZXnYee2cjHIiFARF2gGaG6gIFtLJ87330GeafqhxAemwi/WgQ40v0OQ7pBVljKAMoXg== +css-minimizer-webpack-plugin@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-3.4.1.tgz#ab78f781ced9181992fe7b6e4f3422e76429878f" + integrity sha512-1u6D71zeIfgngN2XNRJefc/hY7Ybsxd74Jm4qngIXyUEk7fss3VUzuHxLAq/R8NAba4QU9OUSaMZlbpRc7bM4Q== dependencies: - "@types/cssnano" "^4.0.1" cssnano "^5.0.6" jest-worker "^27.0.2" postcss "^8.3.5" @@ -2902,9 +3078,9 @@ css-minimizer-webpack-plugin@^3.0.2: source-map "^0.6.1" css-select@^4.1.3: - version "4.2.0" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.2.0.tgz#ab28276d3afb00cc05e818bd33eb030f14f57895" - integrity sha512-6YVG6hsH9yIb/si3Th/is8Pex7qnVHO6t7q7U6TIUnkQASGbS8tnUDBftnPynLNnuUl/r2+PTd0ekiiq7R0zJw== + version "4.2.1" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.2.1.tgz#9e665d6ae4c7f9d65dbe69d0316e3221fb274cdd" + integrity sha512-/aUslKhzkTNCQUB2qTX84lVmfia9NyjP3WpDGtj/WxhwBzWBYUV3DgUpurHTme8UTPcPlAD1DJ+b0nN/t50zDQ== dependencies: boolbase "^1.0.0" css-what "^5.1.0" @@ -2935,7 +3111,7 @@ css-what@2.1: resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg== -css-what@^5.1.0: +css-what@^5.0.1, css-what@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/css-what/-/css-what-5.1.0.tgz#3f7b707aadf633baf62c2ceb8579b545bb40f7fe" integrity sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw== @@ -2945,64 +3121,64 @@ cssesc@^3.0.0: resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== -cssnano-preset-advanced@^5.1.4: - version "5.1.9" - resolved "https://registry.yarnpkg.com/cssnano-preset-advanced/-/cssnano-preset-advanced-5.1.9.tgz#7f392122a5b26368cb05d30750d7a50d6ede7f8b" - integrity sha512-lWyaSP22ixL8pi9k+yz7VQwa1OHDCZ3SIZeq5K40NIRDII42ua2pO9HRtWQ9N+xh/AQTTHZR4ZOSxouB7VjCIQ== +cssnano-preset-advanced@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/cssnano-preset-advanced/-/cssnano-preset-advanced-5.3.1.tgz#f4fa7006aab67e354289b3efd512c93a272b3874" + integrity sha512-kfCknalY5VX/JKJ3Iri5/5rhZmQIqkbqgXsA6oaTnfA4flY/tt+w0hMxbExr0/fVuJL8w56j211op+pkQoNzoQ== dependencies: autoprefixer "^10.3.7" - cssnano-preset-default "^5.1.9" - postcss-discard-unused "^5.0.1" - postcss-merge-idents "^5.0.1" - postcss-reduce-idents "^5.0.1" - postcss-zindex "^5.0.1" + cssnano-preset-default "^5.2.5" + postcss-discard-unused "^5.1.0" + postcss-merge-idents "^5.1.1" + postcss-reduce-idents "^5.2.0" + postcss-zindex "^5.1.0" -cssnano-preset-default@^5.1.9: - version "5.1.9" - resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.1.9.tgz#79628ac48eccbdad570f70b4018cc38d43d1b7df" - integrity sha512-RhkEucqlQ+OxEi14K1p8gdXcMQy1mSpo7P1oC44oRls7BYIj8p+cht4IFBFV3W4iOjTP8EUB33XV1fX9KhDzyA== +cssnano-preset-default@^5.2.5: + version "5.2.5" + resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.2.5.tgz#267ded811a3e1664d78707f5355fcd89feeb38ac" + integrity sha512-WopL7PzN7sos3X8B54/QGl+CZUh1f0qN4ds+y2d5EPwRSSc3jsitVw81O+Uyop0pXyOfPfZxnc+LmA8w/Ki/WQ== dependencies: css-declaration-sorter "^6.0.3" - cssnano-utils "^2.0.1" - postcss-calc "^8.0.0" - postcss-colormin "^5.2.2" - postcss-convert-values "^5.0.2" - postcss-discard-comments "^5.0.1" - postcss-discard-duplicates "^5.0.1" - postcss-discard-empty "^5.0.1" - postcss-discard-overridden "^5.0.1" - postcss-merge-longhand "^5.0.4" - postcss-merge-rules "^5.0.3" - postcss-minify-font-values "^5.0.1" - postcss-minify-gradients "^5.0.3" - postcss-minify-params "^5.0.2" - postcss-minify-selectors "^5.1.0" - postcss-normalize-charset "^5.0.1" - postcss-normalize-display-values "^5.0.1" - postcss-normalize-positions "^5.0.1" - postcss-normalize-repeat-style "^5.0.1" - postcss-normalize-string "^5.0.1" - postcss-normalize-timing-functions "^5.0.1" - postcss-normalize-unicode "^5.0.1" - postcss-normalize-url "^5.0.4" - postcss-normalize-whitespace "^5.0.1" - postcss-ordered-values "^5.0.2" - postcss-reduce-initial "^5.0.2" - postcss-reduce-transforms "^5.0.1" - postcss-svgo "^5.0.3" - postcss-unique-selectors "^5.0.2" - -cssnano-utils@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-2.0.1.tgz#8660aa2b37ed869d2e2f22918196a9a8b6498ce2" - integrity sha512-i8vLRZTnEH9ubIyfdZCAdIdgnHAUeQeByEeQ2I7oTilvP9oHO6RScpeq3GsFUVqeB8uZgOQ9pw8utofNn32hhQ== + cssnano-utils "^3.1.0" + postcss-calc "^8.2.3" + postcss-colormin "^5.3.0" + postcss-convert-values "^5.1.0" + postcss-discard-comments "^5.1.1" + postcss-discard-duplicates "^5.1.0" + postcss-discard-empty "^5.1.1" + postcss-discard-overridden "^5.1.0" + postcss-merge-longhand "^5.1.3" + postcss-merge-rules "^5.1.1" + postcss-minify-font-values "^5.1.0" + postcss-minify-gradients "^5.1.1" + postcss-minify-params "^5.1.2" + postcss-minify-selectors "^5.2.0" + postcss-normalize-charset "^5.1.0" + postcss-normalize-display-values "^5.1.0" + postcss-normalize-positions "^5.1.0" + postcss-normalize-repeat-style "^5.1.0" + postcss-normalize-string "^5.1.0" + postcss-normalize-timing-functions "^5.1.0" + postcss-normalize-unicode "^5.1.0" + postcss-normalize-url "^5.1.0" + postcss-normalize-whitespace "^5.1.1" + postcss-ordered-values "^5.1.1" + postcss-reduce-initial "^5.1.0" + postcss-reduce-transforms "^5.1.0" + postcss-svgo "^5.1.0" + postcss-unique-selectors "^5.1.1" + +cssnano-utils@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-3.1.0.tgz#95684d08c91511edfc70d2636338ca37ef3a6861" + integrity sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA== -cssnano@^5.0.6, cssnano@^5.0.8: - version "5.0.14" - resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.0.14.tgz#99bc550f663b48c38e9b8e0ae795697c9de84b47" - integrity sha512-qzhRkFvBhv08tbyKCIfWbxBXmkIpLl1uNblt8SpTHkgLfON5OCPX/CCnkdNmEosvo8bANQYmTTMEgcVBlisHaw== +cssnano@^5.0.6, cssnano@^5.1.5: + version "5.1.5" + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.1.5.tgz#5f3f519538c7f1c182c527096892243db3e17397" + integrity sha512-VZO1e+bRRVixMeia1zKagrv0lLN1B/r/u12STGNNUFxnp97LIFgZHQa0JxqlwEkvzUyA9Oz/WnCTAFkdEbONmg== dependencies: - cssnano-preset-default "^5.1.9" + cssnano-preset-default "^5.2.5" lilconfig "^2.0.3" yaml "^1.10.2" @@ -3014,9 +3190,9 @@ csso@^4.2.0: css-tree "^1.1.2" csstype@^3.0.2: - version "3.0.10" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.10.tgz#2ad3a7bed70f35b965707c092e5f30b327c290e5" - integrity sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA== + version "3.0.11" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.11.tgz#d66700c5eacfac1940deb4e3ee5642792d85cd33" + integrity sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw== debug@2.6.9, debug@^2.6.0: version "2.6.9" @@ -3033,9 +3209,9 @@ debug@^3.1.1: ms "^2.1.1" debug@^4.1.0, debug@^4.1.1: - version "4.3.3" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" - integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: ms "2.1.2" @@ -3186,7 +3362,7 @@ dom-serializer@0: domelementtype "^2.0.1" entities "^2.0.0" -dom-serializer@^1.0.1: +dom-serializer@^1.0.1, dom-serializer@^1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.3.2.tgz#6206437d32ceefaec7161803230c7a20bc1b4d91" integrity sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig== @@ -3221,9 +3397,9 @@ domhandler@^2.3.0: domelementtype "1" domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.0.tgz#16c658c626cf966967e306f966b431f77d4a5626" - integrity sha512-fC0aXNQXqKSFTr2wDNZDhsEYjCiYsDWl3D01kwt25hm1YIPyDGHvvi3rw+PLqHAl/m71MaiF7d5zvBr0p5UB2g== + version "4.3.1" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c" + integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ== dependencies: domelementtype "^2.2.0" @@ -3243,7 +3419,7 @@ domutils@^1.5.1: dom-serializer "0" domelementtype "1" -domutils@^2.5.2, domutils@^2.8.0: +domutils@^2.5.2, domutils@^2.7.0, domutils@^2.8.0: version "2.8.0" resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== @@ -3272,26 +3448,36 @@ duplexer3@^0.1.4: resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= -duplexer@^0.1.1, duplexer@^0.1.2: +duplexer@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= -electron-to-chromium@^1.4.17: - version "1.4.25" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.25.tgz#ce95e6678f8c6893ae892c7e95a5000e83f1957f" - integrity sha512-bTwub9Y/76EiNmfaiJih+hAy6xn7Ns95S4KvI2NuKNOz8TEEKKQUu44xuy0PYMudjM9zdjKRS1bitsUvHTfuUg== +electron-to-chromium@^1.4.84: + version "1.4.93" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.93.tgz#2e87ac28721cb31d472ec2bd04f7daf9f2e13de2" + integrity sha512-ywq9Pc5Gwwpv7NG767CtoU8xF3aAUQJjH9//Wy3MBCg4w5JSLbJUq2L8IsCdzPMjvSgxuue9WcVaTOyyxCL0aQ== emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + emojis-list@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" @@ -3314,10 +3500,10 @@ end-of-stream@^1.1.0: dependencies: once "^1.4.0" -enhanced-resolve@^5.8.3: - version "5.8.3" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.8.3.tgz#6d552d465cce0423f5b3d718511ea53826a7b2f0" - integrity sha512-EGAbGvH7j7Xt2nc0E7D99La1OiEs8LnyimkRgwExpUMScN6O+3x9tIWs7PLQZVNx4YD+00skHXPXi1yQHpAmZA== +enhanced-resolve@^5.9.2: + version "5.9.2" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.9.2.tgz#0224dcd6a43389ebfb2d55efee517e5466772dd9" + integrity sha512-GIm3fQfwLJ8YZx2smuHpBKkXC1yOk+OBEmKckVyL0i/ea8mqDEykK3ld5dgH1QYPNyT/lIllxV2LULnxCHaHkA== dependencies: graceful-fs "^4.2.4" tapable "^2.2.0" @@ -3369,11 +3555,6 @@ escape-string-regexp@^1.0.5: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= -escape-string-regexp@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" - integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== - escape-string-regexp@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" @@ -3424,11 +3605,12 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= -eval@^0.1.4: - version "0.1.6" - resolved "https://registry.yarnpkg.com/eval/-/eval-0.1.6.tgz#9620d7d8c85515e97e6b47c5814f46ae381cb3cc" - integrity sha512-o0XUw+5OGkXw4pJZzQoXUk+H87DHuC+7ZE//oSrRGtatTmr12oTnLfg6QOq9DyTt0c/p4TwzgmkKrBzWTSizyQ== +eval@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/eval/-/eval-0.1.8.tgz#2b903473b8cc1d1989b83a1e7923f883eb357f85" + integrity sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw== dependencies: + "@types/node" "*" require-like ">= 0.1.1" eventemitter3@^4.0.0: @@ -3457,16 +3639,16 @@ execa@^5.0.0: strip-final-newline "^2.0.0" express@^4.17.1: - version "4.17.2" - resolved "https://registry.yarnpkg.com/express/-/express-4.17.2.tgz#c18369f265297319beed4e5558753cc8c1364cb3" - integrity sha512-oxlxJxcQlYwqPWKVJJtvQiwHgosH/LrLSPA+H4UxpyvSS6jC5aH+5MoHFM+KABgTOt0APue4w66Ha8jCUo9QGg== + version "4.17.3" + resolved "https://registry.yarnpkg.com/express/-/express-4.17.3.tgz#f6c7302194a4fb54271b73a1fe7a06478c8f85a1" + integrity sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg== dependencies: - accepts "~1.3.7" + accepts "~1.3.8" array-flatten "1.1.1" - body-parser "1.19.1" + body-parser "1.19.2" content-disposition "0.5.4" content-type "~1.0.4" - cookie "0.4.1" + cookie "0.4.2" cookie-signature "1.0.6" debug "2.6.9" depd "~1.1.2" @@ -3481,7 +3663,7 @@ express@^4.17.1: parseurl "~1.3.3" path-to-regexp "0.1.7" proxy-addr "~2.0.7" - qs "6.9.6" + qs "6.9.7" range-parser "~1.2.1" safe-buffer "5.2.1" send "0.17.2" @@ -3509,10 +3691,10 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-glob@^3.1.1, fast-glob@^3.2.7: - version "3.2.7" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1" - integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q== +fast-glob@^3.2.7, fast-glob@^3.2.9: + version "3.2.11" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" + integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" @@ -3559,11 +3741,11 @@ fbjs-css-vars@^1.0.0: integrity sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ== fbjs@^3.0.0, fbjs@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-3.0.2.tgz#dfae08a85c66a58372993ce2caf30863f569ff94" - integrity sha512-qv+boqYndjElAJHNN3NoM8XuwQZ1j2m3kEvTgdle8IDjr6oUbkEpvABWtj/rQl3vq4ew7dnElBxL4YJAwTVqQQ== + version "3.0.4" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-3.0.4.tgz#e1871c6bd3083bac71ff2da868ad5067d37716c6" + integrity sha512-ucV0tDODnGV3JCnnkmoszb5lf4bNpzjv80K41wd4k798Etq+UYD0y0TIfalLjZoKgjive6/adkRnszwapiDgBQ== dependencies: - cross-fetch "^3.0.4" + cross-fetch "^3.1.5" fbjs-css-vars "^1.0.0" loose-envify "^1.0.0" object-assign "^4.1.0" @@ -3586,10 +3768,10 @@ file-loader@^6.2.0: loader-utils "^2.0.0" schema-utils "^3.0.0" -filesize@^6.1.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/filesize/-/filesize-6.4.0.tgz#914f50471dd66fdca3cefe628bd0cde4ef769bcd" - integrity sha512-mjFIpOHC4jbfcTfoh4rkWpI31mF7viw9ikj/JyLoKzqlwG/YsefKfvYlYhdYdg/9mtK2z1AzgN/0LvVQ3zdlSQ== +filesize@^8.0.6: + version "8.0.7" + resolved "https://registry.yarnpkg.com/filesize/-/filesize-8.0.7.tgz#695e70d80f4e47012c132d57a059e80c6b580bd8" + integrity sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ== fill-range@^7.0.1: version "7.0.1" @@ -3627,7 +3809,7 @@ find-up@^3.0.0: dependencies: locate-path "^3.0.0" -find-up@^4.0.0, find-up@^4.1.0: +find-up@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== @@ -3651,12 +3833,12 @@ flux@^4.0.1: fbemitter "^3.0.0" fbjs "^3.0.1" -follow-redirects@^1.0.0, follow-redirects@^1.14.0: - version "1.14.6" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.6.tgz#8cfb281bbc035b3c067d6cd975b0f6ade6e855cd" - integrity sha512-fhUl5EwSJbbl8AR+uYL2KQDxLkdSjZGR36xy46AO7cOMTrCMON6Sa28FmAnC2tRTDbd/Uuzz3aJBv7EBN7JH8A== +follow-redirects@^1.0.0, follow-redirects@^1.14.7: + version "1.14.9" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7" + integrity sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w== -fork-ts-checker-webpack-plugin@^6.0.5: +fork-ts-checker-webpack-plugin@^6.5.0: version "6.5.0" resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.0.tgz#0282b335fa495a97e167f69018f566ea7d2a2b5e" integrity sha512-cS178Y+xxtIjEUorcHddKS7yCMlrDPV31mt47blKKRfMd70Kxu5xruAFE2o9sDY6wVC5deuob/u/alD04YYHnw== @@ -3680,20 +3862,20 @@ forwarded@0.2.0: resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== -fraction.js@^4.1.1: - version "4.1.2" - resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.1.2.tgz#13e420a92422b6cf244dff8690ed89401029fbe8" - integrity sha512-o2RiJQ6DZaR/5+Si0qJUIy637QMRudSi9kU/FFzx9EZazrIdnBgpU+3sEWCxAVhH2RtxW2Oz+T4p2o8uOPVcgA== +fraction.js@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950" + integrity sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA== fresh@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= -fs-extra@^10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.0.0.tgz#9ff61b655dde53fb34a82df84bb214ce802e17c1" - integrity sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ== +fs-extra@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.0.1.tgz#27de43b4320e833f6867cc044bfce29fdf0ef3b8" + integrity sha512-NbdoVMZso2Lsrn/QwLXOy6rm0ufY2zEOKCDzJR/0kBsb0E6qed0P3iYK+Ath3BfvXEeu4JhEtXLgILx5psUfag== dependencies: graceful-fs "^4.2.0" jsonfile "^6.0.1" @@ -3831,18 +4013,30 @@ globals@^11.1.0: resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== -globby@^11.0.1, globby@^11.0.2, globby@^11.0.3, globby@^11.0.4: - version "11.0.4" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5" - integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg== +globby@^11.0.1, globby@^11.0.4, globby@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== dependencies: array-union "^2.1.0" dir-glob "^3.0.1" - fast-glob "^3.1.1" - ignore "^5.1.4" - merge2 "^1.3.0" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" slash "^3.0.0" +globby@^12.0.2: + version "12.2.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-12.2.0.tgz#2ab8046b4fba4ff6eede835b29f678f90e3d3c22" + integrity sha512-wiSuFQLZ+urS9x2gGPl1H5drc5twabmm4m2gTR27XDFyjUHJUNsS8o/2aKyIF6IoBaR630atdher0XJ5g6OMmA== + dependencies: + array-union "^3.0.1" + dir-glob "^3.0.1" + fast-glob "^3.2.7" + ignore "^5.1.9" + merge2 "^1.4.1" + slash "^4.0.0" + got@^9.6.0: version "9.6.0" resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" @@ -3860,10 +4054,10 @@ got@^9.6.0: to-readable-stream "^1.0.0" url-parse-lax "^3.0.0" -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6: - version "4.2.8" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" - integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: + version "4.2.9" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" + integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== gray-matter@^4.0.3: version "4.0.3" @@ -3875,14 +4069,6 @@ gray-matter@^4.0.3: section-matter "^1.0.0" strip-bom-string "^1.0.0" -gzip-size@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.1.1.tgz#cb9bee692f87c0612b232840a873904e4c135274" - integrity sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA== - dependencies: - duplexer "^0.1.1" - pify "^4.0.1" - gzip-size@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462" @@ -3906,9 +4092,9 @@ has-flag@^4.0.0: integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== has-symbols@^1.0.1, has-symbols@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" - integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== has-tostringtag@^1.0.0: version "1.0.0" @@ -4057,7 +4243,7 @@ html-entities@^2.3.2: resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.3.2.tgz#760b404685cb1d794e4f4b744332e3b00dcfe488" integrity sha512-c3Ab/url5ksaT0WyleslpBEthOzWhrjQbg75y7XUsfSzi3Dgzt0l8w5e7DylRn15MTlMMD58dTfzddNS2kcAjQ== -html-minifier-terser@^6.0.2: +html-minifier-terser@^6.0.2, html-minifier-terser@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#bfc818934cc07918f6b3669f5774ecdfd48f32ab" integrity sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw== @@ -4080,7 +4266,7 @@ html-void-elements@^1.0.0: resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-1.0.5.tgz#ce9159494e86d95e45795b166c2021c2cfca4483" integrity sha512-uE/TxKuyNIcx44cIWnjr/rfIATDH7ZaOMmstu0CwhFG1Dunhlp4OC6/NMbhiwoq5BpW0ubi303qnEk/PZj614w== -html-webpack-plugin@^5.4.0: +html-webpack-plugin@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz#c3911936f57681c1f9f4d8b68c158cd9dfe52f50" integrity sha512-sy88PC2cRTVxvETRgUHFrL4No3UxvcH8G1NepGhqaTT+GXN2kTamqasot0inS5hXeg1cMbFDt27zzo9p35lZVw== @@ -4145,16 +4331,16 @@ http-errors@~1.6.2: statuses ">= 1.4.0 < 2" http-parser-js@>=0.5.1: - version "0.5.5" - resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.5.tgz#d7c30d5d3c90d865b4a2e870181f9d6f22ac7ac5" - integrity sha512-x+JVEkO2PoM8qqpbPbOL3cqHPwerep7OwzK7Ay+sMQjKzaKCqWvjoXm5tqMP9tXWWTnTzAjIhXg+J99XYuPhPA== + version "0.5.6" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.6.tgz#2e02406ab2df8af8a7abfba62e0da01c62b95afd" + integrity sha512-vDlkRPDJn93swjcjqMSaGSPABbIarsr1TLAui/gLDXzV5VsJNdXNzMYDyNBLQkjWQCJ1uizu8T2oDMhmGt0PRA== http-proxy-middleware@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.1.tgz#7ef3417a479fb7666a571e09966c66a39bd2c15f" - integrity sha512-cfaXRVoZxSed/BmkA7SwBVNI9Kj7HFltaE5rqYOub5kWzWZ+gofV2koVN1j2rMW7pEfSSlCHGJ31xmuyFyfLOg== + version "2.0.4" + resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.4.tgz#03af0f4676d172ae775cb5c33f592f40e1a4e07a" + integrity sha512-m/4FxX17SUvz4lJ5WPXOHDUuCwIqXLfLHs1s0uZ3oYjhoXlx9csYxaOa0ElDEJ+h8Q4iJ1s+lTMbiCa4EXIJqg== dependencies: - "@types/http-proxy" "^1.17.5" + "@types/http-proxy" "^1.17.8" http-proxy "^1.18.1" is-glob "^4.0.1" is-plain-obj "^3.0.0" @@ -4186,17 +4372,24 @@ icss-utils@^5.0.0, icss-utils@^5.1.0: resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== -ignore@^5.1.4: +ignore@^5.1.9, ignore@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== -immer@^9.0.6: - version "9.0.7" - resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.7.tgz#b6156bd7db55db7abc73fd2fdadf4e579a701075" - integrity sha512-KGllzpbamZDvOIxnmJ0jI840g7Oikx58lBPWV0hUh7dtAyZpFqqrBZdKka5GlTwMTZ1Tjc/bKKW4VSFAt6BqMA== +image-size@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/image-size/-/image-size-1.0.1.tgz#86d6cfc2b1d19eab5d2b368d4b9194d9e48541c5" + integrity sha512-VAwkvNSNGClRw9mDHhc5Efax8PLlsOGcUTh0T/LIriC8vPA3U5PdqXWqkz406MoYHMKW8Uf9gWr05T/rYB44kQ== + dependencies: + queue "6.0.2" + +immer@^9.0.7: + version "9.0.12" + resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.12.tgz#2d33ddf3ee1d247deab9d707ca472c8c942a0f20" + integrity sha512-lk7UNmSbAukB5B6dh9fnh5D0bJTOFKxVg2cyJWTYrWRfhLrLMBquONcUs3aFq507hNoIZEDDh8lb8UtOizSMhA== -import-fresh@^3.1.0, import-fresh@^3.2.1, import-fresh@^3.2.2, import-fresh@^3.3.0: +import-fresh@^3.1.0, import-fresh@^3.2.1, import-fresh@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== @@ -4219,10 +4412,10 @@ indent-string@^4.0.0: resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== -infima@0.2.0-alpha.37: - version "0.2.0-alpha.37" - resolved "https://registry.yarnpkg.com/infima/-/infima-0.2.0-alpha.37.tgz#b87ff42d528d6d050098a560f0294fbdd12adb78" - integrity sha512-4GX7Baw+/lwS4PPW/UJNY89tWSvYG1DL6baKVdpK6mC593iRgMssxNtORMTFArLPJ/A/lzsGhRmx+z6MaMxj0Q== +infima@0.2.0-alpha.38: + version "0.2.0-alpha.38" + resolved "https://registry.yarnpkg.com/infima/-/infima-0.2.0-alpha.38.tgz#e41d95c7cd82756549b17df12f613fed4af3d528" + integrity sha512-1WsmqSMI5IqzrUx3goq+miJznHBonbE3aoqZ1AR/i/oHhroxNeSV6Awv5VoVfXBhfTzLSnxkHaRI2qpAMYcCzw== inflight@^1.0.4: version "1.0.6" @@ -4262,6 +4455,13 @@ interpret@^1.0.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== +invariant@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + dependencies: + loose-envify "^1.0.0" + ip@^1.1.0: version "1.1.5" resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" @@ -4322,10 +4522,10 @@ is-ci@^2.0.0: dependencies: ci-info "^2.0.0" -is-core-module@^2.2.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.0.tgz#0321336c3d0925e497fd97f5d95cb114a5ccd548" - integrity sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw== +is-core-module@^2.8.1: + version "2.8.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.1.tgz#f59fdfca701d5879d0a6b100a40aa1560ce27211" + integrity sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA== dependencies: has "^1.0.3" @@ -4466,7 +4666,7 @@ is-word-character@^1.0.0: resolved "https://registry.yarnpkg.com/is-word-character/-/is-word-character-1.0.4.tgz#ce0e73216f98599060592f62ff31354ddbeb0230" integrity sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA== -is-wsl@^2.1.1, is-wsl@^2.2.0: +is-wsl@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== @@ -4498,19 +4698,19 @@ isobject@^3.0.1: resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= -jest-worker@^27.0.2, jest-worker@^27.4.1: - version "27.4.5" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.4.5.tgz#d696e3e46ae0f24cff3fa7195ffba22889262242" - integrity sha512-f2s8kEdy15cv9r7q4KkzGXvlY0JTcmCbMHZBfSQDwW77REr45IDWwd0lksDFeVHH2jJ5pqb90T77XscrjeGzzg== +jest-worker@^27.0.2, jest-worker@^27.4.5: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" + integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== dependencies: "@types/node" "*" merge-stream "^2.0.0" supports-color "^8.0.0" -joi@^17.4.0, joi@^17.4.2: - version "17.5.0" - resolved "https://registry.yarnpkg.com/joi/-/joi-17.5.0.tgz#7e66d0004b5045d971cf416a55fb61d33ac6e011" - integrity sha512-R7hR50COp7StzLnDi4ywOXHrBrgNXuUUfJWIR5lPY5Bm/pOD3jZaTwpluUXVLRWcoWZxkrHBBJ5hLxgnlehbdw== +joi@^17.6.0: + version "17.6.0" + resolved "https://registry.yarnpkg.com/joi/-/joi-17.6.0.tgz#0bb54f2f006c09a96e75ce687957bd04290054b2" + integrity sha512-OX5dG6DTbcr/kbMFj0KGYxuew69HPcAE3K/sZpEV2nP6e/j/C0HV+HNiBPCASxdx5T7DMoa0s8UeHWMnb6n2zw== dependencies: "@hapi/hoek" "^9.0.0" "@hapi/topo" "^5.0.0" @@ -4531,7 +4731,7 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@^4.0.0: +js-yaml@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== @@ -4573,19 +4773,10 @@ json-schema-traverse@^1.0.0: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== -json5@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" - integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== - dependencies: - minimist "^1.2.0" - json5@^2.1.2: - version "2.2.0" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" - integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== - dependencies: - minimist "^1.2.5" + version "2.2.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" + integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== jsonfile@^6.0.1: version "6.1.0" @@ -4631,9 +4822,9 @@ leven@^3.1.0: integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== lilconfig@^2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.4.tgz#f4507d043d7058b380b6a8f5cb7bcd4b34cee082" - integrity sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA== + version "2.0.5" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.5.tgz#19e57fd06ccc3848fd1891655b5a447092225b25" + integrity sha512-xaYmXZtTHPAw5m+xLN8ab9C+3a8YmV3asNSPOATITbtwrfbwaLJj8h66H1WMIpALCkqsIzK3h7oQ+PdX+LQ9Eg== lines-and-columns@^1.1.6: version "1.2.4" @@ -4645,15 +4836,6 @@ loader-runner@^4.2.0: resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.2.0.tgz#d7022380d66d14c5fb1d496b89864ebcfd478384" integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw== -loader-utils@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" - integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== - dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^1.0.1" - loader-utils@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.2.tgz#d6e3b4fb81870721ae4e0868ab11dd638368c129" @@ -4663,6 +4845,11 @@ loader-utils@^2.0.0: emojis-list "^3.0.0" json5 "^2.1.2" +loader-utils@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-3.2.0.tgz#bcecc51a7898bee7473d4bc6b845b23af8304d4f" + integrity sha512-HVl9ZqccQihZ7JM85dco1MvO9G+ONvxoGa9rkhzFsneGLKSUg1gJf9bWzhRhcvm2qChhWpebQhP44qxjKIUCaQ== + locate-path@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" @@ -4806,13 +4993,6 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" -magic-string@^0.25.3: - version "0.25.7" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" - integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA== - dependencies: - sourcemap-codec "^1.4.4" - make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" @@ -4873,10 +5053,10 @@ media-typer@0.3.0: resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= -memfs@^3.1.2, memfs@^3.2.2: - version "3.4.0" - resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.4.0.tgz#8bc12062b973be6b295d4340595736a656f0a257" - integrity sha512-o/RfP0J1d03YwsAxyHxAYs2kyJp55AFkMazlFAZFR2I2IXkxiUTXRabJ6RmNNCQ83LAD2jy52Khj0m3OffpNdA== +memfs@^3.1.2, memfs@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.4.1.tgz#b78092f466a0dce054d63d39275b24c71d3f1305" + integrity sha512-1c9VPVvW5P7I85c35zAdEr1TD5+F11IToIHIlrVIcflfnzPkJa0ZoYEoEdYDP8KgPFoSZ/opDrUsAoZWym3mtw== dependencies: fs-monkey "1.0.3" @@ -4890,7 +5070,7 @@ merge-stream@^2.0.0: resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== -merge2@^1.3.0: +merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== @@ -4900,18 +5080,18 @@ methods@~1.1.2: resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= -micromatch@^4.0.2, micromatch@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" - integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== +micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== dependencies: - braces "^3.0.1" - picomatch "^2.2.3" + braces "^3.0.2" + picomatch "^2.3.1" -mime-db@1.51.0, "mime-db@>= 1.43.0 < 2": - version "1.51.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.51.0.tgz#d9ff62451859b18342d960850dc3cfb77e63fb0c" - integrity sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g== +mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== mime-db@~1.33.0: version "1.33.0" @@ -4925,12 +5105,12 @@ mime-types@2.1.18: dependencies: mime-db "~1.33.0" -mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24: - version "2.1.34" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.34.tgz#5a712f9ec1503511a945803640fafe09d3793c24" - integrity sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A== +mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== dependencies: - mime-db "1.51.0" + mime-db "1.52.0" mime@1.6.0: version "1.6.0" @@ -4955,38 +5135,43 @@ mini-create-react-context@^0.4.0: "@babel/runtime" "^7.12.1" tiny-warning "^1.0.3" -mini-css-extract-plugin@^1.6.0: - version "1.6.2" - resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-1.6.2.tgz#83172b4fd812f8fc4a09d6f6d16f924f53990ca8" - integrity sha512-WhDvO3SjGm40oV5y26GjMJYjd2UMqrLAGKy5YS2/3QKJy2F7jgynuHTir/tgUUOiNQu5saXHdc8reo7YuhhT4Q== +mini-css-extract-plugin@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.6.0.tgz#578aebc7fc14d32c0ad304c2c34f08af44673f5e" + integrity sha512-ndG8nxCEnAemsg4FSgS+yNyHKgkTB4nPKqCOgh65j3/30qqC5RaSQQXMm++Y6sb6E1zRSxPkztj9fqxhS1Eo6w== dependencies: - loader-utils "^2.0.0" - schema-utils "^3.0.0" - webpack-sources "^1.1.0" + schema-utils "^4.0.0" minimalistic-assert@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== -minimatch@3.0.4, minimatch@^3.0.4: +minimatch@3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== dependencies: brace-expansion "^1.1.7" -minimist@^1.2.0, minimist@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" - integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== +minimatch@^3.0.4: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: + version "1.2.6" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" + integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== mkdirp@^0.5.5: - version "0.5.5" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" - integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== + version "0.5.6" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" + integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== dependencies: - minimist "^1.2.5" + minimist "^1.2.6" mrmime@^1.0.0: version "1.0.0" @@ -5021,15 +5206,15 @@ multicast-dns@^6.0.1: dns-packet "^1.3.1" thunky "^1.0.2" -nanoid@^3.1.30: - version "3.1.30" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.30.tgz#63f93cc548d2a113dc5dfbc63bfa09e2b9b64362" - integrity sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ== +nanoid@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35" + integrity sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw== -negotiator@0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" - integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== +negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== neo-async@^2.6.2: version "2.6.2" @@ -5051,20 +5236,22 @@ node-emoji@^1.10.0: dependencies: lodash "^4.17.21" -node-fetch@2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" - integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== +node-fetch@2.6.7: + version "2.6.7" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" + integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== + dependencies: + whatwg-url "^5.0.0" -node-forge@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" - integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA== +node-forge@^1.2.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.0.tgz#37a874ea723855f37db091e6c186e5b67a01d4b2" + integrity sha512-08ARB91bUi6zNKzVmaj3QO7cr397uiDT2nJ63cHjyNtCTWIgvS47j3eT0WfzUwS9+6Z5YshRaoasFkXCKrIYbA== -node-releases@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.1.tgz#3d1d395f204f1f2f29a54358b9fb678765ad2fc5" - integrity sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA== +node-releases@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.2.tgz#7139fe71e2f4f11b47d4d2986aaf8c48699e0c01" + integrity sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg== normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" @@ -5171,15 +5358,7 @@ onetime@^5.1.2: dependencies: mimic-fn "^2.1.0" -open@^7.0.2: - version "7.4.2" - resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" - integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q== - dependencies: - is-docker "^2.0.0" - is-wsl "^2.1.1" - -open@^8.0.9: +open@^8.0.9, open@^8.4.0: version "8.4.0" resolved "https://registry.yarnpkg.com/open/-/open-8.4.0.tgz#345321ae18f8138f82565a910fdc6b39e8c244f8" integrity sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q== @@ -5305,12 +5484,19 @@ parse-numeric-range@^1.3.0: resolved "https://registry.yarnpkg.com/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz#7c63b61190d61e4d53a1197f0c83c47bb670ffa3" integrity sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ== +parse5-htmlparser2-tree-adapter@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6" + integrity sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA== + dependencies: + parse5 "^6.0.1" + parse5@^5.0.0: version "5.1.1" resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178" integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug== -parse5@^6.0.0: +parse5@^6.0.0, parse5@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== @@ -5353,7 +5539,7 @@ path-key@^3.0.0, path-key@^3.1.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== -path-parse@^1.0.6: +path-parse@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== @@ -5380,25 +5566,15 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -picocolors@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-0.2.1.tgz#570670f793646851d1ba135996962abad587859f" - integrity sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA== - picocolors@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== -picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3: - version "2.3.0" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" - integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== - -pify@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" - integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== pkg-dir@^4.1.0: version "4.2.0" @@ -5423,59 +5599,59 @@ portfinder@^1.0.28: debug "^3.1.1" mkdirp "^0.5.5" -postcss-calc@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-8.0.0.tgz#a05b87aacd132740a5db09462a3612453e5df90a" - integrity sha512-5NglwDrcbiy8XXfPM11F3HeC6hoT9W7GUH/Zi5U/p7u3Irv4rHhdDcIZwG0llHXV4ftsBjpfWMXAnXNl4lnt8g== +postcss-calc@^8.2.3: + version "8.2.4" + resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-8.2.4.tgz#77b9c29bfcbe8a07ff6693dc87050828889739a5" + integrity sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q== dependencies: - postcss-selector-parser "^6.0.2" - postcss-value-parser "^4.0.2" + postcss-selector-parser "^6.0.9" + postcss-value-parser "^4.2.0" -postcss-colormin@^5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-5.2.2.tgz#019cd6912bef9e7e0924462c5e4ffae241e2f437" - integrity sha512-tSEe3NpqWARUTidDlF0LntPkdlhXqfDFuA1yslqpvvGAfpZ7oBaw+/QXd935NKm2U9p4PED0HDZlzmMk7fVC6g== +postcss-colormin@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-5.3.0.tgz#3cee9e5ca62b2c27e84fce63affc0cfb5901956a" + integrity sha512-WdDO4gOFG2Z8n4P8TWBpshnL3JpmNmJwdnfP2gbk2qBA8PWwOYcmjmI/t3CmMeL72a7Hkd+x/Mg9O2/0rD54Pg== dependencies: browserslist "^4.16.6" caniuse-api "^3.0.0" colord "^2.9.1" postcss-value-parser "^4.2.0" -postcss-convert-values@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-5.0.2.tgz#879b849dc3677c7d6bc94b6a2c1a3f0808798059" - integrity sha512-KQ04E2yadmfa1LqXm7UIDwW1ftxU/QWZmz6NKnHnUvJ3LEYbbcX6i329f/ig+WnEByHegulocXrECaZGLpL8Zg== +postcss-convert-values@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-5.1.0.tgz#f8d3abe40b4ce4b1470702a0706343eac17e7c10" + integrity sha512-GkyPbZEYJiWtQB0KZ0X6qusqFHUepguBCNFi9t5JJc7I2OTXG7C0twbTLvCfaKOLl3rSXmpAwV7W5txd91V84g== dependencies: - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-discard-comments@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-5.0.1.tgz#9eae4b747cf760d31f2447c27f0619d5718901fe" - integrity sha512-lgZBPTDvWrbAYY1v5GYEv8fEO/WhKOu/hmZqmCYfrpD6eyDWWzAOsl2rF29lpvziKO02Gc5GJQtlpkTmakwOWg== +postcss-discard-comments@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-5.1.1.tgz#e90019e1a0e5b99de05f63516ce640bd0df3d369" + integrity sha512-5JscyFmvkUxz/5/+TB3QTTT9Gi9jHkcn8dcmmuN68JQcv3aQg4y88yEHHhwFB52l/NkaJ43O0dbksGMAo49nfQ== -postcss-discard-duplicates@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-5.0.1.tgz#68f7cc6458fe6bab2e46c9f55ae52869f680e66d" - integrity sha512-svx747PWHKOGpAXXQkCc4k/DsWo+6bc5LsVrAsw+OU+Ibi7klFZCyX54gjYzX4TH+f2uzXjRviLARxkMurA2bA== +postcss-discard-duplicates@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz#9eb4fe8456706a4eebd6d3b7b777d07bad03e848" + integrity sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw== -postcss-discard-empty@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-5.0.1.tgz#ee136c39e27d5d2ed4da0ee5ed02bc8a9f8bf6d8" - integrity sha512-vfU8CxAQ6YpMxV2SvMcMIyF2LX1ZzWpy0lqHDsOdaKKLQVQGVP1pzhrI9JlsO65s66uQTfkQBKBD/A5gp9STFw== +postcss-discard-empty@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz#e57762343ff7f503fe53fca553d18d7f0c369c6c" + integrity sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A== -postcss-discard-overridden@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-5.0.1.tgz#454b41f707300b98109a75005ca4ab0ff2743ac6" - integrity sha512-Y28H7y93L2BpJhrdUR2SR2fnSsT+3TVx1NmVQLbcnZWwIUpJ7mfcTC6Za9M2PG6w8j7UQRfzxqn8jU2VwFxo3Q== +postcss-discard-overridden@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz#7e8c5b53325747e9d90131bb88635282fb4a276e" + integrity sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw== -postcss-discard-unused@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-unused/-/postcss-discard-unused-5.0.1.tgz#63e35a74a154912f93d4e75a1e6ff3cc146f934b" - integrity sha512-tD6xR/xyZTwfhKYRw0ylfCY8wbfhrjpKAMnDKRTLMy2fNW5hl0hoV6ap5vo2JdCkuHkP3CHw72beO4Y8pzFdww== +postcss-discard-unused@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-discard-unused/-/postcss-discard-unused-5.1.0.tgz#8974e9b143d887677304e558c1166d3762501142" + integrity sha512-KwLWymI9hbwXmJa0dkrzpRbSJEh0vVUd7r8t0yOGPcfKzyJJxFM8kLyC5Ev9avji6nY95pOp1W6HqIrfT+0VGw== dependencies: postcss-selector-parser "^6.0.5" -postcss-loader@^6.1.1: +postcss-loader@^6.2.1: version "6.2.1" resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-6.2.1.tgz#0895f7346b1702103d30fdc66e4d494a93c008ef" integrity sha512-WbbYpmAaKcux/P66bZ40bpWsBucjx/TTgVVzRZ9yUO8yQfVBlameJ0ZGVaPfH64hNSBh63a+ICP5nqOpBA0w+Q== @@ -5484,64 +5660,62 @@ postcss-loader@^6.1.1: klona "^2.0.5" semver "^7.3.5" -postcss-merge-idents@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-merge-idents/-/postcss-merge-idents-5.0.1.tgz#6b5856fc28f2571f28ecce49effb9b0e64be9437" - integrity sha512-xu8ueVU0RszbI2gKkxR6mluupsOSSLvt8q4gA2fcKFkA+x6SlH3cb4cFHpDvcRCNFbUmCR/VUub+Y6zPOjPx+Q== +postcss-merge-idents@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-merge-idents/-/postcss-merge-idents-5.1.1.tgz#7753817c2e0b75d0853b56f78a89771e15ca04a1" + integrity sha512-pCijL1TREiCoog5nQp7wUe+TUonA2tC2sQ54UGeMmryK3UFGIYKqDyjnqd6RcuI4znFn9hWSLNN8xKE/vWcUQw== dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" + cssnano-utils "^3.1.0" + postcss-value-parser "^4.2.0" -postcss-merge-longhand@^5.0.4: - version "5.0.4" - resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-5.0.4.tgz#41f4f3270282ea1a145ece078b7679f0cef21c32" - integrity sha512-2lZrOVD+d81aoYkZDpWu6+3dTAAGkCKbV5DoRhnIR7KOULVrI/R7bcMjhrH9KTRy6iiHKqmtG+n/MMj1WmqHFw== +postcss-merge-longhand@^5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-5.1.3.tgz#a49e2be6237316e3b55e329e0a8da15d1f9f47ab" + integrity sha512-lX8GPGvZ0iGP/IboM7HXH5JwkXvXod1Rr8H8ixwiA372hArk0zP4ZcCy4z4Prg/bfNlbbTf0KCOjCF9kKnpP/w== dependencies: - postcss-value-parser "^4.1.0" - stylehacks "^5.0.1" + postcss-value-parser "^4.2.0" + stylehacks "^5.1.0" -postcss-merge-rules@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-5.0.3.tgz#b5cae31f53129812a77e3eb1eeee448f8cf1a1db" - integrity sha512-cEKTMEbWazVa5NXd8deLdCnXl+6cYG7m2am+1HzqH0EnTdy8fRysatkaXb2dEnR+fdaDxTvuZ5zoBdv6efF6hg== +postcss-merge-rules@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-5.1.1.tgz#d327b221cd07540bcc8d9ff84446d8b404d00162" + integrity sha512-8wv8q2cXjEuCcgpIB1Xx1pIy8/rhMPIQqYKNzEdyx37m6gpq83mQQdCxgIkFgliyEnKvdwJf/C61vN4tQDq4Ww== dependencies: browserslist "^4.16.6" caniuse-api "^3.0.0" - cssnano-utils "^2.0.1" + cssnano-utils "^3.1.0" postcss-selector-parser "^6.0.5" -postcss-minify-font-values@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-5.0.1.tgz#a90cefbfdaa075bd3dbaa1b33588bb4dc268addf" - integrity sha512-7JS4qIsnqaxk+FXY1E8dHBDmraYFWmuL6cgt0T1SWGRO5bzJf8sUoelwa4P88LEWJZweHevAiDKxHlofuvtIoA== +postcss-minify-font-values@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz#f1df0014a726083d260d3bd85d7385fb89d1f01b" + integrity sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA== dependencies: - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-minify-gradients@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-5.0.3.tgz#f970a11cc71e08e9095e78ec3a6b34b91c19550e" - integrity sha512-Z91Ol22nB6XJW+5oe31+YxRsYooxOdFKcbOqY/V8Fxse1Y3vqlNRpi1cxCqoACZTQEhl+xvt4hsbWiV5R+XI9Q== +postcss-minify-gradients@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz#f1fe1b4f498134a5068240c2f25d46fcd236ba2c" + integrity sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw== dependencies: colord "^2.9.1" - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" + cssnano-utils "^3.1.0" + postcss-value-parser "^4.2.0" -postcss-minify-params@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-5.0.2.tgz#1b644da903473fbbb18fbe07b8e239883684b85c" - integrity sha512-qJAPuBzxO1yhLad7h2Dzk/F7n1vPyfHfCCh5grjGfjhi1ttCnq4ZXGIW77GSrEbh9Hus9Lc/e/+tB4vh3/GpDg== +postcss-minify-params@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-5.1.2.tgz#77e250780c64198289c954884ebe3ee4481c3b1c" + integrity sha512-aEP+p71S/urY48HWaRHasyx4WHQJyOYaKpQ6eXl8k0kxg66Wt/30VR6/woh8THgcpRbonJD5IeD+CzNhPi1L8g== dependencies: - alphanum-sort "^1.0.2" browserslist "^4.16.6" - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" + cssnano-utils "^3.1.0" + postcss-value-parser "^4.2.0" -postcss-minify-selectors@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-5.1.0.tgz#4385c845d3979ff160291774523ffa54eafd5a54" - integrity sha512-NzGBXDa7aPsAcijXZeagnJBKBPMYLaJJzB8CQh6ncvyl2sIndLVWfbcDi0SBjRWk5VqEjXvf8tYwzoKf4Z07og== +postcss-minify-selectors@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-5.2.0.tgz#17c2be233e12b28ffa8a421a02fc8b839825536c" + integrity sha512-vYxvHkW+iULstA+ctVNx0VoRAR4THQQRkG77o0oa4/mBS0OzGvvzLIvHDv/nNEM0crzN2WIyFU5X7wZhaUK3RA== dependencies: - alphanum-sort "^1.0.2" postcss-selector-parser "^6.0.5" postcss-modules-extract-imports@^3.0.0: @@ -5572,160 +5746,147 @@ postcss-modules-values@^4.0.0: dependencies: icss-utils "^5.0.0" -postcss-normalize-charset@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-5.0.1.tgz#121559d1bebc55ac8d24af37f67bd4da9efd91d0" - integrity sha512-6J40l6LNYnBdPSk+BHZ8SF+HAkS4q2twe5jnocgd+xWpz/mx/5Sa32m3W1AA8uE8XaXN+eg8trIlfu8V9x61eg== +postcss-normalize-charset@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz#9302de0b29094b52c259e9b2cf8dc0879879f0ed" + integrity sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg== -postcss-normalize-display-values@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-5.0.1.tgz#62650b965981a955dffee83363453db82f6ad1fd" - integrity sha512-uupdvWk88kLDXi5HEyI9IaAJTE3/Djbcrqq8YgjvAVuzgVuqIk3SuJWUisT2gaJbZm1H9g5k2w1xXilM3x8DjQ== +postcss-normalize-display-values@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz#72abbae58081960e9edd7200fcf21ab8325c3da8" + integrity sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA== dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-normalize-positions@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-5.0.1.tgz#868f6af1795fdfa86fbbe960dceb47e5f9492fe5" - integrity sha512-rvzWAJai5xej9yWqlCb1OWLd9JjW2Ex2BCPzUJrbaXmtKtgfL8dBMOOMTX6TnvQMtjk3ei1Lswcs78qKO1Skrg== +postcss-normalize-positions@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-5.1.0.tgz#902a7cb97cf0b9e8b1b654d4a43d451e48966458" + integrity sha512-8gmItgA4H5xiUxgN/3TVvXRoJxkAWLW6f/KKhdsH03atg0cB8ilXnrB5PpSshwVu/dD2ZsRFQcR1OEmSBDAgcQ== dependencies: - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-normalize-repeat-style@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.0.1.tgz#cbc0de1383b57f5bb61ddd6a84653b5e8665b2b5" - integrity sha512-syZ2itq0HTQjj4QtXZOeefomckiV5TaUO6ReIEabCh3wgDs4Mr01pkif0MeVwKyU/LHEkPJnpwFKRxqWA/7O3w== +postcss-normalize-repeat-style@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.0.tgz#f6d6fd5a54f51a741cc84a37f7459e60ef7a6398" + integrity sha512-IR3uBjc+7mcWGL6CtniKNQ4Rr5fTxwkaDHwMBDGGs1x9IVRkYIT/M4NelZWkAOBdV6v3Z9S46zqaKGlyzHSchw== dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-normalize-string@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-5.0.1.tgz#d9eafaa4df78c7a3b973ae346ef0e47c554985b0" - integrity sha512-Ic8GaQ3jPMVl1OEn2U//2pm93AXUcF3wz+OriskdZ1AOuYV25OdgS7w9Xu2LO5cGyhHCgn8dMXh9bO7vi3i9pA== +postcss-normalize-string@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz#411961169e07308c82c1f8c55f3e8a337757e228" + integrity sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w== dependencies: - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-normalize-timing-functions@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.0.1.tgz#8ee41103b9130429c6cbba736932b75c5e2cb08c" - integrity sha512-cPcBdVN5OsWCNEo5hiXfLUnXfTGtSFiBU9SK8k7ii8UD7OLuznzgNRYkLZow11BkQiiqMcgPyh4ZqXEEUrtQ1Q== +postcss-normalize-timing-functions@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz#d5614410f8f0b2388e9f240aa6011ba6f52dafbb" + integrity sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg== dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-normalize-unicode@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-5.0.1.tgz#82d672d648a411814aa5bf3ae565379ccd9f5e37" - integrity sha512-kAtYD6V3pK0beqrU90gpCQB7g6AOfP/2KIPCVBKJM2EheVsBQmx/Iof+9zR9NFKLAx4Pr9mDhogB27pmn354nA== +postcss-normalize-unicode@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.0.tgz#3d23aede35e160089a285e27bf715de11dc9db75" + integrity sha512-J6M3MizAAZ2dOdSjy2caayJLQT8E8K9XjLce8AUQMwOrCvjCHv24aLC/Lps1R1ylOfol5VIDMaM/Lo9NGlk1SQ== dependencies: - browserslist "^4.16.0" - postcss-value-parser "^4.1.0" + browserslist "^4.16.6" + postcss-value-parser "^4.2.0" -postcss-normalize-url@^5.0.4: - version "5.0.4" - resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-5.0.4.tgz#3b0322c425e31dd275174d0d5db0e466f50810fb" - integrity sha512-cNj3RzK2pgQQyNp7dzq0dqpUpQ/wYtdDZM3DepPmFjCmYIfceuD9VIAcOdvrNetjIU65g1B4uwdP/Krf6AFdXg== +postcss-normalize-url@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz#ed9d88ca82e21abef99f743457d3729a042adcdc" + integrity sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew== dependencies: normalize-url "^6.0.1" postcss-value-parser "^4.2.0" -postcss-normalize-whitespace@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.0.1.tgz#b0b40b5bcac83585ff07ead2daf2dcfbeeef8e9a" - integrity sha512-iPklmI5SBnRvwceb/XH568yyzK0qRVuAG+a1HFUsFRf11lEJTiQQa03a4RSCQvLKdcpX7XsI1Gen9LuLoqwiqA== +postcss-normalize-whitespace@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz#08a1a0d1ffa17a7cc6efe1e6c9da969cc4493cfa" + integrity sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA== dependencies: - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-ordered-values@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-5.0.2.tgz#1f351426977be00e0f765b3164ad753dac8ed044" - integrity sha512-8AFYDSOYWebJYLyJi3fyjl6CqMEG/UVworjiyK1r573I56kb3e879sCJLGvR3merj+fAdPpVplXKQZv+ey6CgQ== +postcss-ordered-values@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-5.1.1.tgz#0b41b610ba02906a3341e92cab01ff8ebc598adb" + integrity sha512-7lxgXF0NaoMIgyihL/2boNAEZKiW0+HkMhdKMTD93CjW8TdCy2hSdj8lsAo+uwm7EDG16Da2Jdmtqpedl0cMfw== dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" + cssnano-utils "^3.1.0" + postcss-value-parser "^4.2.0" -postcss-reduce-idents@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-reduce-idents/-/postcss-reduce-idents-5.0.1.tgz#99b49ce8ee6f9c179447671cc9693e198e877bb7" - integrity sha512-6Rw8iIVFbqtaZExgWK1rpVgP7DPFRPh0DDFZxJ/ADNqPiH10sPCoq5tgo6kLiTyfh9sxjKYjXdc8udLEcPOezg== +postcss-reduce-idents@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/postcss-reduce-idents/-/postcss-reduce-idents-5.2.0.tgz#c89c11336c432ac4b28792f24778859a67dfba95" + integrity sha512-BTrLjICoSB6gxbc58D5mdBK8OhXRDqud/zodYfdSi52qvDHdMwk+9kB9xsM8yJThH/sZU5A6QVSmMmaN001gIg== dependencies: - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-reduce-initial@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-5.0.2.tgz#fa424ce8aa88a89bc0b6d0f94871b24abe94c048" - integrity sha512-v/kbAAQ+S1V5v9TJvbGkV98V2ERPdU6XvMcKMjqAlYiJ2NtsHGlKYLPjWWcXlaTKNxooId7BGxeraK8qXvzKtw== +postcss-reduce-initial@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-5.1.0.tgz#fc31659ea6e85c492fb2a7b545370c215822c5d6" + integrity sha512-5OgTUviz0aeH6MtBjHfbr57tml13PuedK/Ecg8szzd4XRMbYxH4572JFG067z+FqBIf6Zp/d+0581glkvvWMFw== dependencies: browserslist "^4.16.6" caniuse-api "^3.0.0" -postcss-reduce-transforms@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-5.0.1.tgz#93c12f6a159474aa711d5269923e2383cedcf640" - integrity sha512-a//FjoPeFkRuAguPscTVmRQUODP+f3ke2HqFNgGPwdYnpeC29RZdCBvGRGTsKpMURb/I3p6jdKoBQ2zI+9Q7kA== +postcss-reduce-transforms@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz#333b70e7758b802f3dd0ddfe98bb1ccfef96b6e9" + integrity sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ== dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5: - version "6.0.7" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.7.tgz#48404830a635113a71fd79397de8209ed05a66fc" - integrity sha512-U+b/Deoi4I/UmE6KOVPpnhS7I7AYdKbhGcat+qTQ27gycvaACvNEw11ba6RrkwVmDVRW7sigWgLj4/KbbJjeDA== +postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5, postcss-selector-parser@^6.0.9: + version "6.0.9" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.9.tgz#ee71c3b9ff63d9cd130838876c13a2ec1a992b2f" + integrity sha512-UO3SgnZOVTwu4kyLR22UQ1xZh086RyNZppb7lLAKBFK8a32ttG5i87Y/P3+2bRSjZNyJ1B7hfFNo273tKe9YxQ== dependencies: cssesc "^3.0.0" util-deprecate "^1.0.2" -postcss-sort-media-queries@^4.1.0: +postcss-sort-media-queries@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/postcss-sort-media-queries/-/postcss-sort-media-queries-4.2.1.tgz#a99bae69ef1098ee3b64a5fa94d258ec240d0355" integrity sha512-9VYekQalFZ3sdgcTjXMa0dDjsfBVHXlraYJEMiOJ/2iMmI2JGCMavP16z3kWOaRu8NSaJCTgVpB/IVpH5yT9YQ== dependencies: sort-css-media-queries "2.0.4" -postcss-svgo@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-5.0.3.tgz#d945185756e5dfaae07f9edb0d3cae7ff79f9b30" - integrity sha512-41XZUA1wNDAZrQ3XgWREL/M2zSw8LJPvb5ZWivljBsUQAGoEKMYm6okHsTjJxKYI4M75RQEH4KYlEM52VwdXVA== +postcss-svgo@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-5.1.0.tgz#0a317400ced789f233a28826e77523f15857d80d" + integrity sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA== dependencies: - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" svgo "^2.7.0" -postcss-unique-selectors@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-5.0.2.tgz#5d6893daf534ae52626708e0d62250890108c0c1" - integrity sha512-w3zBVlrtZm7loQWRPVC0yjUwwpty7OM6DnEHkxcSQXO1bMS3RJ+JUS5LFMSDZHJcvGsRwhZinCWVqn8Kej4EDA== +postcss-unique-selectors@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz#a9f273d1eacd09e9aa6088f4b0507b18b1b541b6" + integrity sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA== dependencies: - alphanum-sort "^1.0.2" postcss-selector-parser "^6.0.5" -postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: +postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss-zindex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-zindex/-/postcss-zindex-5.0.1.tgz#c585724beb69d356af8c7e68847b28d6298ece03" - integrity sha512-nwgtJJys+XmmSGoYCcgkf/VczP8Mp/0OfSv3v0+fw0uABY4yxw+eFs0Xp9nAZHIKnS5j+e9ywQ+RD+ONyvl5pA== - -"postcss@5 - 7": - version "7.0.39" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.39.tgz#9624375d965630e2e1f2c02a935c82a59cb48309" - integrity sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA== - dependencies: - picocolors "^0.2.1" - source-map "^0.6.1" +postcss-zindex@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-zindex/-/postcss-zindex-5.1.0.tgz#4a5c7e5ff1050bd4c01d95b1847dfdcc58a496ff" + integrity sha512-fgFMf0OtVSBR1va1JNHYgMxYk73yhn/qb4uQDq1DLGYolz8gHCyr/sesEuGUaYs58E3ZJRcpoGuPVoB7Meiq9A== -postcss@^8.2.15, postcss@^8.3.11, postcss@^8.3.5, postcss@^8.3.7: - version "8.4.5" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.5.tgz#bae665764dfd4c6fcc24dc0fdf7e7aa00cc77f95" - integrity sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg== +postcss@^8.3.11, postcss@^8.3.5, postcss@^8.4.12, postcss@^8.4.7: + version "8.4.12" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.12.tgz#1e7de78733b28970fa4743f7da6f3763648b1905" + integrity sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg== dependencies: - nanoid "^3.1.30" + nanoid "^3.3.1" picocolors "^1.0.0" - source-map-js "^1.0.1" + source-map-js "^1.0.2" prepend-http@^2.0.0: version "2.0.0" @@ -5745,15 +5906,15 @@ pretty-time@^1.1.0: resolved "https://registry.yarnpkg.com/pretty-time/-/pretty-time-1.1.0.tgz#ffb7429afabb8535c346a34e41873adf3d74dd0e" integrity sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA== -prism-react-renderer@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-1.2.1.tgz#392460acf63540960e5e3caa699d851264e99b89" - integrity sha512-w23ch4f75V1Tnz8DajsYKvY5lF7H1+WvzvLUcF0paFxkTHSp42RS0H5CttdN2Q8RR3DRGZ9v5xD/h3n8C8kGmg== +prism-react-renderer@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-1.3.1.tgz#88fc9d0df6bed06ca2b9097421349f8c2f24e30d" + integrity sha512-xUeDMEz074d0zc5y6rxiMp/dlC7C+5IDDlaEUlcBOFE2wddz7hz5PNupb087mPwTt7T9BrFmewObfCBuf/LKwQ== -prismjs@^1.23.0: - version "1.25.0" - resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.25.0.tgz#6f822df1bdad965734b310b315a23315cf999756" - integrity sha512-WCjJHl1KEWbnkQom1+SzftbtXMKQoezOCYs5rECqMN+jP+apI7ftoflyqigqzopSO3hMhTEb0mFClA8lkolgEg== +prismjs@^1.27.0: + version "1.27.0" + resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.27.0.tgz#bb6ee3138a0b438a3653dd4d6ce0cc6510a45057" + integrity sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA== process-nextick-args@~2.0.0: version "2.0.1" @@ -5767,7 +5928,7 @@ promise@^7.1.1: dependencies: asap "~2.0.3" -prompts@^2.4.0, prompts@^2.4.1: +prompts@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== @@ -5776,13 +5937,13 @@ prompts@^2.4.0, prompts@^2.4.1: sisteransi "^1.0.5" prop-types@^15.6.2, prop-types@^15.7.2: - version "15.7.2" - resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" - integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== + version "15.8.1" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" + integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== dependencies: loose-envify "^1.4.0" object-assign "^4.1.1" - react-is "^16.8.1" + react-is "^16.13.1" property-information@^5.0.0, property-information@^5.3.0: version "5.6.0" @@ -5807,11 +5968,6 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" -punycode@1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" - integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= - punycode@^1.3.2: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" @@ -5834,21 +5990,23 @@ pure-color@^1.2.0: resolved "https://registry.yarnpkg.com/pure-color/-/pure-color-1.3.0.tgz#1fe064fb0ac851f0de61320a8bf796836422f33e" integrity sha1-H+Bk+wrIUfDeYTIKi/eWg2Qi8z4= -qs@6.9.6: - version "6.9.6" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.6.tgz#26ed3c8243a431b2924aca84cc90471f35d5a0ee" - integrity sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ== - -querystring@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" - integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= +qs@6.9.7: + version "6.9.7" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.7.tgz#4610846871485e1e048f44ae3b94033f0e675afe" + integrity sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw== queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== +queue@6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/queue/-/queue-6.0.2.tgz#b91525283e2315c7553d2efa18d83e76432fed65" + integrity sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA== + dependencies: + inherits "~2.0.3" + randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" @@ -5866,12 +6024,12 @@ range-parser@^1.2.1, range-parser@~1.2.1: resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== -raw-body@2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.2.tgz#baf3e9c21eebced59dd6533ac872b71f7b61cb32" - integrity sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ== +raw-body@2.4.3: + version "2.4.3" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.3.tgz#8f80305d11c2a0a545c2d9d89d7a0286fcead43c" + integrity sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g== dependencies: - bytes "3.1.1" + bytes "3.1.2" http-errors "1.8.1" iconv-lite "0.4.24" unpipe "1.0.0" @@ -5896,37 +6054,37 @@ react-base16-styling@^0.6.0: lodash.flow "^3.3.0" pure-color "^1.2.0" -react-dev-utils@12.0.0-next.47: - version "12.0.0-next.47" - resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-12.0.0-next.47.tgz#e55c31a05eb30cfd69ca516e8b87d61724e880fb" - integrity sha512-PsE71vP15TZMmp/RZKOJC4fYD5Pvt0+wCoyG3QHclto0d4FyIJI78xGRICOOThZFROqgXYlZP6ddmeybm+jO4w== +react-dev-utils@^12.0.0: + version "12.0.0" + resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-12.0.0.tgz#4eab12cdb95692a077616770b5988f0adf806526" + integrity sha512-xBQkitdxozPxt1YZ9O1097EJiVpwHr9FoAuEVURCKV0Av8NBERovJauzP7bo1ThvuhZ4shsQ1AJiu4vQpoT1AQ== dependencies: - "@babel/code-frame" "^7.10.4" + "@babel/code-frame" "^7.16.0" address "^1.1.2" - browserslist "^4.16.5" - chalk "^2.4.2" + browserslist "^4.18.1" + chalk "^4.1.2" cross-spawn "^7.0.3" detect-port-alt "^1.1.6" - escape-string-regexp "^2.0.0" - filesize "^6.1.0" - find-up "^4.1.0" - fork-ts-checker-webpack-plugin "^6.0.5" + escape-string-regexp "^4.0.0" + filesize "^8.0.6" + find-up "^5.0.0" + fork-ts-checker-webpack-plugin "^6.5.0" global-modules "^2.0.0" - globby "^11.0.1" - gzip-size "^5.1.1" - immer "^9.0.6" + globby "^11.0.4" + gzip-size "^6.0.0" + immer "^9.0.7" is-root "^2.1.0" - loader-utils "^2.0.0" - open "^7.0.2" + loader-utils "^3.2.0" + open "^8.4.0" pkg-up "^3.1.0" - prompts "^2.4.0" - react-error-overlay "7.0.0-next.54+1465357b" + prompts "^2.4.2" + react-error-overlay "^6.0.10" recursive-readdir "^2.2.2" - shell-quote "^1.7.2" - strip-ansi "^6.0.0" + shell-quote "^1.7.3" + strip-ansi "^6.0.1" text-table "^0.2.0" -react-dom@^17.0.1: +react-dom@^17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA== @@ -5935,32 +6093,28 @@ react-dom@^17.0.1: object-assign "^4.1.1" scheduler "^0.20.2" -react-error-overlay@7.0.0-next.54+1465357b: - version "7.0.0-next.54" - resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-7.0.0-next.54.tgz#c1eb5ab86aee15e9552e6d97897b08f2bd06d140" - integrity sha512-b96CiTnZahXPDNH9MKplvt5+jD+BkxDw7q5R3jnkUXze/ux1pLv32BBZmlj0OfCUeMqyz4sAmF+0ccJGVMlpXw== - -react-error-overlay@^6.0.9: +react-error-overlay@^6.0.10: version "6.0.10" resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.10.tgz#0fe26db4fa85d9dbb8624729580e90e7159a59a6" integrity sha512-mKR90fX7Pm5seCOfz8q9F+66VCc1PGsWSBxKbITjfKVQHMNF2zudxHnMdJiB1fRCb+XsbQV9sO9DCkgsMQgBIA== -react-fast-compare@^3.1.1: +react-fast-compare@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb" integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA== -react-helmet@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/react-helmet/-/react-helmet-6.1.0.tgz#a750d5165cb13cf213e44747502652e794468726" - integrity sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw== +react-helmet-async@*, react-helmet-async@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/react-helmet-async/-/react-helmet-async-1.2.3.tgz#57326a69304ea3293036eafb49475e9ba454cb37" + integrity sha512-mCk2silF53Tq/YaYdkl2sB+/tDoPnaxN7dFS/6ZLJb/rhUY2EWGI5Xj2b4jHppScMqY45MbgPSwTxDchKpZ5Kw== dependencies: - object-assign "^4.1.1" + "@babel/runtime" "^7.12.5" + invariant "^2.2.4" prop-types "^15.7.2" - react-fast-compare "^3.1.1" - react-side-effect "^2.1.0" + react-fast-compare "^3.2.0" + shallowequal "^1.1.0" -react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1: +react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -6023,11 +6177,6 @@ react-router@5.2.1, react-router@^5.2.0: tiny-invariant "^1.0.2" tiny-warning "^1.0.0" -react-side-effect@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-2.1.1.tgz#66c5701c3e7560ab4822a4ee2742dee215d72eb3" - integrity sha512-2FoTQzRNTncBVtnzxFOk2mCpcfxQpenBMbk5kSVBg5UcPqV9fRbgY2zhb7GTWWOlpFmAxhClBDlIq8Rsubz1yQ== - react-textarea-autosize@^8.3.2: version "8.3.3" resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.3.3.tgz#f70913945369da453fd554c168f6baacd1fa04d8" @@ -6037,7 +6186,7 @@ react-textarea-autosize@^8.3.2: use-composed-ref "^1.0.0" use-latest "^1.0.0" -react@^17.0.1: +react@^17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA== @@ -6093,10 +6242,10 @@ recursive-readdir@^2.2.2: dependencies: minimatch "3.0.4" -regenerate-unicode-properties@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-9.0.0.tgz#54d09c7115e1f53dc2314a974b32c1c344efe326" - integrity sha512-3E12UeNSPfjrgwjkR81m5J7Aw/T55Tu7nUyZVQYCKEOs+2dkxEY+DpPtZzO4YruuiPb7NkYLVcyJC4+zCbk5pA== +regenerate-unicode-properties@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz#7f442732aa7934a3740c779bb9b3340dccc1fb56" + integrity sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw== dependencies: regenerate "^1.4.2" @@ -6118,22 +6267,22 @@ regenerator-transform@^0.14.2: "@babel/runtime" "^7.8.4" regexp.prototype.flags@^1.2.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz#7ef352ae8d159e758c0eadca6f8fcb4eef07be26" - integrity sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA== + version "1.4.1" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.1.tgz#b3f4c0059af9e47eca9f3f660e51d81307e72307" + integrity sha512-pMR7hBVUUGI7PMA37m2ofIdQCsomVnas+Jn5UPGAHQ+/LlwKm/aTLJHdasmHRzlfeZwHiAOaRSo2rbBDm3nNUQ== dependencies: call-bind "^1.0.2" define-properties "^1.1.3" -regexpu-core@^4.5.4, regexpu-core@^4.7.1: - version "4.8.0" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.8.0.tgz#e5605ba361b67b1718478501327502f4479a98f0" - integrity sha512-1F6bYsoYiz6is+oz70NWur2Vlh9KWtswuRuzJOfeYUrfPX2o8n74AnUVaOGDbUqVGO9fNHu48/pjJO4sNVwsOg== +regexpu-core@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.0.1.tgz#c531122a7840de743dcf9c83e923b5560323ced3" + integrity sha512-CriEZlrKK9VJw/xQGJpQM5rY88BtuL8DM+AEwvcThHilbxiTAy8vq4iJnd2tqq8wLmjbGZzP7ZcKFjbGkmEFrw== dependencies: regenerate "^1.4.2" - regenerate-unicode-properties "^9.0.0" - regjsgen "^0.5.2" - regjsparser "^0.7.0" + regenerate-unicode-properties "^10.0.1" + regjsgen "^0.6.0" + regjsparser "^0.8.2" unicode-match-property-ecmascript "^2.0.0" unicode-match-property-value-ecmascript "^2.0.0" @@ -6151,15 +6300,15 @@ registry-url@^5.0.0: dependencies: rc "^1.2.8" -regjsgen@^0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.2.tgz#92ff295fb1deecbf6ecdab2543d207e91aa33733" - integrity sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A== +regjsgen@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.6.0.tgz#83414c5354afd7d6627b16af5f10f41c4e71808d" + integrity sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA== -regjsparser@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.7.0.tgz#a6b667b54c885e18b52554cb4960ef71187e9968" - integrity sha512-A4pcaORqmNMDVwUjWoTzuhwMGpP+NykpfqAsEgI1FSH/EzC7lrN5TMd+kN8YCovX+jMpu8eaqXgXPCa0g8FQNQ== +regjsparser@^0.8.2: + version "0.8.4" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.8.4.tgz#8a14285ffcc5de78c5b95d62bbf413b6bc132d5f" + integrity sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA== dependencies: jsesc "~0.5.0" @@ -6200,20 +6349,6 @@ remark-footnotes@2.0.0: resolved "https://registry.yarnpkg.com/remark-footnotes/-/remark-footnotes-2.0.0.tgz#9001c4c2ffebba55695d2dd80ffb8b82f7e6303f" integrity sha512-3Clt8ZMH75Ayjp9q4CorNeyjwIxHFcTkaektplKGl2A1jNGEUey8cKL0ZC5vJwfcD5GFGsNLImLG/NGzWIzoMQ== -remark-mdx-remove-exports@^1.6.22: - version "1.6.22" - resolved "https://registry.yarnpkg.com/remark-mdx-remove-exports/-/remark-mdx-remove-exports-1.6.22.tgz#9e34f3d02c9c54b02ca0a1fde946449338d06ecb" - integrity sha512-7g2uiTmTGfz5QyVb+toeX25frbk1Y6yd03RXGPtqx0+DVh86Gb7MkNYbk7H2X27zdZ3CQv1W/JqlFO0Oo8IxVA== - dependencies: - unist-util-remove "2.0.0" - -remark-mdx-remove-imports@^1.6.22: - version "1.6.22" - resolved "https://registry.yarnpkg.com/remark-mdx-remove-imports/-/remark-mdx-remove-imports-1.6.22.tgz#79f711c95359cff437a120d1fbdc1326ec455826" - integrity sha512-lmjAXD8Ltw0TsvBzb45S+Dxx7LTJAtDaMneMAv8LAUIPEyYoKkmGbmVsiF0/pY6mhM1Q16swCmu1TN+ie/vn/A== - dependencies: - unist-util-remove "2.0.0" - remark-mdx@1.6.22: version "1.6.22" resolved "https://registry.yarnpkg.com/remark-mdx/-/remark-mdx-1.6.22.tgz#06a8dab07dcfdd57f3373af7f86bd0e992108bbd" @@ -6299,12 +6434,13 @@ resolve-pathname@^3.0.0: integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng== resolve@^1.1.6, resolve@^1.14.2, resolve@^1.3.2: - version "1.20.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" - integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== + version "1.22.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" + integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw== dependencies: - is-core-module "^2.2.0" - path-parse "^1.0.6" + is-core-module "^2.8.1" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" responselike@^1.0.2: version "1.0.2" @@ -6335,7 +6471,7 @@ rtl-detect@^1.0.4: resolved "https://registry.yarnpkg.com/rtl-detect/-/rtl-detect-1.0.4.tgz#40ae0ea7302a150b96bc75af7d749607392ecac6" integrity sha512-EBR4I2VDSSYr7PkBmFy04uhycIpDKp+21p/jARYXlCSjQksTBQcJ0HFUPOO79EPPH5JS6VAhiIQbycf0O3JAxQ== -rtlcss@^3.3.0: +rtlcss@^3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/rtlcss/-/rtlcss-3.5.0.tgz#c9eb91269827a102bac7ae3115dd5d049de636c3" integrity sha512-wzgMaMFHQTnyi9YOwsx9LjOxYXJPzS8sYnFaKm6R5ysvTkwzHiB0vxnbHwchHQT65PTdBjDG21/kQBWI7q9O7A== @@ -6352,12 +6488,12 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -rxjs@^7.1.0: - version "7.4.0" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.4.0.tgz#a12a44d7eebf016f5ff2441b87f28c9a51cebc68" - integrity sha512-7SQDi7xeTMCJpqViXh8gL/lebcwlp3d831F05+9B44A4B0WfsEwUQHR64gsH1kvJ+Ep/J9K2+n1hVl1CsGN23w== +rxjs@^7.5.4: + version "7.5.5" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.5.5.tgz#2ebad89af0f560f460ad5cc4213219e1f7dd4e9f" + integrity sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw== dependencies: - tslib "~2.1.0" + tslib "^2.1.0" safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" @@ -6437,12 +6573,12 @@ select-hose@^2.0.0: resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= -selfsigned@^1.10.11: - version "1.10.11" - resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.11.tgz#24929cd906fe0f44b6d01fb23999a739537acbe9" - integrity sha512-aVmbPOfViZqOZPgRBT0+3u4yZFHpmnIghLMlAcb5/xhp5ZtB/RVnKhz5vl2M32CLXAqR4kha9zfhNg0Lf/sxKA== +selfsigned@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.0.0.tgz#e927cd5377cbb0a1075302cff8df1042cc2bce5b" + integrity sha512-cUdFiCbKoa1mZ6osuJs2uDHrs0k0oprsKveFiiaBKCNq3SYyb5gs2HxhQyDNLCmL51ZZThqi4YNDpCK6GOP1iQ== dependencies: - node-forge "^0.10.0" + node-forge "^1.2.0" semver-diff@^3.1.1: version "3.1.1" @@ -6558,6 +6694,11 @@ shallow-clone@^3.0.0: dependencies: kind-of "^6.0.2" +shallowequal@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" + integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== + shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -6570,24 +6711,24 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -shell-quote@^1.7.2: +shell-quote@^1.7.3: version "1.7.3" resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.3.tgz#aa40edac170445b9a431e17bb62c0b881b9c4123" integrity sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw== -shelljs@^0.8.4: - version "0.8.4" - resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.4.tgz#de7684feeb767f8716b326078a8a00875890e3c2" - integrity sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ== +shelljs@^0.8.5: + version "0.8.5" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.5.tgz#de055408d8361bed66c669d2f000538ced8ee20c" + integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow== dependencies: glob "^7.0.0" interpret "^1.0.0" rechoir "^0.6.2" signal-exit@^3.0.2, signal-exit@^3.0.3: - version "3.0.6" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.6.tgz#24e630c4b0f03fea446a2bd299e62b4a6ca8d0af" - integrity sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ== + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== sirv@^1.0.7: version "1.0.19" @@ -6603,12 +6744,12 @@ sisteransi@^1.0.5: resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== -sitemap@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/sitemap/-/sitemap-7.0.0.tgz#022bef4df8cba42e38e1fe77039f234cab0372b6" - integrity sha512-Ud0jrRQO2k7fEtPAM+cQkBKoMvxQyPKNXKDLn8tRVHxRCsdDQ2JZvw+aZ5IRYYQVAV9iGxEar6boTwZzev+x3g== +sitemap@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/sitemap/-/sitemap-7.1.1.tgz#eeed9ad6d95499161a3eadc60f8c6dce4bea2bef" + integrity sha512-mK3aFtjz4VdJN0igpIJrinf3EO8U8mxOPsTBzSsy06UtjZQJ3YY3o3Xa7zSc5nMqcMrRwlChHZ18Kxg0caiPBg== dependencies: - "@types/node" "^15.0.1" + "@types/node" "^17.0.5" "@types/sax" "^1.2.1" arg "^5.0.0" sax "^1.2.4" @@ -6618,6 +6759,11 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== +slash@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7" + integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== + sockjs@^0.3.21: version "0.3.24" resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.24.tgz#c9bc8995f33a111bea0395ec30aa3206bdb5ccce" @@ -6637,10 +6783,10 @@ source-list-map@^2.0.0: resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== -source-map-js@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.1.tgz#a1741c131e3c77d048252adfa24e23b908670caf" - integrity sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA== +source-map-js@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" + integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== source-map-support@~0.5.20: version "0.5.21" @@ -6665,11 +6811,6 @@ source-map@~0.7.2: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== -sourcemap-codec@^1.4.4: - version "1.4.8" - resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" - integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== - space-separated-tokens@^1.0.0: version "1.1.5" resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz#85f32c3d10d9682007e917414ddc5c26d1aa6899" @@ -6723,7 +6864,7 @@ std-env@^3.0.1: resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.0.1.tgz#bc4cbc0e438610197e34c2d79c3df30b491f5182" integrity sha512-mC1Ps9l77/97qeOZc+HrOL7TIaOboHqMZ24dGVQrlxFcpPpfCHpH+qfUT7Dz+6mlG8+JPA1KfBQo19iC/+Ngcw== -string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.2: +string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -6732,6 +6873,15 @@ string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.2: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" +string-width@^5.0.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -6762,7 +6912,7 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1: dependencies: ansi-regex "^5.0.1" -strip-ansi@^7.0.0: +strip-ansi@^7.0.0, strip-ansi@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2" integrity sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw== @@ -6796,12 +6946,12 @@ style-to-object@0.3.0, style-to-object@^0.3.0: dependencies: inline-style-parser "0.1.1" -stylehacks@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-5.0.1.tgz#323ec554198520986806388c7fdaebc38d2c06fb" - integrity sha512-Es0rVnHIqbWzveU1b24kbw92HsebBepxfcqe5iix7t9j0PQqhs0IxXVXv0pY2Bxa08CgMkzD6OWql7kbGOuEdA== +stylehacks@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-5.1.0.tgz#a40066490ca0caca04e96c6b02153ddc39913520" + integrity sha512-SzLmvHQTrIWfSgljkQCw2++C9+Ne91d/6Sp92I8c5uHTcy/PgeHamwITIbBW9wnFTY/3ZfSXR9HIL6Ikqmcu6Q== dependencies: - browserslist "^4.16.0" + browserslist "^4.16.6" postcss-selector-parser "^6.0.4" supports-color@^5.3.0: @@ -6825,6 +6975,11 @@ supports-color@^8.0.0: dependencies: has-flag "^4.0.0" +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + svg-parser@^2.0.2: version "2.0.4" resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5" @@ -6853,22 +7008,23 @@ tapable@^2.0.0, tapable@^2.1.1, tapable@^2.2.0: resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== -terser-webpack-plugin@^5.1.3, terser-webpack-plugin@^5.2.4: - version "5.3.0" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.0.tgz#21641326486ecf91d8054161c816e464435bae9f" - integrity sha512-LPIisi3Ol4chwAaPP8toUJ3L4qCM1G0wao7L3qNv57Drezxj6+VEyySpPw4B1HSO2Eg/hDY/MNF5XihCAoqnsQ== +terser-webpack-plugin@^5.1.3, terser-webpack-plugin@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.1.tgz#0320dcc270ad5372c1e8993fabbd927929773e54" + integrity sha512-GvlZdT6wPQKbDNW/GDQzZFg/j4vKU96yl2q6mcUkzKOgW4gwf1Z8cZToUCrz31XHlPWH8MVb1r2tFtdDtTGJ7g== dependencies: - jest-worker "^27.4.1" + jest-worker "^27.4.5" schema-utils "^3.1.1" serialize-javascript "^6.0.0" source-map "^0.6.1" terser "^5.7.2" terser@^5.10.0, terser@^5.7.2: - version "5.10.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.10.0.tgz#b86390809c0389105eb0a0b62397563096ddafcc" - integrity sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA== + version "5.12.1" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.12.1.tgz#4cf2ebed1f5bceef5c83b9f60104ac4a78b49e9c" + integrity sha512-NXbs+7nisos5E+yXwAD+y7zrcTkMqb0dEJxIGtSKPdCBzopf7ni4odPul2aechpV7EXNvOudYOX2bb5tln1jbQ== dependencies: + acorn "^8.5.0" commander "^2.20.0" source-map "~0.7.2" source-map-support "~0.5.20" @@ -6925,6 +7081,11 @@ totalist@^1.0.0: resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df" integrity sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g== +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= + trim-trailing-lines@^1.0.0: version "1.1.4" resolved "https://registry.yarnpkg.com/trim-trailing-lines/-/trim-trailing-lines-1.1.4.tgz#bd4abbec7cc880462f10b2c8b5ce1d8d1ec7c2c0" @@ -6940,26 +7101,21 @@ trough@^1.0.0: resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406" integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA== -ts-essentials@^2.0.3: - version "2.0.12" - resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-2.0.12.tgz#c9303f3d74f75fa7528c3d49b80e089ab09d8745" - integrity sha512-3IVX4nI6B5cc31/GFFE+i8ey/N2eA0CZDbo6n0yrz0zDX8ZJ8djmU1p+XRz7G3is0F3bB3pu2pAroFdAWQKU3w== - -tslib@^2.0.3, tslib@^2.3.1: +tslib@^2.0.3, tslib@^2.1.0, tslib@^2.2.0, tslib@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== -tslib@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a" - integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A== - type-fest@^0.20.2: version "0.20.2" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== +type-fest@^2.5.0: + version "2.12.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.12.1.tgz#d2be8f50bf5f8f0a5fd916d29bf3e98c17e960be" + integrity sha512-AiknQSEqKVGDDjtZqeKrUoTlcj7FKhupmnVUgz6KoOKtvMwRGE6hUNJ/nVear+h7fnUPO1q/htSkYKb1pyntkQ== + type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" @@ -7068,13 +7224,6 @@ unist-util-remove-position@^2.0.0: dependencies: unist-util-visit "^2.0.0" -unist-util-remove@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unist-util-remove/-/unist-util-remove-2.0.0.tgz#32c2ad5578802f2ca62ab808173d505b2c898488" - integrity sha512-HwwWyNHKkeg/eXRnE11IpzY8JT55JNM1YCwwU9YNCnfzk6s8GhPXrVBBZWiwLeATJbI7euvoGSzcy9M29UeW3g== - dependencies: - unist-util-is "^4.0.0" - unist-util-remove@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/unist-util-remove/-/unist-util-remove-2.1.0.tgz#b0b4738aa7ee445c402fda9328d604a02d010588" @@ -7159,20 +7308,10 @@ url-parse-lax@^3.0.0: dependencies: prepend-http "^2.0.0" -url@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" - integrity sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE= - dependencies: - punycode "1.3.2" - querystring "0.2.0" - use-composed-ref@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/use-composed-ref/-/use-composed-ref-1.1.0.tgz#9220e4e94a97b7b02d7d27eaeab0b37034438bbc" - integrity sha512-my1lNHGWsSDAhhVAT4MKs6IjBUtG6ZG11uUqexPH9PptiIZDQOzaF4f5tEbJ2+7qvNbtXNBbU3SfmN+fXlWDhg== - dependencies: - ts-essentials "^2.0.3" + version "1.2.1" + resolved "https://registry.yarnpkg.com/use-composed-ref/-/use-composed-ref-1.2.1.tgz#9bdcb5ccd894289105da2325e1210079f56bf849" + integrity sha512-6+X1FLlIcjvFMAeAD/hcxDT8tmyrWnbSPMU0EnxQuDLIxokuFzWliXBiYZuGIx+mrAMLBw0WFfCkaPw8ebzAhw== use-isomorphic-layout-effect@^1.0.0: version "1.1.1" @@ -7244,16 +7383,16 @@ vfile@^4.0.0: unist-util-stringify-position "^2.0.0" vfile-message "^2.0.0" -wait-on@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-6.0.0.tgz#7e9bf8e3d7fe2daecbb7a570ac8ca41e9311c7e7" - integrity sha512-tnUJr9p5r+bEYXPUdRseolmz5XqJTTj98JgOsfBn7Oz2dxfE2g3zw1jE+Mo8lopM3j3et/Mq1yW7kKX6qw7RVw== +wait-on@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-6.0.1.tgz#16bbc4d1e4ebdd41c5b4e63a2e16dbd1f4e5601e" + integrity sha512-zht+KASY3usTY5u2LgaNqn/Cd8MukxLGjdcZxT2ns5QzDmTFc4XoWBgC+C/na+sMRZTuVygQoMYwdcVjHnYIVw== dependencies: - axios "^0.21.1" - joi "^17.4.0" + axios "^0.25.0" + joi "^17.6.0" lodash "^4.17.21" minimist "^1.2.5" - rxjs "^7.1.0" + rxjs "^7.5.4" watchpack@^2.3.1: version "2.3.1" @@ -7275,7 +7414,12 @@ web-namespaces@^1.0.0, web-namespaces@^1.1.2: resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-1.1.4.tgz#bc98a3de60dadd7faefc403d1076d529f5e030ec" integrity sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw== -webpack-bundle-analyzer@^4.4.2: +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= + +webpack-bundle-analyzer@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.5.0.tgz#1b0eea2947e73528754a6f9af3e91b2b6e0f79d5" integrity sha512-GUMZlM3SKwS8Z+CKeIFx7CVoHn3dXFcUAjT/dcZQQmfSZGvitPfMob2ipjai7ovFFqPvTqkEZ/leL4O0YOdAYQ== @@ -7290,25 +7434,31 @@ webpack-bundle-analyzer@^4.4.2: sirv "^1.0.7" ws "^7.3.1" -webpack-dev-middleware@^5.2.1: - version "5.3.0" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.0.tgz#8fc02dba6e72e1d373eca361623d84610f27be7c" - integrity sha512-MouJz+rXAm9B1OTOYaJnn6rtD/lWZPy2ufQCH3BPs8Rloh/Du6Jze4p7AeLYHkVi0giJnYLaSGDC7S+GM9arhg== +webpack-dev-middleware@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.1.tgz#aa079a8dedd7e58bfeab358a9af7dab304cee57f" + integrity sha512-81EujCKkyles2wphtdrnPg/QqegC/AtqNH//mQkBYSMqwFVCQrxM6ktB2O/SPlZy7LqeEfTbV3cZARGQz6umhg== dependencies: colorette "^2.0.10" - memfs "^3.2.2" + memfs "^3.4.1" mime-types "^2.1.31" range-parser "^1.2.1" schema-utils "^4.0.0" -webpack-dev-server@^4.5.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.6.0.tgz#e8648601c440172d9b6f248d28db98bed335315a" - integrity sha512-oojcBIKvx3Ya7qs1/AVWHDgmP1Xml8rGsEBnSobxU/UJSX1xP1GPM3MwsAnDzvqcVmVki8tV7lbcsjEjk0PtYg== - dependencies: +webpack-dev-server@^4.7.4: + version "4.7.4" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.7.4.tgz#d0ef7da78224578384e795ac228d8efb63d5f945" + integrity sha512-nfdsb02Zi2qzkNmgtZjkrMOcXnYZ6FLKcQwpxT7MvmHKc+oTtDsBju8j+NMyAygZ9GW1jMEUpy3itHtqgEhe1A== + dependencies: + "@types/bonjour" "^3.5.9" + "@types/connect-history-api-fallback" "^1.3.5" + "@types/express" "^4.17.13" + "@types/serve-index" "^1.9.1" + "@types/sockjs" "^0.3.33" + "@types/ws" "^8.2.2" ansi-html-community "^0.0.8" bonjour "^3.5.0" - chokidar "^3.5.2" + chokidar "^3.5.3" colorette "^2.0.10" compression "^1.7.4" connect-history-api-fallback "^1.6.0" @@ -7323,14 +7473,13 @@ webpack-dev-server@^4.5.0: p-retry "^4.5.0" portfinder "^1.0.28" schema-utils "^4.0.0" - selfsigned "^1.10.11" + selfsigned "^2.0.0" serve-index "^1.9.1" sockjs "^0.3.21" spdy "^4.0.2" strip-ansi "^7.0.0" - url "^0.11.0" - webpack-dev-middleware "^5.2.1" - ws "^8.1.0" + webpack-dev-middleware "^5.3.1" + ws "^8.4.2" webpack-merge@^5.8.0: version "5.8.0" @@ -7340,7 +7489,7 @@ webpack-merge@^5.8.0: clone-deep "^4.0.1" wildcard "^2.0.0" -webpack-sources@^1.1.0, webpack-sources@^1.4.3: +webpack-sources@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== @@ -7348,18 +7497,18 @@ webpack-sources@^1.1.0, webpack-sources@^1.4.3: source-list-map "^2.0.0" source-map "~0.6.1" -webpack-sources@^3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.2.tgz#d88e3741833efec57c4c789b6010db9977545260" - integrity sha512-cp5qdmHnu5T8wRg2G3vZZHoJPN14aqQ89SyQ11NpGH5zEMDCclt49rzo+MaRazk7/UeILhAI+/sEtcM+7Fr0nw== +webpack-sources@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" + integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== -webpack@^5.61.0: - version "5.65.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.65.0.tgz#ed2891d9145ba1f0d318e4ea4f89c3fa18e6f9be" - integrity sha512-Q5or2o6EKs7+oKmJo7LaqZaMOlDWQse9Tm5l1WAfU/ujLGN5Pb0SqGeVkN/4bpPmEqEP5RnVhiqsOtWtUVwGRw== +webpack@^5.70.0: + version "5.70.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.70.0.tgz#3461e6287a72b5e6e2f4872700bc8de0d7500e6d" + integrity sha512-ZMWWy8CeuTTjCxbeaQI21xSswseF2oNOwc70QSKNePvmxE7XW36i7vpBMYZFAUHPwQiEbNGCEYIOOlyRbdGmxw== dependencies: - "@types/eslint-scope" "^3.7.0" - "@types/estree" "^0.0.50" + "@types/eslint-scope" "^3.7.3" + "@types/estree" "^0.0.51" "@webassemblyjs/ast" "1.11.1" "@webassemblyjs/wasm-edit" "1.11.1" "@webassemblyjs/wasm-parser" "1.11.1" @@ -7367,12 +7516,12 @@ webpack@^5.61.0: acorn-import-assertions "^1.7.6" browserslist "^4.14.5" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.8.3" + enhanced-resolve "^5.9.2" es-module-lexer "^0.9.0" eslint-scope "5.1.1" events "^3.2.0" glob-to-regexp "^0.4.1" - graceful-fs "^4.2.4" + graceful-fs "^4.2.9" json-parse-better-errors "^1.0.2" loader-runner "^4.2.0" mime-types "^2.1.27" @@ -7381,9 +7530,9 @@ webpack@^5.61.0: tapable "^2.1.1" terser-webpack-plugin "^5.1.3" watchpack "^2.3.1" - webpack-sources "^3.2.2" + webpack-sources "^3.2.3" -webpackbar@^5.0.0-3: +webpackbar@^5.0.2: version "5.0.2" resolved "https://registry.yarnpkg.com/webpackbar/-/webpackbar-5.0.2.tgz#d3dd466211c73852741dfc842b7556dcbc2b0570" integrity sha512-BmFJo7veBDgQzfWXl/wwYXr/VFus0614qZ8i9znqcl9fnEdiVkdbi0TedLQ6xAK92HZHDJ0QmyQ0fmuZPAgCYQ== @@ -7407,6 +7556,14 @@ websocket-extensions@>=0.1.1: resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0= + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + which@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" @@ -7428,6 +7585,13 @@ widest-line@^3.1.0: dependencies: string-width "^4.0.0" +widest-line@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-4.0.1.tgz#a0fc673aaba1ea6f0a0d35b3c2795c9a9cc2ebf2" + integrity sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig== + dependencies: + string-width "^5.0.1" + wildcard@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" @@ -7442,6 +7606,15 @@ wrap-ansi@^7.0.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.0.1.tgz#2101e861777fec527d0ea90c57c6b03aac56a5b3" + integrity sha512-QFF+ufAqhoYHvoHdajT/Po7KoXVBPXS2bgjIam5isfWJPfIOnQZ50JtUiVvCv/sjgacf3yRrt2ZKUZ/V4itN4g== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" @@ -7458,14 +7631,14 @@ write-file-atomic@^3.0.0: typedarray-to-buffer "^3.1.5" ws@^7.3.1: - version "7.5.6" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.6.tgz#e59fc509fb15ddfb65487ee9765c5a51dec5fe7b" - integrity sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA== - -ws@^8.1.0: - version "8.4.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.4.0.tgz#f05e982a0a88c604080e8581576e2a063802bed6" - integrity sha512-IHVsKe2pjajSUIl4KYMQOdlyliovpEPquKkqbwswulszzI7r0SfQrxnXdWAEqOlDCLrVSJzo+O1hAwdog2sKSQ== + version "7.5.7" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.7.tgz#9e0ac77ee50af70d58326ecff7e85eb3fa375e67" + integrity sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A== + +ws@^8.4.2: + version "8.5.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f" + integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg== xdg-basedir@^4.0.0: version "4.0.0" diff --git a/examples/facebook/.stackblitzrc b/examples/facebook/.stackblitzrc index a8c490e81a63..5490eb1ecc12 100644 --- a/examples/facebook/.stackblitzrc +++ b/examples/facebook/.stackblitzrc @@ -1,4 +1,4 @@ { "installDependencies": true, "startCommand": "npm start" -} \ No newline at end of file +} diff --git a/examples/facebook/docs/intro.md b/examples/facebook/docs/intro.md index 440ad3373eb7..500260230bfc 100644 --- a/examples/facebook/docs/intro.md +++ b/examples/facebook/docs/intro.md @@ -12,24 +12,36 @@ Get started by **creating a new site**. Or **try Docusaurus immediately** with **[docusaurus.new](https://docusaurus.new)**. +### What you'll need + +- [Node.js](https://nodejs.org/en/download/) version 14 or above: + - When installing Node.js, you are recommended to check all checkboxes related to dependencies. + ## Generate a new site -Generate a new Docusaurus site using the **classic template**: +Generate a new Docusaurus site using the **classic template**. + +The classic template will automatically be added to your project after you run the command: ```bash npm init docusaurus@latest my-website classic ``` +You can type this command into Command Prompt, Powershell, Terminal, or any other integrated terminal of your code editor. + +The command also installs all necessary dependencies you need to run Docusaurus. + ## Start your site Run the development server: ```bash cd my-website - -npx docusaurus start +npm run start ``` -Your site starts at `http://localhost:3000`. +The `cd` command changes the directory you're working with. In order to work with your newly created Docusaurus site, you'll need to navigate the terminal there. + +The `npm run start` command builds your website locally and serves it through a development server, ready for you to view at http://localhost:3000/. -Open `docs/intro.md` and edit some lines: the site **reloads automatically** and displays your changes. +Open `docs/intro.md` (this page) and edit some lines: the site **reloads automatically** and displays your changes. diff --git a/examples/facebook/docs/tutorial-basics/create-a-document.md b/examples/facebook/docs/tutorial-basics/create-a-document.md index feaced79d0a3..a9bb9a4140b1 100644 --- a/examples/facebook/docs/tutorial-basics/create-a-document.md +++ b/examples/facebook/docs/tutorial-basics/create-a-document.md @@ -41,14 +41,14 @@ This is my **first Docusaurus document**! It is also possible to create your sidebar explicitly in `sidebars.js`: -```diff title="sidebars.js" +```js title="sidebars.js" module.exports = { tutorialSidebar: [ { type: 'category', label: 'Tutorial', -- items: [...], -+ items: ['hello'], + // highlight-next-line + items: ['hello'], }, ], }; diff --git a/examples/facebook/package.json b/examples/facebook/package.json index 02d0fd20de0a..49d830ef9e63 100644 --- a/examples/facebook/package.json +++ b/examples/facebook/package.json @@ -19,25 +19,25 @@ "dev": "docusaurus start" }, "dependencies": { - "@docusaurus/core": "2.0.0-beta.14", - "@docusaurus/preset-classic": "2.0.0-beta.14", - "@mdx-js/react": "^1.6.21", + "@docusaurus/core": "2.0.0-beta.18", + "@docusaurus/preset-classic": "2.0.0-beta.18", + "@mdx-js/react": "^1.6.22", "clsx": "^1.1.1", - "react": "^17.0.1", - "react-dom": "^17.0.1" + "react": "^17.0.2", + "react-dom": "^17.0.2" }, "devDependencies": { - "@babel/eslint-parser": "^7.16.3", - "eslint": "^8.2.0", - "eslint-config-airbnb": "^19.0.0", - "eslint-config-prettier": "^8.3.0", + "@babel/eslint-parser": "^7.17.0", + "eslint": "^8.11.0", + "eslint-config-airbnb": "^19.0.4", + "eslint-config-prettier": "^8.5.0", "eslint-plugin-header": "^3.1.1", - "eslint-plugin-import": "^2.25.3", + "eslint-plugin-import": "^2.25.4", "eslint-plugin-jsx-a11y": "^6.5.1", - "eslint-plugin-react": "^7.27.0", + "eslint-plugin-react": "^7.29.4", "eslint-plugin-react-hooks": "^4.3.0", - "prettier": "^2.5.1", - "stylelint": "^13.2.1" + "prettier": "^2.6.0", + "stylelint": "^14.6.0" }, "browserslist": { "production": [ diff --git a/examples/facebook/sandbox.config.json b/examples/facebook/sandbox.config.json index 069492640e53..95df40889ad5 100644 --- a/examples/facebook/sandbox.config.json +++ b/examples/facebook/sandbox.config.json @@ -7,4 +7,4 @@ "container": { "node": "14" } -} \ No newline at end of file +} diff --git a/examples/facebook/src/css/custom.css b/examples/facebook/src/css/custom.css index 3fcaec3bc4b4..29e525236964 100644 --- a/examples/facebook/src/css/custom.css +++ b/examples/facebook/src/css/custom.css @@ -15,16 +15,27 @@ /* You can override the default Infima variables here. */ :root { - --ifm-color-primary: #25c2a0; - --ifm-color-primary-dark: rgb(33, 175, 144); - --ifm-color-primary-darker: rgb(31, 165, 136); - --ifm-color-primary-darkest: rgb(26, 136, 112); - --ifm-color-primary-light: rgb(70, 203, 174); - --ifm-color-primary-lighter: rgb(102, 212, 189); - --ifm-color-primary-lightest: rgb(146, 224, 208); + --ifm-color-primary: #2e8555; + --ifm-color-primary-dark: #29784c; + --ifm-color-primary-darker: #277148; + --ifm-color-primary-darkest: #205d3b; + --ifm-color-primary-light: #33925d; + --ifm-color-primary-lighter: #359962; + --ifm-color-primary-lightest: #3cad6e; --ifm-code-font-size: 95%; } +/* For readability concerns, you should choose a lighter palette in dark mode. */ +[data-theme='dark'] { + --ifm-color-primary: #25c2a0; + --ifm-color-primary-dark: #21af90; + --ifm-color-primary-darker: #1fa588; + --ifm-color-primary-darkest: #1a8870; + --ifm-color-primary-light: #29d5b0; + --ifm-color-primary-lighter: #32d8b4; + --ifm-color-primary-lightest: #4fddbf; +} + .docusaurus-highlight-code-line { background-color: rgb(72, 77, 91); display: block; diff --git a/examples/facebook/src/pages/styles.module.css b/examples/facebook/src/pages/styles.module.css index 76db00c17c0a..da1454b3f3b5 100644 --- a/examples/facebook/src/pages/styles.module.css +++ b/examples/facebook/src/pages/styles.module.css @@ -19,7 +19,7 @@ overflow: hidden; } -@media screen and (max-width: 966px) { +@media screen and (max-width: 996px) { .heroBanner { padding: 2rem; } diff --git a/examples/facebook/static/img/undraw_docusaurus_mountain.svg b/examples/facebook/static/img/undraw_docusaurus_mountain.svg index 431cef2f7fec..af961c49a888 100644 --- a/examples/facebook/static/img/undraw_docusaurus_mountain.svg +++ b/examples/facebook/static/img/undraw_docusaurus_mountain.svg @@ -1,4 +1,5 @@ + Easy to Use diff --git a/examples/facebook/static/img/undraw_docusaurus_react.svg b/examples/facebook/static/img/undraw_docusaurus_react.svg index e41705043338..94b5cf08f88f 100644 --- a/examples/facebook/static/img/undraw_docusaurus_react.svg +++ b/examples/facebook/static/img/undraw_docusaurus_react.svg @@ -1,4 +1,5 @@ + Powered by React diff --git a/examples/facebook/static/img/undraw_docusaurus_tree.svg b/examples/facebook/static/img/undraw_docusaurus_tree.svg index a05cc03dda90..d9161d33920c 100644 --- a/examples/facebook/static/img/undraw_docusaurus_tree.svg +++ b/examples/facebook/static/img/undraw_docusaurus_tree.svg @@ -1 +1,40 @@ -docu_tree \ No newline at end of file + + Focus on What Matters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/facebook/yarn.lock b/examples/facebook/yarn.lock index c03c79320d10..c469926e4eb2 100644 --- a/examples/facebook/yarn.lock +++ b/examples/facebook/yarn.lock @@ -2,145 +2,152 @@ # yarn lockfile v1 -"@algolia/autocomplete-core@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-core/-/autocomplete-core-1.5.0.tgz#6c91c9de7748e9c103846828a58dfe92bd4d6689" - integrity sha512-E7+VJwcvwMM8vPeaVn7fNUgix8WHV8A1WUeHDi2KHemCaaGc8lvUnP3QnvhMxiDhTe7OpMEv4o2TBUMyDgThaw== +"@algolia/autocomplete-core@1.5.2": + version "1.5.2" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-core/-/autocomplete-core-1.5.2.tgz#ec0178e07b44fd74a057728ac157291b26cecf37" + integrity sha512-DY0bhyczFSS1b/CqJlTE/nQRtnTAHl6IemIkBy0nEWnhDzRDdtdx4p5Uuk3vwAFxwEEgi1WqKwgSSMx6DpNL4A== dependencies: - "@algolia/autocomplete-shared" "1.5.0" + "@algolia/autocomplete-shared" "1.5.2" -"@algolia/autocomplete-preset-algolia@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.5.0.tgz#61671f09c0c77133d9baf1356719f8378c48437a" - integrity sha512-iiFxKERGHkvkiupmrFJbvESpP/zv5jSgH714XRiP5LDvUHaYOo4GLAwZCFf2ef/L5tdtPBARvekn6k1Xf33gjA== +"@algolia/autocomplete-preset-algolia@1.5.2": + version "1.5.2" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.5.2.tgz#36c5638cc6dba6ea46a86e5a0314637ca40a77ca" + integrity sha512-3MRYnYQFJyovANzSX2CToS6/5cfVjbLLqFsZTKcvF3abhQzxbqwwaMBlJtt620uBUOeMzhdfasKhCc40+RHiZw== dependencies: - "@algolia/autocomplete-shared" "1.5.0" + "@algolia/autocomplete-shared" "1.5.2" -"@algolia/autocomplete-shared@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.5.0.tgz#09580bc89408a2ab5f29e312120dad68f58019bd" - integrity sha512-bRSkqHHHSwZYbFY3w9hgMyQRm86Wz27bRaGCbNldLfbk0zUjApmE4ajx+ZCVSLqxvcUEjMqZFJzDsder12eKsg== +"@algolia/autocomplete-shared@1.5.2": + version "1.5.2" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.5.2.tgz#e157f9ad624ab8fd940ff28bd2094cdf199cdd79" + integrity sha512-ylQAYv5H0YKMfHgVWX0j0NmL8XBcAeeeVQUmppnnMtzDbDnca6CzhKj3Q8eF9cHCgcdTDdb5K+3aKyGWA0obug== -"@algolia/cache-browser-local-storage@4.11.0": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.11.0.tgz#1c168add00b398a860db6c86039e33b2843a9425" - integrity sha512-4sr9vHIG1fVA9dONagdzhsI/6M5mjs/qOe2xUP0yBmwsTsuwiZq3+Xu6D3dsxsuFetcJgC6ydQoCW8b7fDJHYQ== +"@algolia/cache-browser-local-storage@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.13.0.tgz#f8aa4fe31104b19d616ea392f9ed5c2ea847d964" + integrity sha512-nj1vHRZauTqP/bluwkRIgEADEimqojJgoTRCel5f6q8WCa9Y8QeI4bpDQP28FoeKnDRYa3J5CauDlN466jqRhg== dependencies: - "@algolia/cache-common" "4.11.0" + "@algolia/cache-common" "4.13.0" -"@algolia/cache-common@4.11.0": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@algolia/cache-common/-/cache-common-4.11.0.tgz#066fe6d58b18e4b028dbef9bb8de07c5e22a3594" - integrity sha512-lODcJRuPXqf+6mp0h6bOxPMlbNoyn3VfjBVcQh70EDP0/xExZbkpecgHyyZK4kWg+evu+mmgvTK3GVHnet/xKw== +"@algolia/cache-common@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@algolia/cache-common/-/cache-common-4.13.0.tgz#27b83fd3939d08d72261b36a07eeafc4cb4d2113" + integrity sha512-f9mdZjskCui/dA/fA/5a+6hZ7xnHaaZI5tM/Rw9X8rRB39SUlF/+o3P47onZ33n/AwkpSbi5QOyhs16wHd55kA== -"@algolia/cache-in-memory@4.11.0": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@algolia/cache-in-memory/-/cache-in-memory-4.11.0.tgz#763c8cb655e6fd2261588e04214fca0959ac07c1" - integrity sha512-aBz+stMSTBOBaBEQ43zJXz2DnwS7fL6dR0e2myehAgtfAWlWwLDHruc/98VOy1ZAcBk1blE2LCU02bT5HekGxQ== +"@algolia/cache-in-memory@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@algolia/cache-in-memory/-/cache-in-memory-4.13.0.tgz#10801a74550cbabb64b59ff08c56bce9c278ff2d" + integrity sha512-hHdc+ahPiMM92CQMljmObE75laYzNFYLrNOu0Q3/eyvubZZRtY2SUsEEgyUEyzXruNdzrkcDxFYa7YpWBJYHAg== dependencies: - "@algolia/cache-common" "4.11.0" + "@algolia/cache-common" "4.13.0" -"@algolia/client-account@4.11.0": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@algolia/client-account/-/client-account-4.11.0.tgz#67fadd3b0802b013ebaaa4b47bb7babae892374e" - integrity sha512-jwmFBoUSzoMwMqgD3PmzFJV/d19p1RJXB6C1ADz4ju4mU7rkaQLtqyZroQpheLoU5s5Tilmn/T8/0U2XLoJCRQ== +"@algolia/client-account@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@algolia/client-account/-/client-account-4.13.0.tgz#f8646dd40d1e9e3353e10abbd5d6c293ea92a8e2" + integrity sha512-FzFqFt9b0g/LKszBDoEsW+dVBuUe1K3scp2Yf7q6pgHWM1WqyqUlARwVpLxqyc+LoyJkTxQftOKjyFUqddnPKA== dependencies: - "@algolia/client-common" "4.11.0" - "@algolia/client-search" "4.11.0" - "@algolia/transporter" "4.11.0" + "@algolia/client-common" "4.13.0" + "@algolia/client-search" "4.13.0" + "@algolia/transporter" "4.13.0" -"@algolia/client-analytics@4.11.0": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@algolia/client-analytics/-/client-analytics-4.11.0.tgz#cbdc8128205e2da749cafc79e54708d14c413974" - integrity sha512-v5U9585aeEdYml7JqggHAj3E5CQ+jPwGVztPVhakBk8H/cmLyPS2g8wvmIbaEZCHmWn4TqFj3EBHVYxAl36fSA== +"@algolia/client-analytics@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@algolia/client-analytics/-/client-analytics-4.13.0.tgz#a00bd02df45d71becb9dd4c5c993d805f2e1786d" + integrity sha512-klmnoq2FIiiMHImkzOm+cGxqRLLu9CMHqFhbgSy9wtXZrqb8BBUIUE2VyBe7azzv1wKcxZV2RUyNOMpFqmnRZA== dependencies: - "@algolia/client-common" "4.11.0" - "@algolia/client-search" "4.11.0" - "@algolia/requester-common" "4.11.0" - "@algolia/transporter" "4.11.0" + "@algolia/client-common" "4.13.0" + "@algolia/client-search" "4.13.0" + "@algolia/requester-common" "4.13.0" + "@algolia/transporter" "4.13.0" -"@algolia/client-common@4.11.0": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@algolia/client-common/-/client-common-4.11.0.tgz#9a2d1f6f8eaad25ba5d6d4ce307ba5bd84e6f999" - integrity sha512-Qy+F+TZq12kc7tgfC+FM3RvYH/Ati7sUiUv/LkvlxFwNwNPwWGoZO81AzVSareXT/ksDDrabD4mHbdTbBPTRmQ== +"@algolia/client-common@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@algolia/client-common/-/client-common-4.13.0.tgz#8bc373d164dbdcce38b4586912bbe162492bcb86" + integrity sha512-GoXfTp0kVcbgfSXOjfrxx+slSipMqGO9WnNWgeMmru5Ra09MDjrcdunsiiuzF0wua6INbIpBQFTC2Mi5lUNqGA== dependencies: - "@algolia/requester-common" "4.11.0" - "@algolia/transporter" "4.11.0" + "@algolia/requester-common" "4.13.0" + "@algolia/transporter" "4.13.0" -"@algolia/client-personalization@4.11.0": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@algolia/client-personalization/-/client-personalization-4.11.0.tgz#d3bf0e760f85df876b4baf5b81996f0aa3a59940" - integrity sha512-mI+X5IKiijHAzf9fy8VSl/GTT67dzFDnJ0QAM8D9cMPevnfX4U72HRln3Mjd0xEaYUOGve8TK/fMg7d3Z5yG6g== +"@algolia/client-personalization@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@algolia/client-personalization/-/client-personalization-4.13.0.tgz#10fb7af356422551f11a67222b39c52306f1512c" + integrity sha512-KneLz2WaehJmNfdr5yt2HQETpLaCYagRdWwIwkTqRVFCv4DxRQ2ChPVW9jeTj4YfAAhfzE6F8hn7wkQ/Jfj6ZA== dependencies: - "@algolia/client-common" "4.11.0" - "@algolia/requester-common" "4.11.0" - "@algolia/transporter" "4.11.0" + "@algolia/client-common" "4.13.0" + "@algolia/requester-common" "4.13.0" + "@algolia/transporter" "4.13.0" -"@algolia/client-search@4.11.0": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@algolia/client-search/-/client-search-4.11.0.tgz#c1105d715a2a04ba27231eca86f5d6620f68f4ae" - integrity sha512-iovPLc5YgiXBdw2qMhU65sINgo9umWbHFzInxoNErWnYoTQWfXsW6P54/NlKx5uscoLVjSf+5RUWwFu5BX+lpw== +"@algolia/client-search@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@algolia/client-search/-/client-search-4.13.0.tgz#2d8ff8e755c4a37ec89968f3f9b358eed005c7f0" + integrity sha512-blgCKYbZh1NgJWzeGf+caKE32mo3j54NprOf0LZVCubQb3Kx37tk1Hc8SDs9bCAE8hUvf3cazMPIg7wscSxspA== dependencies: - "@algolia/client-common" "4.11.0" - "@algolia/requester-common" "4.11.0" - "@algolia/transporter" "4.11.0" + "@algolia/client-common" "4.13.0" + "@algolia/requester-common" "4.13.0" + "@algolia/transporter" "4.13.0" "@algolia/events@^4.0.1": version "4.0.1" resolved "https://registry.yarnpkg.com/@algolia/events/-/events-4.0.1.tgz#fd39e7477e7bc703d7f893b556f676c032af3950" integrity sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ== -"@algolia/logger-common@4.11.0": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@algolia/logger-common/-/logger-common-4.11.0.tgz#bac1c2d59d29dee378b57412c8edd435b97de663" - integrity sha512-pRMJFeOY8hoWKIxWuGHIrqnEKN/kqKh7UilDffG/+PeEGxBuku+Wq5CfdTFG0C9ewUvn8mAJn5BhYA5k8y0Jqg== +"@algolia/logger-common@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@algolia/logger-common/-/logger-common-4.13.0.tgz#be2606e71aae618a1ff1ea9a1b5f5a74284b35a8" + integrity sha512-8yqXk7rMtmQJ9wZiHOt/6d4/JDEg5VCk83gJ39I+X/pwUPzIsbKy9QiK4uJ3aJELKyoIiDT1hpYVt+5ia+94IA== -"@algolia/logger-console@4.11.0": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@algolia/logger-console/-/logger-console-4.11.0.tgz#ced19e3abb22eb782ed5268d51efb5aa9ef109ef" - integrity sha512-wXztMk0a3VbNmYP8Kpc+F7ekuvaqZmozM2eTLok0XIshpAeZ/NJDHDffXK2Pw+NF0wmHqurptLYwKoikjBYvhQ== +"@algolia/logger-console@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@algolia/logger-console/-/logger-console-4.13.0.tgz#f28028a760e3d9191e28a10b12925e48f6c9afde" + integrity sha512-YepRg7w2/87L0vSXRfMND6VJ5d6699sFJBRWzZPOlek2p5fLxxK7O0VncYuc/IbVHEgeApvgXx0WgCEa38GVuQ== dependencies: - "@algolia/logger-common" "4.11.0" + "@algolia/logger-common" "4.13.0" -"@algolia/requester-browser-xhr@4.11.0": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.11.0.tgz#f9e1ad56f185432aa8dde8cad53ae271fd5d6181" - integrity sha512-Fp3SfDihAAFR8bllg8P5ouWi3+qpEVN5e7hrtVIYldKBOuI/qFv80Zv/3/AMKNJQRYglS4zWyPuqrXm58nz6KA== +"@algolia/requester-browser-xhr@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.13.0.tgz#e2483f4e8d7f09e27cd0daf6c77711d15c5a919f" + integrity sha512-Dj+bnoWR5MotrnjblzGKZ2kCdQi2cK/VzPURPnE616NU/il7Ypy6U6DLGZ/ZYz+tnwPa0yypNf21uqt84fOgrg== dependencies: - "@algolia/requester-common" "4.11.0" + "@algolia/requester-common" "4.13.0" -"@algolia/requester-common@4.11.0": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@algolia/requester-common/-/requester-common-4.11.0.tgz#d16de98d3ff72434bac39e4d915eab08035946a9" - integrity sha512-+cZGe/9fuYgGuxjaBC+xTGBkK7OIYdfapxhfvEf03dviLMPmhmVYFJtJlzAjQ2YmGDJpHrGgAYj3i/fbs8yhiA== +"@algolia/requester-common@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@algolia/requester-common/-/requester-common-4.13.0.tgz#47fb3464cfb26b55ba43676d13f295d812830596" + integrity sha512-BRTDj53ecK+gn7ugukDWOOcBRul59C4NblCHqj4Zm5msd5UnHFjd/sGX+RLOEoFMhetILAnmg6wMrRrQVac9vw== -"@algolia/requester-node-http@4.11.0": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@algolia/requester-node-http/-/requester-node-http-4.11.0.tgz#beb2b6b68d5f4ce15aec80ede623f0ac96991368" - integrity sha512-qJIk9SHRFkKDi6dMT9hba8X1J1z92T5AZIgl+tsApjTGIRQXJLTIm+0q4yOefokfu4CoxYwRZ9QAq+ouGwfeOg== +"@algolia/requester-node-http@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@algolia/requester-node-http/-/requester-node-http-4.13.0.tgz#7d981bbd31492f51dd11820a665f9d8906793c37" + integrity sha512-9b+3O4QFU4azLhGMrZAr/uZPydvzOR4aEZfSL8ZrpLZ7fbbqTO0S/5EVko+QIgglRAtVwxvf8UJ1wzTD2jvKxQ== dependencies: - "@algolia/requester-common" "4.11.0" + "@algolia/requester-common" "4.13.0" -"@algolia/transporter@4.11.0": - version "4.11.0" - resolved "https://registry.yarnpkg.com/@algolia/transporter/-/transporter-4.11.0.tgz#a8de3c173093ceceb02b26b577395ce3b3d4b96f" - integrity sha512-k4dyxiaEfYpw4UqybK9q7lrFzehygo6KV3OCYJMMdX0IMWV0m4DXdU27c1zYRYtthaFYaBzGF4Kjcl8p8vxCKw== +"@algolia/transporter@4.13.0": + version "4.13.0" + resolved "https://registry.yarnpkg.com/@algolia/transporter/-/transporter-4.13.0.tgz#f6379e5329efa2127da68c914d1141f5f21dbd07" + integrity sha512-8tSQYE+ykQENAdeZdofvtkOr5uJ9VcQSWgRhQ9h01AehtBIPAczk/b2CLrMsw5yQZziLs5cZ3pJ3478yI+urhA== dependencies: - "@algolia/cache-common" "4.11.0" - "@algolia/logger-common" "4.11.0" - "@algolia/requester-common" "4.11.0" + "@algolia/cache-common" "4.13.0" + "@algolia/logger-common" "4.13.0" + "@algolia/requester-common" "4.13.0" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.8.3": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.0.tgz#0dfc80309beec8411e65e706461c408b0bb9b431" - integrity sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA== +"@ampproject/remapping@^2.1.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.1.2.tgz#4edca94973ded9630d20101cd8559cedb8d8bd34" + integrity sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg== + dependencies: + "@jridgewell/trace-mapping" "^0.3.0" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.8.3": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789" + integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg== dependencies: - "@babel/highlight" "^7.16.0" + "@babel/highlight" "^7.16.7" -"@babel/compat-data@^7.13.11", "@babel/compat-data@^7.16.0", "@babel/compat-data@^7.16.4": - version "7.16.4" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.16.4.tgz#081d6bbc336ec5c2435c6346b2ae1fb98b5ac68e" - integrity sha512-1o/jo7D+kC9ZjHX5v+EHrdjl3PhxMrLSOTGsOdHJ+KL8HCaEK6ehrVL2RS6oHDZp+L7xLirLrPmQtEng769J/Q== +"@babel/compat-data@^7.13.11", "@babel/compat-data@^7.16.8", "@babel/compat-data@^7.17.0", "@babel/compat-data@^7.17.7": + version "7.17.7" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.17.7.tgz#078d8b833fbbcc95286613be8c716cef2b519fa2" + integrity sha512-p8pdE6j0a29TNGebNm7NzYZWB3xVZJBZ7XGs42uAKzQo8VQ3F0By/cQCtUEABwIqw5zo6WA4NbmxsfzADzMKnQ== "@babel/core@7.12.9": version "7.12.9" @@ -164,95 +171,95 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/core@>=7.9.0", "@babel/core@^7.15.5", "@babel/core@^7.16.0": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.16.5.tgz#924aa9e1ae56e1e55f7184c8bf073a50d8677f5c" - integrity sha512-wUcenlLzuWMZ9Zt8S0KmFwGlH6QKRh3vsm/dhDA3CHkiTA45YuG1XkHRcNRl73EFPXDp/d5kVOU0/y7x2w6OaQ== - dependencies: - "@babel/code-frame" "^7.16.0" - "@babel/generator" "^7.16.5" - "@babel/helper-compilation-targets" "^7.16.3" - "@babel/helper-module-transforms" "^7.16.5" - "@babel/helpers" "^7.16.5" - "@babel/parser" "^7.16.5" - "@babel/template" "^7.16.0" - "@babel/traverse" "^7.16.5" - "@babel/types" "^7.16.0" +"@babel/core@^7.15.5", "@babel/core@^7.17.8": + version "7.17.8" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.17.8.tgz#3dac27c190ebc3a4381110d46c80e77efe172e1a" + integrity sha512-OdQDV/7cRBtJHLSOBqqbYNkOcydOgnX59TZx4puf41fzcVtN3e/4yqY8lMQsK+5X2lJtAdmA+6OHqsj1hBJ4IQ== + dependencies: + "@ampproject/remapping" "^2.1.0" + "@babel/code-frame" "^7.16.7" + "@babel/generator" "^7.17.7" + "@babel/helper-compilation-targets" "^7.17.7" + "@babel/helper-module-transforms" "^7.17.7" + "@babel/helpers" "^7.17.8" + "@babel/parser" "^7.17.8" + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.17.3" + "@babel/types" "^7.17.0" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" json5 "^2.1.2" semver "^6.3.0" - source-map "^0.5.0" -"@babel/eslint-parser@^7.16.3": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.16.5.tgz#48d3485091d6e36915358e4c0d0b2ebe6da90462" - integrity sha512-mUqYa46lgWqHKQ33Q6LNCGp/wPR3eqOYTUixHFsfrSQqRxH0+WOzca75iEjFr5RDGH1dDz622LaHhLOzOuQRUA== +"@babel/eslint-parser@^7.17.0": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.17.0.tgz#eabb24ad9f0afa80e5849f8240d0e5facc2d90d6" + integrity sha512-PUEJ7ZBXbRkbq3qqM/jZ2nIuakUBqCYc7Qf52Lj7dlZ6zERnqisdHioL0l4wwQZnmskMeasqUNzLBFKs3nylXA== dependencies: eslint-scope "^5.1.1" eslint-visitor-keys "^2.1.0" semver "^6.3.0" -"@babel/generator@^7.12.5", "@babel/generator@^7.16.0", "@babel/generator@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.16.5.tgz#26e1192eb8f78e0a3acaf3eede3c6fc96d22bedf" - integrity sha512-kIvCdjZqcdKqoDbVVdt5R99icaRtrtYhYK/xux5qiWCBmfdvEYMFZ68QCrpE5cbFM1JsuArUNs1ZkuKtTtUcZA== +"@babel/generator@^7.12.5", "@babel/generator@^7.17.3", "@babel/generator@^7.17.7": + version "7.17.7" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.17.7.tgz#8da2599beb4a86194a3b24df6c085931d9ee45ad" + integrity sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w== dependencies: - "@babel/types" "^7.16.0" + "@babel/types" "^7.17.0" jsesc "^2.5.1" source-map "^0.5.0" -"@babel/helper-annotate-as-pure@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.0.tgz#9a1f0ebcda53d9a2d00108c4ceace6a5d5f1f08d" - integrity sha512-ItmYF9vR4zA8cByDocY05o0LGUkp1zhbTQOH1NFyl5xXEqlTJQCEJjieriw+aFpxo16swMxUnUiKS7a/r4vtHg== +"@babel/helper-annotate-as-pure@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz#bb2339a7534a9c128e3102024c60760a3a7f3862" + integrity sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw== dependencies: - "@babel/types" "^7.16.0" + "@babel/types" "^7.16.7" -"@babel/helper-builder-binary-assignment-operator-visitor@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.5.tgz#a8429d064dce8207194b8bf05a70a9ea828746af" - integrity sha512-3JEA9G5dmmnIWdzaT9d0NmFRgYnWUThLsDaL7982H0XqqWr56lRrsmwheXFMjR+TMl7QMBb6mzy9kvgr1lRLUA== +"@babel/helper-builder-binary-assignment-operator-visitor@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.7.tgz#38d138561ea207f0f69eb1626a418e4f7e6a580b" + integrity sha512-C6FdbRaxYjwVu/geKW4ZeQ0Q31AftgRcdSnZ5/jsH6BzCJbtvXvhpfkbkThYSuutZA7nCXpPR6AD9zd1dprMkA== dependencies: - "@babel/helper-explode-assignable-expression" "^7.16.0" - "@babel/types" "^7.16.0" + "@babel/helper-explode-assignable-expression" "^7.16.7" + "@babel/types" "^7.16.7" -"@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.16.3": - version "7.16.3" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.3.tgz#5b480cd13f68363df6ec4dc8ac8e2da11363cbf0" - integrity sha512-vKsoSQAyBmxS35JUOOt+07cLc6Nk/2ljLIHwmq2/NM6hdioUaqEXq/S+nXvbvXbZkNDlWOymPanJGOc4CBjSJA== +"@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.16.7", "@babel/helper-compilation-targets@^7.17.7": + version "7.17.7" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.7.tgz#a3c2924f5e5f0379b356d4cfb313d1414dc30e46" + integrity sha512-UFzlz2jjd8kroj0hmCFV5zr+tQPi1dpC2cRsDV/3IEW8bJfCPrPpmcSN6ZS8RqIq4LXcmpipCQFPddyFA5Yc7w== dependencies: - "@babel/compat-data" "^7.16.0" - "@babel/helper-validator-option" "^7.14.5" + "@babel/compat-data" "^7.17.7" + "@babel/helper-validator-option" "^7.16.7" browserslist "^4.17.5" semver "^6.3.0" -"@babel/helper-create-class-features-plugin@^7.16.0", "@babel/helper-create-class-features-plugin@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.16.5.tgz#5d1bcd096792c1ebec6249eebc6358eec55d0cad" - integrity sha512-NEohnYA7mkB8L5JhU7BLwcBdU3j83IziR9aseMueWGeAjblbul3zzb8UvJ3a1zuBiqCMObzCJHFqKIQE6hTVmg== +"@babel/helper-create-class-features-plugin@^7.16.10", "@babel/helper-create-class-features-plugin@^7.16.7", "@babel/helper-create-class-features-plugin@^7.17.6": + version "7.17.6" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.6.tgz#3778c1ed09a7f3e65e6d6e0f6fbfcc53809d92c9" + integrity sha512-SogLLSxXm2OkBbSsHZMM4tUi8fUzjs63AT/d0YQIzr6GSd8Hxsbk2KYDX0k0DweAzGMj/YWeiCsorIdtdcW8Eg== dependencies: - "@babel/helper-annotate-as-pure" "^7.16.0" - "@babel/helper-environment-visitor" "^7.16.5" - "@babel/helper-function-name" "^7.16.0" - "@babel/helper-member-expression-to-functions" "^7.16.5" - "@babel/helper-optimise-call-expression" "^7.16.0" - "@babel/helper-replace-supers" "^7.16.5" - "@babel/helper-split-export-declaration" "^7.16.0" + "@babel/helper-annotate-as-pure" "^7.16.7" + "@babel/helper-environment-visitor" "^7.16.7" + "@babel/helper-function-name" "^7.16.7" + "@babel/helper-member-expression-to-functions" "^7.16.7" + "@babel/helper-optimise-call-expression" "^7.16.7" + "@babel/helper-replace-supers" "^7.16.7" + "@babel/helper-split-export-declaration" "^7.16.7" -"@babel/helper-create-regexp-features-plugin@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.16.0.tgz#06b2348ce37fccc4f5e18dcd8d75053f2a7c44ff" - integrity sha512-3DyG0zAFAZKcOp7aVr33ddwkxJ0Z0Jr5V99y3I690eYLpukJsJvAbzTy1ewoCqsML8SbIrjH14Jc/nSQ4TvNPA== +"@babel/helper-create-regexp-features-plugin@^7.16.7": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.17.0.tgz#1dcc7d40ba0c6b6b25618997c5dbfd310f186fe1" + integrity sha512-awO2So99wG6KnlE+TPs6rn83gCz5WlEePJDTnLEqbchMVrBeAujURVphRdigsk094VhvZehFoNOihSlcBjwsXA== dependencies: - "@babel/helper-annotate-as-pure" "^7.16.0" - regexpu-core "^4.7.1" + "@babel/helper-annotate-as-pure" "^7.16.7" + regexpu-core "^5.0.1" -"@babel/helper-define-polyfill-provider@^0.3.0": - version "0.3.0" - resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.0.tgz#c5b10cf4b324ff840140bb07e05b8564af2ae971" - integrity sha512-7hfT8lUljl/tM3h+izTX/pO3W3frz2ok6Pk+gzys8iJqDfZrZy2pXjRTZAvG2YmfHun1X4q8/UZRLatMfqc5Tg== +"@babel/helper-define-polyfill-provider@^0.3.1": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.1.tgz#52411b445bdb2e676869e5a74960d2d3826d2665" + integrity sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA== dependencies: "@babel/helper-compilation-targets" "^7.13.0" "@babel/helper-module-imports" "^7.12.13" @@ -263,114 +270,114 @@ resolve "^1.14.2" semver "^6.1.2" -"@babel/helper-environment-visitor@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.5.tgz#f6a7f38b3c6d8b07c88faea083c46c09ef5451b8" - integrity sha512-ODQyc5AnxmZWm/R2W7fzhamOk1ey8gSguo5SGvF0zcB3uUzRpTRmM/jmLSm9bDMyPlvbyJ+PwPEK0BWIoZ9wjg== +"@babel/helper-environment-visitor@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz#ff484094a839bde9d89cd63cba017d7aae80ecd7" + integrity sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag== dependencies: - "@babel/types" "^7.16.0" + "@babel/types" "^7.16.7" -"@babel/helper-explode-assignable-expression@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.0.tgz#753017337a15f46f9c09f674cff10cee9b9d7778" - integrity sha512-Hk2SLxC9ZbcOhLpg/yMznzJ11W++lg5GMbxt1ev6TXUiJB0N42KPC+7w8a+eWGuqDnUYuwStJoZHM7RgmIOaGQ== +"@babel/helper-explode-assignable-expression@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.7.tgz#12a6d8522fdd834f194e868af6354e8650242b7a" + integrity sha512-KyUenhWMC8VrxzkGP0Jizjo4/Zx+1nNZhgocs+gLzyZyB8SHidhoq9KK/8Ato4anhwsivfkBLftky7gvzbZMtQ== dependencies: - "@babel/types" "^7.16.0" + "@babel/types" "^7.16.7" -"@babel/helper-function-name@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.16.0.tgz#b7dd0797d00bbfee4f07e9c4ea5b0e30c8bb1481" - integrity sha512-BZh4mEk1xi2h4HFjWUXRQX5AEx4rvaZxHgax9gcjdLWdkjsY7MKt5p0otjsg5noXw+pB+clMCjw+aEVYADMjog== +"@babel/helper-function-name@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz#f1ec51551fb1c8956bc8dd95f38523b6cf375f8f" + integrity sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA== dependencies: - "@babel/helper-get-function-arity" "^7.16.0" - "@babel/template" "^7.16.0" - "@babel/types" "^7.16.0" + "@babel/helper-get-function-arity" "^7.16.7" + "@babel/template" "^7.16.7" + "@babel/types" "^7.16.7" -"@babel/helper-get-function-arity@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.0.tgz#0088c7486b29a9cb5d948b1a1de46db66e089cfa" - integrity sha512-ASCquNcywC1NkYh/z7Cgp3w31YW8aojjYIlNg4VeJiHkqyP4AzIvr4qx7pYDb4/s8YcsZWqqOSxgkvjUz1kpDQ== +"@babel/helper-get-function-arity@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz#ea08ac753117a669f1508ba06ebcc49156387419" + integrity sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw== dependencies: - "@babel/types" "^7.16.0" + "@babel/types" "^7.16.7" -"@babel/helper-hoist-variables@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.0.tgz#4c9023c2f1def7e28ff46fc1dbcd36a39beaa81a" - integrity sha512-1AZlpazjUR0EQZQv3sgRNfM9mEVWPK3M6vlalczA+EECcPz3XPh6VplbErL5UoMpChhSck5wAJHthlj1bYpcmg== +"@babel/helper-hoist-variables@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz#86bcb19a77a509c7b77d0e22323ef588fa58c246" + integrity sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg== dependencies: - "@babel/types" "^7.16.0" + "@babel/types" "^7.16.7" -"@babel/helper-member-expression-to-functions@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.5.tgz#1bc9f7e87354e86f8879c67b316cb03d3dc2caab" - integrity sha512-7fecSXq7ZrLE+TWshbGT+HyCLkxloWNhTbU2QM1NTI/tDqyf0oZiMcEfYtDuUDCo528EOlt39G1rftea4bRZIw== +"@babel/helper-member-expression-to-functions@^7.16.7": + version "7.17.7" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.17.7.tgz#a34013b57d8542a8c4ff8ba3f747c02452a4d8c4" + integrity sha512-thxXgnQ8qQ11W2wVUObIqDL4p148VMxkt5T/qpN5k2fboRyzFGFmKsTGViquyM5QHKUy48OZoca8kw4ajaDPyw== dependencies: - "@babel/types" "^7.16.0" + "@babel/types" "^7.17.0" -"@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.0.tgz#90538e60b672ecf1b448f5f4f5433d37e79a3ec3" - integrity sha512-kkH7sWzKPq0xt3H1n+ghb4xEMP8k0U7XV3kkB+ZGy69kDk2ySFW1qPi06sjKzFY3t1j6XbJSqr4mF9L7CYVyhg== +"@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz#25612a8091a999704461c8a222d0efec5d091437" + integrity sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg== dependencies: - "@babel/types" "^7.16.0" + "@babel/types" "^7.16.7" -"@babel/helper-module-transforms@^7.12.1", "@babel/helper-module-transforms@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.16.5.tgz#530ebf6ea87b500f60840578515adda2af470a29" - integrity sha512-CkvMxgV4ZyyioElFwcuWnDCcNIeyqTkCm9BxXZi73RR1ozqlpboqsbGUNvRTflgZtFbbJ1v5Emvm+lkjMYY/LQ== - dependencies: - "@babel/helper-environment-visitor" "^7.16.5" - "@babel/helper-module-imports" "^7.16.0" - "@babel/helper-simple-access" "^7.16.0" - "@babel/helper-split-export-declaration" "^7.16.0" - "@babel/helper-validator-identifier" "^7.15.7" - "@babel/template" "^7.16.0" - "@babel/traverse" "^7.16.5" - "@babel/types" "^7.16.0" +"@babel/helper-module-transforms@^7.12.1", "@babel/helper-module-transforms@^7.16.7", "@babel/helper-module-transforms@^7.17.7": + version "7.17.7" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.17.7.tgz#3943c7f777139e7954a5355c815263741a9c1cbd" + integrity sha512-VmZD99F3gNTYB7fJRDTi+u6l/zxY0BE6OIxPSU7a50s6ZUQkHwSDmV92FfM+oCG0pZRVojGYhkR8I0OGeCVREw== + dependencies: + "@babel/helper-environment-visitor" "^7.16.7" + "@babel/helper-module-imports" "^7.16.7" + "@babel/helper-simple-access" "^7.17.7" + "@babel/helper-split-export-declaration" "^7.16.7" + "@babel/helper-validator-identifier" "^7.16.7" + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.17.3" + "@babel/types" "^7.17.0" -"@babel/helper-optimise-call-expression@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.0.tgz#cecdb145d70c54096b1564f8e9f10cd7d193b338" - integrity sha512-SuI467Gi2V8fkofm2JPnZzB/SUuXoJA5zXe/xzyPP2M04686RzFKFHPK6HDVN6JvWBIEW8tt9hPR7fXdn2Lgpw== +"@babel/helper-optimise-call-expression@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz#a34e3560605abbd31a18546bd2aad3e6d9a174f2" + integrity sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w== dependencies: - "@babel/types" "^7.16.0" + "@babel/types" "^7.16.7" "@babel/helper-plugin-utils@7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz#2f75a831269d4f677de49986dff59927533cf375" integrity sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg== -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.13.0", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.5", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.5.tgz#afe37a45f39fce44a3d50a7958129ea5b1a5c074" - integrity sha512-59KHWHXxVA9K4HNF4sbHCf+eJeFe0Te/ZFGqBT4OjXhrwvA04sGfaEGsVTdsjoszq0YTP49RC9UKe5g8uN2RwQ== +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.13.0", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz#aa3a8ab4c3cceff8e65eb9e73d87dc4ff320b2f5" + integrity sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA== -"@babel/helper-remap-async-to-generator@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.5.tgz#e706646dc4018942acb4b29f7e185bc246d65ac3" - integrity sha512-X+aAJldyxrOmN9v3FKp+Hu1NO69VWgYgDGq6YDykwRPzxs5f2N+X988CBXS7EQahDU+Vpet5QYMqLk+nsp+Qxw== +"@babel/helper-remap-async-to-generator@^7.16.8": + version "7.16.8" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.8.tgz#29ffaade68a367e2ed09c90901986918d25e57e3" + integrity sha512-fm0gH7Flb8H51LqJHy3HJ3wnE1+qtYR2A99K06ahwrawLdOFsCEWjZOrYricXJHoPSudNKxrMBUPEIPxiIIvBw== dependencies: - "@babel/helper-annotate-as-pure" "^7.16.0" - "@babel/helper-wrap-function" "^7.16.5" - "@babel/types" "^7.16.0" + "@babel/helper-annotate-as-pure" "^7.16.7" + "@babel/helper-wrap-function" "^7.16.8" + "@babel/types" "^7.16.8" -"@babel/helper-replace-supers@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.16.5.tgz#96d3988bd0ab0a2d22c88c6198c3d3234ca25326" - integrity sha512-ao3seGVa/FZCMCCNDuBcqnBFSbdr8N2EW35mzojx3TwfIbdPmNK+JV6+2d5bR0Z71W5ocLnQp9en/cTF7pBJiQ== +"@babel/helper-replace-supers@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.16.7.tgz#e9f5f5f32ac90429c1a4bdec0f231ef0c2838ab1" + integrity sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw== dependencies: - "@babel/helper-environment-visitor" "^7.16.5" - "@babel/helper-member-expression-to-functions" "^7.16.5" - "@babel/helper-optimise-call-expression" "^7.16.0" - "@babel/traverse" "^7.16.5" - "@babel/types" "^7.16.0" + "@babel/helper-environment-visitor" "^7.16.7" + "@babel/helper-member-expression-to-functions" "^7.16.7" + "@babel/helper-optimise-call-expression" "^7.16.7" + "@babel/traverse" "^7.16.7" + "@babel/types" "^7.16.7" -"@babel/helper-simple-access@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.16.0.tgz#21d6a27620e383e37534cf6c10bba019a6f90517" - integrity sha512-o1rjBT/gppAqKsYfUdfHq5Rk03lMQrkPHG1OWzHWpLgVXRH4HnMM9Et9CVdIqwkCQlobnGHEJMsgWP/jE1zUiw== +"@babel/helper-simple-access@^7.17.7": + version "7.17.7" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.17.7.tgz#aaa473de92b7987c6dfa7ce9a7d9674724823367" + integrity sha512-txyMCGroZ96i+Pxr3Je3lzEJjqwaRC9buMUgtomcrLe5Nd0+fk1h0LLA+ixUF5OW7AhHuQ7Es1WcQJZmZsz2XA== dependencies: - "@babel/types" "^7.16.0" + "@babel/types" "^7.17.0" "@babel/helper-skip-transparent-expression-wrappers@^7.16.0": version "7.16.0" @@ -379,144 +386,144 @@ dependencies: "@babel/types" "^7.16.0" -"@babel/helper-split-export-declaration@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.0.tgz#29672f43663e936df370aaeb22beddb3baec7438" - integrity sha512-0YMMRpuDFNGTHNRiiqJX19GjNXA4H0E8jZ2ibccfSxaCogbm3am5WN/2nQNj0YnQwGWM1J06GOcQ2qnh3+0paw== +"@babel/helper-split-export-declaration@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz#0b648c0c42da9d3920d85ad585f2778620b8726b" + integrity sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw== dependencies: - "@babel/types" "^7.16.0" + "@babel/types" "^7.16.7" -"@babel/helper-validator-identifier@^7.15.7": - version "7.15.7" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz#220df993bfe904a4a6b02ab4f3385a5ebf6e2389" - integrity sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w== +"@babel/helper-validator-identifier@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad" + integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw== -"@babel/helper-validator-option@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz#6e72a1fff18d5dfcb878e1e62f1a021c4b72d5a3" - integrity sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow== +"@babel/helper-validator-option@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz#b203ce62ce5fe153899b617c08957de860de4d23" + integrity sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ== -"@babel/helper-wrap-function@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.16.5.tgz#0158fca6f6d0889c3fee8a6ed6e5e07b9b54e41f" - integrity sha512-2J2pmLBqUqVdJw78U0KPNdeE2qeuIyKoG4mKV7wAq3mc4jJG282UgjZw4ZYDnqiWQuS3Y3IYdF/AQ6CpyBV3VA== +"@babel/helper-wrap-function@^7.16.8": + version "7.16.8" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.16.8.tgz#58afda087c4cd235de92f7ceedebca2c41274200" + integrity sha512-8RpyRVIAW1RcDDGTA+GpPAwV22wXCfKOoM9bet6TLkGIFTkRQSkH1nMQ5Yet4MpoXe1ZwHPVtNasc2w0uZMqnw== dependencies: - "@babel/helper-function-name" "^7.16.0" - "@babel/template" "^7.16.0" - "@babel/traverse" "^7.16.5" - "@babel/types" "^7.16.0" + "@babel/helper-function-name" "^7.16.7" + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.16.8" + "@babel/types" "^7.16.8" -"@babel/helpers@^7.12.5", "@babel/helpers@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.16.5.tgz#29a052d4b827846dd76ece16f565b9634c554ebd" - integrity sha512-TLgi6Lh71vvMZGEkFuIxzaPsyeYCHQ5jJOOX1f0xXn0uciFuE8cEk0wyBquMcCxBXZ5BJhE2aUB7pnWTD150Tw== +"@babel/helpers@^7.12.5", "@babel/helpers@^7.17.8": + version "7.17.8" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.17.8.tgz#288450be8c6ac7e4e44df37bcc53d345e07bc106" + integrity sha512-QcL86FGxpfSJwGtAvv4iG93UL6bmqBdmoVY0CMCU2g+oD2ezQse3PT5Pa+jiD6LJndBQi0EDlpzOWNlLuhz5gw== dependencies: - "@babel/template" "^7.16.0" - "@babel/traverse" "^7.16.5" - "@babel/types" "^7.16.0" + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.17.3" + "@babel/types" "^7.17.0" -"@babel/highlight@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.16.0.tgz#6ceb32b2ca4b8f5f361fb7fd821e3fddf4a1725a" - integrity sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g== +"@babel/highlight@^7.16.7": + version "7.16.10" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.16.10.tgz#744f2eb81579d6eea753c227b0f570ad785aba88" + integrity sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw== dependencies: - "@babel/helper-validator-identifier" "^7.15.7" + "@babel/helper-validator-identifier" "^7.16.7" chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.12.7", "@babel/parser@^7.16.0", "@babel/parser@^7.16.4", "@babel/parser@^7.16.5": - version "7.16.6" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.16.6.tgz#8f194828193e8fa79166f34a4b4e52f3e769a314" - integrity sha512-Gr86ujcNuPDnNOY8mi383Hvi8IYrJVJYuf3XcuBM/Dgd+bINn/7tHqsj+tKkoreMbmGsFLsltI/JJd8fOFWGDQ== +"@babel/parser@^7.12.7", "@babel/parser@^7.16.7", "@babel/parser@^7.17.3", "@babel/parser@^7.17.8": + version "7.17.8" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.8.tgz#2817fb9d885dd8132ea0f8eb615a6388cca1c240" + integrity sha512-BoHhDJrJXqcg+ZL16Xv39H9n+AqJ4pcDrQBGZN+wHxIysrLZ3/ECwCBUch/1zUNhnsXULcONU3Ei5Hmkfk6kiQ== -"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.16.2": - version "7.16.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.16.2.tgz#2977fca9b212db153c195674e57cfab807733183" - integrity sha512-h37CvpLSf8gb2lIJ2CgC3t+EjFbi0t8qS7LCS1xcJIlEXE4czlofwaW7W1HA8zpgOCzI9C1nmoqNR1zWkk0pQg== +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.16.7.tgz#4eda6d6c2a0aa79c70fa7b6da67763dfe2141050" + integrity sha512-anv/DObl7waiGEnC24O9zqL0pSuI9hljihqiDuFHC8d7/bjr/4RLGPWuc8rYOff/QPzbEPSkzG8wGG9aDuhHRg== dependencies: - "@babel/helper-plugin-utils" "^7.14.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.16.0.tgz#358972eaab006f5eb0826183b0c93cbcaf13e1e2" - integrity sha512-4tcFwwicpWTrpl9qjf7UsoosaArgImF85AxqCRZlgc3IQDvkUHjJpruXAL58Wmj+T6fypWTC/BakfEkwIL/pwA== +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.16.7.tgz#cc001234dfc139ac45f6bcf801866198c8c72ff9" + integrity sha512-di8vUHRdf+4aJ7ltXhaDbPoszdkh59AQtJM5soLsuHpQJdFQZOA4uGj0V2u/CZ8bJ/u8ULDL5yq6FO/bCXnKHw== dependencies: - "@babel/helper-plugin-utils" "^7.14.5" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/helper-skip-transparent-expression-wrappers" "^7.16.0" - "@babel/plugin-proposal-optional-chaining" "^7.16.0" + "@babel/plugin-proposal-optional-chaining" "^7.16.7" -"@babel/plugin-proposal-async-generator-functions@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.16.5.tgz#fd3bd7e0d98404a3d4cbca15a72d533f8c9a2f67" - integrity sha512-C/FX+3HNLV6sz7AqbTQqEo1L9/kfrKjxcVtgyBCmvIgOjvuBVUWooDoi7trsLxOzCEo5FccjRvKHkfDsJFZlfA== +"@babel/plugin-proposal-async-generator-functions@^7.16.8": + version "7.16.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.16.8.tgz#3bdd1ebbe620804ea9416706cd67d60787504bc8" + integrity sha512-71YHIvMuiuqWJQkebWJtdhQTfd4Q4mF76q2IX37uZPkG9+olBxsX+rH1vkhFto4UeJZ9dPY2s+mDvhDm1u2BGQ== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" - "@babel/helper-remap-async-to-generator" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-remap-async-to-generator" "^7.16.8" "@babel/plugin-syntax-async-generators" "^7.8.4" -"@babel/plugin-proposal-class-properties@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.5.tgz#3269f44b89122110f6339806e05d43d84106468a" - integrity sha512-pJD3HjgRv83s5dv1sTnDbZOaTjghKEz8KUn1Kbh2eAIRhGuyQ1XSeI4xVXU3UlIEVA3DAyIdxqT1eRn7Wcn55A== +"@babel/plugin-proposal-class-properties@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.7.tgz#925cad7b3b1a2fcea7e59ecc8eb5954f961f91b0" + integrity sha512-IobU0Xme31ewjYOShSIqd/ZGM/r/cuOz2z0MDbNrhF5FW+ZVgi0f2lyeoj9KFPDOAqsYxmLWZte1WOwlvY9aww== dependencies: - "@babel/helper-create-class-features-plugin" "^7.16.5" - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-create-class-features-plugin" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-proposal-class-static-block@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.16.5.tgz#df58ab015a7d3b0963aafc8f20792dcd834952a9" - integrity sha512-EEFzuLZcm/rNJ8Q5krK+FRKdVkd6FjfzT9tuSZql9sQn64K0hHA2KLJ0DqVot9/iV6+SsuadC5yI39zWnm+nmQ== +"@babel/plugin-proposal-class-static-block@^7.16.7": + version "7.17.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.17.6.tgz#164e8fd25f0d80fa48c5a4d1438a6629325ad83c" + integrity sha512-X/tididvL2zbs7jZCeeRJ8167U/+Ac135AM6jCAx6gYXDUviZV5Ku9UDvWS2NCuWlFjIRXklYhwo6HhAC7ETnA== dependencies: - "@babel/helper-create-class-features-plugin" "^7.16.5" - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-create-class-features-plugin" "^7.17.6" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-class-static-block" "^7.14.5" -"@babel/plugin-proposal-dynamic-import@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.5.tgz#2e0d19d5702db4dcb9bc846200ca02f2e9d60e9e" - integrity sha512-P05/SJZTTvHz79LNYTF8ff5xXge0kk5sIIWAypcWgX4BTRUgyHc8wRxJ/Hk+mU0KXldgOOslKaeqnhthcDJCJQ== +"@babel/plugin-proposal-dynamic-import@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.7.tgz#c19c897eaa46b27634a00fee9fb7d829158704b2" + integrity sha512-I8SW9Ho3/8DRSdmDdH3gORdyUuYnk1m4cMxUAdu5oy4n3OfN8flDEH+d60iG7dUfi0KkYwSvoalHzzdRzpWHTg== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-dynamic-import" "^7.8.3" -"@babel/plugin-proposal-export-namespace-from@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.16.5.tgz#3b4dd28378d1da2fea33e97b9f25d1c2f5bf1ac9" - integrity sha512-i+sltzEShH1vsVydvNaTRsgvq2vZsfyrd7K7vPLUU/KgS0D5yZMe6uipM0+izminnkKrEfdUnz7CxMRb6oHZWw== +"@babel/plugin-proposal-export-namespace-from@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.16.7.tgz#09de09df18445a5786a305681423ae63507a6163" + integrity sha512-ZxdtqDXLRGBL64ocZcs7ovt71L3jhC1RGSyR996svrCi3PYqHNkb3SwPJCs8RIzD86s+WPpt2S73+EHCGO+NUA== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-export-namespace-from" "^7.8.3" -"@babel/plugin-proposal-json-strings@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.16.5.tgz#1e726930fca139caab6b084d232a9270d9d16f9c" - integrity sha512-QQJueTFa0y9E4qHANqIvMsuxM/qcLQmKttBACtPCQzGUEizsXDACGonlPiSwynHfOa3vNw0FPMVvQzbuXwh4SQ== +"@babel/plugin-proposal-json-strings@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.16.7.tgz#9732cb1d17d9a2626a08c5be25186c195b6fa6e8" + integrity sha512-lNZ3EEggsGY78JavgbHsK9u5P3pQaW7k4axlgFLYkMd7UBsiNahCITShLjNQschPyjtO6dADrL24757IdhBrsQ== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-json-strings" "^7.8.3" -"@babel/plugin-proposal-logical-assignment-operators@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.16.5.tgz#df1f2e4b5a0ec07abf061d2c18e53abc237d3ef5" - integrity sha512-xqibl7ISO2vjuQM+MzR3rkd0zfNWltk7n9QhaD8ghMmMceVguYrNDt7MikRyj4J4v3QehpnrU8RYLnC7z/gZLA== +"@babel/plugin-proposal-logical-assignment-operators@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.16.7.tgz#be23c0ba74deec1922e639832904be0bea73cdea" + integrity sha512-K3XzyZJGQCr00+EtYtrDjmwX7o7PLK6U9bi1nCwkQioRFVUv6dJoxbQjtWVtP+bCPy82bONBKG8NPyQ4+i6yjg== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" -"@babel/plugin-proposal-nullish-coalescing-operator@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.5.tgz#652555bfeeeee2d2104058c6225dc6f75e2d0f07" - integrity sha512-YwMsTp/oOviSBhrjwi0vzCUycseCYwoXnLiXIL3YNjHSMBHicGTz7GjVU/IGgz4DtOEXBdCNG72pvCX22ehfqg== +"@babel/plugin-proposal-nullish-coalescing-operator@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.7.tgz#141fc20b6857e59459d430c850a0011e36561d99" + integrity sha512-aUOrYU3EVtjf62jQrCj63pYZ7k6vns2h/DQvHPWGmsJRYzWXZ6/AsfgpiRy6XiuIDADhJzP2Q9MwSMKauBQ+UQ== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" -"@babel/plugin-proposal-numeric-separator@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.5.tgz#edcb6379b6cf4570be64c45965d8da7a2debf039" - integrity sha512-DvB9l/TcsCRvsIV9v4jxR/jVP45cslTVC0PMVHvaJhhNuhn2Y1SOhCSFlPK777qLB5wb8rVDaNoqMTyOqtY5Iw== +"@babel/plugin-proposal-numeric-separator@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.7.tgz#d6b69f4af63fb38b6ca2558442a7fb191236eba9" + integrity sha512-vQgPMknOIgiuVqbokToyXbkY/OmmjAzr/0lhSIbG/KmnzXPGwW/AdhdKpi+O4X/VkWiWjnkKOBiqJrTaC98VKw== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-numeric-separator" "^7.10.4" "@babel/plugin-proposal-object-rest-spread@7.12.1": @@ -528,59 +535,59 @@ "@babel/plugin-syntax-object-rest-spread" "^7.8.0" "@babel/plugin-transform-parameters" "^7.12.1" -"@babel/plugin-proposal-object-rest-spread@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.16.5.tgz#f30f80dacf7bc1404bf67f99c8d9c01665e830ad" - integrity sha512-UEd6KpChoyPhCoE840KRHOlGhEZFutdPDMGj+0I56yuTTOaT51GzmnEl/0uT41fB/vD2nT+Pci2KjezyE3HmUw== +"@babel/plugin-proposal-object-rest-spread@^7.16.7": + version "7.17.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.17.3.tgz#d9eb649a54628a51701aef7e0ea3d17e2b9dd390" + integrity sha512-yuL5iQA/TbZn+RGAfxQXfi7CNLmKi1f8zInn4IgobuCWcAb7i+zj4TYzQ9l8cEzVyJ89PDGuqxK1xZpUDISesw== dependencies: - "@babel/compat-data" "^7.16.4" - "@babel/helper-compilation-targets" "^7.16.3" - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/compat-data" "^7.17.0" + "@babel/helper-compilation-targets" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.16.5" + "@babel/plugin-transform-parameters" "^7.16.7" -"@babel/plugin-proposal-optional-catch-binding@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.5.tgz#1a5405765cf589a11a33a1fd75b2baef7d48b74e" - integrity sha512-ihCMxY1Iljmx4bWy/PIMJGXN4NS4oUj1MKynwO07kiKms23pNvIn1DMB92DNB2R0EA882sw0VXIelYGdtF7xEQ== +"@babel/plugin-proposal-optional-catch-binding@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.7.tgz#c623a430674ffc4ab732fd0a0ae7722b67cb74cf" + integrity sha512-eMOH/L4OvWSZAE1VkHbr1vckLG1WUcHGJSLqqQwl2GaUqG6QjddvrOaTUMNYiv77H5IKPMZ9U9P7EaHwvAShfA== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" -"@babel/plugin-proposal-optional-chaining@^7.16.0", "@babel/plugin-proposal-optional-chaining@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.5.tgz#a5fa61056194d5059366c0009cb9a9e66ed75c1f" - integrity sha512-kzdHgnaXRonttiTfKYnSVafbWngPPr2qKw9BWYBESl91W54e+9R5pP70LtWxV56g0f05f/SQrwHYkfvbwcdQ/A== +"@babel/plugin-proposal-optional-chaining@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.7.tgz#7cd629564724816c0e8a969535551f943c64c39a" + integrity sha512-eC3xy+ZrUcBtP7x+sq62Q/HYd674pPTb/77XZMb5wbDPGWIdUbSr4Agr052+zaUPSb+gGRnjxXfKFvx5iMJ+DA== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/helper-skip-transparent-expression-wrappers" "^7.16.0" "@babel/plugin-syntax-optional-chaining" "^7.8.3" -"@babel/plugin-proposal-private-methods@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.16.5.tgz#2086f7d78c1b0c712d49b5c3fbc2d1ca21a7ee12" - integrity sha512-+yFMO4BGT3sgzXo+lrq7orX5mAZt57DwUK6seqII6AcJnJOIhBJ8pzKH47/ql/d426uQ7YhN8DpUFirQzqYSUA== +"@babel/plugin-proposal-private-methods@^7.16.11": + version "7.16.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.16.11.tgz#e8df108288555ff259f4527dbe84813aac3a1c50" + integrity sha512-F/2uAkPlXDr8+BHpZvo19w3hLFKge+k75XUprE6jaqKxjGkSYcK+4c+bup5PdW/7W/Rpjwql7FTVEDW+fRAQsw== dependencies: - "@babel/helper-create-class-features-plugin" "^7.16.5" - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-create-class-features-plugin" "^7.16.10" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-proposal-private-property-in-object@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.16.5.tgz#a42d4b56005db3d405b12841309dbca647e7a21b" - integrity sha512-+YGh5Wbw0NH3y/E5YMu6ci5qTDmAEVNoZ3I54aB6nVEOZ5BQ7QJlwKq5pYVucQilMByGn/bvX0af+uNaPRCabA== +"@babel/plugin-proposal-private-property-in-object@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.16.7.tgz#b0b8cef543c2c3d57e59e2c611994861d46a3fce" + integrity sha512-rMQkjcOFbm+ufe3bTZLyOfsOUOxyvLXZJCTARhJr+8UMSoZmqTe1K1BgkFcrW37rAchWg57yI69ORxiWvUINuQ== dependencies: - "@babel/helper-annotate-as-pure" "^7.16.0" - "@babel/helper-create-class-features-plugin" "^7.16.5" - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-annotate-as-pure" "^7.16.7" + "@babel/helper-create-class-features-plugin" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-private-property-in-object" "^7.14.5" -"@babel/plugin-proposal-unicode-property-regex@^7.16.5", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.16.5.tgz#35fe753afa7c572f322bd068ff3377bde0f37080" - integrity sha512-s5sKtlKQyFSatt781HQwv1hoM5BQ9qRH30r+dK56OLDsHmV74mzwJNX7R1yMuE7VZKG5O6q/gmOGSAO6ikTudg== +"@babel/plugin-proposal-unicode-property-regex@^7.16.7", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.16.7.tgz#635d18eb10c6214210ffc5ff4932552de08188a2" + integrity sha512-QRK0YI/40VLhNVGIjRNAAQkEHws0cswSdFFjpFyt943YmJIU1da9uW63Iu6NFV6CxTZW5eTDCrwZUstBWgp/Rg== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.16.0" - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-create-regexp-features-plugin" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" @@ -631,12 +638,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-syntax-jsx@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.5.tgz#bf255d252f78bc8b77a17cadc37d1aa5b8ed4394" - integrity sha512-42OGssv9NPk4QHKVgIHlzeLgPOW5rGgfV5jzG90AhcXXIv6hu/eqj63w4VgvRxdvZY3AlYeDgPiSJ3BqAd1Y6Q== +"@babel/plugin-syntax-jsx@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.7.tgz#50b6571d13f764266a113d77c82b4a6508bbe665" + integrity sha512-Esxmk7YjA8QysKeT3VhTXvF6y77f/a91SIs4pWb4H2eWGQkCKFgQaG6hdoEVZtGsrAcb2K5BW66XsOErD4WU3Q== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-logical-assignment-operators@^7.10.4": version "7.10.4" @@ -694,349 +701,350 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-syntax-typescript@^7.16.0": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.5.tgz#f47a33e4eee38554f00fb6b2f894fa1f5649b0b3" - integrity sha512-/d4//lZ1Vqb4mZ5xTep3dDK888j7BGM/iKqBmndBaoYAFPlPKrGU608VVBz5JeyAb6YQDjRu1UKqj86UhwWVgw== +"@babel/plugin-syntax-typescript@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.7.tgz#39c9b55ee153151990fb038651d58d3fd03f98f8" + integrity sha512-YhUIJHHGkqPgEcMYkPCKTyGUdoGKWtopIycQyjJH8OjvRgOYsXsaKehLVPScKJWAULPxMa4N1vCe6szREFlZ7A== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-arrow-functions@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.16.5.tgz#04c18944dd55397b521d9d7511e791acea7acf2d" - integrity sha512-8bTHiiZyMOyfZFULjsCnYOWG059FVMes0iljEHSfARhNgFfpsqE92OrCffv3veSw9rwMkYcFe9bj0ZoXU2IGtQ== +"@babel/plugin-transform-arrow-functions@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.16.7.tgz#44125e653d94b98db76369de9c396dc14bef4154" + integrity sha512-9ffkFFMbvzTvv+7dTp/66xvZAWASuPD5Tl9LK3Z9vhOmANo6j94rik+5YMBt4CwHVMWLWpMsriIc2zsa3WW3xQ== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-async-to-generator@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.16.5.tgz#89c9b501e65bb14c4579a6ce9563f859de9b34e4" - integrity sha512-TMXgfioJnkXU+XRoj7P2ED7rUm5jbnDWwlCuFVTpQboMfbSya5WrmubNBAMlk7KXvywpo8rd8WuYZkis1o2H8w== +"@babel/plugin-transform-async-to-generator@^7.16.8": + version "7.16.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.16.8.tgz#b83dff4b970cf41f1b819f8b49cc0cfbaa53a808" + integrity sha512-MtmUmTJQHCnyJVrScNzNlofQJ3dLFuobYn3mwOTKHnSCMtbNsqvF71GQmJfFjdrXSsAA7iysFmYWw4bXZ20hOg== dependencies: - "@babel/helper-module-imports" "^7.16.0" - "@babel/helper-plugin-utils" "^7.16.5" - "@babel/helper-remap-async-to-generator" "^7.16.5" + "@babel/helper-module-imports" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-remap-async-to-generator" "^7.16.8" -"@babel/plugin-transform-block-scoped-functions@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.5.tgz#af087494e1c387574260b7ee9b58cdb5a4e9b0b0" - integrity sha512-BxmIyKLjUGksJ99+hJyL/HIxLIGnLKtw772zYDER7UuycDZ+Xvzs98ZQw6NGgM2ss4/hlFAaGiZmMNKvValEjw== +"@babel/plugin-transform-block-scoped-functions@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.7.tgz#4d0d57d9632ef6062cdf354bb717102ee042a620" + integrity sha512-JUuzlzmF40Z9cXyytcbZEZKckgrQzChbQJw/5PuEHYeqzCsvebDx0K0jWnIIVcmmDOAVctCgnYs0pMcrYj2zJg== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-block-scoping@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.16.5.tgz#b91f254fe53e210eabe4dd0c40f71c0ed253c5e7" - integrity sha512-JxjSPNZSiOtmxjX7PBRBeRJTUKTyJ607YUYeT0QJCNdsedOe+/rXITjP08eG8xUpsLfPirgzdCFN+h0w6RI+pQ== +"@babel/plugin-transform-block-scoping@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.16.7.tgz#f50664ab99ddeaee5bc681b8f3a6ea9d72ab4f87" + integrity sha512-ObZev2nxVAYA4bhyusELdo9hb3H+A56bxH3FZMbEImZFiEDYVHXQSJ1hQKFlDnlt8G9bBrCZ5ZpURZUrV4G5qQ== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-classes@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.16.5.tgz#6acf2ec7adb50fb2f3194dcd2909dbd056dcf216" - integrity sha512-DzJ1vYf/7TaCYy57J3SJ9rV+JEuvmlnvvyvYKFbk5u46oQbBvuB9/0w+YsVsxkOv8zVWKpDmUoj4T5ILHoXevA== +"@babel/plugin-transform-classes@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.16.7.tgz#8f4b9562850cd973de3b498f1218796eb181ce00" + integrity sha512-WY7og38SFAGYRe64BrjKf8OrE6ulEHtr5jEYaZMwox9KebgqPi67Zqz8K53EKk1fFEJgm96r32rkKZ3qA2nCWQ== dependencies: - "@babel/helper-annotate-as-pure" "^7.16.0" - "@babel/helper-environment-visitor" "^7.16.5" - "@babel/helper-function-name" "^7.16.0" - "@babel/helper-optimise-call-expression" "^7.16.0" - "@babel/helper-plugin-utils" "^7.16.5" - "@babel/helper-replace-supers" "^7.16.5" - "@babel/helper-split-export-declaration" "^7.16.0" + "@babel/helper-annotate-as-pure" "^7.16.7" + "@babel/helper-environment-visitor" "^7.16.7" + "@babel/helper-function-name" "^7.16.7" + "@babel/helper-optimise-call-expression" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-replace-supers" "^7.16.7" + "@babel/helper-split-export-declaration" "^7.16.7" globals "^11.1.0" -"@babel/plugin-transform-computed-properties@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.16.5.tgz#2af91ebf0cceccfcc701281ada7cfba40a9b322a" - integrity sha512-n1+O7xtU5lSLraRzX88CNcpl7vtGdPakKzww74bVwpAIRgz9JVLJJpOLb0uYqcOaXVM0TL6X0RVeIJGD2CnCkg== +"@babel/plugin-transform-computed-properties@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.16.7.tgz#66dee12e46f61d2aae7a73710f591eb3df616470" + integrity sha512-gN72G9bcmenVILj//sv1zLNaPyYcOzUho2lIJBMh/iakJ9ygCo/hEF9cpGb61SCMEDxbbyBoVQxrt+bWKu5KGw== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-destructuring@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.16.5.tgz#89ebc87499ac4a81b897af53bb5d3eed261bd568" - integrity sha512-GuRVAsjq+c9YPK6NeTkRLWyQskDC099XkBSVO+6QzbnOnH2d/4mBVXYStaPrZD3dFRfg00I6BFJ9Atsjfs8mlg== +"@babel/plugin-transform-destructuring@^7.16.7": + version "7.17.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.17.7.tgz#49dc2675a7afa9a5e4c6bdee636061136c3408d1" + integrity sha512-XVh0r5yq9sLR4vZ6eVZe8FKfIcSgaTBxVBRSYokRj2qksf6QerYnTxz9/GTuKTH/n/HwLP7t6gtlybHetJ/6hQ== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-dotall-regex@^7.16.5", "@babel/plugin-transform-dotall-regex@^7.4.4": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.16.5.tgz#b40739c00b6686820653536d6d143e311de67936" - integrity sha512-iQiEMt8Q4/5aRGHpGVK2Zc7a6mx7qEAO7qehgSug3SDImnuMzgmm/wtJALXaz25zUj1PmnNHtShjFgk4PDx4nw== +"@babel/plugin-transform-dotall-regex@^7.16.7", "@babel/plugin-transform-dotall-regex@^7.4.4": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.16.7.tgz#6b2d67686fab15fb6a7fd4bd895d5982cfc81241" + integrity sha512-Lyttaao2SjZF6Pf4vk1dVKv8YypMpomAbygW+mU5cYP3S5cWTfCJjG8xV6CFdzGFlfWK81IjL9viiTvpb6G7gQ== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.16.0" - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-create-regexp-features-plugin" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-duplicate-keys@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.16.5.tgz#2450f2742325412b746d7d005227f5e8973b512a" - integrity sha512-81tijpDg2a6I1Yhj4aWY1l3O1J4Cg/Pd7LfvuaH2VVInAkXtzibz9+zSPdUM1WvuUi128ksstAP0hM5w48vQgg== +"@babel/plugin-transform-duplicate-keys@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.16.7.tgz#2207e9ca8f82a0d36a5a67b6536e7ef8b08823c9" + integrity sha512-03DvpbRfvWIXyK0/6QiR1KMTWeT6OcQ7tbhjrXyFS02kjuX/mu5Bvnh5SDSWHxyawit2g5aWhKwI86EE7GUnTw== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-exponentiation-operator@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.5.tgz#36e261fa1ab643cfaf30eeab38e00ed1a76081e2" - integrity sha512-12rba2HwemQPa7BLIKCzm1pT2/RuQHtSFHdNl41cFiC6oi4tcrp7gjB07pxQvFpcADojQywSjblQth6gJyE6CA== +"@babel/plugin-transform-exponentiation-operator@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.7.tgz#efa9862ef97e9e9e5f653f6ddc7b665e8536fe9b" + integrity sha512-8UYLSlyLgRixQvlYH3J2ekXFHDFLQutdy7FfFAMm3CPZ6q9wHCwnUyiXpQCe3gVVnQlHc5nsuiEVziteRNTXEA== dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor" "^7.16.5" - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-for-of@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.16.5.tgz#9b544059c6ca11d565457c0ff1f08e13ce225261" - integrity sha512-+DpCAJFPAvViR17PIMi9x2AE34dll5wNlXO43wagAX2YcRGgEVHCNFC4azG85b4YyyFarvkc/iD5NPrz4Oneqw== +"@babel/plugin-transform-for-of@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.16.7.tgz#649d639d4617dff502a9a158c479b3b556728d8c" + integrity sha512-/QZm9W92Ptpw7sjI9Nx1mbcsWz33+l8kuMIQnDwgQBG5s3fAfQvkRjQ7NqXhtNcKOnPkdICmUHyCaWW06HCsqg== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-function-name@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.16.5.tgz#6896ebb6a5538a75d6a4086a277752f655a7bd15" - integrity sha512-Fuec/KPSpVLbGo6z1RPw4EE1X+z9gZk1uQmnYy7v4xr4TO9p41v1AoUuXEtyqAI7H+xNJYSICzRqZBhDEkd3kQ== +"@babel/plugin-transform-function-name@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.16.7.tgz#5ab34375c64d61d083d7d2f05c38d90b97ec65cf" + integrity sha512-SU/C68YVwTRxqWj5kgsbKINakGag0KTgq9f2iZEXdStoAbOzLHEBRYzImmA6yFo8YZhJVflvXmIHUO7GWHmxxA== dependencies: - "@babel/helper-function-name" "^7.16.0" - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-compilation-targets" "^7.16.7" + "@babel/helper-function-name" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-literals@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.16.5.tgz#af392b90e3edb2bd6dc316844cbfd6b9e009d320" - integrity sha512-B1j9C/IfvshnPcklsc93AVLTrNVa69iSqztylZH6qnmiAsDDOmmjEYqOm3Ts2lGSgTSywnBNiqC949VdD0/gfw== +"@babel/plugin-transform-literals@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.16.7.tgz#254c9618c5ff749e87cb0c0cef1a0a050c0bdab1" + integrity sha512-6tH8RTpTWI0s2sV6uq3e/C9wPo4PTqqZps4uF0kzQ9/xPLFQtipynvmT1g/dOfEJ+0EQsHhkQ/zyRId8J2b8zQ== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-member-expression-literals@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.16.5.tgz#4bd6ecdc11932361631097b779ca5c7570146dd5" - integrity sha512-d57i3vPHWgIde/9Y8W/xSFUndhvhZN5Wu2TjRrN1MVz5KzdUihKnfDVlfP1U7mS5DNj/WHHhaE4/tTi4hIyHwQ== +"@babel/plugin-transform-member-expression-literals@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.16.7.tgz#6e5dcf906ef8a098e630149d14c867dd28f92384" + integrity sha512-mBruRMbktKQwbxaJof32LT9KLy2f3gH+27a5XSuXo6h7R3vqltl0PgZ80C8ZMKw98Bf8bqt6BEVi3svOh2PzMw== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-modules-amd@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.16.5.tgz#92c0a3e83f642cb7e75fada9ab497c12c2616527" - integrity sha512-oHI15S/hdJuSCfnwIz+4lm6wu/wBn7oJ8+QrkzPPwSFGXk8kgdI/AIKcbR/XnD1nQVMg/i6eNaXpszbGuwYDRQ== +"@babel/plugin-transform-modules-amd@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.16.7.tgz#b28d323016a7daaae8609781d1f8c9da42b13186" + integrity sha512-KaaEtgBL7FKYwjJ/teH63oAmE3lP34N3kshz8mm4VMAw7U3PxjVwwUmxEFksbgsNUaO3wId9R2AVQYSEGRa2+g== dependencies: - "@babel/helper-module-transforms" "^7.16.5" - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-module-transforms" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-commonjs@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.16.5.tgz#4ee03b089536f076b2773196529d27c32b9d7bde" - integrity sha512-ABhUkxvoQyqhCWyb8xXtfwqNMJD7tx+irIRnUh6lmyFud7Jln1WzONXKlax1fg/ey178EXbs4bSGNd6PngO+SQ== +"@babel/plugin-transform-modules-commonjs@^7.16.8": + version "7.17.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.17.7.tgz#d86b217c8e45bb5f2dbc11eefc8eab62cf980d19" + integrity sha512-ITPmR2V7MqioMJyrxUo2onHNC3e+MvfFiFIR0RP21d3PtlVb6sfzoxNKiphSZUOM9hEIdzCcZe83ieX3yoqjUA== dependencies: - "@babel/helper-module-transforms" "^7.16.5" - "@babel/helper-plugin-utils" "^7.16.5" - "@babel/helper-simple-access" "^7.16.0" + "@babel/helper-module-transforms" "^7.17.7" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-simple-access" "^7.17.7" babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-systemjs@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.16.5.tgz#07078ba2e3cc94fbdd06836e355c246e98ad006b" - integrity sha512-53gmLdScNN28XpjEVIm7LbWnD/b/TpbwKbLk6KV4KqC9WyU6rq1jnNmVG6UgAdQZVVGZVoik3DqHNxk4/EvrjA== +"@babel/plugin-transform-modules-systemjs@^7.16.7": + version "7.17.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.17.8.tgz#81fd834024fae14ea78fbe34168b042f38703859" + integrity sha512-39reIkMTUVagzgA5x88zDYXPCMT6lcaRKs1+S9K6NKBPErbgO/w/kP8GlNQTC87b412ZTlmNgr3k2JrWgHH+Bw== dependencies: - "@babel/helper-hoist-variables" "^7.16.0" - "@babel/helper-module-transforms" "^7.16.5" - "@babel/helper-plugin-utils" "^7.16.5" - "@babel/helper-validator-identifier" "^7.15.7" + "@babel/helper-hoist-variables" "^7.16.7" + "@babel/helper-module-transforms" "^7.17.7" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-validator-identifier" "^7.16.7" babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-umd@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.16.5.tgz#caa9c53d636fb4e3c99fd35a4c9ba5e5cd7e002e" - integrity sha512-qTFnpxHMoenNHkS3VoWRdwrcJ3FhX567GvDA3hRZKF0Dj8Fmg0UzySZp3AP2mShl/bzcywb/UWAMQIjA1bhXvw== +"@babel/plugin-transform-modules-umd@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.16.7.tgz#23dad479fa585283dbd22215bff12719171e7618" + integrity sha512-EMh7uolsC8O4xhudF2F6wedbSHm1HHZ0C6aJ7K67zcDNidMzVcxWdGr+htW9n21klm+bOn+Rx4CBsAntZd3rEQ== dependencies: - "@babel/helper-module-transforms" "^7.16.5" - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-module-transforms" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-named-capturing-groups-regex@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.16.5.tgz#4afd8cdee377ce3568f4e8a9ee67539b69886a3c" - integrity sha512-/wqGDgvFUeKELW6ex6QB7dLVRkd5ehjw34tpXu1nhKC0sFfmaLabIswnpf8JgDyV2NeDmZiwoOb0rAmxciNfjA== +"@babel/plugin-transform-named-capturing-groups-regex@^7.16.8": + version "7.16.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.16.8.tgz#7f860e0e40d844a02c9dcf9d84965e7dfd666252" + integrity sha512-j3Jw+n5PvpmhRR+mrgIh04puSANCk/T/UA3m3P1MjJkhlK906+ApHhDIqBQDdOgL/r1UYpz4GNclTXxyZrYGSw== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.16.0" + "@babel/helper-create-regexp-features-plugin" "^7.16.7" -"@babel/plugin-transform-new-target@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.16.5.tgz#759ea9d6fbbc20796056a5d89d13977626384416" - integrity sha512-ZaIrnXF08ZC8jnKR4/5g7YakGVL6go6V9ql6Jl3ecO8PQaQqFE74CuM384kezju7Z9nGCCA20BqZaR1tJ/WvHg== +"@babel/plugin-transform-new-target@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.16.7.tgz#9967d89a5c243818e0800fdad89db22c5f514244" + integrity sha512-xiLDzWNMfKoGOpc6t3U+etCE2yRnn3SM09BXqWPIZOBpL2gvVrBWUKnsJx0K/ADi5F5YC5f8APFfWrz25TdlGg== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-object-super@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.5.tgz#8ccd9a1bcd3e7732ff8aa1702d067d8cd70ce380" - integrity sha512-tded+yZEXuxt9Jdtkc1RraW1zMF/GalVxaVVxh41IYwirdRgyAxxxCKZ9XB7LxZqmsjfjALxupNE1MIz9KH+Zg== +"@babel/plugin-transform-object-super@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.7.tgz#ac359cf8d32cf4354d27a46867999490b6c32a94" + integrity sha512-14J1feiQVWaGvRxj2WjyMuXS2jsBkgB3MdSN5HuC2G5nRspa5RK9COcs82Pwy5BuGcjb+fYaUj94mYcOj7rCvw== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" - "@babel/helper-replace-supers" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-replace-supers" "^7.16.7" -"@babel/plugin-transform-parameters@^7.12.1", "@babel/plugin-transform-parameters@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.16.5.tgz#4fc74b18a89638bd90aeec44a11793ecbe031dde" - integrity sha512-B3O6AL5oPop1jAVg8CV+haeUte9oFuY85zu0jwnRNZZi3tVAbJriu5tag/oaO2kGaQM/7q7aGPBlTI5/sr9enA== +"@babel/plugin-transform-parameters@^7.12.1", "@babel/plugin-transform-parameters@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.16.7.tgz#a1721f55b99b736511cb7e0152f61f17688f331f" + integrity sha512-AT3MufQ7zZEhU2hwOA11axBnExW0Lszu4RL/tAlUJBuNoRak+wehQW8h6KcXOcgjY42fHtDxswuMhMjFEuv/aw== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-property-literals@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.5.tgz#58f1465a7202a2bb2e6b003905212dd7a79abe3f" - integrity sha512-+IRcVW71VdF9pEH/2R/Apab4a19LVvdVsr/gEeotH00vSDVlKD+XgfSIw+cgGWsjDB/ziqGv/pGoQZBIiQVXHg== +"@babel/plugin-transform-property-literals@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.7.tgz#2dadac85155436f22c696c4827730e0fe1057a55" + integrity sha512-z4FGr9NMGdoIl1RqavCqGG+ZuYjfZ/hkCIeuH6Do7tXmSm0ls11nYVSJqFEUOSJbDab5wC6lRE/w6YjVcr6Hqw== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-transform-react-constant-elements@^7.14.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.16.5.tgz#4b01ea6b14bd4e55ca92bb2d6c28dd9957118924" - integrity sha512-fdc1s5npHMZ9A+w9bYbrZu4499WyYPVaTTsRO8bU0GJcMuK4ejIX4lyjnpvi+YGLK/EhFQxWszqylO0vaMciFw== + version "7.17.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.17.6.tgz#6cc273c2f612a6a50cb657e63ee1303e5e68d10a" + integrity sha512-OBv9VkyyKtsHZiHLoSfCn+h6yU7YKX8nrs32xUmOa1SRSk+t03FosB6fBZ0Yz4BpD1WV7l73Nsad+2Tz7APpqw== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-react-display-name@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.16.5.tgz#d5e910327d7931fb9f8f9b6c6999473ceae5a286" - integrity sha512-dHYCOnzSsXFz8UcdNQIHGvg94qPL/teF7CCiCEMRxmA1G2p5Mq4JnKVowCDxYfiQ9D7RstaAp9kwaSI+sXbnhw== +"@babel/plugin-transform-react-display-name@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.16.7.tgz#7b6d40d232f4c0f550ea348593db3b21e2404340" + integrity sha512-qgIg8BcZgd0G/Cz916D5+9kqX0c7nPZyXaP8R2tLNN5tkyIZdG5fEwBrxwplzSnjC1jvQmyMNVwUCZPcbGY7Pg== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-react-jsx-development@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.16.5.tgz#87da9204c275ffb57f45d192a1120cf104bc1e86" - integrity sha512-uQSLacMZSGLCxOw20dzo1dmLlKkd+DsayoV54q3MHXhbqgPzoiGerZQgNPl/Ro8/OcXV2ugfnkx+rxdS0sN5Uw== +"@babel/plugin-transform-react-jsx-development@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.16.7.tgz#43a00724a3ed2557ed3f276a01a929e6686ac7b8" + integrity sha512-RMvQWvpla+xy6MlBpPlrKZCMRs2AGiHOGHY3xRwl0pEeim348dDyxeH4xBsMPbIMhujeq7ihE702eM2Ew0Wo+A== dependencies: - "@babel/plugin-transform-react-jsx" "^7.16.5" + "@babel/plugin-transform-react-jsx" "^7.16.7" -"@babel/plugin-transform-react-jsx@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.16.5.tgz#5298aedc5f81e02b1cb702e597e8d6a346675765" - integrity sha512-+arLIz1d7kmwX0fKxTxbnoeG85ONSnLpvdODa4P3pc1sS7CV1hfmtYWufkW/oYsPnkDrEeQFxhUWcFnrXW7jQQ== +"@babel/plugin-transform-react-jsx@^7.16.7": + version "7.17.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.17.3.tgz#eac1565da176ccb1a715dae0b4609858808008c1" + integrity sha512-9tjBm4O07f7mzKSIlEmPdiE6ub7kfIe6Cd+w+oQebpATfTQMAgW+YOuWxogbKVTulA+MEO7byMeIUtQ1z+z+ZQ== dependencies: - "@babel/helper-annotate-as-pure" "^7.16.0" - "@babel/helper-module-imports" "^7.16.0" - "@babel/helper-plugin-utils" "^7.16.5" - "@babel/plugin-syntax-jsx" "^7.16.5" - "@babel/types" "^7.16.0" + "@babel/helper-annotate-as-pure" "^7.16.7" + "@babel/helper-module-imports" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/plugin-syntax-jsx" "^7.16.7" + "@babel/types" "^7.17.0" -"@babel/plugin-transform-react-pure-annotations@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.16.5.tgz#6535d0fe67c7a3a26c5105f92c8cbcbe844cd94b" - integrity sha512-0nYU30hCxnCVCbRjSy9ahlhWZ2Sn6khbY4FqR91W+2RbSqkWEbVu2gXh45EqNy4Bq7sRU+H4i0/6YKwOSzh16A== +"@babel/plugin-transform-react-pure-annotations@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.16.7.tgz#232bfd2f12eb551d6d7d01d13fe3f86b45eb9c67" + integrity sha512-hs71ToC97k3QWxswh2ElzMFABXHvGiJ01IB1TbYQDGeWRKWz/MPUTh5jGExdHvosYKpnJW5Pm3S4+TA3FyX+GA== dependencies: - "@babel/helper-annotate-as-pure" "^7.16.0" - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-annotate-as-pure" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-regenerator@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.16.5.tgz#704cc6d8dd3dd4758267621ab7b36375238cef13" - integrity sha512-2z+it2eVWU8TtQQRauvGUqZwLy4+7rTfo6wO4npr+fvvN1SW30ZF3O/ZRCNmTuu4F5MIP8OJhXAhRV5QMJOuYg== +"@babel/plugin-transform-regenerator@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.16.7.tgz#9e7576dc476cb89ccc5096fff7af659243b4adeb" + integrity sha512-mF7jOgGYCkSJagJ6XCujSQg+6xC1M77/03K2oBmVJWoFGNUtnVJO4WHKJk3dnPC8HCcj4xBQP1Egm8DWh3Pb3Q== dependencies: regenerator-transform "^0.14.2" -"@babel/plugin-transform-reserved-words@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.16.5.tgz#db95e98799675e193dc2b47d3e72a7c0651d0c30" - integrity sha512-aIB16u8lNcf7drkhXJRoggOxSTUAuihTSTfAcpynowGJOZiGf+Yvi7RuTwFzVYSYPmWyARsPqUGoZWWWxLiknw== +"@babel/plugin-transform-reserved-words@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.16.7.tgz#1d798e078f7c5958eec952059c460b220a63f586" + integrity sha512-KQzzDnZ9hWQBjwi5lpY5v9shmm6IVG0U9pB18zvMu2i4H90xpT4gmqwPYsn8rObiadYe2M0gmgsiOIF5A/2rtg== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-runtime@^7.16.0": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.16.5.tgz#0cc3f01d69f299d5a42cd9ec43b92ea7a777b8db" - integrity sha512-gxpfS8XQWDbQ8oP5NcmpXxtEgCJkbO+W9VhZlOhr0xPyVaRjAQPOv7ZDj9fg0d5s9+NiVvMCE6gbkEkcsxwGRw== +"@babel/plugin-transform-runtime@^7.17.0": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.17.0.tgz#0a2e08b5e2b2d95c4b1d3b3371a2180617455b70" + integrity sha512-fr7zPWnKXNc1xoHfrIU9mN/4XKX4VLZ45Q+oMhfsYIaHvg7mHgmhfOy/ckRWqDK7XF3QDigRpkh5DKq6+clE8A== dependencies: - "@babel/helper-module-imports" "^7.16.0" - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-module-imports" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" babel-plugin-polyfill-corejs2 "^0.3.0" - babel-plugin-polyfill-corejs3 "^0.4.0" + babel-plugin-polyfill-corejs3 "^0.5.0" babel-plugin-polyfill-regenerator "^0.3.0" semver "^6.3.0" -"@babel/plugin-transform-shorthand-properties@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.5.tgz#ccb60b1a23b799f5b9a14d97c5bc81025ffd96d7" - integrity sha512-ZbuWVcY+MAXJuuW7qDoCwoxDUNClfZxoo7/4swVbOW1s/qYLOMHlm9YRWMsxMFuLs44eXsv4op1vAaBaBaDMVg== +"@babel/plugin-transform-shorthand-properties@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.7.tgz#e8549ae4afcf8382f711794c0c7b6b934c5fbd2a" + integrity sha512-hah2+FEnoRoATdIb05IOXf+4GzXYTq75TVhIn1PewihbpyrNWUt2JbudKQOETWw6QpLe+AIUpJ5MVLYTQbeeUg== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-spread@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.16.5.tgz#912b06cff482c233025d3e69cf56d3e8fa166c29" - integrity sha512-5d6l/cnG7Lw4tGHEoga4xSkYp1euP7LAtrah1h1PgJ3JY7yNsjybsxQAnVK4JbtReZ/8z6ASVmd3QhYYKLaKZw== +"@babel/plugin-transform-spread@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.16.7.tgz#a303e2122f9f12e0105daeedd0f30fb197d8ff44" + integrity sha512-+pjJpgAngb53L0iaA5gU/1MLXJIfXcYepLgXB3esVRf4fqmj8f2cxM3/FKaHsZms08hFQJkFccEWuIpm429TXg== dependencies: - "@babel/helper-plugin-utils" "^7.16.5" + "@babel/helper-plugin-utils" "^7.16.7" "@babel/helper-skip-transparent-expression-wrappers" "^7.16.0" -"@babel/plugin-transform-sticky-regex@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.5.tgz#593579bb2b5a8adfbe02cb43823275d9098f75f9" - integrity sha512-usYsuO1ID2LXxzuUxifgWtJemP7wL2uZtyrTVM4PKqsmJycdS4U4mGovL5xXkfUheds10Dd2PjoQLXw6zCsCbg== - dependencies: - "@babel/helper-plugin-utils" "^7.16.5" - -"@babel/plugin-transform-template-literals@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.16.5.tgz#343651385fd9923f5aa2275ca352c5d9183e1773" - integrity sha512-gnyKy9RyFhkovex4BjKWL3BVYzUDG6zC0gba7VMLbQoDuqMfJ1SDXs8k/XK41Mmt1Hyp4qNAvGFb9hKzdCqBRQ== - dependencies: - "@babel/helper-plugin-utils" "^7.16.5" - -"@babel/plugin-transform-typeof-symbol@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.16.5.tgz#a1d1bf2c71573fe30965d0e4cd6a3291202e20ed" - integrity sha512-ldxCkW180qbrvyCVDzAUZqB0TAeF8W/vGJoRcaf75awm6By+PxfJKvuqVAnq8N9wz5Xa6mSpM19OfVKKVmGHSQ== - dependencies: - "@babel/helper-plugin-utils" "^7.16.5" - -"@babel/plugin-transform-typescript@^7.16.1": - version "7.16.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.16.1.tgz#cc0670b2822b0338355bc1b3d2246a42b8166409" - integrity sha512-NO4XoryBng06jjw/qWEU2LhcLJr1tWkhpMam/H4eas/CDKMX/b2/Ylb6EI256Y7+FVPCawwSM1rrJNOpDiz+Lg== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.16.0" - "@babel/helper-plugin-utils" "^7.14.5" - "@babel/plugin-syntax-typescript" "^7.16.0" - -"@babel/plugin-transform-unicode-escapes@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.16.5.tgz#80507c225af49b4f4ee647e2a0ce53d2eeff9e85" - integrity sha512-shiCBHTIIChGLdyojsKQjoAyB8MBwat25lKM7MJjbe1hE0bgIppD+LX9afr41lLHOhqceqeWl4FkLp+Bgn9o1Q== - dependencies: - "@babel/helper-plugin-utils" "^7.16.5" - -"@babel/plugin-transform-unicode-regex@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.5.tgz#ac84d6a1def947d71ffb832426aa53b83d7ed49e" - integrity sha512-GTJ4IW012tiPEMMubd7sD07iU9O/LOo8Q/oU4xNhcaq0Xn8+6TcUQaHtC8YxySo1T+ErQ8RaWogIEeFhKGNPzw== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.16.0" - "@babel/helper-plugin-utils" "^7.16.5" - -"@babel/preset-env@^7.15.6", "@babel/preset-env@^7.16.4": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.16.5.tgz#2e94d922f4a890979af04ffeb6a6b4e44ba90847" - integrity sha512-MiJJW5pwsktG61NDxpZ4oJ1CKxM1ncam9bzRtx9g40/WkLRkxFP6mhpkYV0/DxcciqoiHicx291+eUQrXb/SfQ== - dependencies: - "@babel/compat-data" "^7.16.4" - "@babel/helper-compilation-targets" "^7.16.3" - "@babel/helper-plugin-utils" "^7.16.5" - "@babel/helper-validator-option" "^7.14.5" - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.16.2" - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.16.0" - "@babel/plugin-proposal-async-generator-functions" "^7.16.5" - "@babel/plugin-proposal-class-properties" "^7.16.5" - "@babel/plugin-proposal-class-static-block" "^7.16.5" - "@babel/plugin-proposal-dynamic-import" "^7.16.5" - "@babel/plugin-proposal-export-namespace-from" "^7.16.5" - "@babel/plugin-proposal-json-strings" "^7.16.5" - "@babel/plugin-proposal-logical-assignment-operators" "^7.16.5" - "@babel/plugin-proposal-nullish-coalescing-operator" "^7.16.5" - "@babel/plugin-proposal-numeric-separator" "^7.16.5" - "@babel/plugin-proposal-object-rest-spread" "^7.16.5" - "@babel/plugin-proposal-optional-catch-binding" "^7.16.5" - "@babel/plugin-proposal-optional-chaining" "^7.16.5" - "@babel/plugin-proposal-private-methods" "^7.16.5" - "@babel/plugin-proposal-private-property-in-object" "^7.16.5" - "@babel/plugin-proposal-unicode-property-regex" "^7.16.5" +"@babel/plugin-transform-sticky-regex@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.7.tgz#c84741d4f4a38072b9a1e2e3fd56d359552e8660" + integrity sha512-NJa0Bd/87QV5NZZzTuZG5BPJjLYadeSZ9fO6oOUoL4iQx+9EEuw/eEM92SrsT19Yc2jgB1u1hsjqDtH02c3Drw== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-template-literals@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.16.7.tgz#f3d1c45d28967c8e80f53666fc9c3e50618217ab" + integrity sha512-VwbkDDUeenlIjmfNeDX/V0aWrQH2QiVyJtwymVQSzItFDTpxfyJh3EVaQiS0rIN/CqbLGr0VcGmuwyTdZtdIsA== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-typeof-symbol@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.16.7.tgz#9cdbe622582c21368bd482b660ba87d5545d4f7e" + integrity sha512-p2rOixCKRJzpg9JB4gjnG4gjWkWa89ZoYUnl9snJ1cWIcTH/hvxZqfO+WjG6T8DRBpctEol5jw1O5rA8gkCokQ== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-typescript@^7.16.7": + version "7.16.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.16.8.tgz#591ce9b6b83504903fa9dd3652c357c2ba7a1ee0" + integrity sha512-bHdQ9k7YpBDO2d0NVfkj51DpQcvwIzIusJ7mEUaMlbZq3Kt/U47j24inXZHQ5MDiYpCs+oZiwnXyKedE8+q7AQ== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/plugin-syntax-typescript" "^7.16.7" + +"@babel/plugin-transform-unicode-escapes@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.16.7.tgz#da8717de7b3287a2c6d659750c964f302b31ece3" + integrity sha512-TAV5IGahIz3yZ9/Hfv35TV2xEm+kaBDaZQCn2S/hG9/CZ0DktxJv9eKfPc7yYCvOYR4JGx1h8C+jcSOvgaaI/Q== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-unicode-regex@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.7.tgz#0f7aa4a501198976e25e82702574c34cfebe9ef2" + integrity sha512-oC5tYYKw56HO75KZVLQ+R/Nl3Hro9kf8iG0hXoaHP7tjAyCpvqBiSNe6vGrZni1Z6MggmUOC6A7VP7AVmw225Q== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/preset-env@^7.15.6", "@babel/preset-env@^7.16.11": + version "7.16.11" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.16.11.tgz#5dd88fd885fae36f88fd7c8342475c9f0abe2982" + integrity sha512-qcmWG8R7ZW6WBRPZK//y+E3Cli151B20W1Rv7ln27vuPaXU/8TKms6jFdiJtF7UDTxcrb7mZd88tAeK9LjdT8g== + dependencies: + "@babel/compat-data" "^7.16.8" + "@babel/helper-compilation-targets" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-validator-option" "^7.16.7" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.16.7" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.16.7" + "@babel/plugin-proposal-async-generator-functions" "^7.16.8" + "@babel/plugin-proposal-class-properties" "^7.16.7" + "@babel/plugin-proposal-class-static-block" "^7.16.7" + "@babel/plugin-proposal-dynamic-import" "^7.16.7" + "@babel/plugin-proposal-export-namespace-from" "^7.16.7" + "@babel/plugin-proposal-json-strings" "^7.16.7" + "@babel/plugin-proposal-logical-assignment-operators" "^7.16.7" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.16.7" + "@babel/plugin-proposal-numeric-separator" "^7.16.7" + "@babel/plugin-proposal-object-rest-spread" "^7.16.7" + "@babel/plugin-proposal-optional-catch-binding" "^7.16.7" + "@babel/plugin-proposal-optional-chaining" "^7.16.7" + "@babel/plugin-proposal-private-methods" "^7.16.11" + "@babel/plugin-proposal-private-property-in-object" "^7.16.7" + "@babel/plugin-proposal-unicode-property-regex" "^7.16.7" "@babel/plugin-syntax-async-generators" "^7.8.4" "@babel/plugin-syntax-class-properties" "^7.12.13" "@babel/plugin-syntax-class-static-block" "^7.14.5" @@ -1051,44 +1059,44 @@ "@babel/plugin-syntax-optional-chaining" "^7.8.3" "@babel/plugin-syntax-private-property-in-object" "^7.14.5" "@babel/plugin-syntax-top-level-await" "^7.14.5" - "@babel/plugin-transform-arrow-functions" "^7.16.5" - "@babel/plugin-transform-async-to-generator" "^7.16.5" - "@babel/plugin-transform-block-scoped-functions" "^7.16.5" - "@babel/plugin-transform-block-scoping" "^7.16.5" - "@babel/plugin-transform-classes" "^7.16.5" - "@babel/plugin-transform-computed-properties" "^7.16.5" - "@babel/plugin-transform-destructuring" "^7.16.5" - "@babel/plugin-transform-dotall-regex" "^7.16.5" - "@babel/plugin-transform-duplicate-keys" "^7.16.5" - "@babel/plugin-transform-exponentiation-operator" "^7.16.5" - "@babel/plugin-transform-for-of" "^7.16.5" - "@babel/plugin-transform-function-name" "^7.16.5" - "@babel/plugin-transform-literals" "^7.16.5" - "@babel/plugin-transform-member-expression-literals" "^7.16.5" - "@babel/plugin-transform-modules-amd" "^7.16.5" - "@babel/plugin-transform-modules-commonjs" "^7.16.5" - "@babel/plugin-transform-modules-systemjs" "^7.16.5" - "@babel/plugin-transform-modules-umd" "^7.16.5" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.16.5" - "@babel/plugin-transform-new-target" "^7.16.5" - "@babel/plugin-transform-object-super" "^7.16.5" - "@babel/plugin-transform-parameters" "^7.16.5" - "@babel/plugin-transform-property-literals" "^7.16.5" - "@babel/plugin-transform-regenerator" "^7.16.5" - "@babel/plugin-transform-reserved-words" "^7.16.5" - "@babel/plugin-transform-shorthand-properties" "^7.16.5" - "@babel/plugin-transform-spread" "^7.16.5" - "@babel/plugin-transform-sticky-regex" "^7.16.5" - "@babel/plugin-transform-template-literals" "^7.16.5" - "@babel/plugin-transform-typeof-symbol" "^7.16.5" - "@babel/plugin-transform-unicode-escapes" "^7.16.5" - "@babel/plugin-transform-unicode-regex" "^7.16.5" + "@babel/plugin-transform-arrow-functions" "^7.16.7" + "@babel/plugin-transform-async-to-generator" "^7.16.8" + "@babel/plugin-transform-block-scoped-functions" "^7.16.7" + "@babel/plugin-transform-block-scoping" "^7.16.7" + "@babel/plugin-transform-classes" "^7.16.7" + "@babel/plugin-transform-computed-properties" "^7.16.7" + "@babel/plugin-transform-destructuring" "^7.16.7" + "@babel/plugin-transform-dotall-regex" "^7.16.7" + "@babel/plugin-transform-duplicate-keys" "^7.16.7" + "@babel/plugin-transform-exponentiation-operator" "^7.16.7" + "@babel/plugin-transform-for-of" "^7.16.7" + "@babel/plugin-transform-function-name" "^7.16.7" + "@babel/plugin-transform-literals" "^7.16.7" + "@babel/plugin-transform-member-expression-literals" "^7.16.7" + "@babel/plugin-transform-modules-amd" "^7.16.7" + "@babel/plugin-transform-modules-commonjs" "^7.16.8" + "@babel/plugin-transform-modules-systemjs" "^7.16.7" + "@babel/plugin-transform-modules-umd" "^7.16.7" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.16.8" + "@babel/plugin-transform-new-target" "^7.16.7" + "@babel/plugin-transform-object-super" "^7.16.7" + "@babel/plugin-transform-parameters" "^7.16.7" + "@babel/plugin-transform-property-literals" "^7.16.7" + "@babel/plugin-transform-regenerator" "^7.16.7" + "@babel/plugin-transform-reserved-words" "^7.16.7" + "@babel/plugin-transform-shorthand-properties" "^7.16.7" + "@babel/plugin-transform-spread" "^7.16.7" + "@babel/plugin-transform-sticky-regex" "^7.16.7" + "@babel/plugin-transform-template-literals" "^7.16.7" + "@babel/plugin-transform-typeof-symbol" "^7.16.7" + "@babel/plugin-transform-unicode-escapes" "^7.16.7" + "@babel/plugin-transform-unicode-regex" "^7.16.7" "@babel/preset-modules" "^0.1.5" - "@babel/types" "^7.16.0" + "@babel/types" "^7.16.8" babel-plugin-polyfill-corejs2 "^0.3.0" - babel-plugin-polyfill-corejs3 "^0.4.0" + babel-plugin-polyfill-corejs3 "^0.5.0" babel-plugin-polyfill-regenerator "^0.3.0" - core-js-compat "^3.19.1" + core-js-compat "^3.20.2" semver "^6.3.0" "@babel/preset-modules@^0.1.5": @@ -1102,329 +1110,332 @@ "@babel/types" "^7.4.4" esutils "^2.0.2" -"@babel/preset-react@^7.14.5", "@babel/preset-react@^7.16.0": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.16.5.tgz#09df3b7a6522cb3e6682dc89b4dfebb97d22031b" - integrity sha512-3kzUOQeaxY/2vhPDS7CX/KGEGu/1bOYGvdRDJ2U5yjEz5o5jmIeTPLoiQBPGjfhPascLuW5OlMiPzwOOuB6txg== - dependencies: - "@babel/helper-plugin-utils" "^7.16.5" - "@babel/helper-validator-option" "^7.14.5" - "@babel/plugin-transform-react-display-name" "^7.16.5" - "@babel/plugin-transform-react-jsx" "^7.16.5" - "@babel/plugin-transform-react-jsx-development" "^7.16.5" - "@babel/plugin-transform-react-pure-annotations" "^7.16.5" - -"@babel/preset-typescript@^7.15.0", "@babel/preset-typescript@^7.16.0": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.16.5.tgz#b86a5b0ae739ba741347d2f58c52f52e63cf1ba1" - integrity sha512-lmAWRoJ9iOSvs3DqOndQpj8XqXkzaiQs50VG/zESiI9D3eoZhGriU675xNCr0UwvsuXrhMAGvyk1w+EVWF3u8Q== - dependencies: - "@babel/helper-plugin-utils" "^7.16.5" - "@babel/helper-validator-option" "^7.14.5" - "@babel/plugin-transform-typescript" "^7.16.1" - -"@babel/runtime-corejs3@^7.10.2", "@babel/runtime-corejs3@^7.16.3": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.16.5.tgz#9057d879720c136193f0440bc400088212a74894" - integrity sha512-F1pMwvTiUNSAM8mc45kccMQxj31x3y3P+tA/X8hKNWp3/hUsxdGxZ3D3H8JIkxtfA8qGkaBTKvcmvStaYseAFw== - dependencies: - core-js-pure "^3.19.0" +"@babel/preset-react@^7.14.5", "@babel/preset-react@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.16.7.tgz#4c18150491edc69c183ff818f9f2aecbe5d93852" + integrity sha512-fWpyI8UM/HE6DfPBzD8LnhQ/OcH8AgTaqcqP2nGOXEUV+VKBR5JRN9hCk9ai+zQQ57vtm9oWeXguBCPNUjytgA== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-validator-option" "^7.16.7" + "@babel/plugin-transform-react-display-name" "^7.16.7" + "@babel/plugin-transform-react-jsx" "^7.16.7" + "@babel/plugin-transform-react-jsx-development" "^7.16.7" + "@babel/plugin-transform-react-pure-annotations" "^7.16.7" + +"@babel/preset-typescript@^7.15.0", "@babel/preset-typescript@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.16.7.tgz#ab114d68bb2020afc069cd51b37ff98a046a70b9" + integrity sha512-WbVEmgXdIyvzB77AQjGBEyYPZx+8tTsO50XtfozQrkW8QB2rLJpH2lgx0TRw5EJrBxOZQ+wCcyPVQvS8tjEHpQ== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-validator-option" "^7.16.7" + "@babel/plugin-transform-typescript" "^7.16.7" + +"@babel/runtime-corejs3@^7.10.2", "@babel/runtime-corejs3@^7.17.8": + version "7.17.8" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.17.8.tgz#d7dd49fb812f29c61c59126da3792d8740d4e284" + integrity sha512-ZbYSUvoSF6dXZmMl/CYTMOvzIFnbGfv4W3SEHYgMvNsFTeLaF2gkGAF4K2ddmtSK4Emej+0aYcnSC6N5dPCXUQ== + dependencies: + core-js-pure "^3.20.2" regenerator-runtime "^0.13.4" -"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.16.3", "@babel/runtime@^7.8.4": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.5.tgz#7f3e34bf8bdbbadf03fbb7b1ea0d929569c9487a" - integrity sha512-TXWihFIS3Pyv5hzR7j6ihmeLkZfrXGxAr5UfSl8CHf+6q/wpiYDkUau0czckpYG8QmnCIuPpdLtuA9VmuGGyMA== +"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.17.8", "@babel/runtime@^7.8.4": + version "7.17.8" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.8.tgz#3e56e4aff81befa55ac3ac6a0967349fd1c5bca2" + integrity sha512-dQpEpK0O9o6lj6oPu0gRDbbnk+4LeHlNcBpspf6Olzt3GIX4P1lWF1gS+pHLDFlaJvbR6q7jCfQ08zA4QJBnmA== dependencies: regenerator-runtime "^0.13.4" -"@babel/template@^7.12.7", "@babel/template@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.0.tgz#d16a35ebf4cd74e202083356fab21dd89363ddd6" - integrity sha512-MnZdpFD/ZdYhXwiunMqqgyZyucaYsbL0IrjoGjaVhGilz+x8YB++kRfygSOIj1yOtWKPlx7NBp+9I1RQSgsd5A== - dependencies: - "@babel/code-frame" "^7.16.0" - "@babel/parser" "^7.16.0" - "@babel/types" "^7.16.0" - -"@babel/traverse@^7.12.9", "@babel/traverse@^7.13.0", "@babel/traverse@^7.16.3", "@babel/traverse@^7.16.5": - version "7.16.5" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.16.5.tgz#d7d400a8229c714a59b87624fc67b0f1fbd4b2b3" - integrity sha512-FOCODAzqUMROikDYLYxl4nmwiLlu85rNqBML/A5hKRVXG2LV8d0iMqgPzdYTcIpjZEBB7D6UDU9vxRZiriASdQ== - dependencies: - "@babel/code-frame" "^7.16.0" - "@babel/generator" "^7.16.5" - "@babel/helper-environment-visitor" "^7.16.5" - "@babel/helper-function-name" "^7.16.0" - "@babel/helper-hoist-variables" "^7.16.0" - "@babel/helper-split-export-declaration" "^7.16.0" - "@babel/parser" "^7.16.5" - "@babel/types" "^7.16.0" +"@babel/template@^7.12.7", "@babel/template@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155" + integrity sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w== + dependencies: + "@babel/code-frame" "^7.16.7" + "@babel/parser" "^7.16.7" + "@babel/types" "^7.16.7" + +"@babel/traverse@^7.12.9", "@babel/traverse@^7.13.0", "@babel/traverse@^7.16.7", "@babel/traverse@^7.16.8", "@babel/traverse@^7.17.3": + version "7.17.3" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.17.3.tgz#0ae0f15b27d9a92ba1f2263358ea7c4e7db47b57" + integrity sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw== + dependencies: + "@babel/code-frame" "^7.16.7" + "@babel/generator" "^7.17.3" + "@babel/helper-environment-visitor" "^7.16.7" + "@babel/helper-function-name" "^7.16.7" + "@babel/helper-hoist-variables" "^7.16.7" + "@babel/helper-split-export-declaration" "^7.16.7" + "@babel/parser" "^7.17.3" + "@babel/types" "^7.17.0" debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.12.7", "@babel/types@^7.15.6", "@babel/types@^7.16.0", "@babel/types@^7.4.4": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.16.0.tgz#db3b313804f96aadd0b776c4823e127ad67289ba" - integrity sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg== +"@babel/types@^7.12.7", "@babel/types@^7.15.6", "@babel/types@^7.16.0", "@babel/types@^7.16.7", "@babel/types@^7.16.8", "@babel/types@^7.17.0", "@babel/types@^7.4.4": + version "7.17.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.17.0.tgz#a826e368bccb6b3d84acd76acad5c0d87342390b" + integrity sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw== dependencies: - "@babel/helper-validator-identifier" "^7.15.7" + "@babel/helper-validator-identifier" "^7.16.7" to-fast-properties "^2.0.0" -"@docsearch/css@3.0.0-alpha.42": - version "3.0.0-alpha.42" - resolved "https://registry.yarnpkg.com/@docsearch/css/-/css-3.0.0-alpha.42.tgz#deb6049e999d6ca9451eba4793cb5b6da28c8773" - integrity sha512-AGwI2AXUacYhVOHmYnsXoYDJKO6Ued2W+QO80GERbMLhC7GH5tfvtW5REs/s7jSdcU3vzFoxT8iPDBCh/PkrlQ== +"@docsearch/css@3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@docsearch/css/-/css-3.0.0.tgz#fe57b474802ffd706d3246eab25d52fac8aa3698" + integrity sha512-1kkV7tkAsiuEd0shunYRByKJe3xQDG2q7wYg24SOw1nV9/2lwEd4WrUYRJC/ukGTl2/kHeFxsaUvtiOy0y6fFA== -"@docsearch/react@^3.0.0-alpha.39": - version "3.0.0-alpha.42" - resolved "https://registry.yarnpkg.com/@docsearch/react/-/react-3.0.0-alpha.42.tgz#1d22a2b05779f24d090ff8d7ff2699e4d50dff5c" - integrity sha512-1aOslZJDxwUUcm2QRNmlEePUgL8P5fOAeFdOLDMctHQkV2iTja9/rKVbkP8FZbIUnZxuuCCn8ErLrjD/oXWOag== +"@docsearch/react@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@docsearch/react/-/react-3.0.0.tgz#d02ebdc67573412185a6a4df13bc254c7c0da491" + integrity sha512-yhMacqS6TVQYoBh/o603zszIb5Bl8MIXuOc6Vy617I74pirisDzzcNh0NEaYQt50fVVR3khUbeEhUEWEWipESg== dependencies: - "@algolia/autocomplete-core" "1.5.0" - "@algolia/autocomplete-preset-algolia" "1.5.0" - "@docsearch/css" "3.0.0-alpha.42" + "@algolia/autocomplete-core" "1.5.2" + "@algolia/autocomplete-preset-algolia" "1.5.2" + "@docsearch/css" "3.0.0" algoliasearch "^4.0.0" -"@docusaurus/core@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/core/-/core-2.0.0-beta.14.tgz#9baf8fbfe29f444f985616013b5d80435ea5f29e" - integrity sha512-dW95WbD+WE+35Ee1RYIS1QDcBhvUxUWuDmrWr1X0uH5ZHIeOmOnsGVjjn4FA8VN2MkF0uuWknmRakQmJk0KMZw== +"@docusaurus/core@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/core/-/core-2.0.0-beta.18.tgz#44c6eefe29257462df630640a35f0c86bd80639a" + integrity sha512-puV7l+0/BPSi07Xmr8tVktfs1BzhC8P5pm6Bs2CfvysCJ4nefNCD1CosPc1PGBWy901KqeeEJ1aoGwj9tU3AUA== dependencies: - "@babel/core" "^7.16.0" - "@babel/generator" "^7.16.0" + "@babel/core" "^7.17.8" + "@babel/generator" "^7.17.7" "@babel/plugin-syntax-dynamic-import" "^7.8.3" - "@babel/plugin-transform-runtime" "^7.16.0" - "@babel/preset-env" "^7.16.4" - "@babel/preset-react" "^7.16.0" - "@babel/preset-typescript" "^7.16.0" - "@babel/runtime" "^7.16.3" - "@babel/runtime-corejs3" "^7.16.3" - "@babel/traverse" "^7.16.3" - "@docusaurus/cssnano-preset" "2.0.0-beta.14" - "@docusaurus/logger" "2.0.0-beta.14" - "@docusaurus/mdx-loader" "2.0.0-beta.14" + "@babel/plugin-transform-runtime" "^7.17.0" + "@babel/preset-env" "^7.16.11" + "@babel/preset-react" "^7.16.7" + "@babel/preset-typescript" "^7.16.7" + "@babel/runtime" "^7.17.8" + "@babel/runtime-corejs3" "^7.17.8" + "@babel/traverse" "^7.17.3" + "@docusaurus/cssnano-preset" "2.0.0-beta.18" + "@docusaurus/logger" "2.0.0-beta.18" + "@docusaurus/mdx-loader" "2.0.0-beta.18" "@docusaurus/react-loadable" "5.5.2" - "@docusaurus/utils" "2.0.0-beta.14" - "@docusaurus/utils-common" "2.0.0-beta.14" - "@docusaurus/utils-validation" "2.0.0-beta.14" - "@slorber/static-site-generator-webpack-plugin" "^4.0.0" - "@svgr/webpack" "^6.0.0" - autoprefixer "^10.3.5" - babel-loader "^8.2.2" + "@docusaurus/utils" "2.0.0-beta.18" + "@docusaurus/utils-common" "2.0.0-beta.18" + "@docusaurus/utils-validation" "2.0.0-beta.18" + "@slorber/static-site-generator-webpack-plugin" "^4.0.4" + "@svgr/webpack" "^6.2.1" + autoprefixer "^10.4.4" + babel-loader "^8.2.4" babel-plugin-dynamic-import-node "2.3.0" - boxen "^5.0.1" - chokidar "^3.5.2" - clean-css "^5.1.5" + boxen "^6.2.1" + chokidar "^3.5.3" + clean-css "^5.2.4" + cli-table3 "^0.6.1" + combine-promises "^1.1.0" commander "^5.1.0" - copy-webpack-plugin "^9.0.1" - core-js "^3.18.0" - css-loader "^5.1.1" - css-minimizer-webpack-plugin "^3.0.2" - cssnano "^5.0.8" + copy-webpack-plugin "^10.2.4" + core-js "^3.21.1" + css-loader "^6.7.1" + css-minimizer-webpack-plugin "^3.4.1" + cssnano "^5.1.5" del "^6.0.0" detect-port "^1.3.0" escape-html "^1.0.3" eta "^1.12.3" file-loader "^6.2.0" - fs-extra "^10.0.0" - globby "^11.0.2" - html-minifier-terser "^6.0.2" + fs-extra "^10.0.1" + html-minifier-terser "^6.1.0" html-tags "^3.1.0" - html-webpack-plugin "^5.4.0" + html-webpack-plugin "^5.5.0" import-fresh "^3.3.0" is-root "^2.1.0" leven "^3.1.0" - lodash "^4.17.20" - mini-css-extract-plugin "^1.6.0" + lodash "^4.17.21" + mini-css-extract-plugin "^2.6.0" nprogress "^0.2.0" - postcss "^8.3.7" - postcss-loader "^6.1.1" - prompts "^2.4.1" - react-dev-utils "12.0.0-next.47" - react-error-overlay "^6.0.9" - react-helmet "^6.1.0" + postcss "^8.4.12" + postcss-loader "^6.2.1" + prompts "^2.4.2" + react-dev-utils "^12.0.0" + react-helmet-async "^1.2.3" react-loadable "npm:@docusaurus/react-loadable@5.5.2" react-loadable-ssr-addon-v5-slorber "^1.0.1" react-router "^5.2.0" react-router-config "^5.1.1" react-router-dom "^5.2.0" remark-admonitions "^1.2.1" - resolve-pathname "^3.0.0" rtl-detect "^1.0.4" - semver "^7.3.4" + semver "^7.3.5" serve-handler "^6.1.3" - shelljs "^0.8.4" - strip-ansi "^6.0.0" - terser-webpack-plugin "^5.2.4" + shelljs "^0.8.5" + terser-webpack-plugin "^5.3.1" tslib "^2.3.1" update-notifier "^5.1.0" url-loader "^4.1.1" - wait-on "^6.0.0" - webpack "^5.61.0" - webpack-bundle-analyzer "^4.4.2" - webpack-dev-server "^4.5.0" + wait-on "^6.0.1" + webpack "^5.70.0" + webpack-bundle-analyzer "^4.5.0" + webpack-dev-server "^4.7.4" webpack-merge "^5.8.0" - webpackbar "^5.0.0-3" + webpackbar "^5.0.2" -"@docusaurus/cssnano-preset@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/cssnano-preset/-/cssnano-preset-2.0.0-beta.14.tgz#99bad713e3b58a89f63c25cec90b83437c3b3f2d" - integrity sha512-O5CebLXrytSQSpa0cgoMIUZ19gnLfCHhHPYqMfKxk0kvgR6g8b5AbsXxaMbgFNAqH690zPRsXmXb39BmXC7fMg== +"@docusaurus/cssnano-preset@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/cssnano-preset/-/cssnano-preset-2.0.0-beta.18.tgz#235ac9064fe8f8da618349ce5305be3ed3a44e29" + integrity sha512-VxhYmpyx16Wv00W9TUfLVv0NgEK/BwP7pOdWoaiELEIAMV7SO1+6iB8gsFUhtfKZ31I4uPVLMKrCyWWakoFeFA== dependencies: - cssnano-preset-advanced "^5.1.4" - postcss "^8.3.7" - postcss-sort-media-queries "^4.1.0" + cssnano-preset-advanced "^5.3.1" + postcss "^8.4.12" + postcss-sort-media-queries "^4.2.1" -"@docusaurus/logger@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/logger/-/logger-2.0.0-beta.14.tgz#d8c4e5f1c8b39149705587b98ca926549be51064" - integrity sha512-KNK8RgTGArXXlTUGhHUcYLJCI51gTMerSoebNXpTxAOBHFqjwJKv95LqVOy/uotoJZDUeEWR4vS/szGz4g7NaA== +"@docusaurus/logger@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/logger/-/logger-2.0.0-beta.18.tgz#12302f312a083eb018caa28505b63f5dd4ab6a91" + integrity sha512-frNe5vhH3mbPmH980Lvzaz45+n1PQl3TkslzWYXQeJOkFX17zUd3e3U7F9kR1+DocmAqHkgAoWuXVcvEoN29fg== dependencies: chalk "^4.1.2" tslib "^2.3.1" -"@docusaurus/mdx-loader@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/mdx-loader/-/mdx-loader-2.0.0-beta.14.tgz#f4750a02a8d178c843bc50f29f5a92d6cd0692cd" - integrity sha512-lusTVTHc4WbNQY8bDM9zPQWZBIo70SnEyWzCqtznxpV7L3kjSoWEpBCHaYWE/lY2VhvayRsZtrqLwNs3KQgqXw== - dependencies: - "@babel/parser" "^7.16.4" - "@babel/traverse" "^7.16.3" - "@docusaurus/logger" "2.0.0-beta.14" - "@docusaurus/utils" "2.0.0-beta.14" - "@mdx-js/mdx" "^1.6.21" - "@mdx-js/react" "^1.6.21" +"@docusaurus/mdx-loader@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/mdx-loader/-/mdx-loader-2.0.0-beta.18.tgz#4a9fc0607e0a210a7d7db3108415208dd36e33d3" + integrity sha512-pOmAQM4Y1jhuZTbEhjh4ilQa74Mh6Q0pMZn1xgIuyYDdqvIOrOlM/H0i34YBn3+WYuwsGim4/X0qynJMLDUA4A== + dependencies: + "@babel/parser" "^7.17.8" + "@babel/traverse" "^7.17.3" + "@docusaurus/logger" "2.0.0-beta.18" + "@docusaurus/utils" "2.0.0-beta.18" + "@mdx-js/mdx" "^1.6.22" escape-html "^1.0.3" file-loader "^6.2.0" - fs-extra "^10.0.0" - gray-matter "^4.0.3" + fs-extra "^10.0.1" + image-size "^1.0.1" mdast-util-to-string "^2.0.0" remark-emoji "^2.1.0" stringify-object "^3.3.0" tslib "^2.3.1" unist-util-visit "^2.0.2" url-loader "^4.1.1" - webpack "^5.61.0" - -"@docusaurus/plugin-content-blog@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.0.0-beta.14.tgz#d390ab0ab3aceaeb0be7d49ccde0cf5a2e0b1566" - integrity sha512-MLDRNbQKxwBDsWADyBT/fES7F7xzEEGS8CsdTnm48l7yGSWL8GM3PT6YvjdqHxNxZw3RCRRPUAiJcjZwfOjd8w== - dependencies: - "@docusaurus/core" "2.0.0-beta.14" - "@docusaurus/logger" "2.0.0-beta.14" - "@docusaurus/mdx-loader" "2.0.0-beta.14" - "@docusaurus/utils" "2.0.0-beta.14" - "@docusaurus/utils-validation" "2.0.0-beta.14" - escape-string-regexp "^4.0.0" + webpack "^5.70.0" + +"@docusaurus/module-type-aliases@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/module-type-aliases/-/module-type-aliases-2.0.0-beta.18.tgz#001379229c58cbc3ed565e19437cbda86d5e8742" + integrity sha512-e6mples8FZRyT7QyqidGS6BgkROjM+gljJsdOqoctbtBp+SZ5YDjwRHOmoY7eqEfsQNOaFZvT2hK38ui87hCRA== + dependencies: + "@docusaurus/types" "2.0.0-beta.18" + "@types/react" "*" + "@types/react-router-config" "*" + "@types/react-router-dom" "*" + react-helmet-async "*" + +"@docusaurus/plugin-content-blog@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.0.0-beta.18.tgz#95fe3dfc8bae9bf153c65a3a441234c450cbac0a" + integrity sha512-qzK83DgB+mxklk3PQC2nuTGPQD/8ogw1nXSmaQpyXAyhzcz4CXAZ9Swl/Ee9A/bvPwQGnSHSP3xqIYl8OkFtfw== + dependencies: + "@docusaurus/core" "2.0.0-beta.18" + "@docusaurus/logger" "2.0.0-beta.18" + "@docusaurus/mdx-loader" "2.0.0-beta.18" + "@docusaurus/utils" "2.0.0-beta.18" + "@docusaurus/utils-common" "2.0.0-beta.18" + "@docusaurus/utils-validation" "2.0.0-beta.18" + cheerio "^1.0.0-rc.10" feed "^4.2.2" - fs-extra "^10.0.0" - globby "^11.0.2" - js-yaml "^4.0.0" - loader-utils "^2.0.0" - lodash "^4.17.20" + fs-extra "^10.0.1" + lodash "^4.17.21" reading-time "^1.5.0" remark-admonitions "^1.2.1" tslib "^2.3.1" utility-types "^3.10.0" - webpack "^5.61.0" - -"@docusaurus/plugin-content-docs@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-docs/-/plugin-content-docs-2.0.0-beta.14.tgz#533ba6ba471b45ba7a7867207b251f281a6bed1e" - integrity sha512-pjAhfFevIkVl/t+6x9RVsE+6c+VN8Ru1uImTgXk5uVkp6yS1AxW7neEngsczZ1gSiENfTiYyhgWmTXK/uy03kw== - dependencies: - "@docusaurus/core" "2.0.0-beta.14" - "@docusaurus/logger" "2.0.0-beta.14" - "@docusaurus/mdx-loader" "2.0.0-beta.14" - "@docusaurus/utils" "2.0.0-beta.14" - "@docusaurus/utils-validation" "2.0.0-beta.14" + webpack "^5.70.0" + +"@docusaurus/plugin-content-docs@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-docs/-/plugin-content-docs-2.0.0-beta.18.tgz#fef52d945da2928e0f4f3f9a9384d9ee7f2d4288" + integrity sha512-z4LFGBJuzn4XQiUA7OEA2SZTqlp+IYVjd3NrCk/ZUfNi1tsTJS36ATkk9Y6d0Nsp7K2kRXqaXPsz4adDgeIU+Q== + dependencies: + "@docusaurus/core" "2.0.0-beta.18" + "@docusaurus/logger" "2.0.0-beta.18" + "@docusaurus/mdx-loader" "2.0.0-beta.18" + "@docusaurus/utils" "2.0.0-beta.18" + "@docusaurus/utils-validation" "2.0.0-beta.18" combine-promises "^1.1.0" - escape-string-regexp "^4.0.0" - fs-extra "^10.0.0" - globby "^11.0.2" - import-fresh "^3.2.2" - js-yaml "^4.0.0" - loader-utils "^2.0.0" - lodash "^4.17.20" + fs-extra "^10.0.1" + import-fresh "^3.3.0" + js-yaml "^4.1.0" + lodash "^4.17.21" remark-admonitions "^1.2.1" - shelljs "^0.8.4" tslib "^2.3.1" utility-types "^3.10.0" - webpack "^5.61.0" - -"@docusaurus/plugin-content-pages@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-pages/-/plugin-content-pages-2.0.0-beta.14.tgz#7f176d585994339cbe5c65332ed321eec82f53e3" - integrity sha512-gGcMPG4e+K57cbBPf7IfV5lFCBdraXcpJeDqXlD8ArTeZrAe8Lx3SGz2lco25DgdRGqjMivab3BoT6Hkmo7vVA== - dependencies: - "@docusaurus/core" "2.0.0-beta.14" - "@docusaurus/mdx-loader" "2.0.0-beta.14" - "@docusaurus/utils" "2.0.0-beta.14" - "@docusaurus/utils-validation" "2.0.0-beta.14" - globby "^11.0.2" + webpack "^5.70.0" + +"@docusaurus/plugin-content-pages@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-pages/-/plugin-content-pages-2.0.0-beta.18.tgz#0fef392be3fea3d85c212caf4eb744ead920c30b" + integrity sha512-CJ2Xeb9hQrMeF4DGywSDVX2TFKsQpc8ZA7czyeBAAbSFsoRyxXPYeSh8aWljqR4F1u/EKGSKy0Shk/D4wumaHw== + dependencies: + "@docusaurus/core" "2.0.0-beta.18" + "@docusaurus/mdx-loader" "2.0.0-beta.18" + "@docusaurus/utils" "2.0.0-beta.18" + "@docusaurus/utils-validation" "2.0.0-beta.18" + fs-extra "^10.0.1" remark-admonitions "^1.2.1" tslib "^2.3.1" - webpack "^5.61.0" + webpack "^5.70.0" -"@docusaurus/plugin-debug@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-debug/-/plugin-debug-2.0.0-beta.14.tgz#74d661a5cfefded7c9c281956ec2ec02260b576d" - integrity sha512-l0T26nZ9keyG2HrWwfwwHdqRzJg6cEJahyvKmnAOFfKieHPMxCJ9axBW+Ecy2PUMwJO7rILc6UObbhifNH7PnQ== +"@docusaurus/plugin-debug@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-debug/-/plugin-debug-2.0.0-beta.18.tgz#d4582532e59b538a23398f7c444b005367efa922" + integrity sha512-inLnLERgG7q0WlVmK6nYGHwVqREz13ivkynmNygEibJZToFRdgnIPW+OwD8QzgC5MpQTJw7+uYjcitpBumy1Gw== dependencies: - "@docusaurus/core" "2.0.0-beta.14" - "@docusaurus/utils" "2.0.0-beta.14" - fs-extra "^10.0.0" + "@docusaurus/core" "2.0.0-beta.18" + "@docusaurus/utils" "2.0.0-beta.18" + fs-extra "^10.0.1" react-json-view "^1.21.3" tslib "^2.3.1" -"@docusaurus/plugin-google-analytics@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-2.0.0-beta.14.tgz#16bfdd9245767e008be88cfeb47c7ceeef3884f6" - integrity sha512-fVtAwqK9iHjj32Dtg0j+T6ikD8yjTh5ruYru7rKYxld6LSSkU29Q0wp39qYxR390jn3rkrXLRCZ7qHT/Hs0zZg== +"@docusaurus/plugin-google-analytics@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-2.0.0-beta.18.tgz#a9b1659abb3f588e866aaa742ec4c82fe943eda3" + integrity sha512-s9dRBWDrZ1uu3wFXPCF7yVLo/+5LUFAeoxpXxzory8gn9GYDt8ZDj80h5DUyCLxiy72OG6bXWNOYS/Vc6cOPXQ== dependencies: - "@docusaurus/core" "2.0.0-beta.14" - "@docusaurus/utils-validation" "2.0.0-beta.14" + "@docusaurus/core" "2.0.0-beta.18" + "@docusaurus/utils-validation" "2.0.0-beta.18" tslib "^2.3.1" -"@docusaurus/plugin-google-gtag@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-2.0.0-beta.14.tgz#be950af01da784965a7fd7ba61d557055cceeb5e" - integrity sha512-DcaNRvu0VLS/C6qRAG0QNWjnuP8dAdzH0NOfl86AxdK6dWOP5NlGD9QoIFKTa19PB8iTzM2XZn/hOCub4hR6MQ== +"@docusaurus/plugin-google-gtag@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-2.0.0-beta.18.tgz#b51611ac01915523ddcfc9732f7862cf4996a0e1" + integrity sha512-h7vPuLVo/9pHmbFcvb4tCpjg4SxxX4k+nfVDyippR254FM++Z/nA5pRB0WvvIJ3ZTe0ioOb5Wlx2xdzJIBHUNg== dependencies: - "@docusaurus/core" "2.0.0-beta.14" - "@docusaurus/utils-validation" "2.0.0-beta.14" + "@docusaurus/core" "2.0.0-beta.18" + "@docusaurus/utils-validation" "2.0.0-beta.18" tslib "^2.3.1" -"@docusaurus/plugin-sitemap@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-sitemap/-/plugin-sitemap-2.0.0-beta.14.tgz#13042fee40ab2a66615c44d9ef440abb3df5c42a" - integrity sha512-ikSgz4VAttDB2uOrPa7fq/E/GKS5HAtKfD572kBj8RvppdlgFYwCLZ88ex5cnRFF//2ccaobYkU4QwDw2UKWMA== - dependencies: - "@docusaurus/core" "2.0.0-beta.14" - "@docusaurus/utils" "2.0.0-beta.14" - "@docusaurus/utils-common" "2.0.0-beta.14" - "@docusaurus/utils-validation" "2.0.0-beta.14" - fs-extra "^10.0.0" - sitemap "^7.0.0" +"@docusaurus/plugin-sitemap@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-sitemap/-/plugin-sitemap-2.0.0-beta.18.tgz#7e8217e95bede5719bd02265dcf7eb2fea76b675" + integrity sha512-Klonht0Ye3FivdBpS80hkVYNOH+8lL/1rbCPEV92rKhwYdwnIejqhdKct4tUTCl8TYwWiyeUFQqobC/5FNVZPQ== + dependencies: + "@docusaurus/core" "2.0.0-beta.18" + "@docusaurus/utils" "2.0.0-beta.18" + "@docusaurus/utils-common" "2.0.0-beta.18" + "@docusaurus/utils-validation" "2.0.0-beta.18" + fs-extra "^10.0.1" + sitemap "^7.1.1" tslib "^2.3.1" -"@docusaurus/preset-classic@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/preset-classic/-/preset-classic-2.0.0-beta.14.tgz#128026fb201fdc6271614587ca09187bc83d930a" - integrity sha512-43rHA6wM4FcbHLPiBpqY4VSUjUXOWvW/N4q0wvf1LMoPH25lUzIaldpjD3Unzq5+UCYCFES24ktl58QOh7PB2g== - dependencies: - "@docusaurus/core" "2.0.0-beta.14" - "@docusaurus/plugin-content-blog" "2.0.0-beta.14" - "@docusaurus/plugin-content-docs" "2.0.0-beta.14" - "@docusaurus/plugin-content-pages" "2.0.0-beta.14" - "@docusaurus/plugin-debug" "2.0.0-beta.14" - "@docusaurus/plugin-google-analytics" "2.0.0-beta.14" - "@docusaurus/plugin-google-gtag" "2.0.0-beta.14" - "@docusaurus/plugin-sitemap" "2.0.0-beta.14" - "@docusaurus/theme-classic" "2.0.0-beta.14" - "@docusaurus/theme-search-algolia" "2.0.0-beta.14" +"@docusaurus/preset-classic@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/preset-classic/-/preset-classic-2.0.0-beta.18.tgz#82f6905d34a13e46289ac4d2f1125e47033bd9d8" + integrity sha512-TfDulvFt/vLWr/Yy7O0yXgwHtJhdkZ739bTlFNwEkRMAy8ggi650e52I1I0T79s67llecb4JihgHPW+mwiVkCQ== + dependencies: + "@docusaurus/core" "2.0.0-beta.18" + "@docusaurus/plugin-content-blog" "2.0.0-beta.18" + "@docusaurus/plugin-content-docs" "2.0.0-beta.18" + "@docusaurus/plugin-content-pages" "2.0.0-beta.18" + "@docusaurus/plugin-debug" "2.0.0-beta.18" + "@docusaurus/plugin-google-analytics" "2.0.0-beta.18" + "@docusaurus/plugin-google-gtag" "2.0.0-beta.18" + "@docusaurus/plugin-sitemap" "2.0.0-beta.18" + "@docusaurus/theme-classic" "2.0.0-beta.18" + "@docusaurus/theme-common" "2.0.0-beta.18" + "@docusaurus/theme-search-algolia" "2.0.0-beta.18" "@docusaurus/react-loadable@5.5.2", "react-loadable@npm:@docusaurus/react-loadable@5.5.2": version "5.5.2" @@ -1434,122 +1445,136 @@ "@types/react" "*" prop-types "^15.6.2" -"@docusaurus/theme-classic@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-classic/-/theme-classic-2.0.0-beta.14.tgz#1e11f0e034bbb530ce38e669bc61a8eeea839132" - integrity sha512-gAatNruzgPh1NdCcIJPkhBpZE4jmbO+nYwpk/scatYQWBkhOs/fcI9tieIaGZIqi60N6lAUYQkPH+qXtLxX7Iw== - dependencies: - "@docusaurus/core" "2.0.0-beta.14" - "@docusaurus/plugin-content-blog" "2.0.0-beta.14" - "@docusaurus/plugin-content-docs" "2.0.0-beta.14" - "@docusaurus/plugin-content-pages" "2.0.0-beta.14" - "@docusaurus/theme-common" "2.0.0-beta.14" - "@docusaurus/theme-translations" "2.0.0-beta.14" - "@docusaurus/utils" "2.0.0-beta.14" - "@docusaurus/utils-validation" "2.0.0-beta.14" - "@mdx-js/mdx" "^1.6.21" - "@mdx-js/react" "^1.6.21" +"@docusaurus/theme-classic@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-classic/-/theme-classic-2.0.0-beta.18.tgz#a3632e83923ed4372f80999128375cd0b378d3f8" + integrity sha512-WJWofvSGKC4Luidk0lyUwkLnO3DDynBBHwmt4QrV+aAVWWSOHUjA2mPOF6GLGuzkZd3KfL9EvAfsU0aGE1Hh5g== + dependencies: + "@docusaurus/core" "2.0.0-beta.18" + "@docusaurus/plugin-content-blog" "2.0.0-beta.18" + "@docusaurus/plugin-content-docs" "2.0.0-beta.18" + "@docusaurus/plugin-content-pages" "2.0.0-beta.18" + "@docusaurus/theme-common" "2.0.0-beta.18" + "@docusaurus/theme-translations" "2.0.0-beta.18" + "@docusaurus/utils" "2.0.0-beta.18" + "@docusaurus/utils-common" "2.0.0-beta.18" + "@docusaurus/utils-validation" "2.0.0-beta.18" + "@mdx-js/react" "^1.6.22" clsx "^1.1.1" copy-text-to-clipboard "^3.0.1" - globby "^11.0.2" - infima "0.2.0-alpha.37" - lodash "^4.17.20" - postcss "^8.3.7" - prism-react-renderer "^1.2.1" - prismjs "^1.23.0" + infima "0.2.0-alpha.38" + lodash "^4.17.21" + postcss "^8.4.12" + prism-react-renderer "^1.3.1" + prismjs "^1.27.0" react-router-dom "^5.2.0" - rtlcss "^3.3.0" + rtlcss "^3.5.0" -"@docusaurus/theme-common@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-common/-/theme-common-2.0.0-beta.14.tgz#9795071a0df62b7700f6fbdea09946f3aae8183d" - integrity sha512-hr/+rx9mszjMEbrR329WFSj1jl/VxglSggLWhXqswiA3Lh5rbbeQv2ExwpBl4JBG5HxvtHUYmwYOuOTMuvRYTQ== +"@docusaurus/theme-common@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-common/-/theme-common-2.0.0-beta.18.tgz#abf74f82c37d2ce813f92447cb020831290059fb" + integrity sha512-3pI2Q6ttScDVTDbuUKAx+TdC8wmwZ2hfWk8cyXxksvC9bBHcyzXhSgcK8LTsszn2aANyZ3e3QY2eNSOikTFyng== dependencies: - "@docusaurus/plugin-content-blog" "2.0.0-beta.14" - "@docusaurus/plugin-content-docs" "2.0.0-beta.14" - "@docusaurus/plugin-content-pages" "2.0.0-beta.14" + "@docusaurus/module-type-aliases" "2.0.0-beta.18" + "@docusaurus/plugin-content-blog" "2.0.0-beta.18" + "@docusaurus/plugin-content-docs" "2.0.0-beta.18" + "@docusaurus/plugin-content-pages" "2.0.0-beta.18" clsx "^1.1.1" - fs-extra "^10.0.0" parse-numeric-range "^1.3.0" + prism-react-renderer "^1.3.1" tslib "^2.3.1" utility-types "^3.10.0" -"@docusaurus/theme-search-algolia@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-search-algolia/-/theme-search-algolia-2.0.0-beta.14.tgz#0238622324251c42098b2ccada4e19c3e92cd772" - integrity sha512-kTQl8vKXn8FAVVkCeN4XvU8PGWZTHToc+35F9GL06b4rv33zL9HaFIRX3nPM1NHC7I8qh+6gGeV0DRKGjO+j2g== - dependencies: - "@docsearch/react" "^3.0.0-alpha.39" - "@docusaurus/core" "2.0.0-beta.14" - "@docusaurus/logger" "2.0.0-beta.14" - "@docusaurus/theme-common" "2.0.0-beta.14" - "@docusaurus/theme-translations" "2.0.0-beta.14" - "@docusaurus/utils" "2.0.0-beta.14" - "@docusaurus/utils-validation" "2.0.0-beta.14" - algoliasearch "^4.10.5" - algoliasearch-helper "^3.5.5" +"@docusaurus/theme-search-algolia@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-search-algolia/-/theme-search-algolia-2.0.0-beta.18.tgz#cbdda8982deac4556848e04853b7f32d93886c02" + integrity sha512-2w97KO/gnjI49WVtYQqENpQ8iO1Sem0yaTxw7/qv/ndlmIAQD0syU4yx6GsA7bTQCOGwKOWWzZSetCgUmTnWgA== + dependencies: + "@docsearch/react" "^3.0.0" + "@docusaurus/core" "2.0.0-beta.18" + "@docusaurus/logger" "2.0.0-beta.18" + "@docusaurus/plugin-content-docs" "2.0.0-beta.18" + "@docusaurus/theme-common" "2.0.0-beta.18" + "@docusaurus/theme-translations" "2.0.0-beta.18" + "@docusaurus/utils" "2.0.0-beta.18" + "@docusaurus/utils-validation" "2.0.0-beta.18" + algoliasearch "^4.13.0" + algoliasearch-helper "^3.7.4" clsx "^1.1.1" eta "^1.12.3" - lodash "^4.17.20" + fs-extra "^10.0.1" + lodash "^4.17.21" tslib "^2.3.1" + utility-types "^3.10.0" -"@docusaurus/theme-translations@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-translations/-/theme-translations-2.0.0-beta.14.tgz#30f230c66aad3e085d680d49db484b663041be75" - integrity sha512-b67qJJIWc3A2tanYslDGpAUGfJ7oVAl+AdjGBYG3j3hYEUSyVUBzm8Y4iyCFEfW6BTx9pjqC/ECNO3iH2L3Ixg== +"@docusaurus/theme-translations@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-translations/-/theme-translations-2.0.0-beta.18.tgz#292699ce89b013262683faf7f4ee7b75a8745a79" + integrity sha512-1uTEUXlKC9nco1Lx9H5eOwzB+LP4yXJG5wfv1PMLE++kJEdZ40IVorlUi3nJnaa9/lJNq5vFvvUDrmeNWsxy/Q== dependencies: - fs-extra "^10.0.0" + fs-extra "^10.0.1" tslib "^2.3.1" -"@docusaurus/utils-common@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/utils-common/-/utils-common-2.0.0-beta.14.tgz#4ee8a266366722b2c98e17c12b109236dd2b32fb" - integrity sha512-hNWyy083Bm+6jEzsm05gFmEfwumXph0E46s2HrWkSM8tClrOVmu/C1Rm7kWYn561gXHhrATtyXr/u8bKXByFcQ== +"@docusaurus/types@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/types/-/types-2.0.0-beta.18.tgz#9446928a6b751eefde390420b39eac32ba26abb2" + integrity sha512-zkuSmPQYP3+z4IjGHlW0nGzSSpY7Sit0Nciu/66zSb5m07TK72t6T1MlpCAn/XijcB9Cq6nenC3kJh66nGsKYg== + dependencies: + commander "^5.1.0" + joi "^17.6.0" + utility-types "^3.10.0" + webpack "^5.70.0" + webpack-merge "^5.8.0" + +"@docusaurus/utils-common@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/utils-common/-/utils-common-2.0.0-beta.18.tgz#46cf0bed2a7c532b2b85eab5bb914ff118b2c4e9" + integrity sha512-pK83EcOIiKCLGhrTwukZMo5jqd1sqqqhQwOVyxyvg+x9SY/lsnNzScA96OEfm+qQLBwK1OABA7Xc1wfkgkUxvw== dependencies: tslib "^2.3.1" -"@docusaurus/utils-validation@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/utils-validation/-/utils-validation-2.0.0-beta.14.tgz#c5e54adbe6dd4b3d6f5525ae5138c0214e75a6c2" - integrity sha512-ttDp/fXjbM6rTfP8XCmBKtNygfPg8cncp+rPsWHdSFjGmE7HkinilFTtaw0Zos/096TtxsQx3DgGQyPOl6prnA== +"@docusaurus/utils-validation@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/utils-validation/-/utils-validation-2.0.0-beta.18.tgz#0dabf113d2c53ee685a715cd4caae6e219e9e41e" + integrity sha512-3aDrXjJJ8Cw2MAYEk5JMNnr8UHPxmVNbPU/PIHFWmWK09nJvs3IQ8nc9+8I30aIjRdIyc/BIOCxgvAcJ4hsxTA== dependencies: - "@docusaurus/logger" "2.0.0-beta.14" - "@docusaurus/utils" "2.0.0-beta.14" - joi "^17.4.2" + "@docusaurus/logger" "2.0.0-beta.18" + "@docusaurus/utils" "2.0.0-beta.18" + joi "^17.6.0" + js-yaml "^4.1.0" tslib "^2.3.1" -"@docusaurus/utils@2.0.0-beta.14": - version "2.0.0-beta.14" - resolved "https://registry.yarnpkg.com/@docusaurus/utils/-/utils-2.0.0-beta.14.tgz#494d2181cc0fd264ebe12f2a08c6ae04878e5f90" - integrity sha512-7V+X70a+7UJHS7PeXS/BO2jz+zXaKhRlT7MUe5khu6i6n1oQA3Jqx1sfu78slemqEWe8u337jxal6uILcB0IWQ== +"@docusaurus/utils@2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@docusaurus/utils/-/utils-2.0.0-beta.18.tgz#c3fe0e9fac30db4510962263993fd0ee2679eebb" + integrity sha512-v2vBmH7xSbPwx3+GB90HgLSQdj+Rh5ELtZWy7M20w907k0ROzDmPQ/8Ke2DK3o5r4pZPGnCrsB3SaYI83AEmAA== dependencies: - "@docusaurus/logger" "2.0.0-beta.14" - "@mdx-js/runtime" "^1.6.22" - "@svgr/webpack" "^6.0.0" - escape-string-regexp "^4.0.0" + "@docusaurus/logger" "2.0.0-beta.18" + "@svgr/webpack" "^6.2.1" file-loader "^6.2.0" - fs-extra "^10.0.0" + fs-extra "^10.0.1" github-slugger "^1.4.0" - globby "^11.0.4" + globby "^11.1.0" gray-matter "^4.0.3" - lodash "^4.17.20" - micromatch "^4.0.4" - remark-mdx-remove-exports "^1.6.22" - remark-mdx-remove-imports "^1.6.22" + js-yaml "^4.1.0" + lodash "^4.17.21" + micromatch "^4.0.5" resolve-pathname "^3.0.0" + shelljs "^0.8.5" tslib "^2.3.1" url-loader "^4.1.1" + webpack "^5.70.0" -"@eslint/eslintrc@^1.0.5": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.0.5.tgz#33f1b838dbf1f923bfa517e008362b78ddbbf318" - integrity sha512-BLxsnmK3KyPunz5wmCCpqy0YelEoxxGmH73Is+Z74oOTMtExcjkr3dDR6quwrjh1YspA8DH9gnX1o069KiS9AQ== +"@eslint/eslintrc@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.2.1.tgz#8b5e1c49f4077235516bc9ec7d41378c0f69b8c6" + integrity sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ== dependencies: ajv "^6.12.4" debug "^4.3.2" - espree "^9.2.0" + espree "^9.3.1" globals "^13.9.0" - ignore "^4.0.6" + ignore "^5.2.0" import-fresh "^3.2.1" js-yaml "^4.1.0" minimatch "^3.0.4" @@ -1568,9 +1593,9 @@ "@hapi/hoek" "^9.0.0" "@humanwhocodes/config-array@^0.9.2": - version "0.9.2" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.9.2.tgz#68be55c737023009dfc5fe245d51181bb6476914" - integrity sha512-UXOuFCGcwciWckOpmfKDq/GyhlTf9pN/BzG//x8p8zTOFEcGuA68ANXheFS0AGvy3qgZqLBUkMs7hqzqCKOVwA== + version "0.9.5" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.9.5.tgz#2cbaf9a89460da24b5ca6531b8bbfc23e1df50c7" + integrity sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw== dependencies: "@humanwhocodes/object-schema" "^1.2.1" debug "^4.1.1" @@ -1581,7 +1606,25 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== -"@mdx-js/mdx@1.6.22", "@mdx-js/mdx@^1.6.21": +"@jridgewell/resolve-uri@^3.0.3": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz#68eb521368db76d040a6315cdb24bf2483037b9c" + integrity sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew== + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.11" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz#771a1d8d744eeb71b6adb35808e1a6c7b9b8c8ec" + integrity sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg== + +"@jridgewell/trace-mapping@^0.3.0": + version "0.3.4" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.4.tgz#f6a0832dffd5b8a6aaa633b7d9f8e8e94c83a0c3" + integrity sha512-vFv9ttIedivx0ux3QSjhgtCVjPZd5l46ZOMDSCwnH1yUO2e964gO8LZGyv2QkqcgR6TnBU1v+1IFqmeoG+0UJQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@mdx-js/mdx@^1.6.22": version "1.6.22" resolved "https://registry.yarnpkg.com/@mdx-js/mdx/-/mdx-1.6.22.tgz#8a723157bf90e78f17dc0f27995398e6c731f1ba" integrity sha512-AMxuLxPz2j5/6TpF/XSdKpQP1NlG0z11dFOlq+2IP/lSgl11GY8ji6S/rgsViN/L0BDvHvUMruRb7ub+24LUYA== @@ -1606,20 +1649,11 @@ unist-builder "2.0.3" unist-util-visit "2.0.3" -"@mdx-js/react@1.6.22", "@mdx-js/react@^1.6.21": +"@mdx-js/react@^1.6.22": version "1.6.22" resolved "https://registry.yarnpkg.com/@mdx-js/react/-/react-1.6.22.tgz#ae09b4744fddc74714ee9f9d6f17a66e77c43573" integrity sha512-TDoPum4SHdfPiGSAaRBw7ECyI8VaHpK8GJugbJIJuqyh6kzw9ZLJZW3HGL3NNrJGxcAixUvqROm+YuQOo5eXtg== -"@mdx-js/runtime@^1.6.22": - version "1.6.22" - resolved "https://registry.yarnpkg.com/@mdx-js/runtime/-/runtime-1.6.22.tgz#3edd388bf68a519ffa1aaf9c446b548165102345" - integrity sha512-p17spaO2+55VLCuxXA3LVHC4phRx60NR2XMdZ+qgVU1lKvEX4y88dmFNOzGDCPLJ03IZyKrJ/rPWWRiBrd9JrQ== - dependencies: - "@mdx-js/mdx" "1.6.22" - "@mdx-js/react" "1.6.22" - buble-jsx-only "^0.19.8" - "@mdx-js/util@1.6.22": version "1.6.22" resolved "https://registry.yarnpkg.com/@mdx-js/util/-/util-1.6.22.tgz#219dfd89ae5b97a8801f015323ffa4b62f45718b" @@ -1652,9 +1686,9 @@ integrity sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g== "@sideway/address@^4.1.3": - version "4.1.3" - resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.3.tgz#d93cce5d45c5daec92ad76db492cc2ee3c64ab27" - integrity sha512-8ncEUtmnTsMmL7z1YPB47kPUq7LpKWJNFPsRzHiIajGC5uXlWGn+AmkYPcHNl8S4tcEGx+cnORnNYaw2wvL+LQ== + version "4.1.4" + resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.4.tgz#03dccebc6ea47fdc226f7d3d1ad512955d4783f0" + integrity sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw== dependencies: "@hapi/hoek" "^9.0.0" @@ -1673,32 +1707,16 @@ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== -"@slorber/static-site-generator-webpack-plugin@^4.0.0": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@slorber/static-site-generator-webpack-plugin/-/static-site-generator-webpack-plugin-4.0.1.tgz#0c8852146441aaa683693deaa5aee2f991d94841" - integrity sha512-PSv4RIVO1Y3kvHxjvqeVisk3E9XFoO04uwYBDWe217MFqKspplYswTuKLiJu0aLORQWzuQjfVsSlLPojwfYsLw== +"@slorber/static-site-generator-webpack-plugin@^4.0.4": + version "4.0.4" + resolved "https://registry.yarnpkg.com/@slorber/static-site-generator-webpack-plugin/-/static-site-generator-webpack-plugin-4.0.4.tgz#2bf4a2545e027830d2aa5eb950437c26a289b0f1" + integrity sha512-FvMavoWEIePps6/JwGCOLYKCRhuwIHhMtmbKpBFgzNkxwpa/569LfTkrbRk1m1I3n+ezJK4on9E1A6cjuZmD9g== dependencies: bluebird "^3.7.1" cheerio "^0.22.0" - eval "^0.1.4" - url "^0.11.0" + eval "^0.1.8" webpack-sources "^1.4.3" -"@stylelint/postcss-css-in-js@^0.37.2": - version "0.37.2" - resolved "https://registry.yarnpkg.com/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.37.2.tgz#7e5a84ad181f4234a2480803422a47b8749af3d2" - integrity sha512-nEhsFoJurt8oUmieT8qy4nk81WRHmJynmVwn/Vts08PL9fhgIsMhk1GId5yAN643OzqEEb5S/6At2TZW7pqPDA== - dependencies: - "@babel/core" ">=7.9.0" - -"@stylelint/postcss-markdown@^0.36.2": - version "0.36.2" - resolved "https://registry.yarnpkg.com/@stylelint/postcss-markdown/-/postcss-markdown-0.36.2.tgz#0a540c4692f8dcdfc13c8e352c17e7bfee2bb391" - integrity sha512-2kGbqUVJUGE8dM+bMzXG/PYUWKkjLIkRLWNh39OaADkiabDRdw8ATFCgbMz5xdIcvwspPAluSL7uY+ZiTWdWmQ== - dependencies: - remark "^13.0.0" - unist-util-find-all-after "^3.0.2" - "@svgr/babel-plugin-add-jsx-attribute@^6.0.0": version "6.0.0" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-6.0.0.tgz#bd6d1ff32a31b82b601e73672a789cc41e84fe18" @@ -1734,15 +1752,15 @@ resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-6.0.0.tgz#eb688d0a5f539e34d268d8a516e81f5d7fede7c9" integrity sha512-VaphyHZ+xIKv5v0K0HCzyfAaLhPGJXSk2HkpYfXIOKb7DjLBv0soHDxNv6X0vr2titsxE7klb++u7iOf7TSrFQ== -"@svgr/babel-plugin-transform-svg-component@^6.1.0": - version "6.1.0" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-6.1.0.tgz#39f92954f7611c269a4ca6906d19e66cdc12babe" - integrity sha512-1zacrn08K5RyV2NtXahOZ5Im/+aB1Y0LVh6QpzwgQV05sY7H5Npq+OcW/UqXbfB2Ua/WnHsFossFQqigCjarYg== +"@svgr/babel-plugin-transform-svg-component@^6.2.0": + version "6.2.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-6.2.0.tgz#7ba61d9fc1fb42b0ba1a04e4630019fa7e993c4f" + integrity sha512-bhYIpsORb++wpsp91fymbFkf09Z/YEKR0DnFjxvN+8JHeCUD2unnh18jIMKnDJTWtvpTaGYPXELVe4OOzFI0xg== -"@svgr/babel-preset@^6.1.0": - version "6.1.0" - resolved "https://registry.yarnpkg.com/@svgr/babel-preset/-/babel-preset-6.1.0.tgz#b8a6b0019537bcd75b3e23fd33c180476c1ef446" - integrity sha512-f9XrTqcwhHLVkjvXBw6QJVxuIfmW22z8iTdGqGvUGGxWoeRV2EzSHstWMBgIVd7t+TmkerqowRvBYiT0OEx3cw== +"@svgr/babel-preset@^6.2.0": + version "6.2.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-preset/-/babel-preset-6.2.0.tgz#1d3ad8c7664253a4be8e4a0f0e6872f30d8af627" + integrity sha512-4WQNY0J71JIaL03DRn0vLiz87JXx0b9dYm2aA8XHlQJQoixMl4r/soYHm8dsaJZ3jWtkCiOYy48dp9izvXhDkQ== dependencies: "@svgr/babel-plugin-add-jsx-attribute" "^6.0.0" "@svgr/babel-plugin-remove-jsx-attribute" "^6.0.0" @@ -1751,57 +1769,57 @@ "@svgr/babel-plugin-svg-dynamic-title" "^6.0.0" "@svgr/babel-plugin-svg-em-dimensions" "^6.0.0" "@svgr/babel-plugin-transform-react-native-svg" "^6.0.0" - "@svgr/babel-plugin-transform-svg-component" "^6.1.0" + "@svgr/babel-plugin-transform-svg-component" "^6.2.0" -"@svgr/core@^6.1.2": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@svgr/core/-/core-6.1.2.tgz#17db14b8d559cb9dc4afa459aa487c00bf6cab80" - integrity sha512-G1UVZcPS5R+HfBG5QC7n2ibkax8RXki2sbKHySTTnajeNXbzriBJcpF4GpYzWptfvD2gmqTDY9XaX+x08TUyGQ== +"@svgr/core@^6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@svgr/core/-/core-6.2.1.tgz#195de807a9f27f9e0e0d678e01084b05c54fdf61" + integrity sha512-NWufjGI2WUyrg46mKuySfviEJ6IxHUOm/8a3Ph38VCWSp+83HBraCQrpEM3F3dB6LBs5x8OElS8h3C0oOJaJAA== dependencies: - "@svgr/plugin-jsx" "^6.1.2" + "@svgr/plugin-jsx" "^6.2.1" camelcase "^6.2.0" cosmiconfig "^7.0.1" -"@svgr/hast-util-to-babel-ast@^6.0.0": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-6.0.0.tgz#423329ad866b6c169009cc82b5e28ffee80c857c" - integrity sha512-S+TxtCdDyRGafH1VG1t/uPZ87aOYOHzWL8kqz4FoSZcIbzWA6rnOmjNViNiDzqmEpzp2PW5o5mZfvC9DiVZhTQ== +"@svgr/hast-util-to-babel-ast@^6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-6.2.1.tgz#ae065567b74cbe745afae617053adf9a764bea25" + integrity sha512-pt7MMkQFDlWJVy9ULJ1h+hZBDGFfSCwlBNW1HkLnVi7jUhyEXUaGYWi1x6bM2IXuAR9l265khBT4Av4lPmaNLQ== dependencies: "@babel/types" "^7.15.6" entities "^3.0.1" -"@svgr/plugin-jsx@^6.1.2": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@svgr/plugin-jsx/-/plugin-jsx-6.1.2.tgz#8a2815aaa46cc3d5cffa963e92b06bd0c33e7748" - integrity sha512-K/w16g3BznTjVjLyUyV0fE7LLl1HSq5KJjvczFVVvx9QG0+3xtU7RX6gvoVnTvYlrNo8QxxqLWVAU3HQm68Eew== +"@svgr/plugin-jsx@^6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@svgr/plugin-jsx/-/plugin-jsx-6.2.1.tgz#5668f1d2aa18c2f1bb7a1fc9f682d3f9aed263bd" + integrity sha512-u+MpjTsLaKo6r3pHeeSVsh9hmGRag2L7VzApWIaS8imNguqoUwDq/u6U/NDmYs/KAsrmtBjOEaAAPbwNGXXp1g== dependencies: "@babel/core" "^7.15.5" - "@svgr/babel-preset" "^6.1.0" - "@svgr/hast-util-to-babel-ast" "^6.0.0" + "@svgr/babel-preset" "^6.2.0" + "@svgr/hast-util-to-babel-ast" "^6.2.1" svg-parser "^2.0.2" -"@svgr/plugin-svgo@^6.1.2": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@svgr/plugin-svgo/-/plugin-svgo-6.1.2.tgz#4fe7a2defe237f0493dee947dde6fa5cea57e6c1" - integrity sha512-UHVSRZV3RdaggDT60OMIEmhskN736DOF6PuBcCaql6jBDA9+SZkA5ZMEw73ZLAlwdOAmw+0Gi4vx/xvAfnmerw== +"@svgr/plugin-svgo@^6.2.0": + version "6.2.0" + resolved "https://registry.yarnpkg.com/@svgr/plugin-svgo/-/plugin-svgo-6.2.0.tgz#4cbe6a33ccccdcae4e3b63ded64cc1cbe1faf48c" + integrity sha512-oDdMQONKOJEbuKwuy4Np6VdV6qoaLLvoY86hjvQEgU82Vx1MSWRyYms6Sl0f+NtqxLI/rDVufATbP/ev996k3Q== dependencies: cosmiconfig "^7.0.1" deepmerge "^4.2.2" svgo "^2.5.0" -"@svgr/webpack@^6.0.0": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@svgr/webpack/-/webpack-6.1.2.tgz#23fd605e9163deb7ef3feef52545ff11dc9989bf" - integrity sha512-5RzzWxFquywENwvnsiGjZ7IED+0l2lnICR3OKQ6OUyGgxlu+ac73NmDSXp6EPBz/ZTArpMZtug7jiPMUkXxnlg== +"@svgr/webpack@^6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@svgr/webpack/-/webpack-6.2.1.tgz#ef5d51c1b6be4e7537fb9f76b3f2b2e22b63c58d" + integrity sha512-h09ngMNd13hnePwgXa+Y5CgOjzlCvfWLHg+MBnydEedAnuLRzUHUJmGS3o2OsrhxTOOqEsPOFt5v/f6C5Qulcw== dependencies: "@babel/core" "^7.15.5" "@babel/plugin-transform-react-constant-elements" "^7.14.5" "@babel/preset-env" "^7.15.6" "@babel/preset-react" "^7.14.5" "@babel/preset-typescript" "^7.15.0" - "@svgr/core" "^6.1.2" - "@svgr/plugin-jsx" "^6.1.2" - "@svgr/plugin-svgo" "^6.1.2" + "@svgr/core" "^6.2.1" + "@svgr/plugin-jsx" "^6.2.1" + "@svgr/plugin-svgo" "^6.2.0" "@szmarczak/http-timer@^1.1.2": version "1.1.2" @@ -1815,33 +1833,75 @@ resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== -"@types/cssnano@^4.0.1": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@types/cssnano/-/cssnano-4.0.1.tgz#67fa912753d80973a016e7684a47fedf338aacff" - integrity sha512-hGOroxRTBkYl5gSBRJOffhV4+io+Y2bFX1VP7LgKEVHJt/LPPJaWUIuDAz74Vlp7l7hCDZfaDi7iPxwNwuVA4Q== +"@types/body-parser@*": + version "1.19.2" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" + integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== dependencies: - postcss "5 - 7" + "@types/connect" "*" + "@types/node" "*" -"@types/eslint-scope@^3.7.0": - version "3.7.2" - resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.2.tgz#11e96a868c67acf65bf6f11d10bb89ea71d5e473" - integrity sha512-TzgYCWoPiTeRg6RQYgtuW7iODtVoKu3RVL72k3WohqhjfaOLK5Mg2T4Tg1o2bSfu0vPkoI48wdQFv5b/Xe04wQ== +"@types/bonjour@^3.5.9": + version "3.5.10" + resolved "https://registry.yarnpkg.com/@types/bonjour/-/bonjour-3.5.10.tgz#0f6aadfe00ea414edc86f5d106357cda9701e275" + integrity sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw== + dependencies: + "@types/node" "*" + +"@types/connect-history-api-fallback@^1.3.5": + version "1.3.5" + resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz#d1f7a8a09d0ed5a57aee5ae9c18ab9b803205dae" + integrity sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw== + dependencies: + "@types/express-serve-static-core" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.35" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" + integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== + dependencies: + "@types/node" "*" + +"@types/eslint-scope@^3.7.3": + version "3.7.3" + resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.3.tgz#125b88504b61e3c8bc6f870882003253005c3224" + integrity sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g== dependencies: "@types/eslint" "*" "@types/estree" "*" "@types/eslint@*": - version "8.2.1" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.2.1.tgz#13f3d69bac93c2ae008019c28783868d0a1d6605" - integrity sha512-UP9rzNn/XyGwb5RQ2fok+DzcIRIYwc16qTXse5+Smsy8MOIccCChT15KAwnsgQx4PzJkaMq4myFyZ4CL5TjhIQ== + version "8.4.1" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.4.1.tgz#c48251553e8759db9e656de3efc846954ac32304" + integrity sha512-GE44+DNEyxxh2Kc6ro/VkIj+9ma0pO0bwv9+uHSyBrikYOHr8zYcdPvnBOp1aw8s+CjRvuSx7CyWqRrNFQ59mA== dependencies: "@types/estree" "*" "@types/json-schema" "*" -"@types/estree@*", "@types/estree@^0.0.50": - version "0.0.50" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.50.tgz#1e0caa9364d3fccd2931c3ed96fdbeaa5d4cca83" - integrity sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw== +"@types/estree@*", "@types/estree@^0.0.51": + version "0.0.51" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40" + integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ== + +"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.18": + version "4.17.28" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz#c47def9f34ec81dc6328d0b1b5303d1ec98d86b8" + integrity sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + +"@types/express@*", "@types/express@^4.17.13": + version "4.17.13" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034" + integrity sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.18" + "@types/qs" "*" + "@types/serve-static" "*" "@types/hast@^2.0.0": version "2.3.4" @@ -1850,12 +1910,17 @@ dependencies: "@types/unist" "*" +"@types/history@^4.7.11": + version "4.7.11" + resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.11.tgz#56588b17ae8f50c53983a524fc3cc47437969d64" + integrity sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA== + "@types/html-minifier-terser@^6.0.0": version "6.1.0" resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#4fc33a00c1d0c16987b1a20cf92d20614c55ac35" integrity sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg== -"@types/http-proxy@^1.17.5": +"@types/http-proxy@^1.17.8": version "1.17.8" resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.8.tgz#968c66903e7e42b483608030ee85800f22d03f55" integrity sha512-5kPLG5BKpWYkw/LVOGWpiq3nEVqxiN32rTgI53Sk12/xHFQ2rG3ehI9IO+O3W2QoKeyB92dJkoka8SUm6BX1pA== @@ -1863,9 +1928,9 @@ "@types/node" "*" "@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": - version "7.0.9" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" - integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== + version "7.0.11" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" + integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== "@types/json5@^0.0.29": version "0.0.29" @@ -1879,20 +1944,20 @@ dependencies: "@types/unist" "*" +"@types/mime@^1": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" + integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== + "@types/minimist@^1.2.0": version "1.2.2" resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c" integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ== -"@types/node@*": - version "17.0.2" - resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.2.tgz#a4c07d47ff737e8ee7e586fe636ff0e1ddff070a" - integrity sha512-JepeIUPFDARgIs0zD/SKPgFsJEAF0X5/qO80llx59gOxFTboS9Amv3S+QfB7lqBId5sFXJ99BN0J6zFRvL9dDA== - -"@types/node@^15.0.1": - version "15.14.9" - resolved "https://registry.yarnpkg.com/@types/node/-/node-15.14.9.tgz#bc43c990c3c9be7281868bbc7b8fdd6e2b57adfa" - integrity sha512-qjd88DrCxupx/kJD5yQgZdcYKZKSIGBVDIBE1/LTGcNm3d2Np/jxojkdePDdfnBHJc5W7vSMpbJ1aB7p/Py69A== +"@types/node@*", "@types/node@^17.0.5": + version "17.0.23" + resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.23.tgz#3b41a6e643589ac6442bdbd7a4a3ded62f33f7da" + integrity sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw== "@types/normalize-package-data@^2.4.0": version "2.4.1" @@ -1914,10 +1979,46 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11" integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ== +"@types/qs@*": + version "6.9.7" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" + integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== + +"@types/range-parser@*": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" + integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== + +"@types/react-router-config@*": + version "5.0.6" + resolved "https://registry.yarnpkg.com/@types/react-router-config/-/react-router-config-5.0.6.tgz#87c5c57e72d241db900d9734512c50ccec062451" + integrity sha512-db1mx37a1EJDf1XeX8jJN7R3PZABmJQXR8r28yUjVMFSjkmnQo6X6pOEEmNl+Tp2gYQOGPdYbFIipBtdElZ3Yg== + dependencies: + "@types/history" "^4.7.11" + "@types/react" "*" + "@types/react-router" "*" + +"@types/react-router-dom@*": + version "5.3.3" + resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.3.3.tgz#e9d6b4a66fcdbd651a5f106c2656a30088cc1e83" + integrity sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw== + dependencies: + "@types/history" "^4.7.11" + "@types/react" "*" + "@types/react-router" "*" + +"@types/react-router@*": + version "5.1.18" + resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.18.tgz#c8851884b60bc23733500d86c1266e1cfbbd9ef3" + integrity sha512-YYknwy0D0iOwKQgz9v8nOzt2J6l4gouBmDnWqUUznltOTaon+r8US8ky8HvN0tXvc38U9m6z/t2RsVsnd1zM0g== + dependencies: + "@types/history" "^4.7.11" + "@types/react" "*" + "@types/react@*": - version "17.0.37" - resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.37.tgz#6884d0aa402605935c397ae689deed115caad959" - integrity sha512-2FS1oTqBGcH/s0E+CjrCCR9+JMpsu9b69RTFO+40ua43ZqP5MmQ4iUde/dMjWR909KxZwmOQIFq6AV6NjEG5xg== + version "17.0.43" + resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.43.tgz#4adc142887dd4a2601ce730bc56c3436fdb07a55" + integrity sha512-8Q+LNpdxf057brvPu1lMtC5Vn7J119xrP1aq4qiaefNioQUYANF/CYeK4NsKorSZyUGJ66g0IM+4bbjwx45o2A== dependencies: "@types/prop-types" "*" "@types/scheduler" "*" @@ -1940,11 +2041,40 @@ resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== +"@types/serve-index@^1.9.1": + version "1.9.1" + resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.1.tgz#1b5e85370a192c01ec6cec4735cf2917337a6278" + integrity sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg== + dependencies: + "@types/express" "*" + +"@types/serve-static@*": + version "1.13.10" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.10.tgz#f5e0ce8797d2d7cc5ebeda48a52c96c4fa47a8d9" + integrity sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ== + dependencies: + "@types/mime" "^1" + "@types/node" "*" + +"@types/sockjs@^0.3.33": + version "0.3.33" + resolved "https://registry.yarnpkg.com/@types/sockjs/-/sockjs-0.3.33.tgz#570d3a0b99ac995360e3136fd6045113b1bd236f" + integrity sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw== + dependencies: + "@types/node" "*" + "@types/unist@*", "@types/unist@^2.0.0", "@types/unist@^2.0.2", "@types/unist@^2.0.3": version "2.0.6" resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d" integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ== +"@types/ws@^8.2.2": + version "8.5.3" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d" + integrity sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w== + dependencies: + "@types/node" "*" + "@webassemblyjs/ast@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7" @@ -2076,25 +2206,20 @@ resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== -accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: - version "1.3.7" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" - integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== +accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: + version "1.3.8" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== dependencies: - mime-types "~2.1.24" - negotiator "0.6.2" - -acorn-dynamic-import@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz#482210140582a36b83c3e342e1cfebcaa9240948" - integrity sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw== + mime-types "~2.1.34" + negotiator "0.6.3" acorn-import-assertions@^1.7.6: version "1.8.0" resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz#ba2b5939ce62c238db6d93d81c9b111b29b855e9" integrity sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw== -acorn-jsx@^5.0.1, acorn-jsx@^5.3.1: +acorn-jsx@^5.3.1: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== @@ -2104,15 +2229,10 @@ acorn-walk@^8.0.0: resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== -acorn@^6.1.1: - version "6.4.2" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6" - integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ== - -acorn@^8.0.4, acorn@^8.4.1, acorn@^8.6.0: - version "8.6.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.6.0.tgz#e3692ba0eb1a0c83eaa4f37f5fa7368dd7142895" - integrity sha512-U1riIR+lBSNi3IbxtaHOIKdH8sLFv3NYfNv8sg7ZsNhcfl4HF2++BfqqrNAxoCLQW1iiylOj76ecnaUxz+z9yw== +acorn@^8.0.4, acorn@^8.4.1, acorn@^8.5.0, acorn@^8.7.0: + version "8.7.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" + integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ== address@^1.0.1, address@^1.1.2: version "1.1.2" @@ -2157,59 +2277,49 @@ ajv@^6.10.0, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5: uri-js "^4.2.2" ajv@^8.0.0, ajv@^8.0.1, ajv@^8.8.0: - version "8.8.2" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.8.2.tgz#01b4fef2007a28bf75f0b7fc009f62679de4abbb" - integrity sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw== + version "8.11.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.11.0.tgz#977e91dd96ca669f54a11e23e378e33b884a565f" + integrity sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg== dependencies: fast-deep-equal "^3.1.1" json-schema-traverse "^1.0.0" require-from-string "^2.0.2" uri-js "^4.2.2" -algoliasearch-helper@^3.5.5: - version "3.7.0" - resolved "https://registry.yarnpkg.com/algoliasearch-helper/-/algoliasearch-helper-3.7.0.tgz#c0a0493df84d850360f664ad7a9d4fc78a94fd78" - integrity sha512-XJ3QfERBLfeVCyTVx80gon7r3/rgm/CE8Ha1H7cbablRe/X7SfYQ14g/eO+MhjVKIQp+gy9oC6G5ilmLwS1k6w== +algoliasearch-helper@^3.7.4: + version "3.7.4" + resolved "https://registry.yarnpkg.com/algoliasearch-helper/-/algoliasearch-helper-3.7.4.tgz#3812ea161da52463ec88da52612c9a363c1b181d" + integrity sha512-KmJrsHVm5TmxZ9Oj53XdXuM4CQeu7eVFnB15tpSFt+7is1d1yVCv3hxCLMqYSw/rH42ccv013miQpRr268P8vw== dependencies: "@algolia/events" "^4.0.1" -algoliasearch@^4.0.0, algoliasearch@^4.10.5: - version "4.11.0" - resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-4.11.0.tgz#234befb3ac355c094077f0edf3777240b1ee013c" - integrity sha512-IXRj8kAP2WrMmj+eoPqPc6P7Ncq1yZkFiyDrjTBObV1ADNL8Z/KdZ+dWC5MmYcBLAbcB/mMCpak5N/D1UIZvsA== - dependencies: - "@algolia/cache-browser-local-storage" "4.11.0" - "@algolia/cache-common" "4.11.0" - "@algolia/cache-in-memory" "4.11.0" - "@algolia/client-account" "4.11.0" - "@algolia/client-analytics" "4.11.0" - "@algolia/client-common" "4.11.0" - "@algolia/client-personalization" "4.11.0" - "@algolia/client-search" "4.11.0" - "@algolia/logger-common" "4.11.0" - "@algolia/logger-console" "4.11.0" - "@algolia/requester-browser-xhr" "4.11.0" - "@algolia/requester-common" "4.11.0" - "@algolia/requester-node-http" "4.11.0" - "@algolia/transporter" "4.11.0" - -alphanum-sort@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" - integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM= - -ansi-align@^3.0.0: +algoliasearch@^4.0.0, algoliasearch@^4.13.0: + version "4.13.0" + resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-4.13.0.tgz#e36611fda82b1fc548c156ae7929a7f486e4b663" + integrity sha512-oHv4faI1Vl2s+YC0YquwkK/TsaJs79g2JFg5FDm2rKN12VItPTAeQ7hyJMHarOPPYuCnNC5kixbtcqvb21wchw== + dependencies: + "@algolia/cache-browser-local-storage" "4.13.0" + "@algolia/cache-common" "4.13.0" + "@algolia/cache-in-memory" "4.13.0" + "@algolia/client-account" "4.13.0" + "@algolia/client-analytics" "4.13.0" + "@algolia/client-common" "4.13.0" + "@algolia/client-personalization" "4.13.0" + "@algolia/client-search" "4.13.0" + "@algolia/logger-common" "4.13.0" + "@algolia/logger-console" "4.13.0" + "@algolia/requester-browser-xhr" "4.13.0" + "@algolia/requester-common" "4.13.0" + "@algolia/requester-node-http" "4.13.0" + "@algolia/transporter" "4.13.0" + +ansi-align@^3.0.0, ansi-align@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.1.tgz#0cdf12e111ace773a86e9a1fad1225c43cb19a59" integrity sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w== dependencies: string-width "^4.1.0" -ansi-colors@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" - integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== - ansi-html-community@^0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz#69fbc4d6ccbe383f9736934ae34c3f8290f1bf41" @@ -2239,6 +2349,11 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" +ansi-styles@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.1.0.tgz#87313c102b8118abd57371afab34618bf7350ed3" + integrity sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ== + anymatch@~3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" @@ -2298,6 +2413,11 @@ array-union@^2.1.0: resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== +array-union@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-3.0.1.tgz#da52630d327f8b88cfbfb57728e2af5cd9b6b975" + integrity sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw== + array.prototype.flat@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz#07e0975d84bbc7c48cd1879d609e682598d33e13" @@ -2348,55 +2468,42 @@ at-least-node@^1.0.0: resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== -autoprefixer@^10.3.5, autoprefixer@^10.3.7: - version "10.4.0" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.0.tgz#c3577eb32a1079a440ec253e404eaf1eb21388c8" - integrity sha512-7FdJ1ONtwzV1G43GDD0kpVMn/qbiNqyOPMFTX5nRffI+7vgWoFEc6DcXOxHJxrWNDXrZh18eDsZjvZGUljSRGA== +autoprefixer@^10.3.7, autoprefixer@^10.4.4: + version "10.4.4" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.4.tgz#3e85a245b32da876a893d3ac2ea19f01e7ea5a1e" + integrity sha512-Tm8JxsB286VweiZ5F0anmbyGiNI3v3wGv3mz9W+cxEDYB/6jbnj6GM9H9mK3wIL8ftgl+C07Lcwb8PG5PCCPzA== dependencies: - browserslist "^4.17.5" - caniuse-lite "^1.0.30001272" - fraction.js "^4.1.1" + browserslist "^4.20.2" + caniuse-lite "^1.0.30001317" + fraction.js "^4.2.0" normalize-range "^0.1.2" picocolors "^1.0.0" - postcss-value-parser "^4.1.0" - -autoprefixer@^9.8.6: - version "9.8.8" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.8.tgz#fd4bd4595385fa6f06599de749a4d5f7a474957a" - integrity sha512-eM9d/swFopRt5gdJ7jrpCwgvEMIayITpojhkkSMRsFHYuH5bkSQ4p/9qTEHtmNudUZh22Tehu7I6CxAW0IXTKA== - dependencies: - browserslist "^4.12.0" - caniuse-lite "^1.0.30001109" - normalize-range "^0.1.2" - num2fraction "^1.2.2" - picocolors "^0.2.1" - postcss "^7.0.32" - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" axe-core@^4.3.5: - version "4.3.5" - resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.3.5.tgz#78d6911ba317a8262bfee292aeafcc1e04b49cc5" - integrity sha512-WKTW1+xAzhMS5dJsxWkliixlO/PqC4VhmO9T4juNYcaTg9jzWiJsou6m5pxWYGfigWbwzJWeFY6z47a+4neRXA== + version "4.4.1" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.4.1.tgz#7dbdc25989298f9ad006645cd396782443757413" + integrity sha512-gd1kmb21kwNuWr6BQz8fv6GNECPBnUasepcoLbekws23NVBLODdsClRZ+bQ8+9Uomf3Sm3+Vwn0oYG9NvwnJCw== -axios@^0.21.1: - version "0.21.4" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" - integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== +axios@^0.25.0: + version "0.25.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.25.0.tgz#349cfbb31331a9b4453190791760a8d35b093e0a" + integrity sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g== dependencies: - follow-redirects "^1.14.0" + follow-redirects "^1.14.7" axobject-query@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be" integrity sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA== -babel-loader@^8.2.2: - version "8.2.3" - resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.2.3.tgz#8986b40f1a64cacfcb4b8429320085ef68b1342d" - integrity sha512-n4Zeta8NC3QAsuyiizu0GkmRcQ6clkV9WFUnUf1iXP//IeSKbWjofW3UHyZVwlOB4y039YQKefawyTn64Zwbuw== +babel-loader@^8.2.4: + version "8.2.4" + resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.2.4.tgz#95f5023c791b2e9e2ca6f67b0984f39c82ff384b" + integrity sha512-8dytA3gcvPPPv4Grjhnt8b5IIiTcq/zeXOPk4iTYI0SVXcsmuGg7JtBRDp8S9X+gJfhQ8ektjXZlDu1Bb33U8A== dependencies: find-cache-dir "^3.3.1" - loader-utils "^1.4.0" + loader-utils "^2.0.0" make-dir "^3.1.0" schema-utils "^2.6.5" @@ -2430,28 +2537,28 @@ babel-plugin-extract-import-names@1.6.22: "@babel/helper-plugin-utils" "7.10.4" babel-plugin-polyfill-corejs2@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.0.tgz#407082d0d355ba565af24126fb6cb8e9115251fd" - integrity sha512-wMDoBJ6uG4u4PNFh72Ty6t3EgfA91puCuAwKIazbQlci+ENb/UU9A3xG5lutjUIiXCIn1CY5L15r9LimiJyrSA== + version "0.3.1" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.1.tgz#440f1b70ccfaabc6b676d196239b138f8a2cfba5" + integrity sha512-v7/T6EQcNfVLfcN2X8Lulb7DjprieyLWJK/zOWH5DUYcAgex9sP3h25Q+DLsX9TloXe3y1O8l2q2Jv9q8UVB9w== dependencies: "@babel/compat-data" "^7.13.11" - "@babel/helper-define-polyfill-provider" "^0.3.0" + "@babel/helper-define-polyfill-provider" "^0.3.1" semver "^6.1.1" -babel-plugin-polyfill-corejs3@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.4.0.tgz#0b571f4cf3d67f911512f5c04842a7b8e8263087" - integrity sha512-YxFreYwUfglYKdLUGvIF2nJEsGwj+RhWSX/ije3D2vQPOXuyMLMtg/cCGMDpOA7Nd+MwlNdnGODbd2EwUZPlsw== +babel-plugin-polyfill-corejs3@^0.5.0: + version "0.5.2" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.2.tgz#aabe4b2fa04a6e038b688c5e55d44e78cd3a5f72" + integrity sha512-G3uJih0XWiID451fpeFaYGVuxHEjzKTHtc9uGFEjR6hHrvNzeS/PX+LLLcetJcytsB5m4j+K3o/EpXJNb/5IEQ== dependencies: - "@babel/helper-define-polyfill-provider" "^0.3.0" - core-js-compat "^3.18.0" + "@babel/helper-define-polyfill-provider" "^0.3.1" + core-js-compat "^3.21.0" babel-plugin-polyfill-regenerator@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.0.tgz#9ebbcd7186e1a33e21c5e20cae4e7983949533be" - integrity sha512-dhAPTDLGoMW5/84wkgwiLRwMnio2i1fUe53EuvtKMv0pn2p3S8OCoV1xAzfJPl0KOX7IB89s2ib85vbYiea3jg== + version "0.3.1" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz#2c0678ea47c75c8cc2fbb1852278d8fb68233990" + integrity sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A== dependencies: - "@babel/helper-define-polyfill-provider" "^0.3.0" + "@babel/helper-define-polyfill-provider" "^0.3.1" bail@^1.0.0: version "1.0.5" @@ -2493,20 +2600,20 @@ bluebird@^3.7.1: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== -body-parser@1.19.1: - version "1.19.1" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.1.tgz#1499abbaa9274af3ecc9f6f10396c995943e31d4" - integrity sha512-8ljfQi5eBk8EJfECMrgqNGWPEY5jWP+1IzkzkGdFFEwFQZZyaZ21UqdaHktgiMlH0xLHqIFtE/u2OYE5dOtViA== +body-parser@1.19.2: + version "1.19.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.2.tgz#4714ccd9c157d44797b8b5607d72c0b89952f26e" + integrity sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw== dependencies: - bytes "3.1.1" + bytes "3.1.2" content-type "~1.0.4" debug "2.6.9" depd "~1.1.2" http-errors "1.8.1" iconv-lite "0.4.24" on-finished "~2.3.0" - qs "6.9.6" - raw-body "2.4.2" + qs "6.9.7" + raw-body "2.4.3" type-is "~1.6.18" bonjour@^3.5.0: @@ -2526,7 +2633,7 @@ boolbase@^1.0.0, boolbase@~1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= -boxen@^5.0.0, boxen@^5.0.1: +boxen@^5.0.0: version "5.1.2" resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.1.2.tgz#788cb686fc83c1f486dfa8a40c68fc2b831d2b50" integrity sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ== @@ -2540,6 +2647,20 @@ boxen@^5.0.0, boxen@^5.0.1: widest-line "^3.1.0" wrap-ansi "^7.0.0" +boxen@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-6.2.1.tgz#b098a2278b2cd2845deef2dff2efc38d329b434d" + integrity sha512-H4PEsJXfFI/Pt8sjDWbHlQPx4zL/bvSQjcilJmaulGt5mLDorHOHpmdXAJcBcmru7PhYSp/cDMWRko4ZUMFkSw== + dependencies: + ansi-align "^3.0.1" + camelcase "^6.2.0" + chalk "^4.1.2" + cli-boxes "^3.0.0" + string-width "^5.0.1" + type-fest "^2.5.0" + widest-line "^4.0.1" + wrap-ansi "^8.0.1" + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -2548,37 +2669,24 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^3.0.1, braces@~3.0.2: +braces@^3.0.2, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== dependencies: fill-range "^7.0.1" -browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.14.5, browserslist@^4.16.0, browserslist@^4.16.5, browserslist@^4.16.6, browserslist@^4.17.5, browserslist@^4.19.1: - version "4.19.1" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.19.1.tgz#4ac0435b35ab655896c31d53018b6dd5e9e4c9a3" - integrity sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A== +browserslist@^4.0.0, browserslist@^4.14.5, browserslist@^4.16.6, browserslist@^4.17.5, browserslist@^4.18.1, browserslist@^4.19.1, browserslist@^4.20.2: + version "4.20.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.20.2.tgz#567b41508757ecd904dab4d1c646c612cd3d4f88" + integrity sha512-CQOBCqp/9pDvDbx3xfMi+86pr4KXIf2FDkTTdeuYw8OxS9t898LA1Khq57gtufFILXpfgsSx5woNgsBgvGjpsA== dependencies: - caniuse-lite "^1.0.30001286" - electron-to-chromium "^1.4.17" + caniuse-lite "^1.0.30001317" + electron-to-chromium "^1.4.84" escalade "^3.1.1" - node-releases "^2.0.1" + node-releases "^2.0.2" picocolors "^1.0.0" -buble-jsx-only@^0.19.8: - version "0.19.8" - resolved "https://registry.yarnpkg.com/buble-jsx-only/-/buble-jsx-only-0.19.8.tgz#6e3524aa0f1c523de32496ac9aceb9cc2b493867" - integrity sha512-7AW19pf7PrKFnGTEDzs6u9+JZqQwM1VnLS19OlqYDhXomtFFknnoQJAPHeg84RMFWAvOhYrG7harizJNwUKJsA== - dependencies: - acorn "^6.1.1" - acorn-dynamic-import "^4.0.0" - acorn-jsx "^5.0.1" - chalk "^2.4.2" - magic-string "^0.25.3" - minimist "^1.2.0" - regexpu-core "^4.5.4" - buffer-from@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" @@ -2594,10 +2702,10 @@ bytes@3.0.0: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= -bytes@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.1.tgz#3f018291cb4cbad9accb6e6970bca9c8889e879a" - integrity sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg== +bytes@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== cacheable-request@^6.0.0: version "6.1.0" @@ -2653,9 +2761,9 @@ camelcase@^5.3.1: integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== camelcase@^6.2.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.1.tgz#250fd350cfd555d0d2160b1d51510eaf8326e86e" - integrity sha512-tVI4q5jjFV5CavAU8DXfza/TJcZutVKo/5Foskmsqcm0MsL91moHvwiGNnqaa2o6PF/7yT5ikDRcVcl8Rj6LCA== + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== caniuse-api@^3.0.0: version "3.0.0" @@ -2667,17 +2775,17 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001272, caniuse-lite@^1.0.30001286: - version "1.0.30001291" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001291.tgz#08a8d2cfea0b2cf2e1d94dd795942d0daef6108c" - integrity sha512-roMV5V0HNGgJ88s42eE70sstqGW/gwFndosYrikHthw98N5tLnOTxFqMLQjZVRxTWFlJ4rn+MsgXrR7MDPY4jA== +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001317: + version "1.0.30001320" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001320.tgz#8397391bec389b8ccce328636499b7284ee13285" + integrity sha512-MWPzG54AGdo3nWx7zHZTefseM5Y1ccM7hlQKHRqJkPozUaw3hNbBTMmLn16GG2FUzjR13Cr3NPfhIieX5PzXDA== ccount@^1.0.0, ccount@^1.0.3: version "1.1.0" resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.1.0.tgz#246687debb6014735131be8abab2d93898f8d043" integrity sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg== -chalk@^2.0.0, chalk@^2.4.2: +chalk@^2.0.0: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -2686,7 +2794,7 @@ chalk@^2.0.0, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: +chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -2709,6 +2817,17 @@ character-reference-invalid@^1.0.0: resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz#083329cda0eae272ab3dbbf37e9a382c13af1560" integrity sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg== +cheerio-select@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/cheerio-select/-/cheerio-select-1.5.0.tgz#faf3daeb31b17c5e1a9dabcee288aaf8aafa5823" + integrity sha512-qocaHPv5ypefh6YNxvnbABM07KMxExbtbfuJoIie3iZXX1ERwYmJcIiRrr9H05ucQP1k28dav8rpdDgjQd8drg== + dependencies: + css-select "^4.1.3" + css-what "^5.0.1" + domelementtype "^2.2.0" + domhandler "^4.2.0" + domutils "^2.7.0" + cheerio@^0.22.0: version "0.22.0" resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-0.22.0.tgz#a9baa860a3f9b595a6b81b1a86873121ed3a269e" @@ -2731,10 +2850,23 @@ cheerio@^0.22.0: lodash.reject "^4.4.0" lodash.some "^4.4.0" -chokidar@^3.4.2, chokidar@^3.5.2: - version "3.5.2" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75" - integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ== +cheerio@^1.0.0-rc.10: + version "1.0.0-rc.10" + resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.10.tgz#2ba3dcdfcc26e7956fc1f440e61d51c643379f3e" + integrity sha512-g0J0q/O6mW8z5zxQ3A8E8J1hUgp4SMOvEoW/x84OwyHKe/Zccz83PVT4y5Crcr530FV6NgmKI1qvGTKVl9XXVw== + dependencies: + cheerio-select "^1.5.0" + dom-serializer "^1.3.2" + domhandler "^4.2.0" + htmlparser2 "^6.1.0" + parse5 "^6.0.1" + parse5-htmlparser2-tree-adapter "^6.0.1" + tslib "^2.2.0" + +chokidar@^3.4.2, chokidar@^3.5.3: + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== dependencies: anymatch "~3.1.2" braces "~3.0.2" @@ -2756,10 +2888,10 @@ ci-info@^2.0.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== -clean-css@^5.1.5, clean-css@^5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.2.2.tgz#d3a7c6ee2511011e051719838bdcf8314dc4548d" - integrity sha512-/eR8ru5zyxKzpBLv9YZvMXgTSSQn7AdkMItMYynsFgGwTveCRVam9IUPFloE85B4vAIj05IuKmmEoV7/AQjT0w== +clean-css@^5.2.2, clean-css@^5.2.4: + version "5.2.4" + resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.2.4.tgz#982b058f8581adb2ae062520808fb2429bd487a4" + integrity sha512-nKseG8wCzEuji/4yrgM/5cthL9oTDc5UOQyFMvW/Q53oP6gLH690o1NbuTh6Y18nujr7BxlsFuS7gXLnLzKJGg== dependencies: source-map "~0.6.0" @@ -2773,6 +2905,20 @@ cli-boxes@^2.2.1: resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== +cli-boxes@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-3.0.0.tgz#71a10c716feeba005e4504f36329ef0b17cf3145" + integrity sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g== + +cli-table3@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.1.tgz#36ce9b7af4847f288d3cdd081fbd09bf7bd237b8" + integrity sha512-w0q/enDHhPLq44ovMGdQeeDLvwxwavsJX7oQGYt/LrBlYsyaxyDnp6z3QzFut/6kLLKnlcUVJLrpB7KBfgG/RA== + dependencies: + string-width "^4.2.0" + optionalDependencies: + colors "1.4.0" + clone-deep@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" @@ -2830,7 +2976,7 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -colord@^2.9.1: +colord@^2.9.1, colord@^2.9.2: version "2.9.2" resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.2.tgz#25e2bacbbaa65991422c07ea209e2089428effb1" integrity sha512-Uqbg+J445nc1TKn4FoDPS6ZZqAvEDnwrH42yo8B40JSOgSLxMZ/gt3h4nmCtPLQeXhjJJkqBx7SCY35WnIixaQ== @@ -2840,6 +2986,11 @@ colorette@^2.0.10: resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.16.tgz#713b9af84fdb000139f04546bd4a93f62a5085da" integrity sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g== +colors@1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" + integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== + combine-promises@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/combine-promises/-/combine-promises-1.1.0.tgz#72db90743c0ca7aab7d0d8d2052fd7b0f674de71" @@ -2956,45 +3107,45 @@ cookie-signature@1.0.6: resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= -cookie@0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" - integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== +cookie@0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" + integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== copy-text-to-clipboard@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/copy-text-to-clipboard/-/copy-text-to-clipboard-3.0.1.tgz#8cbf8f90e0a47f12e4a24743736265d157bce69c" integrity sha512-rvVsHrpFcL4F2P8ihsoLdFHmd404+CMg71S756oRSeQgqk51U3kicGdnvfkrxva0xXH92SjGS62B0XIJsbh+9Q== -copy-webpack-plugin@^9.0.1: - version "9.1.0" - resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-9.1.0.tgz#2d2c460c4c4695ec0a58afb2801a1205256c4e6b" - integrity sha512-rxnR7PaGigJzhqETHGmAcxKnLZSR5u1Y3/bcIv/1FnqXedcL/E2ewK7ZCNrArJKCiSv8yVXhTqetJh8inDvfsA== +copy-webpack-plugin@^10.2.4: + version "10.2.4" + resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-10.2.4.tgz#6c854be3fdaae22025da34b9112ccf81c63308fe" + integrity sha512-xFVltahqlsRcyyJqQbDY6EYTtyQZF9rf+JPjwHObLdPFMEISqkFkr7mFoVOC6BfYS/dNThyoQKvziugm+OnwBg== dependencies: fast-glob "^3.2.7" glob-parent "^6.0.1" - globby "^11.0.3" + globby "^12.0.2" normalize-path "^3.0.0" - schema-utils "^3.1.1" + schema-utils "^4.0.0" serialize-javascript "^6.0.0" -core-js-compat@^3.18.0, core-js-compat@^3.19.1: - version "3.20.0" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.20.0.tgz#fd704640c5a213816b6d10ec0192756111e2c9d1" - integrity sha512-relrah5h+sslXssTTOkvqcC/6RURifB0W5yhYBdBkaPYa5/2KBMiog3XiD+s3TwEHWxInWVv4Jx2/Lw0vng+IQ== +core-js-compat@^3.20.2, core-js-compat@^3.21.0: + version "3.21.1" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.21.1.tgz#cac369f67c8d134ff8f9bd1623e3bc2c42068c82" + integrity sha512-gbgX5AUvMb8gwxC7FLVWYT7Kkgu/y7+h/h1X43yJkNqhlK2fuYyQimqvKGNZFAY6CKii/GFKJ2cp/1/42TN36g== dependencies: browserslist "^4.19.1" semver "7.0.0" -core-js-pure@^3.19.0: - version "3.20.0" - resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.20.0.tgz#7253feccf8bb05b72c153ddccdbe391ddbffbe03" - integrity sha512-qsrbIwWSEEYOM7z616jAVgwhuDDtPLwZSpUsU3vyUkHYqKTf/uwOJBZg2V7lMurYWkpVlaVOxBrfX0Q3ppvjfg== +core-js-pure@^3.20.2: + version "3.21.1" + resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.21.1.tgz#8c4d1e78839f5f46208de7230cebfb72bc3bdb51" + integrity sha512-12VZfFIu+wyVbBebyHmRTuEE/tZrB4tJToWcwAMcsp3h4+sHR+fMJWbKpYiCRWlhFBq+KNyO8rIV9rTkeVmznQ== -core-js@^3.18.0: - version "3.20.0" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.20.0.tgz#1c5ac07986b8d15473ab192e45a2e115a4a95b79" - integrity sha512-KjbKU7UEfg4YPpskMtMXPhUKn7m/1OdTHTVjy09ScR2LVaoUXe8Jh0UdvN2EKUR6iKTJph52SJP95mAB0MnVLQ== +core-js@^3.21.1: + version "3.21.1" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.21.1.tgz#f2e0ddc1fc43da6f904706e8e955bc19d06a0d94" + integrity sha512-FRq5b/VMrWlrmCzwRrpDYNxyHP9BcAZC+xHJaqTgIE5091ZV1NTmyh0sGOg5XqpnHvR0svdy0sv1gWA1zmhxig== core-util-is@~1.0.0: version "1.0.3" @@ -3023,12 +3174,12 @@ cosmiconfig@^7.0.0, cosmiconfig@^7.0.1: path-type "^4.0.0" yaml "^1.10.0" -cross-fetch@^3.0.4: - version "3.1.4" - resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.4.tgz#9723f3a3a247bf8b89039f3a380a9244e8fa2f39" - integrity sha512-1eAtFWdIubi6T4XPy6ei9iUFoKpUkIF971QLN8lIvvvwueI65+Nw5haMNKUwfJxabqlIIDODJKGrQ66gxC0PbQ== +cross-fetch@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f" + integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw== dependencies: - node-fetch "2.6.1" + node-fetch "2.6.7" cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" @@ -3045,34 +3196,36 @@ crypto-random-string@^2.0.0: integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== css-declaration-sorter@^6.0.3: - version "6.1.3" - resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.1.3.tgz#e9852e4cf940ba79f509d9425b137d1f94438dc2" - integrity sha512-SvjQjNRZgh4ULK1LDJ2AduPKUKxIqmtU7ZAyi47BTV+M90Qvxr9AB6lKlLbDUfXqI9IQeYA8LbAsCZPpJEV3aA== + version "6.1.4" + resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.1.4.tgz#b9bfb4ed9a41f8dcca9bf7184d849ea94a8294b4" + integrity sha512-lpfkqS0fctcmZotJGhnxkIyJWvBXgpyi2wsFd4J8VB7wzyrT6Ch/3Q+FMNJpjK4gu1+GN5khOnpU2ZVKrLbhCw== dependencies: timsort "^0.3.0" -css-loader@^5.1.1: - version "5.2.7" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.2.7.tgz#9b9f111edf6fb2be5dc62525644cbc9c232064ae" - integrity sha512-Q7mOvpBNBG7YrVGMxRxcBJZFL75o+cH2abNASdibkj/fffYD8qWbInZrD0S9ccI6vZclF3DsHE7njGlLtaHbhg== +css-functions-list@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/css-functions-list/-/css-functions-list-3.0.1.tgz#1460df7fb584d1692c30b105151dbb988c8094f9" + integrity sha512-PriDuifDt4u4rkDgnqRCLnjfMatufLmWNfQnGCq34xZwpY3oabwhB9SqRBmuvWUgndbemCFlKqg+nO7C2q0SBw== + +css-loader@^6.7.1: + version "6.7.1" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.7.1.tgz#e98106f154f6e1baf3fc3bc455cb9981c1d5fd2e" + integrity sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw== dependencies: icss-utils "^5.1.0" - loader-utils "^2.0.0" - postcss "^8.2.15" + postcss "^8.4.7" postcss-modules-extract-imports "^3.0.0" postcss-modules-local-by-default "^4.0.0" postcss-modules-scope "^3.0.0" postcss-modules-values "^4.0.0" - postcss-value-parser "^4.1.0" - schema-utils "^3.0.0" + postcss-value-parser "^4.2.0" semver "^7.3.5" -css-minimizer-webpack-plugin@^3.0.2: - version "3.3.0" - resolved "https://registry.yarnpkg.com/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-3.3.0.tgz#e61515072e788c4134b9ca395adc56243cf4d3e1" - integrity sha512-+SU5aHgGZkk2kxKsq/BZXnYee2cjHIiFARF2gGaG6gIFtLJ87330GeafqhxAemwi/WgQ40v0OQ7pBVljKAMoXg== +css-minimizer-webpack-plugin@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-3.4.1.tgz#ab78f781ced9181992fe7b6e4f3422e76429878f" + integrity sha512-1u6D71zeIfgngN2XNRJefc/hY7Ybsxd74Jm4qngIXyUEk7fss3VUzuHxLAq/R8NAba4QU9OUSaMZlbpRc7bM4Q== dependencies: - "@types/cssnano" "^4.0.1" cssnano "^5.0.6" jest-worker "^27.0.2" postcss "^8.3.5" @@ -3081,9 +3234,9 @@ css-minimizer-webpack-plugin@^3.0.2: source-map "^0.6.1" css-select@^4.1.3: - version "4.2.0" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.2.0.tgz#ab28276d3afb00cc05e818bd33eb030f14f57895" - integrity sha512-6YVG6hsH9yIb/si3Th/is8Pex7qnVHO6t7q7U6TIUnkQASGbS8tnUDBftnPynLNnuUl/r2+PTd0ekiiq7R0zJw== + version "4.2.1" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.2.1.tgz#9e665d6ae4c7f9d65dbe69d0316e3221fb274cdd" + integrity sha512-/aUslKhzkTNCQUB2qTX84lVmfia9NyjP3WpDGtj/WxhwBzWBYUV3DgUpurHTme8UTPcPlAD1DJ+b0nN/t50zDQ== dependencies: boolbase "^1.0.0" css-what "^5.1.0" @@ -3114,7 +3267,7 @@ css-what@2.1: resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg== -css-what@^5.1.0: +css-what@^5.0.1, css-what@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/css-what/-/css-what-5.1.0.tgz#3f7b707aadf633baf62c2ceb8579b545bb40f7fe" integrity sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw== @@ -3124,64 +3277,64 @@ cssesc@^3.0.0: resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== -cssnano-preset-advanced@^5.1.4: - version "5.1.9" - resolved "https://registry.yarnpkg.com/cssnano-preset-advanced/-/cssnano-preset-advanced-5.1.9.tgz#7f392122a5b26368cb05d30750d7a50d6ede7f8b" - integrity sha512-lWyaSP22ixL8pi9k+yz7VQwa1OHDCZ3SIZeq5K40NIRDII42ua2pO9HRtWQ9N+xh/AQTTHZR4ZOSxouB7VjCIQ== +cssnano-preset-advanced@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/cssnano-preset-advanced/-/cssnano-preset-advanced-5.3.1.tgz#f4fa7006aab67e354289b3efd512c93a272b3874" + integrity sha512-kfCknalY5VX/JKJ3Iri5/5rhZmQIqkbqgXsA6oaTnfA4flY/tt+w0hMxbExr0/fVuJL8w56j211op+pkQoNzoQ== dependencies: autoprefixer "^10.3.7" - cssnano-preset-default "^5.1.9" - postcss-discard-unused "^5.0.1" - postcss-merge-idents "^5.0.1" - postcss-reduce-idents "^5.0.1" - postcss-zindex "^5.0.1" + cssnano-preset-default "^5.2.5" + postcss-discard-unused "^5.1.0" + postcss-merge-idents "^5.1.1" + postcss-reduce-idents "^5.2.0" + postcss-zindex "^5.1.0" -cssnano-preset-default@^5.1.9: - version "5.1.9" - resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.1.9.tgz#79628ac48eccbdad570f70b4018cc38d43d1b7df" - integrity sha512-RhkEucqlQ+OxEi14K1p8gdXcMQy1mSpo7P1oC44oRls7BYIj8p+cht4IFBFV3W4iOjTP8EUB33XV1fX9KhDzyA== +cssnano-preset-default@^5.2.5: + version "5.2.5" + resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.2.5.tgz#267ded811a3e1664d78707f5355fcd89feeb38ac" + integrity sha512-WopL7PzN7sos3X8B54/QGl+CZUh1f0qN4ds+y2d5EPwRSSc3jsitVw81O+Uyop0pXyOfPfZxnc+LmA8w/Ki/WQ== dependencies: css-declaration-sorter "^6.0.3" - cssnano-utils "^2.0.1" - postcss-calc "^8.0.0" - postcss-colormin "^5.2.2" - postcss-convert-values "^5.0.2" - postcss-discard-comments "^5.0.1" - postcss-discard-duplicates "^5.0.1" - postcss-discard-empty "^5.0.1" - postcss-discard-overridden "^5.0.1" - postcss-merge-longhand "^5.0.4" - postcss-merge-rules "^5.0.3" - postcss-minify-font-values "^5.0.1" - postcss-minify-gradients "^5.0.3" - postcss-minify-params "^5.0.2" - postcss-minify-selectors "^5.1.0" - postcss-normalize-charset "^5.0.1" - postcss-normalize-display-values "^5.0.1" - postcss-normalize-positions "^5.0.1" - postcss-normalize-repeat-style "^5.0.1" - postcss-normalize-string "^5.0.1" - postcss-normalize-timing-functions "^5.0.1" - postcss-normalize-unicode "^5.0.1" - postcss-normalize-url "^5.0.4" - postcss-normalize-whitespace "^5.0.1" - postcss-ordered-values "^5.0.2" - postcss-reduce-initial "^5.0.2" - postcss-reduce-transforms "^5.0.1" - postcss-svgo "^5.0.3" - postcss-unique-selectors "^5.0.2" - -cssnano-utils@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-2.0.1.tgz#8660aa2b37ed869d2e2f22918196a9a8b6498ce2" - integrity sha512-i8vLRZTnEH9ubIyfdZCAdIdgnHAUeQeByEeQ2I7oTilvP9oHO6RScpeq3GsFUVqeB8uZgOQ9pw8utofNn32hhQ== + cssnano-utils "^3.1.0" + postcss-calc "^8.2.3" + postcss-colormin "^5.3.0" + postcss-convert-values "^5.1.0" + postcss-discard-comments "^5.1.1" + postcss-discard-duplicates "^5.1.0" + postcss-discard-empty "^5.1.1" + postcss-discard-overridden "^5.1.0" + postcss-merge-longhand "^5.1.3" + postcss-merge-rules "^5.1.1" + postcss-minify-font-values "^5.1.0" + postcss-minify-gradients "^5.1.1" + postcss-minify-params "^5.1.2" + postcss-minify-selectors "^5.2.0" + postcss-normalize-charset "^5.1.0" + postcss-normalize-display-values "^5.1.0" + postcss-normalize-positions "^5.1.0" + postcss-normalize-repeat-style "^5.1.0" + postcss-normalize-string "^5.1.0" + postcss-normalize-timing-functions "^5.1.0" + postcss-normalize-unicode "^5.1.0" + postcss-normalize-url "^5.1.0" + postcss-normalize-whitespace "^5.1.1" + postcss-ordered-values "^5.1.1" + postcss-reduce-initial "^5.1.0" + postcss-reduce-transforms "^5.1.0" + postcss-svgo "^5.1.0" + postcss-unique-selectors "^5.1.1" + +cssnano-utils@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-3.1.0.tgz#95684d08c91511edfc70d2636338ca37ef3a6861" + integrity sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA== -cssnano@^5.0.6, cssnano@^5.0.8: - version "5.0.14" - resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.0.14.tgz#99bc550f663b48c38e9b8e0ae795697c9de84b47" - integrity sha512-qzhRkFvBhv08tbyKCIfWbxBXmkIpLl1uNblt8SpTHkgLfON5OCPX/CCnkdNmEosvo8bANQYmTTMEgcVBlisHaw== +cssnano@^5.0.6, cssnano@^5.1.5: + version "5.1.5" + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.1.5.tgz#5f3f519538c7f1c182c527096892243db3e17397" + integrity sha512-VZO1e+bRRVixMeia1zKagrv0lLN1B/r/u12STGNNUFxnp97LIFgZHQa0JxqlwEkvzUyA9Oz/WnCTAFkdEbONmg== dependencies: - cssnano-preset-default "^5.1.9" + cssnano-preset-default "^5.2.5" lilconfig "^2.0.3" yaml "^1.10.2" @@ -3193,14 +3346,14 @@ csso@^4.2.0: css-tree "^1.1.2" csstype@^3.0.2: - version "3.0.10" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.10.tgz#2ad3a7bed70f35b965707c092e5f30b327c290e5" - integrity sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA== + version "3.0.11" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.11.tgz#d66700c5eacfac1940deb4e3ee5642792d85cd33" + integrity sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw== damerau-levenshtein@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.7.tgz#64368003512a1a6992593741a09a9d31a836f55d" - integrity sha512-VvdQIPGdWP0SqFXghj79Wf/5LArmreyMsGLa6FG6iC4t3j7j5s71TrwWmT/4akbDQIqjfACkLZmjXhA7g2oUZw== + version "1.0.8" + resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7" + integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA== debug@2.6.9, debug@^2.6.0, debug@^2.6.9: version "2.6.9" @@ -3216,10 +3369,10 @@ debug@^3.1.1, debug@^3.2.7: dependencies: ms "^2.1.1" -debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2: - version "4.3.3" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" - integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== +debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: ms "2.1.2" @@ -3402,7 +3555,7 @@ dom-serializer@0: domelementtype "^2.0.1" entities "^2.0.0" -dom-serializer@^1.0.1: +dom-serializer@^1.0.1, dom-serializer@^1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.3.2.tgz#6206437d32ceefaec7161803230c7a20bc1b4d91" integrity sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig== @@ -3437,9 +3590,9 @@ domhandler@^2.3.0: domelementtype "1" domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.0.tgz#16c658c626cf966967e306f966b431f77d4a5626" - integrity sha512-fC0aXNQXqKSFTr2wDNZDhsEYjCiYsDWl3D01kwt25hm1YIPyDGHvvi3rw+PLqHAl/m71MaiF7d5zvBr0p5UB2g== + version "4.3.1" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c" + integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ== dependencies: domelementtype "^2.2.0" @@ -3459,7 +3612,7 @@ domutils@^1.5.1: dom-serializer "0" domelementtype "1" -domutils@^2.5.2, domutils@^2.8.0: +domutils@^2.5.2, domutils@^2.7.0, domutils@^2.8.0: version "2.8.0" resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== @@ -3488,20 +3641,25 @@ duplexer3@^0.1.4: resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= -duplexer@^0.1.1, duplexer@^0.1.2: +duplexer@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= -electron-to-chromium@^1.4.17: - version "1.4.25" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.25.tgz#ce95e6678f8c6893ae892c7e95a5000e83f1957f" - integrity sha512-bTwub9Y/76EiNmfaiJih+hAy6xn7Ns95S4KvI2NuKNOz8TEEKKQUu44xuy0PYMudjM9zdjKRS1bitsUvHTfuUg== +electron-to-chromium@^1.4.84: + version "1.4.93" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.93.tgz#2e87ac28721cb31d472ec2bd04f7daf9f2e13de2" + integrity sha512-ywq9Pc5Gwwpv7NG767CtoU8xF3aAUQJjH9//Wy3MBCg4w5JSLbJUq2L8IsCdzPMjvSgxuue9WcVaTOyyxCL0aQ== emoji-regex@^8.0.0: version "8.0.0" @@ -3535,21 +3693,14 @@ end-of-stream@^1.1.0: dependencies: once "^1.4.0" -enhanced-resolve@^5.8.3: - version "5.8.3" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.8.3.tgz#6d552d465cce0423f5b3d718511ea53826a7b2f0" - integrity sha512-EGAbGvH7j7Xt2nc0E7D99La1OiEs8LnyimkRgwExpUMScN6O+3x9tIWs7PLQZVNx4YD+00skHXPXi1yQHpAmZA== +enhanced-resolve@^5.9.2: + version "5.9.2" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.9.2.tgz#0224dcd6a43389ebfb2d55efee517e5466772dd9" + integrity sha512-GIm3fQfwLJ8YZx2smuHpBKkXC1yOk+OBEmKckVyL0i/ea8mqDEykK3ld5dgH1QYPNyT/lIllxV2LULnxCHaHkA== dependencies: graceful-fs "^4.2.4" tapable "^2.2.0" -enquirer@^2.3.5: - version "2.3.6" - resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" - integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== - dependencies: - ansi-colors "^4.1.1" - entities@^1.1.1, entities@~1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" @@ -3632,11 +3783,6 @@ escape-string-regexp@^1.0.5: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= -escape-string-regexp@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" - integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== - escape-string-regexp@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" @@ -3652,19 +3798,19 @@ eslint-config-airbnb-base@^15.0.0: object.entries "^1.1.5" semver "^6.3.0" -eslint-config-airbnb@^19.0.0: - version "19.0.2" - resolved "https://registry.yarnpkg.com/eslint-config-airbnb/-/eslint-config-airbnb-19.0.2.tgz#3a1681e39b68cc6abeae58300014ed5d5706b625" - integrity sha512-4v5DEMVSl043LaCT+gsxPcoiIk0iYG5zxJKKjIy80H/D//2E0vtuOBWkb0CBDxjF+y26yQzspIXYuY6wMmt9Cw== +eslint-config-airbnb@^19.0.4: + version "19.0.4" + resolved "https://registry.yarnpkg.com/eslint-config-airbnb/-/eslint-config-airbnb-19.0.4.tgz#84d4c3490ad70a0ffa571138ebcdea6ab085fdc3" + integrity sha512-T75QYQVQX57jiNgpF9r1KegMICE94VYwoFQyMGhrvc+lB8YF2E/M/PYDaQe1AJcWaEgqLE+ErXV1Og/+6Vyzew== dependencies: eslint-config-airbnb-base "^15.0.0" object.assign "^4.1.2" object.entries "^1.1.5" -eslint-config-prettier@^8.3.0: - version "8.3.0" - resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz#f7471b20b6fe8a9a9254cc684454202886a2dd7a" - integrity sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew== +eslint-config-prettier@^8.5.0: + version "8.5.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz#5a81680ec934beca02c7b1a61cf8ca34b66feab1" + integrity sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q== eslint-import-resolver-node@^0.3.6: version "0.3.6" @@ -3674,38 +3820,37 @@ eslint-import-resolver-node@^0.3.6: debug "^3.2.7" resolve "^1.20.0" -eslint-module-utils@^2.7.1: - version "2.7.1" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.1.tgz#b435001c9f8dd4ab7f6d0efcae4b9696d4c24b7c" - integrity sha512-fjoetBXQZq2tSTWZ9yWVl2KuFrTZZH3V+9iD1V1RfpDgxzJR+mPd/KZmMiA8gbPqdBzpNiEHOuT7IYEWxrH0zQ== +eslint-module-utils@^2.7.2: + version "2.7.3" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz#ad7e3a10552fdd0642e1e55292781bd6e34876ee" + integrity sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ== dependencies: debug "^3.2.7" find-up "^2.1.0" - pkg-dir "^2.0.0" eslint-plugin-header@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/eslint-plugin-header/-/eslint-plugin-header-3.1.1.tgz#6ce512432d57675265fac47292b50d1eff11acd6" integrity sha512-9vlKxuJ4qf793CmeeSrZUvVClw6amtpghq3CuWcB5cUNnWHQhgcqy5eF8oVKFk1G3Y/CbchGfEaw3wiIJaNmVg== -eslint-plugin-import@^2.25.3: - version "2.25.3" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.25.3.tgz#a554b5f66e08fb4f6dc99221866e57cfff824766" - integrity sha512-RzAVbby+72IB3iOEL8clzPLzL3wpDrlwjsTBAQXgyp5SeTqqY+0bFubwuo+y/HLhNZcXV4XqTBO4LGsfyHIDXg== +eslint-plugin-import@^2.25.4: + version "2.25.4" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.25.4.tgz#322f3f916a4e9e991ac7af32032c25ce313209f1" + integrity sha512-/KJBASVFxpu0xg1kIBn9AUa8hQVnszpwgE7Ld0lKAlx7Ie87yzEzCgSkekt+le/YVhiaosO4Y14GDAOc41nfxA== dependencies: array-includes "^3.1.4" array.prototype.flat "^1.2.5" debug "^2.6.9" doctrine "^2.1.0" eslint-import-resolver-node "^0.3.6" - eslint-module-utils "^2.7.1" + eslint-module-utils "^2.7.2" has "^1.0.3" is-core-module "^2.8.0" is-glob "^4.0.3" minimatch "^3.0.4" object.values "^1.1.5" resolve "^1.20.0" - tsconfig-paths "^3.11.0" + tsconfig-paths "^3.12.0" eslint-plugin-jsx-a11y@^6.5.1: version "6.5.1" @@ -3730,22 +3875,22 @@ eslint-plugin-react-hooks@^4.3.0: resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.3.0.tgz#318dbf312e06fab1c835a4abef00121751ac1172" integrity sha512-XslZy0LnMn+84NEG9jSGR6eGqaZB3133L8xewQo3fQagbQuGt7a63gf+P1NGKZavEYEC3UXaWEAA/AqDkuN6xA== -eslint-plugin-react@^7.27.0: - version "7.27.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.27.1.tgz#469202442506616f77a854d91babaae1ec174b45" - integrity sha512-meyunDjMMYeWr/4EBLTV1op3iSG3mjT/pz5gti38UzfM4OPpNc2m0t2xvKCOMU5D6FSdd34BIMFOvQbW+i8GAA== +eslint-plugin-react@^7.29.4: + version "7.29.4" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.29.4.tgz#4717de5227f55f3801a5fd51a16a4fa22b5914d2" + integrity sha512-CVCXajliVh509PcZYRFyu/BoUEz452+jtQJq2b3Bae4v3xBUWPLCmtmBM+ZinG4MzwmxJgJ2M5rMqhqLVn7MtQ== dependencies: array-includes "^3.1.4" array.prototype.flatmap "^1.2.5" doctrine "^2.1.0" estraverse "^5.3.0" jsx-ast-utils "^2.4.1 || ^3.0.0" - minimatch "^3.0.4" + minimatch "^3.1.2" object.entries "^1.1.5" object.fromentries "^2.0.5" object.hasown "^1.1.0" object.values "^1.1.5" - prop-types "^15.7.2" + prop-types "^15.8.1" resolve "^2.0.0-next.3" semver "^6.3.0" string.prototype.matchall "^4.0.6" @@ -3758,10 +3903,10 @@ eslint-scope@5.1.1, eslint-scope@^5.1.1: esrecurse "^4.3.0" estraverse "^4.1.1" -eslint-scope@^7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.0.tgz#c1f6ea30ac583031f203d65c73e723b01298f153" - integrity sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg== +eslint-scope@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642" + integrity sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw== dependencies: esrecurse "^4.3.0" estraverse "^5.2.0" @@ -3778,29 +3923,28 @@ eslint-visitor-keys@^2.0.0, eslint-visitor-keys@^2.1.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== -eslint-visitor-keys@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.1.0.tgz#eee4acea891814cda67a7d8812d9647dd0179af2" - integrity sha512-yWJFpu4DtjsWKkt5GeNBBuZMlNcYVs6vRCLoCVEJrTjaSB6LC98gFipNK/erM2Heg/E8mIK+hXG/pJMLK+eRZA== +eslint-visitor-keys@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" + integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== -eslint@^8.2.0: - version "8.5.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.5.0.tgz#ddd2c1afd8f412036f87ae2a063d2aa296d3175f" - integrity sha512-tVGSkgNbOfiHyVte8bCM8OmX+xG9PzVG/B4UCF60zx7j61WIVY/AqJECDgpLD4DbbESD0e174gOg3ZlrX15GDg== +eslint@^8.11.0: + version "8.11.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.11.0.tgz#88b91cfba1356fc10bb9eb592958457dfe09fb37" + integrity sha512-/KRpd9mIRg2raGxHRGwW9ZywYNAClZrHjdueHcrVDuO3a6bj83eoTirCCk0M0yPwOjWYKHwRVRid+xK4F/GHgA== dependencies: - "@eslint/eslintrc" "^1.0.5" + "@eslint/eslintrc" "^1.2.1" "@humanwhocodes/config-array" "^0.9.2" ajv "^6.10.0" chalk "^4.0.0" cross-spawn "^7.0.2" debug "^4.3.2" doctrine "^3.0.0" - enquirer "^2.3.5" escape-string-regexp "^4.0.0" - eslint-scope "^7.1.0" + eslint-scope "^7.1.1" eslint-utils "^3.0.0" - eslint-visitor-keys "^3.1.0" - espree "^9.2.0" + eslint-visitor-keys "^3.3.0" + espree "^9.3.1" esquery "^1.4.0" esutils "^2.0.2" fast-deep-equal "^3.1.3" @@ -3808,7 +3952,7 @@ eslint@^8.2.0: functional-red-black-tree "^1.0.1" glob-parent "^6.0.1" globals "^13.6.0" - ignore "^4.0.6" + ignore "^5.2.0" import-fresh "^3.0.0" imurmurhash "^0.1.4" is-glob "^4.0.0" @@ -3819,22 +3963,20 @@ eslint@^8.2.0: minimatch "^3.0.4" natural-compare "^1.4.0" optionator "^0.9.1" - progress "^2.0.0" regexpp "^3.2.0" - semver "^7.2.1" strip-ansi "^6.0.1" strip-json-comments "^3.1.0" text-table "^0.2.0" v8-compile-cache "^2.0.3" -espree@^9.2.0: - version "9.2.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.2.0.tgz#c50814e01611c2d0f8bd4daa83c369eabba80dbc" - integrity sha512-oP3utRkynpZWF/F2x/HZJ+AGtnIclaR7z1pYPxy7NYM2fSO6LgK/Rkny8anRSPK/VwEA1eqm2squui0T7ZMOBg== +espree@^9.3.1: + version "9.3.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.1.tgz#8793b4bc27ea4c778c19908e0719e7b8f4115bcd" + integrity sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ== dependencies: - acorn "^8.6.0" + acorn "^8.7.0" acorn-jsx "^5.3.1" - eslint-visitor-keys "^3.1.0" + eslint-visitor-keys "^3.3.0" esprima@^4.0.0: version "4.0.1" @@ -3880,11 +4022,12 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= -eval@^0.1.4: - version "0.1.6" - resolved "https://registry.yarnpkg.com/eval/-/eval-0.1.6.tgz#9620d7d8c85515e97e6b47c5814f46ae381cb3cc" - integrity sha512-o0XUw+5OGkXw4pJZzQoXUk+H87DHuC+7ZE//oSrRGtatTmr12oTnLfg6QOq9DyTt0c/p4TwzgmkKrBzWTSizyQ== +eval@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/eval/-/eval-0.1.8.tgz#2b903473b8cc1d1989b83a1e7923f883eb357f85" + integrity sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw== dependencies: + "@types/node" "*" require-like ">= 0.1.1" eventemitter3@^4.0.0: @@ -3920,16 +4063,16 @@ execall@^2.0.0: clone-regexp "^2.1.0" express@^4.17.1: - version "4.17.2" - resolved "https://registry.yarnpkg.com/express/-/express-4.17.2.tgz#c18369f265297319beed4e5558753cc8c1364cb3" - integrity sha512-oxlxJxcQlYwqPWKVJJtvQiwHgosH/LrLSPA+H4UxpyvSS6jC5aH+5MoHFM+KABgTOt0APue4w66Ha8jCUo9QGg== + version "4.17.3" + resolved "https://registry.yarnpkg.com/express/-/express-4.17.3.tgz#f6c7302194a4fb54271b73a1fe7a06478c8f85a1" + integrity sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg== dependencies: - accepts "~1.3.7" + accepts "~1.3.8" array-flatten "1.1.1" - body-parser "1.19.1" + body-parser "1.19.2" content-disposition "0.5.4" content-type "~1.0.4" - cookie "0.4.1" + cookie "0.4.2" cookie-signature "1.0.6" debug "2.6.9" depd "~1.1.2" @@ -3944,7 +4087,7 @@ express@^4.17.1: parseurl "~1.3.3" path-to-regexp "0.1.7" proxy-addr "~2.0.7" - qs "6.9.6" + qs "6.9.7" range-parser "~1.2.1" safe-buffer "5.2.1" send "0.17.2" @@ -3972,10 +4115,10 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-glob@^3.1.1, fast-glob@^3.2.5, fast-glob@^3.2.7: - version "3.2.7" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1" - integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q== +fast-glob@^3.2.11, fast-glob@^3.2.7, fast-glob@^3.2.9: + version "3.2.11" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" + integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" @@ -4032,11 +4175,11 @@ fbjs-css-vars@^1.0.0: integrity sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ== fbjs@^3.0.0, fbjs@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-3.0.2.tgz#dfae08a85c66a58372993ce2caf30863f569ff94" - integrity sha512-qv+boqYndjElAJHNN3NoM8XuwQZ1j2m3kEvTgdle8IDjr6oUbkEpvABWtj/rQl3vq4ew7dnElBxL4YJAwTVqQQ== + version "3.0.4" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-3.0.4.tgz#e1871c6bd3083bac71ff2da868ad5067d37716c6" + integrity sha512-ucV0tDODnGV3JCnnkmoszb5lf4bNpzjv80K41wd4k798Etq+UYD0y0TIfalLjZoKgjive6/adkRnszwapiDgBQ== dependencies: - cross-fetch "^3.0.4" + cross-fetch "^3.1.5" fbjs-css-vars "^1.0.0" loose-envify "^1.0.0" object-assign "^4.1.0" @@ -4066,10 +4209,10 @@ file-loader@^6.2.0: loader-utils "^2.0.0" schema-utils "^3.0.0" -filesize@^6.1.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/filesize/-/filesize-6.4.0.tgz#914f50471dd66fdca3cefe628bd0cde4ef769bcd" - integrity sha512-mjFIpOHC4jbfcTfoh4rkWpI31mF7viw9ikj/JyLoKzqlwG/YsefKfvYlYhdYdg/9mtK2z1AzgN/0LvVQ3zdlSQ== +filesize@^8.0.6: + version "8.0.7" + resolved "https://registry.yarnpkg.com/filesize/-/filesize-8.0.7.tgz#695e70d80f4e47012c132d57a059e80c6b580bd8" + integrity sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ== fill-range@^7.0.1: version "7.0.1" @@ -4139,9 +4282,9 @@ flat-cache@^3.0.4: rimraf "^3.0.2" flatted@^3.1.0: - version "3.2.4" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.4.tgz#28d9969ea90661b5134259f312ab6aa7929ac5e2" - integrity sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw== + version "3.2.5" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.5.tgz#76c8584f4fc843db64702a6bd04ab7a8bd666da3" + integrity sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg== flux@^4.0.1: version "4.0.3" @@ -4151,12 +4294,12 @@ flux@^4.0.1: fbemitter "^3.0.0" fbjs "^3.0.1" -follow-redirects@^1.0.0, follow-redirects@^1.14.0: - version "1.14.6" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.6.tgz#8cfb281bbc035b3c067d6cd975b0f6ade6e855cd" - integrity sha512-fhUl5EwSJbbl8AR+uYL2KQDxLkdSjZGR36xy46AO7cOMTrCMON6Sa28FmAnC2tRTDbd/Uuzz3aJBv7EBN7JH8A== +follow-redirects@^1.0.0, follow-redirects@^1.14.7: + version "1.14.9" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7" + integrity sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w== -fork-ts-checker-webpack-plugin@^6.0.5: +fork-ts-checker-webpack-plugin@^6.5.0: version "6.5.0" resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.0.tgz#0282b335fa495a97e167f69018f566ea7d2a2b5e" integrity sha512-cS178Y+xxtIjEUorcHddKS7yCMlrDPV31mt47blKKRfMd70Kxu5xruAFE2o9sDY6wVC5deuob/u/alD04YYHnw== @@ -4180,20 +4323,20 @@ forwarded@0.2.0: resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== -fraction.js@^4.1.1: - version "4.1.2" - resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.1.2.tgz#13e420a92422b6cf244dff8690ed89401029fbe8" - integrity sha512-o2RiJQ6DZaR/5+Si0qJUIy637QMRudSi9kU/FFzx9EZazrIdnBgpU+3sEWCxAVhH2RtxW2Oz+T4p2o8uOPVcgA== +fraction.js@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950" + integrity sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA== fresh@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= -fs-extra@^10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.0.0.tgz#9ff61b655dde53fb34a82df84bb214ce802e17c1" - integrity sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ== +fs-extra@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.0.1.tgz#27de43b4320e833f6867cc044bfce29fdf0ef3b8" + integrity sha512-NbdoVMZso2Lsrn/QwLXOy6rm0ufY2zEOKCDzJR/0kBsb0E6qed0P3iYK+Ath3BfvXEeu4JhEtXLgILx5psUfag== dependencies: graceful-fs "^4.2.0" jsonfile "^6.0.1" @@ -4350,36 +4493,41 @@ globals@^11.1.0: integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== globals@^13.6.0, globals@^13.9.0: - version "13.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.12.0.tgz#4d733760304230a0082ed96e21e5c565f898089e" - integrity sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg== + version "13.13.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.13.0.tgz#ac32261060d8070e2719dd6998406e27d2b5727b" + integrity sha512-EQ7Q18AJlPwp3vUDL4mKA0KXrXyNIQyWon6T6XQiBQF0XHvRsiCSrWmmeATpUzdJN2HhWZU6Pdl0a9zdep5p6A== dependencies: type-fest "^0.20.2" -globby@^11.0.1, globby@^11.0.2, globby@^11.0.3, globby@^11.0.4: - version "11.0.4" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5" - integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg== +globby@^11.0.1, globby@^11.0.4, globby@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== dependencies: array-union "^2.1.0" dir-glob "^3.0.1" - fast-glob "^3.1.1" - ignore "^5.1.4" - merge2 "^1.3.0" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" slash "^3.0.0" +globby@^12.0.2: + version "12.2.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-12.2.0.tgz#2ab8046b4fba4ff6eede835b29f678f90e3d3c22" + integrity sha512-wiSuFQLZ+urS9x2gGPl1H5drc5twabmm4m2gTR27XDFyjUHJUNsS8o/2aKyIF6IoBaR630atdher0XJ5g6OMmA== + dependencies: + array-union "^3.0.1" + dir-glob "^3.0.1" + fast-glob "^3.2.7" + ignore "^5.1.9" + merge2 "^1.4.1" + slash "^4.0.0" + globjoin@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/globjoin/-/globjoin-0.1.4.tgz#2f4494ac8919e3767c5cbb691e9f463324285d43" integrity sha1-L0SUrIkZ43Z8XLtpHp9GMyQoXUM= -gonzales-pe@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/gonzales-pe/-/gonzales-pe-4.3.0.tgz#fe9dec5f3c557eead09ff868c65826be54d067b3" - integrity sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ== - dependencies: - minimist "^1.2.5" - got@^9.6.0: version "9.6.0" resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" @@ -4397,10 +4545,10 @@ got@^9.6.0: to-readable-stream "^1.0.0" url-parse-lax "^3.0.0" -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6: - version "4.2.8" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" - integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: + version "4.2.9" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" + integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== gray-matter@^4.0.3: version "4.0.3" @@ -4412,14 +4560,6 @@ gray-matter@^4.0.3: section-matter "^1.0.0" strip-bom-string "^1.0.0" -gzip-size@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.1.1.tgz#cb9bee692f87c0612b232840a873904e4c135274" - integrity sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA== - dependencies: - duplexer "^0.1.1" - pify "^4.0.1" - gzip-size@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462" @@ -4452,10 +4592,10 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-symbols@^1.0.1, has-symbols@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" - integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== +has-symbols@^1.0.1, has-symbols@^1.0.2, has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== has-tostringtag@^1.0.0: version "1.0.0" @@ -4595,9 +4735,9 @@ hosted-git-info@^2.1.4: integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== hosted-git-info@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.0.2.tgz#5e425507eede4fea846b7262f0838456c4209961" - integrity sha512-c9OGXbZ3guC/xOlCg1Ci/VgWlwsqDv1yMQL1CWqXDL0hDjXuNcq0zuR4xqPSuasI3kqFDhqSyTjREz5gzq0fXg== + version "4.1.0" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.1.0.tgz#827b82867e9ff1c8d0c4d9d53880397d2c86d224" + integrity sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA== dependencies: lru-cache "^6.0.0" @@ -4616,7 +4756,7 @@ html-entities@^2.3.2: resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.3.2.tgz#760b404685cb1d794e4f4b744332e3b00dcfe488" integrity sha512-c3Ab/url5ksaT0WyleslpBEthOzWhrjQbg75y7XUsfSzi3Dgzt0l8w5e7DylRn15MTlMMD58dTfzddNS2kcAjQ== -html-minifier-terser@^6.0.2: +html-minifier-terser@^6.0.2, html-minifier-terser@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#bfc818934cc07918f6b3669f5774ecdfd48f32ab" integrity sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw== @@ -4639,7 +4779,7 @@ html-void-elements@^1.0.0: resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-1.0.5.tgz#ce9159494e86d95e45795b166c2021c2cfca4483" integrity sha512-uE/TxKuyNIcx44cIWnjr/rfIATDH7ZaOMmstu0CwhFG1Dunhlp4OC6/NMbhiwoq5BpW0ubi303qnEk/PZj614w== -html-webpack-plugin@^5.4.0: +html-webpack-plugin@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz#c3911936f57681c1f9f4d8b68c158cd9dfe52f50" integrity sha512-sy88PC2cRTVxvETRgUHFrL4No3UxvcH8G1NepGhqaTT+GXN2kTamqasot0inS5hXeg1cMbFDt27zzo9p35lZVw== @@ -4650,7 +4790,7 @@ html-webpack-plugin@^5.4.0: pretty-error "^4.0.0" tapable "^2.0.0" -htmlparser2@^3.10.0, htmlparser2@^3.9.1: +htmlparser2@^3.9.1: version "3.10.1" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ== @@ -4704,16 +4844,16 @@ http-errors@~1.6.2: statuses ">= 1.4.0 < 2" http-parser-js@>=0.5.1: - version "0.5.5" - resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.5.tgz#d7c30d5d3c90d865b4a2e870181f9d6f22ac7ac5" - integrity sha512-x+JVEkO2PoM8qqpbPbOL3cqHPwerep7OwzK7Ay+sMQjKzaKCqWvjoXm5tqMP9tXWWTnTzAjIhXg+J99XYuPhPA== + version "0.5.6" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.6.tgz#2e02406ab2df8af8a7abfba62e0da01c62b95afd" + integrity sha512-vDlkRPDJn93swjcjqMSaGSPABbIarsr1TLAui/gLDXzV5VsJNdXNzMYDyNBLQkjWQCJ1uizu8T2oDMhmGt0PRA== http-proxy-middleware@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.1.tgz#7ef3417a479fb7666a571e09966c66a39bd2c15f" - integrity sha512-cfaXRVoZxSed/BmkA7SwBVNI9Kj7HFltaE5rqYOub5kWzWZ+gofV2koVN1j2rMW7pEfSSlCHGJ31xmuyFyfLOg== + version "2.0.4" + resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.4.tgz#03af0f4676d172ae775cb5c33f592f40e1a4e07a" + integrity sha512-m/4FxX17SUvz4lJ5WPXOHDUuCwIqXLfLHs1s0uZ3oYjhoXlx9csYxaOa0ElDEJ+h8Q4iJ1s+lTMbiCa4EXIJqg== dependencies: - "@types/http-proxy" "^1.17.5" + "@types/http-proxy" "^1.17.8" http-proxy "^1.18.1" is-glob "^4.0.1" is-plain-obj "^3.0.0" @@ -4745,22 +4885,24 @@ icss-utils@^5.0.0, icss-utils@^5.1.0: resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== -ignore@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" - integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== - -ignore@^5.1.4, ignore@^5.1.8: +ignore@^5.1.9, ignore@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== -immer@^9.0.6: - version "9.0.7" - resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.7.tgz#b6156bd7db55db7abc73fd2fdadf4e579a701075" - integrity sha512-KGllzpbamZDvOIxnmJ0jI840g7Oikx58lBPWV0hUh7dtAyZpFqqrBZdKka5GlTwMTZ1Tjc/bKKW4VSFAt6BqMA== +image-size@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/image-size/-/image-size-1.0.1.tgz#86d6cfc2b1d19eab5d2b368d4b9194d9e48541c5" + integrity sha512-VAwkvNSNGClRw9mDHhc5Efax8PLlsOGcUTh0T/LIriC8vPA3U5PdqXWqkz406MoYHMKW8Uf9gWr05T/rYB44kQ== + dependencies: + queue "6.0.2" + +immer@^9.0.7: + version "9.0.12" + resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.12.tgz#2d33ddf3ee1d247deab9d707ca472c8c942a0f20" + integrity sha512-lk7UNmSbAukB5B6dh9fnh5D0bJTOFKxVg2cyJWTYrWRfhLrLMBquONcUs3aFq507hNoIZEDDh8lb8UtOizSMhA== -import-fresh@^3.0.0, import-fresh@^3.1.0, import-fresh@^3.2.1, import-fresh@^3.2.2, import-fresh@^3.3.0: +import-fresh@^3.0.0, import-fresh@^3.1.0, import-fresh@^3.2.1, import-fresh@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== @@ -4788,10 +4930,10 @@ indent-string@^4.0.0: resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== -infima@0.2.0-alpha.37: - version "0.2.0-alpha.37" - resolved "https://registry.yarnpkg.com/infima/-/infima-0.2.0-alpha.37.tgz#b87ff42d528d6d050098a560f0294fbdd12adb78" - integrity sha512-4GX7Baw+/lwS4PPW/UJNY89tWSvYG1DL6baKVdpK6mC593iRgMssxNtORMTFArLPJ/A/lzsGhRmx+z6MaMxj0Q== +infima@0.2.0-alpha.38: + version "0.2.0-alpha.38" + resolved "https://registry.yarnpkg.com/infima/-/infima-0.2.0-alpha.38.tgz#e41d95c7cd82756549b17df12f613fed4af3d528" + integrity sha512-1WsmqSMI5IqzrUx3goq+miJznHBonbE3aoqZ1AR/i/oHhroxNeSV6Awv5VoVfXBhfTzLSnxkHaRI2qpAMYcCzw== inflight@^1.0.4: version "1.0.6" @@ -4840,6 +4982,13 @@ interpret@^1.0.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== +invariant@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + dependencies: + loose-envify "^1.0.0" + ip@^1.1.0: version "1.1.5" resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" @@ -4920,10 +5069,10 @@ is-ci@^2.0.0: dependencies: ci-info "^2.0.0" -is-core-module@^2.2.0, is-core-module@^2.5.0, is-core-module@^2.8.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.0.tgz#0321336c3d0925e497fd97f5d95cb114a5ccd548" - integrity sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw== +is-core-module@^2.2.0, is-core-module@^2.5.0, is-core-module@^2.8.0, is-core-module@^2.8.1: + version "2.8.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.1.tgz#f59fdfca701d5879d0a6b100a40aa1560ce27211" + integrity sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA== dependencies: has "^1.0.3" @@ -5043,6 +5192,11 @@ is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" +is-plain-object@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" + integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== + is-regex@^1.0.4, is-regex@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" @@ -5095,11 +5249,6 @@ is-typedarray@^1.0.0: resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= -is-unicode-supported@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" - integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== - is-weakref@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" @@ -5117,7 +5266,7 @@ is-word-character@^1.0.0: resolved "https://registry.yarnpkg.com/is-word-character/-/is-word-character-1.0.4.tgz#ce0e73216f98599060592f62ff31354ddbeb0230" integrity sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA== -is-wsl@^2.1.1, is-wsl@^2.2.0: +is-wsl@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== @@ -5149,19 +5298,19 @@ isobject@^3.0.1: resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= -jest-worker@^27.0.2, jest-worker@^27.4.1: - version "27.4.5" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.4.5.tgz#d696e3e46ae0f24cff3fa7195ffba22889262242" - integrity sha512-f2s8kEdy15cv9r7q4KkzGXvlY0JTcmCbMHZBfSQDwW77REr45IDWwd0lksDFeVHH2jJ5pqb90T77XscrjeGzzg== +jest-worker@^27.0.2, jest-worker@^27.4.5: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" + integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== dependencies: "@types/node" "*" merge-stream "^2.0.0" supports-color "^8.0.0" -joi@^17.4.0, joi@^17.4.2: - version "17.5.0" - resolved "https://registry.yarnpkg.com/joi/-/joi-17.5.0.tgz#7e66d0004b5045d971cf416a55fb61d33ac6e011" - integrity sha512-R7hR50COp7StzLnDi4ywOXHrBrgNXuUUfJWIR5lPY5Bm/pOD3jZaTwpluUXVLRWcoWZxkrHBBJ5hLxgnlehbdw== +joi@^17.6.0: + version "17.6.0" + resolved "https://registry.yarnpkg.com/joi/-/joi-17.6.0.tgz#0bb54f2f006c09a96e75ce687957bd04290054b2" + integrity sha512-OX5dG6DTbcr/kbMFj0KGYxuew69HPcAE3K/sZpEV2nP6e/j/C0HV+HNiBPCASxdx5T7DMoa0s8UeHWMnb6n2zw== dependencies: "@hapi/hoek" "^9.0.0" "@hapi/topo" "^5.0.0" @@ -5182,7 +5331,7 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@^4.0.0, js-yaml@^4.1.0: +js-yaml@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== @@ -5237,11 +5386,9 @@ json5@^1.0.1: minimist "^1.2.0" json5@^2.1.2: - version "2.2.0" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" - integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== - dependencies: - minimist "^1.2.5" + version "2.2.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" + integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== jsonfile@^6.0.1: version "6.1.0" @@ -5282,10 +5429,10 @@ klona@^2.0.5: resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.5.tgz#d166574d90076395d9963aa7a928fabb8d76afbc" integrity sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ== -known-css-properties@^0.21.0: - version "0.21.0" - resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.21.0.tgz#15fbd0bbb83447f3ce09d8af247ed47c68ede80d" - integrity sha512-sZLUnTqimCkvkgRS+kbPlYW5o8q5w1cu+uIisKpEWkj31I8mx8kNG162DwRav8Zirkva6N5uoFsm9kzK4mUXjw== +known-css-properties@^0.24.0: + version "0.24.0" + resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.24.0.tgz#19aefd85003ae5698a5560d2b55135bf5432155c" + integrity sha512-RTSoaUAfLvpR357vWzAz/50Q/BmHfmE6ETSWfutT0AJiw10e6CmcdYRQJlLRd95B53D0Y2aD1jSxD3V3ySF+PA== language-subtag-registry@~0.3.2: version "0.3.21" @@ -5320,9 +5467,9 @@ levn@^0.4.1: type-check "~0.4.0" lilconfig@^2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.4.tgz#f4507d043d7058b380b6a8f5cb7bcd4b34cee082" - integrity sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA== + version "2.0.5" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.5.tgz#19e57fd06ccc3848fd1891655b5a447092225b25" + integrity sha512-xaYmXZtTHPAw5m+xLN8ab9C+3a8YmV3asNSPOATITbtwrfbwaLJj8h66H1WMIpALCkqsIzK3h7oQ+PdX+LQ9Eg== lines-and-columns@^1.1.6: version "1.2.4" @@ -5334,15 +5481,6 @@ loader-runner@^4.2.0: resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.2.0.tgz#d7022380d66d14c5fb1d496b89864ebcfd478384" integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw== -loader-utils@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" - integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== - dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^1.0.1" - loader-utils@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.2.tgz#d6e3b4fb81870721ae4e0868ab11dd638368c129" @@ -5352,6 +5490,11 @@ loader-utils@^2.0.0: emojis-list "^3.0.0" json5 "^2.1.2" +loader-utils@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-3.2.0.tgz#bcecc51a7898bee7473d4bc6b845b23af8304d4f" + integrity sha512-HVl9ZqccQihZ7JM85dco1MvO9G+ONvxoGa9rkhzFsneGLKSUg1gJf9bWzhRhcvm2qChhWpebQhP44qxjKIUCaQ== + locate-path@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" @@ -5477,19 +5620,6 @@ lodash@^4.17.14, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -log-symbols@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" - integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== - dependencies: - chalk "^4.1.0" - is-unicode-supported "^0.1.0" - -longest-streak@^2.0.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.4.tgz#b8599957da5b5dab64dee3fe316fa774597d90e4" - integrity sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg== - loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" @@ -5521,13 +5651,6 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" -magic-string@^0.25.3: - version "0.25.7" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" - integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA== - dependencies: - sourcemap-codec "^1.4.4" - make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" @@ -5569,17 +5692,6 @@ mdast-util-definitions@^4.0.0: dependencies: unist-util-visit "^2.0.0" -mdast-util-from-markdown@^0.8.0: - version "0.8.5" - resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-0.8.5.tgz#d1ef2ca42bc377ecb0463a987910dae89bd9a28c" - integrity sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ== - dependencies: - "@types/mdast" "^3.0.0" - mdast-util-to-string "^2.0.0" - micromark "~2.11.0" - parse-entities "^2.0.0" - unist-util-stringify-position "^2.0.0" - mdast-util-to-hast@10.0.1: version "10.0.1" resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-10.0.1.tgz#0cfc82089494c52d46eb0e3edb7a4eb2aea021eb" @@ -5594,18 +5706,6 @@ mdast-util-to-hast@10.0.1: unist-util-position "^3.0.0" unist-util-visit "^2.0.0" -mdast-util-to-markdown@^0.6.0: - version "0.6.5" - resolved "https://registry.yarnpkg.com/mdast-util-to-markdown/-/mdast-util-to-markdown-0.6.5.tgz#b33f67ca820d69e6cc527a93d4039249b504bebe" - integrity sha512-XeV9sDE7ZlOQvs45C9UKMtfTcctcaj/pGwH8YLbMHoMOXNNCn2LsqVQOqrF1+/NU8lKDAqozme9SCXWyo9oAcQ== - dependencies: - "@types/unist" "^2.0.0" - longest-streak "^2.0.0" - mdast-util-to-string "^2.0.0" - parse-entities "^2.0.0" - repeat-string "^1.0.0" - zwitch "^1.0.0" - mdast-util-to-string@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz#b8cfe6a713e1091cb5b728fc48885a4767f8b97b" @@ -5626,10 +5726,10 @@ media-typer@0.3.0: resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= -memfs@^3.1.2, memfs@^3.2.2: - version "3.4.0" - resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.4.0.tgz#8bc12062b973be6b295d4340595736a656f0a257" - integrity sha512-o/RfP0J1d03YwsAxyHxAYs2kyJp55AFkMazlFAZFR2I2IXkxiUTXRabJ6RmNNCQ83LAD2jy52Khj0m3OffpNdA== +memfs@^3.1.2, memfs@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.4.1.tgz#b78092f466a0dce054d63d39275b24c71d3f1305" + integrity sha512-1c9VPVvW5P7I85c35zAdEr1TD5+F11IToIHIlrVIcflfnzPkJa0ZoYEoEdYDP8KgPFoSZ/opDrUsAoZWym3mtw== dependencies: fs-monkey "1.0.3" @@ -5661,7 +5761,7 @@ merge-stream@^2.0.0: resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== -merge2@^1.3.0: +merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== @@ -5671,26 +5771,18 @@ methods@~1.1.2: resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= -micromark@~2.11.0: - version "2.11.4" - resolved "https://registry.yarnpkg.com/micromark/-/micromark-2.11.4.tgz#d13436138eea826383e822449c9a5c50ee44665a" - integrity sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA== +micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== dependencies: - debug "^4.0.0" - parse-entities "^2.0.0" + braces "^3.0.2" + picomatch "^2.3.1" -micromatch@^4.0.2, micromatch@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" - integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== - dependencies: - braces "^3.0.1" - picomatch "^2.2.3" - -mime-db@1.51.0, "mime-db@>= 1.43.0 < 2": - version "1.51.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.51.0.tgz#d9ff62451859b18342d960850dc3cfb77e63fb0c" - integrity sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g== +mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== mime-db@~1.33.0: version "1.33.0" @@ -5704,12 +5796,12 @@ mime-types@2.1.18: dependencies: mime-db "~1.33.0" -mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24: - version "2.1.34" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.34.tgz#5a712f9ec1503511a945803640fafe09d3793c24" - integrity sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A== +mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== dependencies: - mime-db "1.51.0" + mime-db "1.52.0" mime@1.6.0: version "1.6.0" @@ -5739,27 +5831,32 @@ mini-create-react-context@^0.4.0: "@babel/runtime" "^7.12.1" tiny-warning "^1.0.3" -mini-css-extract-plugin@^1.6.0: - version "1.6.2" - resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-1.6.2.tgz#83172b4fd812f8fc4a09d6f6d16f924f53990ca8" - integrity sha512-WhDvO3SjGm40oV5y26GjMJYjd2UMqrLAGKy5YS2/3QKJy2F7jgynuHTir/tgUUOiNQu5saXHdc8reo7YuhhT4Q== +mini-css-extract-plugin@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.6.0.tgz#578aebc7fc14d32c0ad304c2c34f08af44673f5e" + integrity sha512-ndG8nxCEnAemsg4FSgS+yNyHKgkTB4nPKqCOgh65j3/30qqC5RaSQQXMm++Y6sb6E1zRSxPkztj9fqxhS1Eo6w== dependencies: - loader-utils "^2.0.0" - schema-utils "^3.0.0" - webpack-sources "^1.1.0" + schema-utils "^4.0.0" minimalistic-assert@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== -minimatch@3.0.4, minimatch@^3.0.4: +minimatch@3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== dependencies: brace-expansion "^1.1.7" +minimatch@^3.0.4, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + minimist-options@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619" @@ -5769,17 +5866,17 @@ minimist-options@4.1.0: is-plain-obj "^1.1.0" kind-of "^6.0.3" -minimist@^1.2.0, minimist@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" - integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== +minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: + version "1.2.6" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" + integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== mkdirp@^0.5.5: - version "0.5.5" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" - integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== + version "0.5.6" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" + integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== dependencies: - minimist "^1.2.5" + minimist "^1.2.6" mrmime@^1.0.0: version "1.0.0" @@ -5814,20 +5911,20 @@ multicast-dns@^6.0.1: dns-packet "^1.3.1" thunky "^1.0.2" -nanoid@^3.1.30: - version "3.1.30" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.30.tgz#63f93cc548d2a113dc5dfbc63bfa09e2b9b64362" - integrity sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ== +nanoid@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35" + integrity sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw== natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= -negotiator@0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" - integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== +negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== neo-async@^2.6.2: version "2.6.2" @@ -5849,20 +5946,22 @@ node-emoji@^1.10.0: dependencies: lodash "^4.17.21" -node-fetch@2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" - integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== +node-fetch@2.6.7: + version "2.6.7" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" + integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== + dependencies: + whatwg-url "^5.0.0" -node-forge@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" - integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA== +node-forge@^1.2.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.0.tgz#37a874ea723855f37db091e6c186e5b67a01d4b2" + integrity sha512-08ARB91bUi6zNKzVmaj3QO7cr397uiDT2nJ63cHjyNtCTWIgvS47j3eT0WfzUwS9+6Z5YshRaoasFkXCKrIYbA== -node-releases@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.1.tgz#3d1d395f204f1f2f29a54358b9fb678765ad2fc5" - integrity sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA== +node-releases@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.2.tgz#7139fe71e2f4f11b47d4d2986aaf8c48699e0c01" + integrity sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg== normalize-package-data@^2.5.0: version "2.5.0" @@ -5935,11 +6034,6 @@ nth-check@~1.0.1: dependencies: boolbase "~1.0.0" -num2fraction@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" - integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4= - object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -6039,15 +6133,7 @@ onetime@^5.1.2: dependencies: mimic-fn "^2.1.0" -open@^7.0.2: - version "7.4.2" - resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" - integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q== - dependencies: - is-docker "^2.0.0" - is-wsl "^2.1.1" - -open@^8.0.9: +open@^8.0.9, open@^8.4.0: version "8.4.0" resolved "https://registry.yarnpkg.com/open/-/open-8.4.0.tgz#345321ae18f8138f82565a910fdc6b39e8c244f8" integrity sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q== @@ -6204,12 +6290,19 @@ parse-numeric-range@^1.3.0: resolved "https://registry.yarnpkg.com/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz#7c63b61190d61e4d53a1197f0c83c47bb670ffa3" integrity sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ== +parse5-htmlparser2-tree-adapter@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6" + integrity sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA== + dependencies: + parse5 "^6.0.1" + parse5@^5.0.0: version "5.1.1" resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178" integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug== -parse5@^6.0.0: +parse5@^6.0.0, parse5@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== @@ -6252,7 +6345,7 @@ path-key@^3.0.0, path-key@^3.1.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== -path-parse@^1.0.6: +path-parse@^1.0.6, path-parse@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== @@ -6279,32 +6372,15 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -picocolors@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-0.2.1.tgz#570670f793646851d1ba135996962abad587859f" - integrity sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA== - picocolors@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== -picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3: - version "2.3.0" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" - integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== - -pify@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" - integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== - -pkg-dir@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" - integrity sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s= - dependencies: - find-up "^2.1.0" +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== pkg-dir@^4.1.0: version "4.2.0" @@ -6329,73 +6405,59 @@ portfinder@^1.0.28: debug "^3.1.1" mkdirp "^0.5.5" -postcss-calc@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-8.0.0.tgz#a05b87aacd132740a5db09462a3612453e5df90a" - integrity sha512-5NglwDrcbiy8XXfPM11F3HeC6hoT9W7GUH/Zi5U/p7u3Irv4rHhdDcIZwG0llHXV4ftsBjpfWMXAnXNl4lnt8g== +postcss-calc@^8.2.3: + version "8.2.4" + resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-8.2.4.tgz#77b9c29bfcbe8a07ff6693dc87050828889739a5" + integrity sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q== dependencies: - postcss-selector-parser "^6.0.2" - postcss-value-parser "^4.0.2" + postcss-selector-parser "^6.0.9" + postcss-value-parser "^4.2.0" -postcss-colormin@^5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-5.2.2.tgz#019cd6912bef9e7e0924462c5e4ffae241e2f437" - integrity sha512-tSEe3NpqWARUTidDlF0LntPkdlhXqfDFuA1yslqpvvGAfpZ7oBaw+/QXd935NKm2U9p4PED0HDZlzmMk7fVC6g== +postcss-colormin@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-5.3.0.tgz#3cee9e5ca62b2c27e84fce63affc0cfb5901956a" + integrity sha512-WdDO4gOFG2Z8n4P8TWBpshnL3JpmNmJwdnfP2gbk2qBA8PWwOYcmjmI/t3CmMeL72a7Hkd+x/Mg9O2/0rD54Pg== dependencies: browserslist "^4.16.6" caniuse-api "^3.0.0" colord "^2.9.1" postcss-value-parser "^4.2.0" -postcss-convert-values@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-5.0.2.tgz#879b849dc3677c7d6bc94b6a2c1a3f0808798059" - integrity sha512-KQ04E2yadmfa1LqXm7UIDwW1ftxU/QWZmz6NKnHnUvJ3LEYbbcX6i329f/ig+WnEByHegulocXrECaZGLpL8Zg== +postcss-convert-values@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-5.1.0.tgz#f8d3abe40b4ce4b1470702a0706343eac17e7c10" + integrity sha512-GkyPbZEYJiWtQB0KZ0X6qusqFHUepguBCNFi9t5JJc7I2OTXG7C0twbTLvCfaKOLl3rSXmpAwV7W5txd91V84g== dependencies: - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-discard-comments@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-5.0.1.tgz#9eae4b747cf760d31f2447c27f0619d5718901fe" - integrity sha512-lgZBPTDvWrbAYY1v5GYEv8fEO/WhKOu/hmZqmCYfrpD6eyDWWzAOsl2rF29lpvziKO02Gc5GJQtlpkTmakwOWg== +postcss-discard-comments@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-5.1.1.tgz#e90019e1a0e5b99de05f63516ce640bd0df3d369" + integrity sha512-5JscyFmvkUxz/5/+TB3QTTT9Gi9jHkcn8dcmmuN68JQcv3aQg4y88yEHHhwFB52l/NkaJ43O0dbksGMAo49nfQ== -postcss-discard-duplicates@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-5.0.1.tgz#68f7cc6458fe6bab2e46c9f55ae52869f680e66d" - integrity sha512-svx747PWHKOGpAXXQkCc4k/DsWo+6bc5LsVrAsw+OU+Ibi7klFZCyX54gjYzX4TH+f2uzXjRviLARxkMurA2bA== +postcss-discard-duplicates@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz#9eb4fe8456706a4eebd6d3b7b777d07bad03e848" + integrity sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw== -postcss-discard-empty@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-5.0.1.tgz#ee136c39e27d5d2ed4da0ee5ed02bc8a9f8bf6d8" - integrity sha512-vfU8CxAQ6YpMxV2SvMcMIyF2LX1ZzWpy0lqHDsOdaKKLQVQGVP1pzhrI9JlsO65s66uQTfkQBKBD/A5gp9STFw== +postcss-discard-empty@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz#e57762343ff7f503fe53fca553d18d7f0c369c6c" + integrity sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A== -postcss-discard-overridden@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-5.0.1.tgz#454b41f707300b98109a75005ca4ab0ff2743ac6" - integrity sha512-Y28H7y93L2BpJhrdUR2SR2fnSsT+3TVx1NmVQLbcnZWwIUpJ7mfcTC6Za9M2PG6w8j7UQRfzxqn8jU2VwFxo3Q== +postcss-discard-overridden@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz#7e8c5b53325747e9d90131bb88635282fb4a276e" + integrity sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw== -postcss-discard-unused@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-discard-unused/-/postcss-discard-unused-5.0.1.tgz#63e35a74a154912f93d4e75a1e6ff3cc146f934b" - integrity sha512-tD6xR/xyZTwfhKYRw0ylfCY8wbfhrjpKAMnDKRTLMy2fNW5hl0hoV6ap5vo2JdCkuHkP3CHw72beO4Y8pzFdww== +postcss-discard-unused@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-discard-unused/-/postcss-discard-unused-5.1.0.tgz#8974e9b143d887677304e558c1166d3762501142" + integrity sha512-KwLWymI9hbwXmJa0dkrzpRbSJEh0vVUd7r8t0yOGPcfKzyJJxFM8kLyC5Ev9avji6nY95pOp1W6HqIrfT+0VGw== dependencies: postcss-selector-parser "^6.0.5" -postcss-html@^0.36.0: - version "0.36.0" - resolved "https://registry.yarnpkg.com/postcss-html/-/postcss-html-0.36.0.tgz#b40913f94eaacc2453fd30a1327ad6ee1f88b204" - integrity sha512-HeiOxGcuwID0AFsNAL0ox3mW6MHH5cstWN1Z3Y+n6H+g12ih7LHdYxWwEA/QmrebctLjo79xz9ouK3MroHwOJw== - dependencies: - htmlparser2 "^3.10.0" - -postcss-less@^3.1.4: - version "3.1.4" - resolved "https://registry.yarnpkg.com/postcss-less/-/postcss-less-3.1.4.tgz#369f58642b5928ef898ffbc1a6e93c958304c5ad" - integrity sha512-7TvleQWNM2QLcHqvudt3VYjULVB49uiW6XzEUFmvwHzvsOEF5MwBrIXZDJQvJNFGjJQTzSzZnDoCJ8h/ljyGXA== - dependencies: - postcss "^7.0.14" - -postcss-loader@^6.1.1: +postcss-loader@^6.2.1: version "6.2.1" resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-6.2.1.tgz#0895f7346b1702103d30fdc66e4d494a93c008ef" integrity sha512-WbbYpmAaKcux/P66bZ40bpWsBucjx/TTgVVzRZ9yUO8yQfVBlameJ0ZGVaPfH64hNSBh63a+ICP5nqOpBA0w+Q== @@ -6409,64 +6471,62 @@ postcss-media-query-parser@^0.2.3: resolved "https://registry.yarnpkg.com/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz#27b39c6f4d94f81b1a73b8f76351c609e5cef244" integrity sha1-J7Ocb02U+Bsac7j3Y1HGCeXO8kQ= -postcss-merge-idents@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-merge-idents/-/postcss-merge-idents-5.0.1.tgz#6b5856fc28f2571f28ecce49effb9b0e64be9437" - integrity sha512-xu8ueVU0RszbI2gKkxR6mluupsOSSLvt8q4gA2fcKFkA+x6SlH3cb4cFHpDvcRCNFbUmCR/VUub+Y6zPOjPx+Q== +postcss-merge-idents@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-merge-idents/-/postcss-merge-idents-5.1.1.tgz#7753817c2e0b75d0853b56f78a89771e15ca04a1" + integrity sha512-pCijL1TREiCoog5nQp7wUe+TUonA2tC2sQ54UGeMmryK3UFGIYKqDyjnqd6RcuI4znFn9hWSLNN8xKE/vWcUQw== dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" + cssnano-utils "^3.1.0" + postcss-value-parser "^4.2.0" -postcss-merge-longhand@^5.0.4: - version "5.0.4" - resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-5.0.4.tgz#41f4f3270282ea1a145ece078b7679f0cef21c32" - integrity sha512-2lZrOVD+d81aoYkZDpWu6+3dTAAGkCKbV5DoRhnIR7KOULVrI/R7bcMjhrH9KTRy6iiHKqmtG+n/MMj1WmqHFw== +postcss-merge-longhand@^5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-5.1.3.tgz#a49e2be6237316e3b55e329e0a8da15d1f9f47ab" + integrity sha512-lX8GPGvZ0iGP/IboM7HXH5JwkXvXod1Rr8H8ixwiA372hArk0zP4ZcCy4z4Prg/bfNlbbTf0KCOjCF9kKnpP/w== dependencies: - postcss-value-parser "^4.1.0" - stylehacks "^5.0.1" + postcss-value-parser "^4.2.0" + stylehacks "^5.1.0" -postcss-merge-rules@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-5.0.3.tgz#b5cae31f53129812a77e3eb1eeee448f8cf1a1db" - integrity sha512-cEKTMEbWazVa5NXd8deLdCnXl+6cYG7m2am+1HzqH0EnTdy8fRysatkaXb2dEnR+fdaDxTvuZ5zoBdv6efF6hg== +postcss-merge-rules@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-5.1.1.tgz#d327b221cd07540bcc8d9ff84446d8b404d00162" + integrity sha512-8wv8q2cXjEuCcgpIB1Xx1pIy8/rhMPIQqYKNzEdyx37m6gpq83mQQdCxgIkFgliyEnKvdwJf/C61vN4tQDq4Ww== dependencies: browserslist "^4.16.6" caniuse-api "^3.0.0" - cssnano-utils "^2.0.1" + cssnano-utils "^3.1.0" postcss-selector-parser "^6.0.5" -postcss-minify-font-values@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-5.0.1.tgz#a90cefbfdaa075bd3dbaa1b33588bb4dc268addf" - integrity sha512-7JS4qIsnqaxk+FXY1E8dHBDmraYFWmuL6cgt0T1SWGRO5bzJf8sUoelwa4P88LEWJZweHevAiDKxHlofuvtIoA== +postcss-minify-font-values@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz#f1df0014a726083d260d3bd85d7385fb89d1f01b" + integrity sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA== dependencies: - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-minify-gradients@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-5.0.3.tgz#f970a11cc71e08e9095e78ec3a6b34b91c19550e" - integrity sha512-Z91Ol22nB6XJW+5oe31+YxRsYooxOdFKcbOqY/V8Fxse1Y3vqlNRpi1cxCqoACZTQEhl+xvt4hsbWiV5R+XI9Q== +postcss-minify-gradients@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz#f1fe1b4f498134a5068240c2f25d46fcd236ba2c" + integrity sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw== dependencies: colord "^2.9.1" - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" + cssnano-utils "^3.1.0" + postcss-value-parser "^4.2.0" -postcss-minify-params@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-5.0.2.tgz#1b644da903473fbbb18fbe07b8e239883684b85c" - integrity sha512-qJAPuBzxO1yhLad7h2Dzk/F7n1vPyfHfCCh5grjGfjhi1ttCnq4ZXGIW77GSrEbh9Hus9Lc/e/+tB4vh3/GpDg== +postcss-minify-params@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-5.1.2.tgz#77e250780c64198289c954884ebe3ee4481c3b1c" + integrity sha512-aEP+p71S/urY48HWaRHasyx4WHQJyOYaKpQ6eXl8k0kxg66Wt/30VR6/woh8THgcpRbonJD5IeD+CzNhPi1L8g== dependencies: - alphanum-sort "^1.0.2" browserslist "^4.16.6" - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" + cssnano-utils "^3.1.0" + postcss-value-parser "^4.2.0" -postcss-minify-selectors@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-5.1.0.tgz#4385c845d3979ff160291774523ffa54eafd5a54" - integrity sha512-NzGBXDa7aPsAcijXZeagnJBKBPMYLaJJzB8CQh6ncvyl2sIndLVWfbcDi0SBjRWk5VqEjXvf8tYwzoKf4Z07og== +postcss-minify-selectors@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-5.2.0.tgz#17c2be233e12b28ffa8a421a02fc8b839825536c" + integrity sha512-vYxvHkW+iULstA+ctVNx0VoRAR4THQQRkG77o0oa4/mBS0OzGvvzLIvHDv/nNEM0crzN2WIyFU5X7wZhaUK3RA== dependencies: - alphanum-sort "^1.0.2" postcss-selector-parser "^6.0.5" postcss-modules-extract-imports@^3.0.0: @@ -6497,192 +6557,157 @@ postcss-modules-values@^4.0.0: dependencies: icss-utils "^5.0.0" -postcss-normalize-charset@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-5.0.1.tgz#121559d1bebc55ac8d24af37f67bd4da9efd91d0" - integrity sha512-6J40l6LNYnBdPSk+BHZ8SF+HAkS4q2twe5jnocgd+xWpz/mx/5Sa32m3W1AA8uE8XaXN+eg8trIlfu8V9x61eg== +postcss-normalize-charset@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz#9302de0b29094b52c259e9b2cf8dc0879879f0ed" + integrity sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg== -postcss-normalize-display-values@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-5.0.1.tgz#62650b965981a955dffee83363453db82f6ad1fd" - integrity sha512-uupdvWk88kLDXi5HEyI9IaAJTE3/Djbcrqq8YgjvAVuzgVuqIk3SuJWUisT2gaJbZm1H9g5k2w1xXilM3x8DjQ== +postcss-normalize-display-values@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz#72abbae58081960e9edd7200fcf21ab8325c3da8" + integrity sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA== dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-normalize-positions@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-5.0.1.tgz#868f6af1795fdfa86fbbe960dceb47e5f9492fe5" - integrity sha512-rvzWAJai5xej9yWqlCb1OWLd9JjW2Ex2BCPzUJrbaXmtKtgfL8dBMOOMTX6TnvQMtjk3ei1Lswcs78qKO1Skrg== +postcss-normalize-positions@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-5.1.0.tgz#902a7cb97cf0b9e8b1b654d4a43d451e48966458" + integrity sha512-8gmItgA4H5xiUxgN/3TVvXRoJxkAWLW6f/KKhdsH03atg0cB8ilXnrB5PpSshwVu/dD2ZsRFQcR1OEmSBDAgcQ== dependencies: - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-normalize-repeat-style@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.0.1.tgz#cbc0de1383b57f5bb61ddd6a84653b5e8665b2b5" - integrity sha512-syZ2itq0HTQjj4QtXZOeefomckiV5TaUO6ReIEabCh3wgDs4Mr01pkif0MeVwKyU/LHEkPJnpwFKRxqWA/7O3w== +postcss-normalize-repeat-style@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.0.tgz#f6d6fd5a54f51a741cc84a37f7459e60ef7a6398" + integrity sha512-IR3uBjc+7mcWGL6CtniKNQ4Rr5fTxwkaDHwMBDGGs1x9IVRkYIT/M4NelZWkAOBdV6v3Z9S46zqaKGlyzHSchw== dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-normalize-string@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-5.0.1.tgz#d9eafaa4df78c7a3b973ae346ef0e47c554985b0" - integrity sha512-Ic8GaQ3jPMVl1OEn2U//2pm93AXUcF3wz+OriskdZ1AOuYV25OdgS7w9Xu2LO5cGyhHCgn8dMXh9bO7vi3i9pA== +postcss-normalize-string@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz#411961169e07308c82c1f8c55f3e8a337757e228" + integrity sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w== dependencies: - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-normalize-timing-functions@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.0.1.tgz#8ee41103b9130429c6cbba736932b75c5e2cb08c" - integrity sha512-cPcBdVN5OsWCNEo5hiXfLUnXfTGtSFiBU9SK8k7ii8UD7OLuznzgNRYkLZow11BkQiiqMcgPyh4ZqXEEUrtQ1Q== +postcss-normalize-timing-functions@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz#d5614410f8f0b2388e9f240aa6011ba6f52dafbb" + integrity sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg== dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-normalize-unicode@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-5.0.1.tgz#82d672d648a411814aa5bf3ae565379ccd9f5e37" - integrity sha512-kAtYD6V3pK0beqrU90gpCQB7g6AOfP/2KIPCVBKJM2EheVsBQmx/Iof+9zR9NFKLAx4Pr9mDhogB27pmn354nA== +postcss-normalize-unicode@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.0.tgz#3d23aede35e160089a285e27bf715de11dc9db75" + integrity sha512-J6M3MizAAZ2dOdSjy2caayJLQT8E8K9XjLce8AUQMwOrCvjCHv24aLC/Lps1R1ylOfol5VIDMaM/Lo9NGlk1SQ== dependencies: - browserslist "^4.16.0" - postcss-value-parser "^4.1.0" + browserslist "^4.16.6" + postcss-value-parser "^4.2.0" -postcss-normalize-url@^5.0.4: - version "5.0.4" - resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-5.0.4.tgz#3b0322c425e31dd275174d0d5db0e466f50810fb" - integrity sha512-cNj3RzK2pgQQyNp7dzq0dqpUpQ/wYtdDZM3DepPmFjCmYIfceuD9VIAcOdvrNetjIU65g1B4uwdP/Krf6AFdXg== +postcss-normalize-url@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz#ed9d88ca82e21abef99f743457d3729a042adcdc" + integrity sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew== dependencies: normalize-url "^6.0.1" postcss-value-parser "^4.2.0" -postcss-normalize-whitespace@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.0.1.tgz#b0b40b5bcac83585ff07ead2daf2dcfbeeef8e9a" - integrity sha512-iPklmI5SBnRvwceb/XH568yyzK0qRVuAG+a1HFUsFRf11lEJTiQQa03a4RSCQvLKdcpX7XsI1Gen9LuLoqwiqA== +postcss-normalize-whitespace@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz#08a1a0d1ffa17a7cc6efe1e6c9da969cc4493cfa" + integrity sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA== dependencies: - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-ordered-values@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-5.0.2.tgz#1f351426977be00e0f765b3164ad753dac8ed044" - integrity sha512-8AFYDSOYWebJYLyJi3fyjl6CqMEG/UVworjiyK1r573I56kb3e879sCJLGvR3merj+fAdPpVplXKQZv+ey6CgQ== +postcss-ordered-values@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-5.1.1.tgz#0b41b610ba02906a3341e92cab01ff8ebc598adb" + integrity sha512-7lxgXF0NaoMIgyihL/2boNAEZKiW0+HkMhdKMTD93CjW8TdCy2hSdj8lsAo+uwm7EDG16Da2Jdmtqpedl0cMfw== dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" + cssnano-utils "^3.1.0" + postcss-value-parser "^4.2.0" -postcss-reduce-idents@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-reduce-idents/-/postcss-reduce-idents-5.0.1.tgz#99b49ce8ee6f9c179447671cc9693e198e877bb7" - integrity sha512-6Rw8iIVFbqtaZExgWK1rpVgP7DPFRPh0DDFZxJ/ADNqPiH10sPCoq5tgo6kLiTyfh9sxjKYjXdc8udLEcPOezg== +postcss-reduce-idents@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/postcss-reduce-idents/-/postcss-reduce-idents-5.2.0.tgz#c89c11336c432ac4b28792f24778859a67dfba95" + integrity sha512-BTrLjICoSB6gxbc58D5mdBK8OhXRDqud/zodYfdSi52qvDHdMwk+9kB9xsM8yJThH/sZU5A6QVSmMmaN001gIg== dependencies: - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" -postcss-reduce-initial@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-5.0.2.tgz#fa424ce8aa88a89bc0b6d0f94871b24abe94c048" - integrity sha512-v/kbAAQ+S1V5v9TJvbGkV98V2ERPdU6XvMcKMjqAlYiJ2NtsHGlKYLPjWWcXlaTKNxooId7BGxeraK8qXvzKtw== +postcss-reduce-initial@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-5.1.0.tgz#fc31659ea6e85c492fb2a7b545370c215822c5d6" + integrity sha512-5OgTUviz0aeH6MtBjHfbr57tml13PuedK/Ecg8szzd4XRMbYxH4572JFG067z+FqBIf6Zp/d+0581glkvvWMFw== dependencies: browserslist "^4.16.6" caniuse-api "^3.0.0" -postcss-reduce-transforms@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-5.0.1.tgz#93c12f6a159474aa711d5269923e2383cedcf640" - integrity sha512-a//FjoPeFkRuAguPscTVmRQUODP+f3ke2HqFNgGPwdYnpeC29RZdCBvGRGTsKpMURb/I3p6jdKoBQ2zI+9Q7kA== +postcss-reduce-transforms@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz#333b70e7758b802f3dd0ddfe98bb1ccfef96b6e9" + integrity sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ== dependencies: - cssnano-utils "^2.0.1" - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" postcss-resolve-nested-selector@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz#29ccbc7c37dedfac304e9fff0bf1596b3f6a0e4e" integrity sha1-Kcy8fDfe36wwTp//C/FZaz9qDk4= -postcss-safe-parser@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/postcss-safe-parser/-/postcss-safe-parser-4.0.2.tgz#a6d4e48f0f37d9f7c11b2a581bf00f8ba4870b96" - integrity sha512-Uw6ekxSWNLCPesSv/cmqf2bY/77z11O7jZGPax3ycZMFU/oi2DMH9i89AdHc1tRwFg/arFoEwX0IS3LCUxJh1g== - dependencies: - postcss "^7.0.26" - -postcss-sass@^0.4.4: - version "0.4.4" - resolved "https://registry.yarnpkg.com/postcss-sass/-/postcss-sass-0.4.4.tgz#91f0f3447b45ce373227a98b61f8d8f0785285a3" - integrity sha512-BYxnVYx4mQooOhr+zer0qWbSPYnarAy8ZT7hAQtbxtgVf8gy+LSLT/hHGe35h14/pZDTw1DsxdbrwxBN++H+fg== - dependencies: - gonzales-pe "^4.3.0" - postcss "^7.0.21" - -postcss-scss@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/postcss-scss/-/postcss-scss-2.1.1.tgz#ec3a75fa29a55e016b90bf3269026c53c1d2b383" - integrity sha512-jQmGnj0hSGLd9RscFw9LyuSVAa5Bl1/KBPqG1NQw9w8ND55nY4ZEsdlVuYJvLPpV+y0nwTV5v/4rHPzZRihQbA== - dependencies: - postcss "^7.0.6" +postcss-safe-parser@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz#bb4c29894171a94bc5c996b9a30317ef402adaa1" + integrity sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ== -postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5: - version "6.0.7" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.7.tgz#48404830a635113a71fd79397de8209ed05a66fc" - integrity sha512-U+b/Deoi4I/UmE6KOVPpnhS7I7AYdKbhGcat+qTQ27gycvaACvNEw11ba6RrkwVmDVRW7sigWgLj4/KbbJjeDA== +postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5, postcss-selector-parser@^6.0.9: + version "6.0.9" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.9.tgz#ee71c3b9ff63d9cd130838876c13a2ec1a992b2f" + integrity sha512-UO3SgnZOVTwu4kyLR22UQ1xZh086RyNZppb7lLAKBFK8a32ttG5i87Y/P3+2bRSjZNyJ1B7hfFNo273tKe9YxQ== dependencies: cssesc "^3.0.0" util-deprecate "^1.0.2" -postcss-sort-media-queries@^4.1.0: +postcss-sort-media-queries@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/postcss-sort-media-queries/-/postcss-sort-media-queries-4.2.1.tgz#a99bae69ef1098ee3b64a5fa94d258ec240d0355" integrity sha512-9VYekQalFZ3sdgcTjXMa0dDjsfBVHXlraYJEMiOJ/2iMmI2JGCMavP16z3kWOaRu8NSaJCTgVpB/IVpH5yT9YQ== dependencies: sort-css-media-queries "2.0.4" -postcss-svgo@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-5.0.3.tgz#d945185756e5dfaae07f9edb0d3cae7ff79f9b30" - integrity sha512-41XZUA1wNDAZrQ3XgWREL/M2zSw8LJPvb5ZWivljBsUQAGoEKMYm6okHsTjJxKYI4M75RQEH4KYlEM52VwdXVA== +postcss-svgo@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-5.1.0.tgz#0a317400ced789f233a28826e77523f15857d80d" + integrity sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA== dependencies: - postcss-value-parser "^4.1.0" + postcss-value-parser "^4.2.0" svgo "^2.7.0" -postcss-syntax@^0.36.2: - version "0.36.2" - resolved "https://registry.yarnpkg.com/postcss-syntax/-/postcss-syntax-0.36.2.tgz#f08578c7d95834574e5593a82dfbfa8afae3b51c" - integrity sha512-nBRg/i7E3SOHWxF3PpF5WnJM/jQ1YpY9000OaVXlAQj6Zp/kIqJxEDWIZ67tAd7NLuk7zqN4yqe9nc0oNAOs1w== - -postcss-unique-selectors@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-5.0.2.tgz#5d6893daf534ae52626708e0d62250890108c0c1" - integrity sha512-w3zBVlrtZm7loQWRPVC0yjUwwpty7OM6DnEHkxcSQXO1bMS3RJ+JUS5LFMSDZHJcvGsRwhZinCWVqn8Kej4EDA== +postcss-unique-selectors@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz#a9f273d1eacd09e9aa6088f4b0507b18b1b541b6" + integrity sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA== dependencies: - alphanum-sort "^1.0.2" postcss-selector-parser "^6.0.5" -postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: +postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss-zindex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postcss-zindex/-/postcss-zindex-5.0.1.tgz#c585724beb69d356af8c7e68847b28d6298ece03" - integrity sha512-nwgtJJys+XmmSGoYCcgkf/VczP8Mp/0OfSv3v0+fw0uABY4yxw+eFs0Xp9nAZHIKnS5j+e9ywQ+RD+ONyvl5pA== - -"postcss@5 - 7", postcss@^7.0.14, postcss@^7.0.2, postcss@^7.0.21, postcss@^7.0.26, postcss@^7.0.32, postcss@^7.0.35, postcss@^7.0.6: - version "7.0.39" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.39.tgz#9624375d965630e2e1f2c02a935c82a59cb48309" - integrity sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA== - dependencies: - picocolors "^0.2.1" - source-map "^0.6.1" +postcss-zindex@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-zindex/-/postcss-zindex-5.1.0.tgz#4a5c7e5ff1050bd4c01d95b1847dfdcc58a496ff" + integrity sha512-fgFMf0OtVSBR1va1JNHYgMxYk73yhn/qb4uQDq1DLGYolz8gHCyr/sesEuGUaYs58E3ZJRcpoGuPVoB7Meiq9A== -postcss@^8.2.15, postcss@^8.3.11, postcss@^8.3.5, postcss@^8.3.7: - version "8.4.5" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.5.tgz#bae665764dfd4c6fcc24dc0fdf7e7aa00cc77f95" - integrity sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg== +postcss@^8.3.11, postcss@^8.3.5, postcss@^8.4.12, postcss@^8.4.7: + version "8.4.12" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.12.tgz#1e7de78733b28970fa4743f7da6f3763648b1905" + integrity sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg== dependencies: - nanoid "^3.1.30" + nanoid "^3.3.1" picocolors "^1.0.0" - source-map-js "^1.0.1" + source-map-js "^1.0.2" prelude-ls@^1.2.1: version "1.2.1" @@ -6694,10 +6719,10 @@ prepend-http@^2.0.0: resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= -prettier@^2.5.0: - version "2.5.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.5.1.tgz#fff75fa9d519c54cf0fce328c1017d94546bc56a" - integrity sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg== +prettier@^2.6.0: + version "2.6.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.6.1.tgz#d472797e0d7461605c1609808e27b80c0f9cfe17" + integrity sha512-8UVbTBYGwN37Bs9LERmxCPjdvPxlEowx2urIL6urHzdb3SDq4B/Z6xLFCblrSnE4iKWcS6ziJ3aOYrc1kz/E2A== pretty-error@^4.0.0: version "4.0.0" @@ -6712,26 +6737,21 @@ pretty-time@^1.1.0: resolved "https://registry.yarnpkg.com/pretty-time/-/pretty-time-1.1.0.tgz#ffb7429afabb8535c346a34e41873adf3d74dd0e" integrity sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA== -prism-react-renderer@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-1.2.1.tgz#392460acf63540960e5e3caa699d851264e99b89" - integrity sha512-w23ch4f75V1Tnz8DajsYKvY5lF7H1+WvzvLUcF0paFxkTHSp42RS0H5CttdN2Q8RR3DRGZ9v5xD/h3n8C8kGmg== +prism-react-renderer@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-1.3.1.tgz#88fc9d0df6bed06ca2b9097421349f8c2f24e30d" + integrity sha512-xUeDMEz074d0zc5y6rxiMp/dlC7C+5IDDlaEUlcBOFE2wddz7hz5PNupb087mPwTt7T9BrFmewObfCBuf/LKwQ== -prismjs@^1.23.0: - version "1.25.0" - resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.25.0.tgz#6f822df1bdad965734b310b315a23315cf999756" - integrity sha512-WCjJHl1KEWbnkQom1+SzftbtXMKQoezOCYs5rECqMN+jP+apI7ftoflyqigqzopSO3hMhTEb0mFClA8lkolgEg== +prismjs@^1.27.0: + version "1.27.0" + resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.27.0.tgz#bb6ee3138a0b438a3653dd4d6ce0cc6510a45057" + integrity sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA== process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== -progress@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" - integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== - promise@^7.1.1: version "7.3.1" resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" @@ -6739,7 +6759,7 @@ promise@^7.1.1: dependencies: asap "~2.0.3" -prompts@^2.4.0, prompts@^2.4.1: +prompts@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== @@ -6747,14 +6767,14 @@ prompts@^2.4.0, prompts@^2.4.1: kleur "^3.0.3" sisteransi "^1.0.5" -prop-types@^15.6.2, prop-types@^15.7.2: - version "15.7.2" - resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" - integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== +prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: + version "15.8.1" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" + integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== dependencies: loose-envify "^1.4.0" object-assign "^4.1.1" - react-is "^16.8.1" + react-is "^16.13.1" property-information@^5.0.0, property-information@^5.3.0: version "5.6.0" @@ -6779,11 +6799,6 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" -punycode@1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" - integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= - punycode@^1.3.2: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" @@ -6806,21 +6821,23 @@ pure-color@^1.2.0: resolved "https://registry.yarnpkg.com/pure-color/-/pure-color-1.3.0.tgz#1fe064fb0ac851f0de61320a8bf796836422f33e" integrity sha1-H+Bk+wrIUfDeYTIKi/eWg2Qi8z4= -qs@6.9.6: - version "6.9.6" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.6.tgz#26ed3c8243a431b2924aca84cc90471f35d5a0ee" - integrity sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ== - -querystring@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" - integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= +qs@6.9.7: + version "6.9.7" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.7.tgz#4610846871485e1e048f44ae3b94033f0e675afe" + integrity sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw== queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== +queue@6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/queue/-/queue-6.0.2.tgz#b91525283e2315c7553d2efa18d83e76432fed65" + integrity sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA== + dependencies: + inherits "~2.0.3" + quick-lru@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" @@ -6843,12 +6860,12 @@ range-parser@^1.2.1, range-parser@~1.2.1: resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== -raw-body@2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.2.tgz#baf3e9c21eebced59dd6533ac872b71f7b61cb32" - integrity sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ== +raw-body@2.4.3: + version "2.4.3" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.3.tgz#8f80305d11c2a0a545c2d9d89d7a0286fcead43c" + integrity sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g== dependencies: - bytes "3.1.1" + bytes "3.1.2" http-errors "1.8.1" iconv-lite "0.4.24" unpipe "1.0.0" @@ -6873,37 +6890,37 @@ react-base16-styling@^0.6.0: lodash.flow "^3.3.0" pure-color "^1.2.0" -react-dev-utils@12.0.0-next.47: - version "12.0.0-next.47" - resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-12.0.0-next.47.tgz#e55c31a05eb30cfd69ca516e8b87d61724e880fb" - integrity sha512-PsE71vP15TZMmp/RZKOJC4fYD5Pvt0+wCoyG3QHclto0d4FyIJI78xGRICOOThZFROqgXYlZP6ddmeybm+jO4w== +react-dev-utils@^12.0.0: + version "12.0.0" + resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-12.0.0.tgz#4eab12cdb95692a077616770b5988f0adf806526" + integrity sha512-xBQkitdxozPxt1YZ9O1097EJiVpwHr9FoAuEVURCKV0Av8NBERovJauzP7bo1ThvuhZ4shsQ1AJiu4vQpoT1AQ== dependencies: - "@babel/code-frame" "^7.10.4" + "@babel/code-frame" "^7.16.0" address "^1.1.2" - browserslist "^4.16.5" - chalk "^2.4.2" + browserslist "^4.18.1" + chalk "^4.1.2" cross-spawn "^7.0.3" detect-port-alt "^1.1.6" - escape-string-regexp "^2.0.0" - filesize "^6.1.0" - find-up "^4.1.0" - fork-ts-checker-webpack-plugin "^6.0.5" + escape-string-regexp "^4.0.0" + filesize "^8.0.6" + find-up "^5.0.0" + fork-ts-checker-webpack-plugin "^6.5.0" global-modules "^2.0.0" - globby "^11.0.1" - gzip-size "^5.1.1" - immer "^9.0.6" + globby "^11.0.4" + gzip-size "^6.0.0" + immer "^9.0.7" is-root "^2.1.0" - loader-utils "^2.0.0" - open "^7.0.2" + loader-utils "^3.2.0" + open "^8.4.0" pkg-up "^3.1.0" - prompts "^2.4.0" - react-error-overlay "7.0.0-next.54+1465357b" + prompts "^2.4.2" + react-error-overlay "^6.0.10" recursive-readdir "^2.2.2" - shell-quote "^1.7.2" - strip-ansi "^6.0.0" + shell-quote "^1.7.3" + strip-ansi "^6.0.1" text-table "^0.2.0" -react-dom@^17.0.1: +react-dom@^17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA== @@ -6912,32 +6929,28 @@ react-dom@^17.0.1: object-assign "^4.1.1" scheduler "^0.20.2" -react-error-overlay@7.0.0-next.54+1465357b: - version "7.0.0-next.54" - resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-7.0.0-next.54.tgz#c1eb5ab86aee15e9552e6d97897b08f2bd06d140" - integrity sha512-b96CiTnZahXPDNH9MKplvt5+jD+BkxDw7q5R3jnkUXze/ux1pLv32BBZmlj0OfCUeMqyz4sAmF+0ccJGVMlpXw== - -react-error-overlay@^6.0.9: +react-error-overlay@^6.0.10: version "6.0.10" resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.10.tgz#0fe26db4fa85d9dbb8624729580e90e7159a59a6" integrity sha512-mKR90fX7Pm5seCOfz8q9F+66VCc1PGsWSBxKbITjfKVQHMNF2zudxHnMdJiB1fRCb+XsbQV9sO9DCkgsMQgBIA== -react-fast-compare@^3.1.1: +react-fast-compare@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb" integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA== -react-helmet@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/react-helmet/-/react-helmet-6.1.0.tgz#a750d5165cb13cf213e44747502652e794468726" - integrity sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw== +react-helmet-async@*, react-helmet-async@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/react-helmet-async/-/react-helmet-async-1.2.3.tgz#57326a69304ea3293036eafb49475e9ba454cb37" + integrity sha512-mCk2silF53Tq/YaYdkl2sB+/tDoPnaxN7dFS/6ZLJb/rhUY2EWGI5Xj2b4jHppScMqY45MbgPSwTxDchKpZ5Kw== dependencies: - object-assign "^4.1.1" + "@babel/runtime" "^7.12.5" + invariant "^2.2.4" prop-types "^15.7.2" - react-fast-compare "^3.1.1" - react-side-effect "^2.1.0" + react-fast-compare "^3.2.0" + shallowequal "^1.1.0" -react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1: +react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -7000,11 +7013,6 @@ react-router@5.2.1, react-router@^5.2.0: tiny-invariant "^1.0.2" tiny-warning "^1.0.0" -react-side-effect@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-2.1.1.tgz#66c5701c3e7560ab4822a4ee2742dee215d72eb3" - integrity sha512-2FoTQzRNTncBVtnzxFOk2mCpcfxQpenBMbk5kSVBg5UcPqV9fRbgY2zhb7GTWWOlpFmAxhClBDlIq8Rsubz1yQ== - react-textarea-autosize@^8.3.2: version "8.3.3" resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.3.3.tgz#f70913945369da453fd554c168f6baacd1fa04d8" @@ -7014,7 +7022,7 @@ react-textarea-autosize@^8.3.2: use-composed-ref "^1.0.0" use-latest "^1.0.0" -react@^17.0.1: +react@^17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA== @@ -7097,10 +7105,10 @@ redent@^3.0.0: indent-string "^4.0.0" strip-indent "^3.0.0" -regenerate-unicode-properties@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-9.0.0.tgz#54d09c7115e1f53dc2314a974b32c1c344efe326" - integrity sha512-3E12UeNSPfjrgwjkR81m5J7Aw/T55Tu7nUyZVQYCKEOs+2dkxEY+DpPtZzO4YruuiPb7NkYLVcyJC4+zCbk5pA== +regenerate-unicode-properties@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz#7f442732aa7934a3740c779bb9b3340dccc1fb56" + integrity sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw== dependencies: regenerate "^1.4.2" @@ -7121,10 +7129,10 @@ regenerator-transform@^0.14.2: dependencies: "@babel/runtime" "^7.8.4" -regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz#7ef352ae8d159e758c0eadca6f8fcb4eef07be26" - integrity sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA== +regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.1.tgz#b3f4c0059af9e47eca9f3f660e51d81307e72307" + integrity sha512-pMR7hBVUUGI7PMA37m2ofIdQCsomVnas+Jn5UPGAHQ+/LlwKm/aTLJHdasmHRzlfeZwHiAOaRSo2rbBDm3nNUQ== dependencies: call-bind "^1.0.2" define-properties "^1.1.3" @@ -7134,15 +7142,15 @@ regexpp@^3.2.0: resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== -regexpu-core@^4.5.4, regexpu-core@^4.7.1: - version "4.8.0" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.8.0.tgz#e5605ba361b67b1718478501327502f4479a98f0" - integrity sha512-1F6bYsoYiz6is+oz70NWur2Vlh9KWtswuRuzJOfeYUrfPX2o8n74AnUVaOGDbUqVGO9fNHu48/pjJO4sNVwsOg== +regexpu-core@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.0.1.tgz#c531122a7840de743dcf9c83e923b5560323ced3" + integrity sha512-CriEZlrKK9VJw/xQGJpQM5rY88BtuL8DM+AEwvcThHilbxiTAy8vq4iJnd2tqq8wLmjbGZzP7ZcKFjbGkmEFrw== dependencies: regenerate "^1.4.2" - regenerate-unicode-properties "^9.0.0" - regjsgen "^0.5.2" - regjsparser "^0.7.0" + regenerate-unicode-properties "^10.0.1" + regjsgen "^0.6.0" + regjsparser "^0.8.2" unicode-match-property-ecmascript "^2.0.0" unicode-match-property-value-ecmascript "^2.0.0" @@ -7160,15 +7168,15 @@ registry-url@^5.0.0: dependencies: rc "^1.2.8" -regjsgen@^0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.2.tgz#92ff295fb1deecbf6ecdab2543d207e91aa33733" - integrity sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A== +regjsgen@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.6.0.tgz#83414c5354afd7d6627b16af5f10f41c4e71808d" + integrity sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA== -regjsparser@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.7.0.tgz#a6b667b54c885e18b52554cb4960ef71187e9968" - integrity sha512-A4pcaORqmNMDVwUjWoTzuhwMGpP+NykpfqAsEgI1FSH/EzC7lrN5TMd+kN8YCovX+jMpu8eaqXgXPCa0g8FQNQ== +regjsparser@^0.8.2: + version "0.8.4" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.8.4.tgz#8a14285ffcc5de78c5b95d62bbf413b6bc132d5f" + integrity sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA== dependencies: jsesc "~0.5.0" @@ -7209,20 +7217,6 @@ remark-footnotes@2.0.0: resolved "https://registry.yarnpkg.com/remark-footnotes/-/remark-footnotes-2.0.0.tgz#9001c4c2ffebba55695d2dd80ffb8b82f7e6303f" integrity sha512-3Clt8ZMH75Ayjp9q4CorNeyjwIxHFcTkaektplKGl2A1jNGEUey8cKL0ZC5vJwfcD5GFGsNLImLG/NGzWIzoMQ== -remark-mdx-remove-exports@^1.6.22: - version "1.6.22" - resolved "https://registry.yarnpkg.com/remark-mdx-remove-exports/-/remark-mdx-remove-exports-1.6.22.tgz#9e34f3d02c9c54b02ca0a1fde946449338d06ecb" - integrity sha512-7g2uiTmTGfz5QyVb+toeX25frbk1Y6yd03RXGPtqx0+DVh86Gb7MkNYbk7H2X27zdZ3CQv1W/JqlFO0Oo8IxVA== - dependencies: - unist-util-remove "2.0.0" - -remark-mdx-remove-imports@^1.6.22: - version "1.6.22" - resolved "https://registry.yarnpkg.com/remark-mdx-remove-imports/-/remark-mdx-remove-imports-1.6.22.tgz#79f711c95359cff437a120d1fbdc1326ec455826" - integrity sha512-lmjAXD8Ltw0TsvBzb45S+Dxx7LTJAtDaMneMAv8LAUIPEyYoKkmGbmVsiF0/pY6mhM1Q16swCmu1TN+ie/vn/A== - dependencies: - unist-util-remove "2.0.0" - remark-mdx@1.6.22: version "1.6.22" resolved "https://registry.yarnpkg.com/remark-mdx/-/remark-mdx-1.6.22.tgz#06a8dab07dcfdd57f3373af7f86bd0e992108bbd" @@ -7259,13 +7253,6 @@ remark-parse@8.0.3: vfile-location "^3.0.0" xtend "^4.0.1" -remark-parse@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-9.0.0.tgz#4d20a299665880e4f4af5d90b7c7b8a935853640" - integrity sha512-geKatMwSzEXKHuzBNU1z676sGcDcFoChMK38TgdHJNAYfFtsfHDQG7MoJAjs6sgYMqyLduCYWDIWZIxiPeafEw== - dependencies: - mdast-util-from-markdown "^0.8.0" - remark-squeeze-paragraphs@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/remark-squeeze-paragraphs/-/remark-squeeze-paragraphs-4.0.0.tgz#76eb0e085295131c84748c8e43810159c5653ead" @@ -7273,22 +7260,6 @@ remark-squeeze-paragraphs@4.0.0: dependencies: mdast-squeeze-paragraphs "^4.0.0" -remark-stringify@^9.0.0: - version "9.0.1" - resolved "https://registry.yarnpkg.com/remark-stringify/-/remark-stringify-9.0.1.tgz#576d06e910548b0a7191a71f27b33f1218862894" - integrity sha512-mWmNg3ZtESvZS8fv5PTvaPckdL4iNlCHTt8/e/8oN08nArHRHjNZMKzA/YW3+p7/lYqIw4nx1XsjCBo/AxNChg== - dependencies: - mdast-util-to-markdown "^0.6.0" - -remark@^13.0.0: - version "13.0.0" - resolved "https://registry.yarnpkg.com/remark/-/remark-13.0.0.tgz#d15d9bf71a402f40287ebe36067b66d54868e425" - integrity sha512-HDz1+IKGtOyWN+QgBiAT0kn+2s6ovOxHyPAFGKVE81VSzJ+mq7RwHFledEvB5F1p4iJvOah/LOKdFuzvRnNLCA== - dependencies: - remark-parse "^9.0.0" - remark-stringify "^9.0.0" - unified "^9.1.0" - renderkid@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-3.0.0.tgz#5fd823e4d6951d37358ecc9a58b1f06836b6268a" @@ -7300,7 +7271,7 @@ renderkid@^3.0.0: lodash "^4.17.21" strip-ansi "^6.0.1" -repeat-string@^1.0.0, repeat-string@^1.5.4: +repeat-string@^1.5.4: version "1.6.1" resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= @@ -7336,12 +7307,13 @@ resolve-pathname@^3.0.0: integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng== resolve@^1.1.6, resolve@^1.10.0, resolve@^1.14.2, resolve@^1.20.0, resolve@^1.3.2: - version "1.20.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" - integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== + version "1.22.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" + integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw== dependencies: - is-core-module "^2.2.0" - path-parse "^1.0.6" + is-core-module "^2.8.1" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" resolve@^2.0.0-next.3: version "2.0.0-next.3" @@ -7380,7 +7352,7 @@ rtl-detect@^1.0.4: resolved "https://registry.yarnpkg.com/rtl-detect/-/rtl-detect-1.0.4.tgz#40ae0ea7302a150b96bc75af7d749607392ecac6" integrity sha512-EBR4I2VDSSYr7PkBmFy04uhycIpDKp+21p/jARYXlCSjQksTBQcJ0HFUPOO79EPPH5JS6VAhiIQbycf0O3JAxQ== -rtlcss@^3.3.0: +rtlcss@^3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/rtlcss/-/rtlcss-3.5.0.tgz#c9eb91269827a102bac7ae3115dd5d049de636c3" integrity sha512-wzgMaMFHQTnyi9YOwsx9LjOxYXJPzS8sYnFaKm6R5ysvTkwzHiB0vxnbHwchHQT65PTdBjDG21/kQBWI7q9O7A== @@ -7397,12 +7369,12 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -rxjs@^7.1.0: - version "7.4.0" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.4.0.tgz#a12a44d7eebf016f5ff2441b87f28c9a51cebc68" - integrity sha512-7SQDi7xeTMCJpqViXh8gL/lebcwlp3d831F05+9B44A4B0WfsEwUQHR64gsH1kvJ+Ep/J9K2+n1hVl1CsGN23w== +rxjs@^7.5.4: + version "7.5.5" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.5.5.tgz#2ebad89af0f560f460ad5cc4213219e1f7dd4e9f" + integrity sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw== dependencies: - tslib "~2.1.0" + tslib "^2.1.0" safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" @@ -7482,12 +7454,12 @@ select-hose@^2.0.0: resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= -selfsigned@^1.10.11: - version "1.10.11" - resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.11.tgz#24929cd906fe0f44b6d01fb23999a739537acbe9" - integrity sha512-aVmbPOfViZqOZPgRBT0+3u4yZFHpmnIghLMlAcb5/xhp5ZtB/RVnKhz5vl2M32CLXAqR4kha9zfhNg0Lf/sxKA== +selfsigned@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.0.0.tgz#e927cd5377cbb0a1075302cff8df1042cc2bce5b" + integrity sha512-cUdFiCbKoa1mZ6osuJs2uDHrs0k0oprsKveFiiaBKCNq3SYyb5gs2HxhQyDNLCmL51ZZThqi4YNDpCK6GOP1iQ== dependencies: - node-forge "^0.10.0" + node-forge "^1.2.0" semver-diff@^3.1.1: version "3.1.1" @@ -7511,7 +7483,7 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5: +semver@^7.3.2, semver@^7.3.4, semver@^7.3.5: version "7.3.5" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== @@ -7603,6 +7575,11 @@ shallow-clone@^3.0.0: dependencies: kind-of "^6.0.2" +shallowequal@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" + integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== + shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -7615,15 +7592,15 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -shell-quote@^1.7.2: +shell-quote@^1.7.3: version "1.7.3" resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.3.tgz#aa40edac170445b9a431e17bb62c0b881b9c4123" integrity sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw== -shelljs@^0.8.4: - version "0.8.4" - resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.4.tgz#de7684feeb767f8716b326078a8a00875890e3c2" - integrity sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ== +shelljs@^0.8.5: + version "0.8.5" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.5.tgz#de055408d8361bed66c669d2f000538ced8ee20c" + integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow== dependencies: glob "^7.0.0" interpret "^1.0.0" @@ -7638,10 +7615,10 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" -signal-exit@^3.0.2, signal-exit@^3.0.3: - version "3.0.6" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.6.tgz#24e630c4b0f03fea446a2bd299e62b4a6ca8d0af" - integrity sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ== +signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== sirv@^1.0.7: version "1.0.19" @@ -7657,12 +7634,12 @@ sisteransi@^1.0.5: resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== -sitemap@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/sitemap/-/sitemap-7.0.0.tgz#022bef4df8cba42e38e1fe77039f234cab0372b6" - integrity sha512-Ud0jrRQO2k7fEtPAM+cQkBKoMvxQyPKNXKDLn8tRVHxRCsdDQ2JZvw+aZ5IRYYQVAV9iGxEar6boTwZzev+x3g== +sitemap@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/sitemap/-/sitemap-7.1.1.tgz#eeed9ad6d95499161a3eadc60f8c6dce4bea2bef" + integrity sha512-mK3aFtjz4VdJN0igpIJrinf3EO8U8mxOPsTBzSsy06UtjZQJ3YY3o3Xa7zSc5nMqcMrRwlChHZ18Kxg0caiPBg== dependencies: - "@types/node" "^15.0.1" + "@types/node" "^17.0.5" "@types/sax" "^1.2.1" arg "^5.0.0" sax "^1.2.4" @@ -7672,6 +7649,11 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== +slash@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7" + integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== + slice-ansi@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" @@ -7700,10 +7682,10 @@ source-list-map@^2.0.0: resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== -source-map-js@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.1.tgz#a1741c131e3c77d048252adfa24e23b908670caf" - integrity sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA== +source-map-js@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" + integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== source-map-support@~0.5.20: version "0.5.21" @@ -7728,11 +7710,6 @@ source-map@~0.7.2: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== -sourcemap-codec@^1.4.4: - version "1.4.8" - resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" - integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== - space-separated-tokens@^1.0.0: version "1.1.5" resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz#85f32c3d10d9682007e917414ddc5c26d1aa6899" @@ -7817,7 +7794,7 @@ std-env@^3.0.1: resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.0.1.tgz#bc4cbc0e438610197e34c2d79c3df30b491f5182" integrity sha512-mC1Ps9l77/97qeOZc+HrOL7TIaOboHqMZ24dGVQrlxFcpPpfCHpH+qfUT7Dz+6mlG8+JPA1KfBQo19iC/+Ngcw== -string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.2, string-width@^4.2.3: +string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -7826,18 +7803,27 @@ string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.2, string-width@^4.2 is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" +string-width@^5.0.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + string.prototype.matchall@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.6.tgz#5abb5dabc94c7b0ea2380f65ba610b3a544b15fa" - integrity sha512-6WgDX8HmQqvEd7J+G6VtAahhsQIssiZ8zl7zKh1VDMFyL3hRTJP4FTNA3RbIp2TOQ9AYNDcc7e3fH0Qbup+DBg== + version "4.0.7" + resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz#8e6ecb0d8a1fb1fda470d81acecb2dba057a481d" + integrity sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg== dependencies: call-bind "^1.0.2" define-properties "^1.1.3" es-abstract "^1.19.1" get-intrinsic "^1.1.1" - has-symbols "^1.0.2" + has-symbols "^1.0.3" internal-slot "^1.0.3" - regexp.prototype.flags "^1.3.1" + regexp.prototype.flags "^1.4.1" side-channel "^1.0.4" string.prototype.trimend@^1.0.4: @@ -7886,7 +7872,7 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1: dependencies: ansi-regex "^5.0.1" -strip-ansi@^7.0.0: +strip-ansi@^7.0.0, strip-ansi@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2" integrity sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw== @@ -7937,74 +7923,60 @@ style-to-object@0.3.0, style-to-object@^0.3.0: dependencies: inline-style-parser "0.1.1" -stylehacks@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-5.0.1.tgz#323ec554198520986806388c7fdaebc38d2c06fb" - integrity sha512-Es0rVnHIqbWzveU1b24kbw92HsebBepxfcqe5iix7t9j0PQqhs0IxXVXv0pY2Bxa08CgMkzD6OWql7kbGOuEdA== +stylehacks@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-5.1.0.tgz#a40066490ca0caca04e96c6b02153ddc39913520" + integrity sha512-SzLmvHQTrIWfSgljkQCw2++C9+Ne91d/6Sp92I8c5uHTcy/PgeHamwITIbBW9wnFTY/3ZfSXR9HIL6Ikqmcu6Q== dependencies: - browserslist "^4.16.0" + browserslist "^4.16.6" postcss-selector-parser "^6.0.4" -stylelint@^13.2.1: - version "13.13.1" - resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-13.13.1.tgz#fca9c9f5de7990ab26a00f167b8978f083a18f3c" - integrity sha512-Mv+BQr5XTUrKqAXmpqm6Ddli6Ief+AiPZkRsIrAoUKFuq/ElkUh9ZMYxXD0iQNZ5ADghZKLOWz1h7hTClB7zgQ== +stylelint@^14.6.0: + version "14.6.1" + resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-14.6.1.tgz#aff137b0254515fc36b91921d88a3eb2edc194bf" + integrity sha512-FfNdvZUZdzh9KDQxDnO7Opp+prKh8OQVuSW8S13cBtxrooCbm6J6royhUeb++53WPMt04VB+ZbOz/QmzAijs6Q== dependencies: - "@stylelint/postcss-css-in-js" "^0.37.2" - "@stylelint/postcss-markdown" "^0.36.2" - autoprefixer "^9.8.6" balanced-match "^2.0.0" - chalk "^4.1.1" - cosmiconfig "^7.0.0" - debug "^4.3.1" + colord "^2.9.2" + cosmiconfig "^7.0.1" + css-functions-list "^3.0.1" + debug "^4.3.4" execall "^2.0.0" - fast-glob "^3.2.5" + fast-glob "^3.2.11" fastest-levenshtein "^1.0.12" file-entry-cache "^6.0.1" get-stdin "^8.0.0" global-modules "^2.0.0" - globby "^11.0.3" + globby "^11.1.0" globjoin "^0.1.4" html-tags "^3.1.0" - ignore "^5.1.8" + ignore "^5.2.0" import-lazy "^4.0.0" imurmurhash "^0.1.4" - known-css-properties "^0.21.0" - lodash "^4.17.21" - log-symbols "^4.1.0" + is-plain-object "^5.0.0" + known-css-properties "^0.24.0" mathml-tag-names "^2.1.3" meow "^9.0.0" micromatch "^4.0.4" + normalize-path "^3.0.0" normalize-selector "^0.2.0" - postcss "^7.0.35" - postcss-html "^0.36.0" - postcss-less "^3.1.4" + picocolors "^1.0.0" + postcss "^8.4.12" postcss-media-query-parser "^0.2.3" postcss-resolve-nested-selector "^0.1.1" - postcss-safe-parser "^4.0.2" - postcss-sass "^0.4.4" - postcss-scss "^2.1.1" - postcss-selector-parser "^6.0.5" - postcss-syntax "^0.36.2" - postcss-value-parser "^4.1.0" + postcss-safe-parser "^6.0.0" + postcss-selector-parser "^6.0.9" + postcss-value-parser "^4.2.0" resolve-from "^5.0.0" - slash "^3.0.0" specificity "^0.4.1" - string-width "^4.2.2" - strip-ansi "^6.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" style-search "^0.1.0" - sugarss "^2.0.0" + supports-hyperlinks "^2.2.0" svg-tags "^1.0.0" - table "^6.6.0" + table "^6.8.0" v8-compile-cache "^2.3.0" - write-file-atomic "^3.0.3" - -sugarss@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/sugarss/-/sugarss-2.0.0.tgz#ddd76e0124b297d40bf3cca31c8b22ecb43bc61d" - integrity sha512-WfxjozUk0UVA4jm+U1d736AUpzSrNsQcIbyOkoE364GrtWmIrFdk5lksEupgWMD4VaT/0kVx1dobpiDumSgmJQ== - dependencies: - postcss "^7.0.2" + write-file-atomic "^4.0.1" supports-color@^5.3.0: version "5.5.0" @@ -8013,7 +7985,7 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" -supports-color@^7.1.0: +supports-color@^7.0.0, supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== @@ -8027,6 +7999,19 @@ supports-color@^8.0.0: dependencies: has-flag "^4.0.0" +supports-hyperlinks@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz#4f77b42488765891774b70c79babd87f9bd594bb" + integrity sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ== + dependencies: + has-flag "^4.0.0" + supports-color "^7.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + svg-parser@^2.0.2: version "2.0.4" resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5" @@ -8050,10 +8035,10 @@ svgo@^2.5.0, svgo@^2.7.0: picocolors "^1.0.0" stable "^0.1.8" -table@^6.6.0: - version "6.7.5" - resolved "https://registry.yarnpkg.com/table/-/table-6.7.5.tgz#f04478c351ef3d8c7904f0e8be90a1b62417d238" - integrity sha512-LFNeryOqiQHqCVKzhkymKwt6ozeRhlm8IL1mE8rNUurkir4heF6PzMyRgaTa4tlyPTGGgXuvVOF/OLWiH09Lqw== +table@^6.8.0: + version "6.8.0" + resolved "https://registry.yarnpkg.com/table/-/table-6.8.0.tgz#87e28f14fa4321c3377ba286f07b79b281a3b3ca" + integrity sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA== dependencies: ajv "^8.0.1" lodash.truncate "^4.4.2" @@ -8071,22 +8056,23 @@ tapable@^2.0.0, tapable@^2.1.1, tapable@^2.2.0: resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== -terser-webpack-plugin@^5.1.3, terser-webpack-plugin@^5.2.4: - version "5.3.0" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.0.tgz#21641326486ecf91d8054161c816e464435bae9f" - integrity sha512-LPIisi3Ol4chwAaPP8toUJ3L4qCM1G0wao7L3qNv57Drezxj6+VEyySpPw4B1HSO2Eg/hDY/MNF5XihCAoqnsQ== +terser-webpack-plugin@^5.1.3, terser-webpack-plugin@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.1.tgz#0320dcc270ad5372c1e8993fabbd927929773e54" + integrity sha512-GvlZdT6wPQKbDNW/GDQzZFg/j4vKU96yl2q6mcUkzKOgW4gwf1Z8cZToUCrz31XHlPWH8MVb1r2tFtdDtTGJ7g== dependencies: - jest-worker "^27.4.1" + jest-worker "^27.4.5" schema-utils "^3.1.1" serialize-javascript "^6.0.0" source-map "^0.6.1" terser "^5.7.2" terser@^5.10.0, terser@^5.7.2: - version "5.10.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.10.0.tgz#b86390809c0389105eb0a0b62397563096ddafcc" - integrity sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA== + version "5.12.1" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.12.1.tgz#4cf2ebed1f5bceef5c83b9f60104ac4a78b49e9c" + integrity sha512-NXbs+7nisos5E+yXwAD+y7zrcTkMqb0dEJxIGtSKPdCBzopf7ni4odPul2aechpV7EXNvOudYOX2bb5tln1jbQ== dependencies: + acorn "^8.5.0" commander "^2.20.0" source-map "~0.7.2" source-map-support "~0.5.20" @@ -8143,6 +8129,11 @@ totalist@^1.0.0: resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df" integrity sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g== +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= + trim-newlines@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144" @@ -8163,31 +8154,21 @@ trough@^1.0.0: resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406" integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA== -ts-essentials@^2.0.3: - version "2.0.12" - resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-2.0.12.tgz#c9303f3d74f75fa7528c3d49b80e089ab09d8745" - integrity sha512-3IVX4nI6B5cc31/GFFE+i8ey/N2eA0CZDbo6n0yrz0zDX8ZJ8djmU1p+XRz7G3is0F3bB3pu2pAroFdAWQKU3w== - -tsconfig-paths@^3.11.0: - version "3.12.0" - resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz#19769aca6ee8f6a1a341e38c8fa45dd9fb18899b" - integrity sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg== +tsconfig-paths@^3.12.0: + version "3.14.1" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz#ba0734599e8ea36c862798e920bcf163277b137a" + integrity sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ== dependencies: "@types/json5" "^0.0.29" json5 "^1.0.1" - minimist "^1.2.0" + minimist "^1.2.6" strip-bom "^3.0.0" -tslib@^2.0.3, tslib@^2.3.1: +tslib@^2.0.3, tslib@^2.1.0, tslib@^2.2.0, tslib@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== -tslib@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a" - integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A== - type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" @@ -8215,6 +8196,11 @@ type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== +type-fest@^2.5.0: + version "2.12.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.12.1.tgz#d2be8f50bf5f8f0a5fd916d29bf3e98c17e960be" + integrity sha512-AiknQSEqKVGDDjtZqeKrUoTlcj7FKhupmnVUgz6KoOKtvMwRGE6hUNJ/nVear+h7fnUPO1q/htSkYKb1pyntkQ== + type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" @@ -8299,18 +8285,6 @@ unified@^8.4.2: trough "^1.0.0" vfile "^4.0.0" -unified@^9.1.0: - version "9.2.2" - resolved "https://registry.yarnpkg.com/unified/-/unified-9.2.2.tgz#67649a1abfc3ab85d2969502902775eb03146975" - integrity sha512-Sg7j110mtefBD+qunSLO1lqOEKdrwBFBrR6Qd8f4uwkhWNlbkaqwHse6e7QvD3AP/MNoJdEDLaf8OxYyoWgorQ== - dependencies: - bail "^1.0.0" - extend "^3.0.0" - is-buffer "^2.0.0" - is-plain-obj "^2.0.0" - trough "^1.0.0" - vfile "^4.0.0" - unique-string@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" @@ -8323,13 +8297,6 @@ unist-builder@2.0.3, unist-builder@^2.0.0: resolved "https://registry.yarnpkg.com/unist-builder/-/unist-builder-2.0.3.tgz#77648711b5d86af0942f334397a33c5e91516436" integrity sha512-f98yt5pnlMWlzP539tPc4grGMsFaQQlP/vM396b00jngsiINumNmsY8rkXjfoi1c6QaM8nQ3vaGDuoKWbe/1Uw== -unist-util-find-all-after@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/unist-util-find-all-after/-/unist-util-find-all-after-3.0.2.tgz#fdfecd14c5b7aea5e9ef38d5e0d5f774eeb561f6" - integrity sha512-xaTC/AGZ0rIM2gM28YVRAFPIZpzbpDtU3dRmp7EXlNVA8ziQc4hY3H7BHXM1J49nEmiqc3svnqMReW+PGqbZKQ== - dependencies: - unist-util-is "^4.0.0" - unist-util-generated@^1.0.0: version "1.1.6" resolved "https://registry.yarnpkg.com/unist-util-generated/-/unist-util-generated-1.1.6.tgz#5ab51f689e2992a472beb1b35f2ce7ff2f324d4b" @@ -8352,13 +8319,6 @@ unist-util-remove-position@^2.0.0: dependencies: unist-util-visit "^2.0.0" -unist-util-remove@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unist-util-remove/-/unist-util-remove-2.0.0.tgz#32c2ad5578802f2ca62ab808173d505b2c898488" - integrity sha512-HwwWyNHKkeg/eXRnE11IpzY8JT55JNM1YCwwU9YNCnfzk6s8GhPXrVBBZWiwLeATJbI7euvoGSzcy9M29UeW3g== - dependencies: - unist-util-is "^4.0.0" - unist-util-remove@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/unist-util-remove/-/unist-util-remove-2.1.0.tgz#b0b4738aa7ee445c402fda9328d604a02d010588" @@ -8443,20 +8403,10 @@ url-parse-lax@^3.0.0: dependencies: prepend-http "^2.0.0" -url@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" - integrity sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE= - dependencies: - punycode "1.3.2" - querystring "0.2.0" - use-composed-ref@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/use-composed-ref/-/use-composed-ref-1.1.0.tgz#9220e4e94a97b7b02d7d27eaeab0b37034438bbc" - integrity sha512-my1lNHGWsSDAhhVAT4MKs6IjBUtG6ZG11uUqexPH9PptiIZDQOzaF4f5tEbJ2+7qvNbtXNBbU3SfmN+fXlWDhg== - dependencies: - ts-essentials "^2.0.3" + version "1.2.1" + resolved "https://registry.yarnpkg.com/use-composed-ref/-/use-composed-ref-1.2.1.tgz#9bdcb5ccd894289105da2325e1210079f56bf849" + integrity sha512-6+X1FLlIcjvFMAeAD/hcxDT8tmyrWnbSPMU0EnxQuDLIxokuFzWliXBiYZuGIx+mrAMLBw0WFfCkaPw8ebzAhw== use-isomorphic-layout-effect@^1.0.0: version "1.1.1" @@ -8541,16 +8491,16 @@ vfile@^4.0.0: unist-util-stringify-position "^2.0.0" vfile-message "^2.0.0" -wait-on@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-6.0.0.tgz#7e9bf8e3d7fe2daecbb7a570ac8ca41e9311c7e7" - integrity sha512-tnUJr9p5r+bEYXPUdRseolmz5XqJTTj98JgOsfBn7Oz2dxfE2g3zw1jE+Mo8lopM3j3et/Mq1yW7kKX6qw7RVw== +wait-on@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-6.0.1.tgz#16bbc4d1e4ebdd41c5b4e63a2e16dbd1f4e5601e" + integrity sha512-zht+KASY3usTY5u2LgaNqn/Cd8MukxLGjdcZxT2ns5QzDmTFc4XoWBgC+C/na+sMRZTuVygQoMYwdcVjHnYIVw== dependencies: - axios "^0.21.1" - joi "^17.4.0" + axios "^0.25.0" + joi "^17.6.0" lodash "^4.17.21" minimist "^1.2.5" - rxjs "^7.1.0" + rxjs "^7.5.4" watchpack@^2.3.1: version "2.3.1" @@ -8572,7 +8522,12 @@ web-namespaces@^1.0.0, web-namespaces@^1.1.2: resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-1.1.4.tgz#bc98a3de60dadd7faefc403d1076d529f5e030ec" integrity sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw== -webpack-bundle-analyzer@^4.4.2: +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= + +webpack-bundle-analyzer@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.5.0.tgz#1b0eea2947e73528754a6f9af3e91b2b6e0f79d5" integrity sha512-GUMZlM3SKwS8Z+CKeIFx7CVoHn3dXFcUAjT/dcZQQmfSZGvitPfMob2ipjai7ovFFqPvTqkEZ/leL4O0YOdAYQ== @@ -8587,25 +8542,31 @@ webpack-bundle-analyzer@^4.4.2: sirv "^1.0.7" ws "^7.3.1" -webpack-dev-middleware@^5.2.1: - version "5.3.0" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.0.tgz#8fc02dba6e72e1d373eca361623d84610f27be7c" - integrity sha512-MouJz+rXAm9B1OTOYaJnn6rtD/lWZPy2ufQCH3BPs8Rloh/Du6Jze4p7AeLYHkVi0giJnYLaSGDC7S+GM9arhg== +webpack-dev-middleware@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.1.tgz#aa079a8dedd7e58bfeab358a9af7dab304cee57f" + integrity sha512-81EujCKkyles2wphtdrnPg/QqegC/AtqNH//mQkBYSMqwFVCQrxM6ktB2O/SPlZy7LqeEfTbV3cZARGQz6umhg== dependencies: colorette "^2.0.10" - memfs "^3.2.2" + memfs "^3.4.1" mime-types "^2.1.31" range-parser "^1.2.1" schema-utils "^4.0.0" -webpack-dev-server@^4.5.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.6.0.tgz#e8648601c440172d9b6f248d28db98bed335315a" - integrity sha512-oojcBIKvx3Ya7qs1/AVWHDgmP1Xml8rGsEBnSobxU/UJSX1xP1GPM3MwsAnDzvqcVmVki8tV7lbcsjEjk0PtYg== - dependencies: +webpack-dev-server@^4.7.4: + version "4.7.4" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.7.4.tgz#d0ef7da78224578384e795ac228d8efb63d5f945" + integrity sha512-nfdsb02Zi2qzkNmgtZjkrMOcXnYZ6FLKcQwpxT7MvmHKc+oTtDsBju8j+NMyAygZ9GW1jMEUpy3itHtqgEhe1A== + dependencies: + "@types/bonjour" "^3.5.9" + "@types/connect-history-api-fallback" "^1.3.5" + "@types/express" "^4.17.13" + "@types/serve-index" "^1.9.1" + "@types/sockjs" "^0.3.33" + "@types/ws" "^8.2.2" ansi-html-community "^0.0.8" bonjour "^3.5.0" - chokidar "^3.5.2" + chokidar "^3.5.3" colorette "^2.0.10" compression "^1.7.4" connect-history-api-fallback "^1.6.0" @@ -8620,14 +8581,13 @@ webpack-dev-server@^4.5.0: p-retry "^4.5.0" portfinder "^1.0.28" schema-utils "^4.0.0" - selfsigned "^1.10.11" + selfsigned "^2.0.0" serve-index "^1.9.1" sockjs "^0.3.21" spdy "^4.0.2" strip-ansi "^7.0.0" - url "^0.11.0" - webpack-dev-middleware "^5.2.1" - ws "^8.1.0" + webpack-dev-middleware "^5.3.1" + ws "^8.4.2" webpack-merge@^5.8.0: version "5.8.0" @@ -8637,7 +8597,7 @@ webpack-merge@^5.8.0: clone-deep "^4.0.1" wildcard "^2.0.0" -webpack-sources@^1.1.0, webpack-sources@^1.4.3: +webpack-sources@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== @@ -8645,18 +8605,18 @@ webpack-sources@^1.1.0, webpack-sources@^1.4.3: source-list-map "^2.0.0" source-map "~0.6.1" -webpack-sources@^3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.2.tgz#d88e3741833efec57c4c789b6010db9977545260" - integrity sha512-cp5qdmHnu5T8wRg2G3vZZHoJPN14aqQ89SyQ11NpGH5zEMDCclt49rzo+MaRazk7/UeILhAI+/sEtcM+7Fr0nw== +webpack-sources@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" + integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== -webpack@^5.61.0: - version "5.65.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.65.0.tgz#ed2891d9145ba1f0d318e4ea4f89c3fa18e6f9be" - integrity sha512-Q5or2o6EKs7+oKmJo7LaqZaMOlDWQse9Tm5l1WAfU/ujLGN5Pb0SqGeVkN/4bpPmEqEP5RnVhiqsOtWtUVwGRw== +webpack@^5.70.0: + version "5.70.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.70.0.tgz#3461e6287a72b5e6e2f4872700bc8de0d7500e6d" + integrity sha512-ZMWWy8CeuTTjCxbeaQI21xSswseF2oNOwc70QSKNePvmxE7XW36i7vpBMYZFAUHPwQiEbNGCEYIOOlyRbdGmxw== dependencies: - "@types/eslint-scope" "^3.7.0" - "@types/estree" "^0.0.50" + "@types/eslint-scope" "^3.7.3" + "@types/estree" "^0.0.51" "@webassemblyjs/ast" "1.11.1" "@webassemblyjs/wasm-edit" "1.11.1" "@webassemblyjs/wasm-parser" "1.11.1" @@ -8664,12 +8624,12 @@ webpack@^5.61.0: acorn-import-assertions "^1.7.6" browserslist "^4.14.5" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.8.3" + enhanced-resolve "^5.9.2" es-module-lexer "^0.9.0" eslint-scope "5.1.1" events "^3.2.0" glob-to-regexp "^0.4.1" - graceful-fs "^4.2.4" + graceful-fs "^4.2.9" json-parse-better-errors "^1.0.2" loader-runner "^4.2.0" mime-types "^2.1.27" @@ -8678,9 +8638,9 @@ webpack@^5.61.0: tapable "^2.1.1" terser-webpack-plugin "^5.1.3" watchpack "^2.3.1" - webpack-sources "^3.2.2" + webpack-sources "^3.2.3" -webpackbar@^5.0.0-3: +webpackbar@^5.0.2: version "5.0.2" resolved "https://registry.yarnpkg.com/webpackbar/-/webpackbar-5.0.2.tgz#d3dd466211c73852741dfc842b7556dcbc2b0570" integrity sha512-BmFJo7veBDgQzfWXl/wwYXr/VFus0614qZ8i9znqcl9fnEdiVkdbi0TedLQ6xAK92HZHDJ0QmyQ0fmuZPAgCYQ== @@ -8704,6 +8664,14 @@ websocket-extensions@>=0.1.1: resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0= + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + which-boxed-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" @@ -8736,6 +8704,13 @@ widest-line@^3.1.0: dependencies: string-width "^4.0.0" +widest-line@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-4.0.1.tgz#a0fc673aaba1ea6f0a0d35b3c2795c9a9cc2ebf2" + integrity sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig== + dependencies: + string-width "^5.0.1" + wildcard@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" @@ -8755,12 +8730,21 @@ wrap-ansi@^7.0.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.0.1.tgz#2101e861777fec527d0ea90c57c6b03aac56a5b3" + integrity sha512-QFF+ufAqhoYHvoHdajT/Po7KoXVBPXS2bgjIam5isfWJPfIOnQZ50JtUiVvCv/sjgacf3yRrt2ZKUZ/V4itN4g== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -write-file-atomic@^3.0.0, write-file-atomic@^3.0.3: +write-file-atomic@^3.0.0: version "3.0.3" resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== @@ -8770,15 +8754,23 @@ write-file-atomic@^3.0.0, write-file-atomic@^3.0.3: signal-exit "^3.0.2" typedarray-to-buffer "^3.1.5" +write-file-atomic@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.1.tgz#9faa33a964c1c85ff6f849b80b42a88c2c537c8f" + integrity sha512-nSKUxgAbyioruk6hU87QzVbY279oYT6uiwgDoujth2ju4mJ+TZau7SQBhtbTmUyuNYTuXnSyRn66FV0+eCgcrQ== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^3.0.7" + ws@^7.3.1: - version "7.5.6" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.6.tgz#e59fc509fb15ddfb65487ee9765c5a51dec5fe7b" - integrity sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA== + version "7.5.7" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.7.tgz#9e0ac77ee50af70d58326ecff7e85eb3fa375e67" + integrity sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A== -ws@^8.1.0: - version "8.4.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.4.0.tgz#f05e982a0a88c604080e8581576e2a063802bed6" - integrity sha512-IHVsKe2pjajSUIl4KYMQOdlyliovpEPquKkqbwswulszzI7r0SfQrxnXdWAEqOlDCLrVSJzo+O1hAwdog2sKSQ== +ws@^8.4.2: + version "8.5.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f" + integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg== xdg-basedir@^4.0.0: version "4.0.0" diff --git a/jest.config.mjs b/jest.config.mjs index 343d5b7ded89..7163875c4149 100644 --- a/jest.config.mjs +++ b/jest.config.mjs @@ -7,50 +7,67 @@ import {fileURLToPath} from 'url'; +process.env.TZ = 'UTC'; + const ignorePatterns = [ '/node_modules/', '__fixtures__', + '/testUtils.ts', '/packages/docusaurus/lib', + '/packages/docusaurus-logger/lib', '/packages/docusaurus-utils/lib', + '/packages/docusaurus-utils-common/lib', '/packages/docusaurus-utils-validation/lib', '/packages/docusaurus-plugin-content-blog/lib', '/packages/docusaurus-plugin-content-docs/lib', '/packages/docusaurus-plugin-content-pages/lib', '/packages/docusaurus-theme-classic/lib', '/packages/docusaurus-theme-classic/lib-next', + '/packages/docusaurus-theme-common/lib', '/packages/docusaurus-migrate/lib', ]; export default { rootDir: fileURLToPath(new URL('.', import.meta.url)), verbose: true, - testURL: 'http://localhost/', + testURL: 'https://docusaurus.io/', testEnvironment: 'node', testPathIgnorePatterns: ignorePatterns, - coveragePathIgnorePatterns: ignorePatterns, + coveragePathIgnorePatterns: [ + ...ignorePatterns, + // We also ignore all package entry points + '/packages/docusaurus-utils/src/index.ts', + ], transform: { - '^.+\\.[jt]sx?$': 'babel-jest', + '^.+\\.[jt]sx?$': '@swc/jest', }, + errorOnDeprecated: true, moduleNameMapper: { // Jest can't resolve CSS or asset imports - '^.+\\.(css|jpg|jpeg|png|svg)$': '/jest/emptyModule.js', + '^.+\\.(css|jpe?g|png|svg|webp)$': '/jest/emptyModule.ts', + + // Using src instead of lib, so we always get fresh source + '@docusaurus/(BrowserOnly|ComponentCreator|constants|ExecutionEnvironment|Head|Interpolate|isInternalUrl|Link|Noop|renderRoutes|router|Translate|use.*)': + '@docusaurus/core/src/client/exports/$1', - // TODO we need to allow Jest to resolve core Webpack aliases automatically - '@docusaurus/(browserContext|BrowserOnly|ComponentCreator|constants|docusaurusContext|ExecutionEnvironment|Head|Interpolate|isInternalUrl|Link|Noop|renderRoutes|router|Translate|use.*)': - '@docusaurus/core/lib/client/exports/$1', + // TODO create dedicated testing utility for mocking contexts // Maybe point to a fixture? - '@generated/.*': '/jest/emptyModule.js', - // TODO maybe use "projects" + multiple configs if we plan to add tests to another theme? + '@generated/.*': '/jest/emptyModule.ts', + // TODO use "projects" + multiple configs if we work on another theme? '@theme/(.*)': '@docusaurus/theme-classic/src/theme/$1', '@site/(.*)': 'website/$1', - // TODO why Jest can't figure node package entry points? + // Using src instead of lib, so we always get fresh source '@docusaurus/plugin-content-docs/client': - '@docusaurus/plugin-content-docs/lib/client/index.js', + '@docusaurus/plugin-content-docs/src/client/index.ts', + + '@testing-utils/(.*)': '/jest/utils/$1.ts', }, - globals: { - window: { - location: {href: 'https://docusaurus.io'}, - }, + snapshotSerializers: [ + '/jest/snapshotPathNormalizer.ts', + 'jest-serializer-react-helmet-async', + ], + snapshotFormat: { + printBasicPrototype: false, }, }; diff --git a/jest/emptyModule.js b/jest/emptyModule.ts similarity index 90% rename from jest/emptyModule.js rename to jest/emptyModule.ts index ec757d00e954..2687fdf661e1 100644 --- a/jest/emptyModule.js +++ b/jest/emptyModule.ts @@ -5,4 +5,4 @@ * LICENSE file in the root directory of this source tree. */ -module.exports = {}; +export default {}; diff --git a/jest/snapshotPathNormalizer.ts b/jest/snapshotPathNormalizer.ts new file mode 100644 index 000000000000..d8b2d7a14e45 --- /dev/null +++ b/jest/snapshotPathNormalizer.ts @@ -0,0 +1,147 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +// Forked from https://github.com/tribou/jest-serializer-path/blob/master/lib/index.js +// Added some project-specific handlers + +import _ from 'lodash'; +import {escapePath} from '@docusaurus/utils'; +import {version} from '@docusaurus/core/package.json'; +import os from 'os'; +import path from 'path'; +import fs from 'fs'; + +export function print( + val: unknown, + serialize: (val: unknown) => string, +): string { + if (val instanceof Error) { + const message = normalizePaths(val.message); + const error = new Error(message); + const allKeys = [ + ...Object.getOwnPropertyNames(error), + ...Object.keys(val), + ] as (keyof Error)[]; + allKeys.forEach((key) => { + error[key] = normalizePaths(val[key]) as never; + }); + return serialize(error); + } else if (val && typeof val === 'object') { + const normalizedValue = _.cloneDeep(val) as {[key: string]: unknown}; + + Object.keys(normalizedValue).forEach((key) => { + normalizedValue[key] = normalizePaths(normalizedValue[key]); + }); + return serialize(normalizedValue); + } + return serialize(normalizePaths(val)); +} + +export function test(val: unknown): boolean { + return ( + (typeof val === 'object' && + val && + Object.keys(val).some((key) => + shouldUpdate((val as {[key: string]: unknown})[key]), + )) || + // val.message is non-enumerable in an error + (val instanceof Error && shouldUpdate(val.message)) || + shouldUpdate(val) + ); +} + +/** + * Normalize paths across platforms. + * Filters must be ran on all platforms to guard against false positives + */ +function normalizePaths(value: T): T { + if (typeof value !== 'string') { + return value; + } + + const cwd = process.cwd(); + const cwdReal = getRealPath(cwd); + const tempDir = os.tmpdir(); + const tempDirReal = getRealPath(tempDir); + const homeDir = os.homedir(); + const homeDirReal = getRealPath(homeDir); + + const homeRelativeToTemp = path.relative(tempDir, homeDir); + const homeRelativeToTempReal = path.relative(tempDirReal, homeDir); + const homeRealRelativeToTempReal = path.relative(tempDirReal, homeDirReal); + const homeRealRelativeToTemp = path.relative(tempDir, homeDirReal); + + const runner: ((val: string) => string)[] = [ + // Replace process.cwd with + (val) => val.split(cwdReal).join(''), + (val) => val.split(cwd).join(''), + + // Replace home directory with + (val) => val.split(tempDirReal).join(''), + (val) => val.split(tempDir).join(''), + + // Replace home directory with + (val) => val.split(homeDirReal).join(''), + (val) => val.split(homeDir).join(''), + + // handle HOME_DIR nested inside TEMP_DIR + (val) => + val + .split(`${path.sep + homeRelativeToTemp}`) + .join(''), + (val) => + val + .split(`${path.sep + homeRelativeToTempReal}`) + .join(''), // untested + (val) => + val + .split(`${path.sep + homeRealRelativeToTempReal}`) + .join(''), + (val) => + val + .split(`${path.sep + homeRealRelativeToTemp}`) + .join(''), // untested + + // Replace the Docusaurus version with a stub + (val) => val.split(version).join(''), + + // In case the CWD is escaped + (val) => val.split(escapePath(cwd)).join(''), + + // Remove win32 drive letters, C:\ -> \ + (val) => val.replace(/[a-zA-Z]:\\/g, '\\'), + + // Remove duplicate backslashes created from escapePath + (val) => val.replace(/\\\\/g, '\\'), + + // Convert win32 backslash's to forward slashes, \ -> /; + // ignore some that look like escape sequences. + (val) => val.replace(/\\(?!")/g, '/'), + ]; + + let result = value as string; + runner.forEach((current) => { + result = current(result); + }); + + return result as T & string; +} + +function shouldUpdate(value: unknown) { + // return true if value is different from normalized value + return typeof value === 'string' && normalizePaths(value) !== value; +} + +function getRealPath(pathname: string) { + try { + // eslint-disable-next-line no-restricted-properties + const realPath = fs.realpathSync(pathname); + return realPath; + } catch (error) { + return pathname; + } +} diff --git a/jest/utils/git.ts b/jest/utils/git.ts new file mode 100644 index 000000000000..38db021dccc9 --- /dev/null +++ b/jest/utils/git.ts @@ -0,0 +1,63 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import fs from 'fs-extra'; +import os from 'os'; +import path from 'path'; +import shell from 'shelljs'; + +class Git { + constructor(private dir: string) { + const res = shell.exec('git init', {cwd: dir, silent: true}); + if (res.code !== 0) { + throw new Error(`git init exited with code ${res.code}. +stderr: ${res.stderr} +stdout: ${res.stdout}`); + } + // Doesn't matter currently + shell.exec('git config user.email "test@jc-verse.com"', { + cwd: dir, + silent: true, + }); + shell.exec('git config user.name "Test"', {cwd: dir, silent: true}); + + shell.exec('git commit --allow-empty -m "First commit"', { + cwd: dir, + silent: true, + }); + } + commit(msg: string, date: string, author: string): void { + const addRes = shell.exec('git add .', {cwd: this.dir, silent: true}); + const commitRes = shell.exec( + `git commit -m "${msg}" --date "${date}T00:00:00Z" --author "${author}"`, + { + cwd: this.dir, + env: {GIT_COMMITTER_DATE: `${date}T00:00:00Z`}, + silent: true, + }, + ); + if (addRes.code !== 0) { + throw new Error(`git add exited with code ${addRes.code}. +stderr: ${addRes.stderr} +stdout: ${addRes.stdout}`); + } + if (commitRes.code !== 0) { + throw new Error(`git commit exited with code ${commitRes.code}. +stderr: ${commitRes.stderr} +stdout: ${commitRes.stdout}`); + } + } +} + +// This function is sync so the same mock repo can be shared across tests +export function createTempRepo(): {repoDir: string; git: Git} { + const repoDir = fs.mkdtempSync(path.join(os.tmpdir(), 'git-test-repo')); + + const git = new Git(repoDir); + + return {repoDir, git}; +} diff --git a/lerna.json b/lerna.json index 606f3131a58e..a6c855450b0b 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.0.0-beta.14", + "version": "2.0.0-beta.18", "npmClient": "yarn", "useWorkspaces": true, "changelog": { diff --git a/package.json b/package.json index 89fee283b0e8..6c2fe7139a60 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,9 @@ "build:website": "yarn workspace website build", "build:website:baseUrl": "yarn workspace website build:baseUrl", "build:website:blogOnly": "yarn workspace website build:blogOnly", - "build:website:deployPreview": "cross-env NETLIFY=true CONTEXT='deploy-preview' yarn workspace website build", + "build:website:deployPreview:testWrap": "yarn workspace website test:swizzle:wrap:ts", + "build:website:deployPreview:build": "cross-env NETLIFY=true CONTEXT='deploy-preview' yarn workspace website build", + "build:website:deployPreview": "yarn build:website:deployPreview:testWrap && yarn build:website:deployPreview:build", "build:website:fast": "yarn workspace website build:fast", "build:website:en": "yarn workspace website build --locale en", "clear:website": "yarn workspace website clear", @@ -50,7 +52,7 @@ "lint:spelling": "cspell \"**\" --no-progress", "lint:style": "stylelint \"**/*.css\"", "lerna": "lerna", - "test": "cross-env TZ=UTC jest", + "test": "jest", "test:build:website": "./admin/scripts/test-release.sh", "watch": "yarn lerna run --parallel watch", "clear": "(yarn workspace website clear || echo 'Failure while running docusaurus clear') && yarn lerna exec --ignore docusaurus yarn rimraf lib lib-next", @@ -58,72 +60,58 @@ "lock:update": "npx yarn-deduplicate" }, "devDependencies": { - "@babel/cli": "^7.16.0", - "@babel/core": "^7.16.0", - "@babel/eslint-parser": "^7.16.3", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.0", - "@babel/plugin-proposal-optional-chaining": "^7.16.0", - "@babel/plugin-transform-modules-commonjs": "^7.16.0", - "@babel/preset-typescript": "^7.16.0", - "@crowdin/cli": "^3.7.1", - "@types/fs-extra": "^9.0.6", - "@types/jest": "^26.0.20", - "@types/lodash": "^4.14.168", - "@types/node": "^17.0.8", - "@types/prompts": "^2.0.9", - "@types/react": "^17.0.2", - "@types/react-dev-utils": "^9.0.1", + "@babel/cli": "^7.17.6", + "@babel/core": "^7.17.8", + "@babel/preset-typescript": "^7.16.7", + "@crowdin/cli": "^3.7.8", + "@swc/core": "^1.2.161", + "@swc/jest": "^0.2.20", + "@testing-library/react-hooks": "^7.0.2", + "@types/fs-extra": "^9.0.13", + "@types/jest": "^27.4.1", + "@types/lodash": "^4.14.181", + "@types/node": "^17.0.23", + "@types/prompts": "^2.0.14", + "@types/react": "^17.0.43", + "@types/react-dev-utils": "^9.0.10", "@types/react-test-renderer": "^17.0.1", - "@types/semver": "^7.1.0", - "@types/shelljs": "^0.8.6", - "@typescript-eslint/eslint-plugin": "^5.8.1", - "@typescript-eslint/parser": "^5.8.1", + "@types/semver": "^7.3.9", + "@types/shelljs": "^0.8.11", + "@typescript-eslint/eslint-plugin": "^5.17.0", + "@typescript-eslint/parser": "^5.17.0", "concurrently": "^7.0.0", "cross-env": "^7.0.3", - "cspell": "^5.16.0", - "eslint": "^8.2.0", - "eslint-config-airbnb": "^19.0.0", - "eslint-config-prettier": "^8.3.0", + "cspell": "^5.19.3", + "eslint": "^8.12.0", + "eslint-config-airbnb": "^19.0.4", + "eslint-config-prettier": "^8.5.0", "eslint-plugin-header": "^3.1.1", - "eslint-plugin-import": "^2.25.3", - "eslint-plugin-jest": "^25.7.0", + "eslint-plugin-import": "^2.25.4", + "eslint-plugin-jest": "^26.1.3", "eslint-plugin-jsx-a11y": "^6.5.1", - "eslint-plugin-react": "^7.27.0", - "eslint-plugin-react-hooks": "^4.3.0", - "glob": "^7.1.6", + "eslint-plugin-react": "^7.29.4", + "eslint-plugin-react-hooks": "^4.4.0", + "eslint-plugin-regexp": "^1.6.0", "husky": "^7.0.4", "image-size": "^1.0.1", - "jest": "^26.6.3", + "jest": "^27.5.1", + "jest-serializer-react-helmet-async": "^1.0.21", "lerna": "^4.0.0", - "lerna-changelog": "^1.0.1", - "lint-staged": "^12.1.7", - "netlify-cli": "^8.0.5", - "nodemon": "^2.0.13", - "prettier": "^2.5.1", - "react": "^17.0.1", - "react-dom": "^17.0.1", + "lerna-changelog": "^2.2.0", + "lint-staged": "^12.3.7", + "netlify-cli": "^9.13.6", + "nodemon": "^2.0.15", + "prettier": "^2.6.1", + "react": "^17.0.2", + "react-dom": "^17.0.2", "react-test-renderer": "^17.0.2", + "remark-parse": "^8.0.2", "rimraf": "^3.0.2", - "sharp": "^0.29.1", - "stylelint": "^14.3.0", + "sharp": "^0.30.3", + "stylelint": "^14.6.1", "stylelint-config-prettier": "^9.0.3", - "stylelint-config-standard": "^24.0.0", - "tslib": "^2.3.1", - "typescript": "^4.5.2" - }, - "lint-staged": { - "*.{js,jsx,ts,tsx,mjs}": [ - "eslint --fix" - ], - "*.css": [ - "stylelint --allow-empty-input --fix" - ], - "*": [ - "prettier --ignore-unknown --write", - "cspell --no-must-find-files --no-progress" - ] - }, - "engines": { - "node": ">=14" + "stylelint-config-standard": "^25.0.0", + "typescript": "^4.6.3", + "unified": "^9.2.2" } } diff --git a/packages/create-docusaurus/bin/index.js b/packages/create-docusaurus/bin/index.js index bd608096f061..7673e0025a0f 100755 --- a/packages/create-docusaurus/bin/index.js +++ b/packages/create-docusaurus/bin/index.js @@ -8,12 +8,14 @@ // @ts-check -const logger = require('@docusaurus/logger').default; -const semver = require('semver'); -const path = require('path'); -const program = require('commander'); -const {default: init} = require('../lib'); -const requiredVersion = require('../package.json').engines.node; +import logger from '@docusaurus/logger'; +import semver from 'semver'; +import path from 'path'; +import {program} from 'commander'; +import {createRequire} from 'module'; + +const packageJson = createRequire(import.meta.url)('../package.json'); +const requiredVersion = packageJson.engines.node; if (!semver.satisfies(process.version, requiredVersion)) { logger.error('Minimum Node.js version not met :('); @@ -21,46 +23,54 @@ if (!semver.satisfies(process.version, requiredVersion)) { process.exit(1); } -function wrapCommand(fn) { - return (...args) => - fn(...args).catch((err) => { - logger.error(err.stack); - process.exitCode = 1; - }); -} +program.version(packageJson.version); program - .version(require('../package.json').version) - .usage(' [options]'); - -program - .command('init [siteName] [template] [rootDir]', {isDefault: true}) - .option('--use-npm') - .option('--skip-install') - .option('--typescript') + .arguments('[siteName] [template] [rootDir]') + .option( + '-p, --package-manager ', + 'The package manager used to install dependencies. One of yarn, npm, and pnpm.', + ) + .option( + '-s, --skip-install', + 'Do not run package manager immediately after scaffolding', + ) + .option('-t, --typescript', 'Use the TypeScript template variant') + .option( + '-g, --git-strategy ', + `Only used if the template is a git repository. +\`deep\`: preserve full history +\`shallow\`: clone with --depth=1 +\`copy\`: do a shallow clone, but do not create a git repo +\`custom\`: enter your custom git clone command. We will prompt you for it.`, + ) .description('Initialize website.') .action( ( siteName, template, rootDir = '.', - {useNpm, skipInstall, typescript} = {}, + {packageManager, skipInstall, typescript, gitStrategy} = {}, ) => { - wrapCommand(init)(path.resolve(rootDir), siteName, template, { - useNpm, - skipInstall, - typescript, + // See https://github.com/facebook/docusaurus/pull/6860 + import('../lib/index.js').then(({default: init}) => { + init(path.resolve(rootDir), siteName, template, { + packageManager, + skipInstall, + typescript, + gitStrategy, + }); }); }, ); -program.arguments('').action((cmd) => { - program.outputHelp(); - logger.error`Unknown command code=${cmd}.`; -}); - program.parse(process.argv); if (!process.argv.slice(1).length) { program.outputHelp(); } + +process.on('unhandledRejection', (err) => { + logger.error(err); + process.exit(1); +}); diff --git a/packages/create-docusaurus/package.json b/packages/create-docusaurus/package.json index 2b2a3420544f..f13a742a866e 100755 --- a/packages/create-docusaurus/package.json +++ b/packages/create-docusaurus/package.json @@ -1,7 +1,8 @@ { "name": "create-docusaurus", - "version": "2.0.0-beta.14", + "version": "2.0.0-beta.18", "description": "Create Docusaurus apps easily.", + "type": "module", "repository": { "type": "git", "url": "https://github.com/facebook/docusaurus.git", @@ -12,8 +13,8 @@ }, "scripts": { "create-docusaurus": "create-docusaurus", - "build": "tsc", - "watch": "tsc --watch" + "build": "tsc -p tsconfig.build.json", + "watch": "tsc -p tsconfig.build.json --watch" }, "bin": "bin/index.js", "publishConfig": { @@ -21,20 +22,20 @@ }, "license": "MIT", "dependencies": { - "@docusaurus/logger": "2.0.0-beta.14", + "@docusaurus/logger": "2.0.0-beta.18", "commander": "^5.1.0", - "fs-extra": "^10.0.0", - "lodash": "^4.17.20", - "prompts": "^2.4.1", - "semver": "^7.3.4", - "shelljs": "^0.8.4", - "supports-color": "^8.1.1", + "fs-extra": "^10.0.1", + "lodash": "^4.17.21", + "prompts": "^2.4.2", + "semver": "^7.3.5", + "shelljs": "^0.8.5", + "supports-color": "^9.2.2", "tslib": "^2.3.1" }, - "engines": { - "node": ">=14" - }, "devDependencies": { "@types/supports-color": "^8.1.1" + }, + "engines": { + "node": ">=14" } } diff --git a/packages/create-docusaurus/src/index.ts b/packages/create-docusaurus/src/index.ts index 7e391459268f..1ca4627e7006 100755 --- a/packages/create-docusaurus/src/index.ts +++ b/packages/create-docusaurus/src/index.ts @@ -7,30 +7,102 @@ import logger from '@docusaurus/logger'; import fs from 'fs-extra'; -import {execSync} from 'child_process'; import prompts, {type Choice} from 'prompts'; import path from 'path'; import shell from 'shelljs'; -import {kebabCase, sortBy} from 'lodash'; +import _ from 'lodash'; import supportsColor from 'supports-color'; +import {fileURLToPath} from 'url'; const RecommendedTemplate = 'classic'; const TypeScriptTemplateSuffix = '-typescript'; -function hasYarn() { - try { - execSync('yarnpkg --version', {stdio: 'ignore'}); - return true; - } catch (e) { - return false; +// Only used in the rare, rare case of running globally installed create + +// using --skip-install. We need a default name to show the tip text +const DefaultPackageManager = 'npm'; + +const SupportedPackageManagers = { + npm: 'package-lock.json', + yarn: 'yarn.lock', + pnpm: 'pnpm-lock.yaml', +}; + +type SupportedPackageManager = keyof typeof SupportedPackageManagers; + +const PackageManagersList = Object.keys( + SupportedPackageManagers, +) as SupportedPackageManager[]; + +async function findPackageManagerFromLockFile(): Promise< + SupportedPackageManager | undefined +> { + for (const packageManager of PackageManagersList) { + const lockFilePath = path.resolve(SupportedPackageManagers[packageManager]); + if (await fs.pathExists(lockFilePath)) { + return packageManager; + } + } + return undefined; +} + +function findPackageManagerFromUserAgent(): + | SupportedPackageManager + | undefined { + return PackageManagersList.find((packageManager) => + process.env.npm_config_user_agent?.startsWith(packageManager), + ); +} + +async function askForPackageManagerChoice(): Promise { + const hasYarn = shell.exec('yarn --version', {silent: true}).code === 0; + const hasPNPM = shell.exec('pnpm --version', {silent: true}).code === 0; + + if (!hasYarn && !hasPNPM) { + return 'npm'; + } + const choices = ['npm', hasYarn && 'yarn', hasPNPM && 'pnpm'] + .filter((p): p is string => Boolean(p)) + .map((p) => ({title: p, value: p})); + + return ( + await prompts({ + type: 'select', + name: 'packageManager', + message: 'Select a package manager...', + choices, + }) + ).packageManager; +} + +async function getPackageManager( + packageManagerChoice: SupportedPackageManager | undefined, + skipInstall: boolean = false, +): Promise { + if ( + packageManagerChoice && + !PackageManagersList.includes(packageManagerChoice) + ) { + throw new Error( + `Invalid package manager choice ${packageManagerChoice}. Must be one of ${PackageManagersList.join( + ', ', + )}`, + ); } + + return ( + packageManagerChoice ?? + (await findPackageManagerFromLockFile()) ?? + findPackageManagerFromUserAgent() ?? + // This only happens if the user has a global installation in PATH + (skipInstall ? DefaultPackageManager : askForPackageManagerChoice()) + ); } function isValidGitRepoUrl(gitRepoUrl: string) { return ['https://', 'git@'].some((item) => gitRepoUrl.startsWith(item)); } -async function updatePkg(pkgPath: string, obj: Record) { +async function updatePkg(pkgPath: string, obj: {[key: string]: unknown}) { const content = await fs.readFile(pkgPath, 'utf-8'); const pkg = JSON.parse(content); const newPkg = Object.assign(pkg, obj); @@ -38,19 +110,17 @@ async function updatePkg(pkgPath: string, obj: Record) { await fs.outputFile(pkgPath, `${JSON.stringify(newPkg, null, 2)}\n`); } -function readTemplates(templatesDir: string) { - const templates = fs - .readdirSync(templatesDir) - .filter( - (d) => - !d.startsWith('.') && - !d.startsWith('README') && - !d.endsWith(TypeScriptTemplateSuffix) && - d !== 'shared', - ); +async function readTemplates(templatesDir: string) { + const templates = (await fs.readdir(templatesDir)).filter( + (d) => + !d.startsWith('.') && + !d.startsWith('README') && + !d.endsWith(TypeScriptTemplateSuffix) && + d !== 'shared', + ); // Classic should be first in list! - return sortBy(templates, (t) => t !== RecommendedTemplate); + return _.sortBy(templates, (t) => t !== RecommendedTemplate); } function createTemplateChoices(templates: string[]) { @@ -79,44 +149,67 @@ async function copyTemplate( template: string, dest: string, ) { - await fs.copy(path.resolve(templatesDir, 'shared'), dest); + await fs.copy(path.join(templatesDir, 'shared'), dest); - // TypeScript variants will copy duplicate resources like CSS & config from base template + // TypeScript variants will copy duplicate resources like CSS & config from + // base template const tsBaseTemplate = getTypeScriptBaseTemplate(template); if (tsBaseTemplate) { const tsBaseTemplatePath = path.resolve(templatesDir, tsBaseTemplate); await fs.copy(tsBaseTemplatePath, dest, { - filter: (filePath) => - fs.statSync(filePath).isDirectory() || + filter: async (filePath) => + (await fs.stat(filePath)).isDirectory() || path.extname(filePath) === '.css' || path.basename(filePath) === 'docusaurus.config.js', }); } await fs.copy(path.resolve(templatesDir, template), dest, { - // Symlinks don't exist in published NPM packages anymore, so this is only to prevent errors during local testing - filter: (filePath) => !fs.lstatSync(filePath).isSymbolicLink(), + // Symlinks don't exist in published NPM packages anymore, so this is only + // to prevent errors during local testing + filter: async (filePath) => !(await fs.lstat(filePath)).isSymbolicLink(), }); } +const gitStrategies = ['deep', 'shallow', 'copy', 'custom'] as const; + +async function getGitCommand(gitStrategy: typeof gitStrategies[number]) { + switch (gitStrategy) { + case 'shallow': + case 'copy': + return 'git clone --recursive --depth 1'; + case 'custom': { + const {command} = await prompts({ + type: 'text', + name: 'command', + message: + 'Write your own git clone command. The repository URL and destination directory will be supplied. E.g. "git clone --depth 10"', + }); + return command; + } + case 'deep': + default: + return 'git clone'; + } +} + export default async function init( rootDir: string, siteName?: string, reqTemplate?: string, cliOptions: Partial<{ - useNpm: boolean; + packageManager: SupportedPackageManager; skipInstall: boolean; typescript: boolean; + gitStrategy: typeof gitStrategies[number]; }> = {}, ): Promise { - const useYarn = cliOptions.useNpm ? false : hasYarn(); - const templatesDir = path.resolve(__dirname, '../templates'); - const templates = readTemplates(templatesDir); + const templatesDir = fileURLToPath(new URL('../templates', import.meta.url)); + const templates = await readTemplates(templatesDir); const hasTS = (templateName: string) => - fs.pathExistsSync( - path.resolve(templatesDir, `${templateName}${TypeScriptTemplateSuffix}`), + fs.pathExists( + path.join(templatesDir, `${templateName}${TypeScriptTemplateSuffix}`), ); - let name = siteName; // Prompt if siteName is not passed from CLI. @@ -136,7 +229,7 @@ export default async function init( } const dest = path.resolve(rootDir, name); - if (fs.existsSync(dest)) { + if (await fs.pathExists(dest)) { logger.error`Directory already exists at path=${dest}!`; process.exit(1); } @@ -152,7 +245,7 @@ export default async function init( choices: createTemplateChoices(templates), }); template = templatePrompt.template; - if (template && !useTS && hasTS(template)) { + if (template && !useTS && (await hasTS(template))) { const tsPrompt = await prompts({ type: 'confirm', name: 'useTS', @@ -164,6 +257,8 @@ export default async function init( } } + let gitStrategy = cliOptions.gitStrategy ?? 'deep'; + // If user choose Git repository, we'll prompt for the url. if (template === 'Git repository') { const repoPrompt = await prompts({ @@ -176,17 +271,31 @@ export default async function init( return logger.red('Invalid repository URL'); }, message: logger.interpolate`Enter a repository URL from GitHub, Bitbucket, GitLab, or any other public repo. -(e.g: path=${'https://github.com/ownerName/repoName.git'})`, +(e.g: url=${'https://github.com/ownerName/repoName.git'})`, }); + ({gitStrategy} = await prompts({ + type: 'select', + name: 'gitStrategy', + message: 'How should we clone this repo?', + choices: [ + {title: 'Deep clone: preserve full history', value: 'deep'}, + {title: 'Shallow clone: clone with --depth=1', value: 'shallow'}, + { + title: 'Copy: do a shallow clone, but do not create a git repo', + value: 'copy', + }, + {title: 'Custom: enter your custom git clone command', value: 'custom'}, + ], + })); template = repoPrompt.gitRepoUrl; } else if (template === 'Local template') { const dirPrompt = await prompts({ type: 'text', name: 'templateDir', - validate: (dir?: string) => { + validate: async (dir?: string) => { if (dir) { - const fullDir = path.resolve(process.cwd(), dir); - if (fs.existsSync(fullDir)) { + const fullDir = path.resolve(dir); + if (await fs.pathExists(fullDir)) { return true; } return logger.red( @@ -209,18 +318,25 @@ export default async function init( logger.info('Creating new Docusaurus project...'); if (isValidGitRepoUrl(template)) { - logger.info`Cloning Git template path=${template}...`; - if ( - shell.exec(`git clone --recursive ${template} ${dest}`, {silent: true}) - .code !== 0 - ) { + logger.info`Cloning Git template url=${template}...`; + if (!gitStrategies.includes(gitStrategy)) { + logger.error`Invalid git strategy: name=${gitStrategy}. Value must be one of ${gitStrategies.join( + ', ', + )}.`; + process.exit(1); + } + const command = await getGitCommand(gitStrategy); + if (shell.exec(`${command} ${template} ${dest}`).code !== 0) { logger.error`Cloning Git template name=${template} failed!`; process.exit(1); } + if (gitStrategy === 'copy') { + await fs.remove(path.join(dest, '.git')); + } } else if (templates.includes(template)) { // Docusaurus templates. if (useTS) { - if (!hasTS(template)) { + if (!(await hasTS(template))) { logger.error`Template name=${template} doesn't provide the Typescript variant.`; process.exit(1); } @@ -232,8 +348,8 @@ export default async function init( logger.error`Copying Docusaurus template name=${template} failed!`; throw err; } - } else if (fs.existsSync(path.resolve(process.cwd(), template))) { - const templateDir = path.resolve(process.cwd(), template); + } else if (await fs.pathExists(path.resolve(template))) { + const templateDir = path.resolve(template); try { await fs.copy(templateDir, dest); } catch (err) { @@ -248,7 +364,7 @@ export default async function init( // Update package.json info. try { await updatePkg(path.join(dest, 'package.json'), { - name: kebabCase(name), + name: _.kebabCase(name), version: '0.0.0', private: true, }); @@ -259,29 +375,36 @@ export default async function init( // We need to rename the gitignore file to .gitignore if ( - !fs.pathExistsSync(path.join(dest, '.gitignore')) && - fs.pathExistsSync(path.join(dest, 'gitignore')) + !(await fs.pathExists(path.join(dest, '.gitignore'))) && + (await fs.pathExists(path.join(dest, 'gitignore'))) ) { await fs.move(path.join(dest, 'gitignore'), path.join(dest, '.gitignore')); } - if (fs.pathExistsSync(path.join(dest, 'gitignore'))) { - fs.removeSync(path.join(dest, 'gitignore')); + if (await fs.pathExists(path.join(dest, 'gitignore'))) { + await fs.remove(path.join(dest, 'gitignore')); } - const pkgManager = useYarn ? 'yarn' : 'npm'; // Display the most elegant way to cd. const cdpath = path.relative('.', dest); + const pkgManager = await getPackageManager( + cliOptions.packageManager, + cliOptions.skipInstall, + ); if (!cliOptions.skipInstall) { shell.cd(dest); logger.info`Installing dependencies with name=${pkgManager}...`; if ( - shell.exec(useYarn ? 'yarn' : 'npm install --color always', { - env: { - ...process.env, - // Force coloring the output, since the command is invoked by shelljs, which is not the interactive shell - ...(supportsColor.stdout ? {FORCE_COLOR: '1'} : {}), + shell.exec( + pkgManager === 'yarn' ? 'yarn' : `${pkgManager} install --color always`, + { + env: { + ...process.env, + // Force coloring the output, since the command is invoked, + // by shelljs which is not the interactive shell + ...(supportsColor.stdout ? {FORCE_COLOR: '1'} : {}), + }, }, - }).code !== 0 + ).code !== 0 ) { logger.error('Dependency installation failed.'); logger.info`The site directory has already been created, and you can retry by typing: @@ -292,16 +415,17 @@ export default async function init( } } - logger.success`Created path=${cdpath}.`; + const useNpm = pkgManager === 'npm'; + logger.success`Created name=${cdpath}.`; logger.info`Inside that directory, you can run several commands: code=${`${pkgManager} start`} Starts the development server. - code=${`${pkgManager} ${useYarn ? '' : 'run '}build`} + code=${`${pkgManager} ${useNpm ? 'run ' : ''}build`} Bundles your website into static files for production. - code=${`${pkgManager} ${useYarn ? '' : 'run '}serve`} + code=${`${pkgManager} ${useNpm ? 'run ' : ''}serve`} Serves the built website locally. code=${`${pkgManager} deploy`} diff --git a/packages/create-docusaurus/templates/classic-typescript/package.json b/packages/create-docusaurus/templates/classic-typescript/package.json index 380464a6a458..519ecaed3b66 100644 --- a/packages/create-docusaurus/templates/classic-typescript/package.json +++ b/packages/create-docusaurus/templates/classic-typescript/package.json @@ -1,6 +1,6 @@ { "name": "docusaurus-2-classic-typescript-template", - "version": "2.0.0-beta.14", + "version": "2.0.0-beta.18", "private": true, "scripts": { "docusaurus": "docusaurus", @@ -15,18 +15,18 @@ "typecheck": "tsc" }, "dependencies": { - "@docusaurus/core": "2.0.0-beta.14", - "@docusaurus/preset-classic": "2.0.0-beta.14", - "@mdx-js/react": "^1.6.21", + "@docusaurus/core": "2.0.0-beta.18", + "@docusaurus/preset-classic": "2.0.0-beta.18", + "@mdx-js/react": "^1.6.22", "clsx": "^1.1.1", - "prism-react-renderer": "^1.2.1", - "react": "^17.0.1", - "react-dom": "^17.0.1" + "prism-react-renderer": "^1.3.1", + "react": "^17.0.2", + "react-dom": "^17.0.2" }, "devDependencies": { - "@docusaurus/module-type-aliases": "2.0.0-beta.14", - "@tsconfig/docusaurus": "^1.0.4", - "typescript": "^4.5.2" + "@docusaurus/module-type-aliases": "2.0.0-beta.18", + "@tsconfig/docusaurus": "^1.0.5", + "typescript": "^4.6.3" }, "browserslist": { "production": [ diff --git a/packages/create-docusaurus/templates/classic-typescript/src/components/HomepageFeatures.module.css b/packages/create-docusaurus/templates/classic-typescript/src/components/HomepageFeatures.module.css deleted file mode 120000 index c85ba4554818..000000000000 --- a/packages/create-docusaurus/templates/classic-typescript/src/components/HomepageFeatures.module.css +++ /dev/null @@ -1 +0,0 @@ -../../../classic/src/components/HomepageFeatures.module.css \ No newline at end of file diff --git a/examples/classic-typescript/src/components/HomepageFeatures.tsx b/packages/create-docusaurus/templates/classic-typescript/src/components/HomepageFeatures/index.tsx similarity index 76% rename from examples/classic-typescript/src/components/HomepageFeatures.tsx rename to packages/create-docusaurus/templates/classic-typescript/src/components/HomepageFeatures/index.tsx index e1d1c7908a84..91ef4601d2fc 100644 --- a/examples/classic-typescript/src/components/HomepageFeatures.tsx +++ b/packages/create-docusaurus/templates/classic-typescript/src/components/HomepageFeatures/index.tsx @@ -1,18 +1,17 @@ -import useBaseUrl from '@docusaurus/useBaseUrl'; import React from 'react'; import clsx from 'clsx'; -import styles from './HomepageFeatures.module.css'; +import styles from './styles.module.css'; type FeatureItem = { title: string; - image: string; + Svg: React.ComponentType>; description: JSX.Element; }; const FeatureList: FeatureItem[] = [ { title: 'Easy to Use', - image: '/img/undraw_docusaurus_mountain.svg', + Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default, description: ( <> Docusaurus was designed from the ground up to be easily installed and @@ -22,7 +21,7 @@ const FeatureList: FeatureItem[] = [ }, { title: 'Focus on What Matters', - image: '/img/undraw_docusaurus_tree.svg', + Svg: require('@site/static/img/undraw_docusaurus_tree.svg').default, description: ( <> Docusaurus lets you focus on your docs, and we'll do the chores. Go @@ -32,7 +31,7 @@ const FeatureList: FeatureItem[] = [ }, { title: 'Powered by React', - image: '/img/undraw_docusaurus_react.svg', + Svg: require('@site/static/img/undraw_docusaurus_react.svg').default, description: ( <> Extend or customize your website layout by reusing React. Docusaurus can @@ -42,15 +41,11 @@ const FeatureList: FeatureItem[] = [ }, ]; -function Feature({title, image, description}: FeatureItem) { +function Feature({title, Svg, description}: FeatureItem) { return (
- {title} +

{title}

diff --git a/packages/create-docusaurus/templates/classic-typescript/src/components/HomepageFeatures/styles.module.css b/packages/create-docusaurus/templates/classic-typescript/src/components/HomepageFeatures/styles.module.css new file mode 120000 index 000000000000..26d4838f9dc9 --- /dev/null +++ b/packages/create-docusaurus/templates/classic-typescript/src/components/HomepageFeatures/styles.module.css @@ -0,0 +1 @@ +../../../../classic/src/components/HomepageFeatures/styles.module.css \ No newline at end of file diff --git a/packages/create-docusaurus/templates/classic-typescript/src/pages/index.tsx b/packages/create-docusaurus/templates/classic-typescript/src/pages/index.tsx index cc4f72112724..3408a41e7f23 100644 --- a/packages/create-docusaurus/templates/classic-typescript/src/pages/index.tsx +++ b/packages/create-docusaurus/templates/classic-typescript/src/pages/index.tsx @@ -4,7 +4,7 @@ import Layout from '@theme/Layout'; import Link from '@docusaurus/Link'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import styles from './index.module.css'; -import HomepageFeatures from '../components/HomepageFeatures'; +import HomepageFeatures from '@site/src/components/HomepageFeatures'; function HomepageHeader() { const {siteConfig} = useDocusaurusContext(); diff --git a/packages/create-docusaurus/templates/classic/package.json b/packages/create-docusaurus/templates/classic/package.json index 0f73ec0c1ca0..56e16d39efaa 100644 --- a/packages/create-docusaurus/templates/classic/package.json +++ b/packages/create-docusaurus/templates/classic/package.json @@ -1,6 +1,6 @@ { "name": "docusaurus-2-classic-template", - "version": "2.0.0-beta.14", + "version": "2.0.0-beta.18", "private": true, "scripts": { "docusaurus": "docusaurus", @@ -14,13 +14,13 @@ "write-heading-ids": "docusaurus write-heading-ids" }, "dependencies": { - "@docusaurus/core": "2.0.0-beta.14", - "@docusaurus/preset-classic": "2.0.0-beta.14", - "@mdx-js/react": "^1.6.21", + "@docusaurus/core": "2.0.0-beta.18", + "@docusaurus/preset-classic": "2.0.0-beta.18", + "@mdx-js/react": "^1.6.22", "clsx": "^1.1.1", - "prism-react-renderer": "^1.2.1", - "react": "^17.0.1", - "react-dom": "^17.0.1" + "prism-react-renderer": "^1.3.1", + "react": "^17.0.2", + "react-dom": "^17.0.2" }, "browserslist": { "production": [ diff --git a/examples/classic/src/components/HomepageFeatures.js b/packages/create-docusaurus/templates/classic/src/components/HomepageFeatures/index.js similarity index 82% rename from examples/classic/src/components/HomepageFeatures.js rename to packages/create-docusaurus/templates/classic/src/components/HomepageFeatures/index.js index 16f820b10355..78f410ba6888 100644 --- a/examples/classic/src/components/HomepageFeatures.js +++ b/packages/create-docusaurus/templates/classic/src/components/HomepageFeatures/index.js @@ -1,11 +1,11 @@ import React from 'react'; import clsx from 'clsx'; -import styles from './HomepageFeatures.module.css'; +import styles from './styles.module.css'; const FeatureList = [ { title: 'Easy to Use', - Svg: require('../../static/img/undraw_docusaurus_mountain.svg').default, + Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default, description: ( <> Docusaurus was designed from the ground up to be easily installed and @@ -15,7 +15,7 @@ const FeatureList = [ }, { title: 'Focus on What Matters', - Svg: require('../../static/img/undraw_docusaurus_tree.svg').default, + Svg: require('@site/static/img/undraw_docusaurus_tree.svg').default, description: ( <> Docusaurus lets you focus on your docs, and we'll do the chores. Go @@ -25,7 +25,7 @@ const FeatureList = [ }, { title: 'Powered by React', - Svg: require('../../static/img/undraw_docusaurus_react.svg').default, + Svg: require('@site/static/img/undraw_docusaurus_react.svg').default, description: ( <> Extend or customize your website layout by reusing React. Docusaurus can @@ -39,7 +39,7 @@ function Feature({Svg, title, description}) { return (
- +

{title}

diff --git a/packages/create-docusaurus/templates/classic/src/components/HomepageFeatures.module.css b/packages/create-docusaurus/templates/classic/src/components/HomepageFeatures/styles.module.css similarity index 100% rename from packages/create-docusaurus/templates/classic/src/components/HomepageFeatures.module.css rename to packages/create-docusaurus/templates/classic/src/components/HomepageFeatures/styles.module.css diff --git a/packages/create-docusaurus/templates/classic/src/css/custom.css b/packages/create-docusaurus/templates/classic/src/css/custom.css index 3247c4327c7a..311dc090d973 100644 --- a/packages/create-docusaurus/templates/classic/src/css/custom.css +++ b/packages/create-docusaurus/templates/classic/src/css/custom.css @@ -17,7 +17,7 @@ } /* For readability concerns, you should choose a lighter palette in dark mode. */ -html[data-theme='dark'] { +[data-theme='dark'] { --ifm-color-primary: #25c2a0; --ifm-color-primary-dark: #21af90; --ifm-color-primary-darker: #1fa588; @@ -34,6 +34,6 @@ html[data-theme='dark'] { padding: 0 var(--ifm-pre-padding); } -html[data-theme='dark'] .docusaurus-highlight-code-line { +[data-theme='dark'] .docusaurus-highlight-code-line { background-color: rgba(0, 0, 0, 0.3); } diff --git a/packages/create-docusaurus/templates/classic/src/pages/index.js b/packages/create-docusaurus/templates/classic/src/pages/index.js index 27c21e8f99c4..a4fc2d3f03e6 100644 --- a/packages/create-docusaurus/templates/classic/src/pages/index.js +++ b/packages/create-docusaurus/templates/classic/src/pages/index.js @@ -4,7 +4,7 @@ import Layout from '@theme/Layout'; import Link from '@docusaurus/Link'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import styles from './index.module.css'; -import HomepageFeatures from '../components/HomepageFeatures'; +import HomepageFeatures from '@site/src/components/HomepageFeatures'; function HomepageHeader() { const {siteConfig} = useDocusaurusContext(); diff --git a/packages/create-docusaurus/templates/classic/src/pages/index.module.css b/packages/create-docusaurus/templates/classic/src/pages/index.module.css index 666feb6a172a..9f71a5da775b 100644 --- a/packages/create-docusaurus/templates/classic/src/pages/index.module.css +++ b/packages/create-docusaurus/templates/classic/src/pages/index.module.css @@ -10,7 +10,7 @@ overflow: hidden; } -@media screen and (max-width: 966px) { +@media screen and (max-width: 996px) { .heroBanner { padding: 2rem; } diff --git a/packages/create-docusaurus/templates/facebook/package.json b/packages/create-docusaurus/templates/facebook/package.json index e09e17bc0d6b..9abb5de9bc36 100644 --- a/packages/create-docusaurus/templates/facebook/package.json +++ b/packages/create-docusaurus/templates/facebook/package.json @@ -1,6 +1,6 @@ { "name": "docusaurus-2-facebook-template", - "version": "2.0.0-beta.14", + "version": "2.0.0-beta.18", "private": true, "scripts": { "docusaurus": "docusaurus", @@ -18,25 +18,25 @@ "format:diff": "prettier --config .prettierrc --list-different \"**/*.{js,jsx,ts,tsx,md,mdx}\"" }, "dependencies": { - "@docusaurus/core": "2.0.0-beta.14", - "@docusaurus/preset-classic": "2.0.0-beta.14", - "@mdx-js/react": "^1.6.21", + "@docusaurus/core": "2.0.0-beta.18", + "@docusaurus/preset-classic": "2.0.0-beta.18", + "@mdx-js/react": "^1.6.22", "clsx": "^1.1.1", - "react": "^17.0.1", - "react-dom": "^17.0.1" + "react": "^17.0.2", + "react-dom": "^17.0.2" }, "devDependencies": { - "@babel/eslint-parser": "^7.16.3", - "eslint": "^8.2.0", - "eslint-config-airbnb": "^19.0.0", - "eslint-config-prettier": "^8.3.0", + "@babel/eslint-parser": "^7.17.0", + "eslint": "^8.12.0", + "eslint-config-airbnb": "^19.0.4", + "eslint-config-prettier": "^8.5.0", "eslint-plugin-header": "^3.1.1", - "eslint-plugin-import": "^2.25.3", + "eslint-plugin-import": "^2.25.4", "eslint-plugin-jsx-a11y": "^6.5.1", - "eslint-plugin-react": "^7.27.0", - "eslint-plugin-react-hooks": "^4.3.0", - "prettier": "^2.5.1", - "stylelint": "^13.2.1" + "eslint-plugin-react": "^7.29.4", + "eslint-plugin-react-hooks": "^4.4.0", + "prettier": "^2.6.1", + "stylelint": "^14.6.1" }, "browserslist": { "production": [ diff --git a/packages/create-docusaurus/templates/facebook/src/css/custom.css b/packages/create-docusaurus/templates/facebook/src/css/custom.css index c204c4bbeaa4..29e525236964 100644 --- a/packages/create-docusaurus/templates/facebook/src/css/custom.css +++ b/packages/create-docusaurus/templates/facebook/src/css/custom.css @@ -26,7 +26,7 @@ } /* For readability concerns, you should choose a lighter palette in dark mode. */ -html[data-theme='dark'] { +[data-theme='dark'] { --ifm-color-primary: #25c2a0; --ifm-color-primary-dark: #21af90; --ifm-color-primary-darker: #1fa588; diff --git a/packages/create-docusaurus/templates/facebook/src/pages/styles.module.css b/packages/create-docusaurus/templates/facebook/src/pages/styles.module.css index 76db00c17c0a..da1454b3f3b5 100644 --- a/packages/create-docusaurus/templates/facebook/src/pages/styles.module.css +++ b/packages/create-docusaurus/templates/facebook/src/pages/styles.module.css @@ -19,7 +19,7 @@ overflow: hidden; } -@media screen and (max-width: 966px) { +@media screen and (max-width: 996px) { .heroBanner { padding: 2rem; } diff --git a/packages/create-docusaurus/templates/shared/docs/tutorial-basics/create-a-document.md b/packages/create-docusaurus/templates/shared/docs/tutorial-basics/create-a-document.md index feaced79d0a3..a9bb9a4140b1 100644 --- a/packages/create-docusaurus/templates/shared/docs/tutorial-basics/create-a-document.md +++ b/packages/create-docusaurus/templates/shared/docs/tutorial-basics/create-a-document.md @@ -41,14 +41,14 @@ This is my **first Docusaurus document**! It is also possible to create your sidebar explicitly in `sidebars.js`: -```diff title="sidebars.js" +```js title="sidebars.js" module.exports = { tutorialSidebar: [ { type: 'category', label: 'Tutorial', -- items: [...], -+ items: ['hello'], + // highlight-next-line + items: ['hello'], }, ], }; diff --git a/packages/create-docusaurus/templates/shared/static/img/undraw_docusaurus_mountain.svg b/packages/create-docusaurus/templates/shared/static/img/undraw_docusaurus_mountain.svg index 431cef2f7fec..af961c49a888 100644 --- a/packages/create-docusaurus/templates/shared/static/img/undraw_docusaurus_mountain.svg +++ b/packages/create-docusaurus/templates/shared/static/img/undraw_docusaurus_mountain.svg @@ -1,4 +1,5 @@ + Easy to Use diff --git a/packages/create-docusaurus/templates/shared/static/img/undraw_docusaurus_react.svg b/packages/create-docusaurus/templates/shared/static/img/undraw_docusaurus_react.svg index e41705043338..94b5cf08f88f 100644 --- a/packages/create-docusaurus/templates/shared/static/img/undraw_docusaurus_react.svg +++ b/packages/create-docusaurus/templates/shared/static/img/undraw_docusaurus_react.svg @@ -1,4 +1,5 @@ + Powered by React diff --git a/packages/create-docusaurus/templates/shared/static/img/undraw_docusaurus_tree.svg b/packages/create-docusaurus/templates/shared/static/img/undraw_docusaurus_tree.svg index a05cc03dda90..d9161d33920c 100644 --- a/packages/create-docusaurus/templates/shared/static/img/undraw_docusaurus_tree.svg +++ b/packages/create-docusaurus/templates/shared/static/img/undraw_docusaurus_tree.svg @@ -1 +1,40 @@ -docu_tree \ No newline at end of file + + Focus on What Matters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/create-docusaurus/tsconfig.build.json b/packages/create-docusaurus/tsconfig.build.json new file mode 100644 index 000000000000..41b40e7cf697 --- /dev/null +++ b/packages/create-docusaurus/tsconfig.build.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "module": "es2020", + "incremental": true, + "tsBuildInfoFile": "./lib/.tsbuildinfo", + "rootDir": "src", + "outDir": "lib" + }, + "include": ["src/"], + "exclude": ["templates/"] +} diff --git a/packages/create-docusaurus/tsconfig.json b/packages/create-docusaurus/tsconfig.json index a08f02533c33..edb2c88e1daf 100644 --- a/packages/create-docusaurus/tsconfig.json +++ b/packages/create-docusaurus/tsconfig.json @@ -1,11 +1,10 @@ +// For editor typechecking; includes bin { - "extends": "../../tsconfig.json", + "extends": "./tsconfig.build.json", "compilerOptions": { - "incremental": true, - "tsBuildInfoFile": "./lib/.tsbuildinfo", - "rootDir": "src", - "outDir": "lib" + "noEmit": true, + "allowJs": true, + "rootDir": "." }, - "include": ["src/"], - "exclude": ["templates/"] + "include": ["src", "bin"] } diff --git a/packages/docusaurus-cssnano-preset/package.json b/packages/docusaurus-cssnano-preset/package.json index 2f5e5ffb115d..4b9948e77d90 100644 --- a/packages/docusaurus-cssnano-preset/package.json +++ b/packages/docusaurus-cssnano-preset/package.json @@ -1,6 +1,6 @@ { "name": "@docusaurus/cssnano-preset", - "version": "2.0.0-beta.14", + "version": "2.0.0-beta.18", "description": "Advanced cssnano preset for maximum optimization.", "main": "index.js", "license": "MIT", @@ -13,11 +13,11 @@ "directory": "packages/docusaurus-cssnano-preset" }, "dependencies": { - "cssnano-preset-advanced": "^5.1.4", - "postcss": "^8.3.7", - "postcss-sort-media-queries": "^4.1.0" + "cssnano-preset-advanced": "^5.3.1", + "postcss": "^8.4.12", + "postcss-sort-media-queries": "^4.2.1" }, "devDependencies": { - "to-vfile": "^6.0.0" + "to-vfile": "^6.1.0" } } diff --git a/packages/docusaurus-cssnano-preset/src/remove-overridden-custom-properties/__tests__/index.test.js b/packages/docusaurus-cssnano-preset/src/remove-overridden-custom-properties/__tests__/index.test.js index e847faca8a3a..b54446179796 100644 --- a/packages/docusaurus-cssnano-preset/src/remove-overridden-custom-properties/__tests__/index.test.js +++ b/packages/docusaurus-cssnano-preset/src/remove-overridden-custom-properties/__tests__/index.test.js @@ -23,11 +23,11 @@ const processFixture = (name) => { }; describe('remove-overridden-custom-properties', () => { - test('overridden custom properties should be removed', () => { + it('overridden custom properties should be removed', () => { expect(processFixture('normal')).toMatchSnapshot(); }); - test('overridden custom properties with `!important` rule should not be removed', () => { + it('overridden custom properties with `!important` rule should not be removed', () => { expect(processFixture('important_rule')).toMatchSnapshot(); }); }); diff --git a/packages/docusaurus-cssnano-preset/src/remove-overridden-custom-properties/index.js b/packages/docusaurus-cssnano-preset/src/remove-overridden-custom-properties/index.js index fe19a8fe3c30..51eb53b35327 100644 --- a/packages/docusaurus-cssnano-preset/src/remove-overridden-custom-properties/index.js +++ b/packages/docusaurus-cssnano-preset/src/remove-overridden-custom-properties/index.js @@ -6,12 +6,17 @@ */ /** - * This PostCSS plugin will remove duplicate/same custom properties (which are actually overridden ones) **only** from `:root` selector. + * This PostCSS plugin will remove duplicate/same custom properties (which are + * actually overridden ones) **only** from `:root` selector. * - * Depending on the presence of an `!important` rule in value of custom property, the following actions will happens: + * Depending on the presence of an `!important` rule in value of custom + * property, the following actions will happen: * - * - If the same custom properties do **not** have an `!important` rule, then all of them will be removed except for the last one (which will actually be applied). - * - If the same custom properties have at least one `!important` rule, then only those properties that do not have this rule will be removed. + * - If the same custom properties do **not** have an `!important` rule, then + * all of them will be removed except for the last one (which will actually be + * applied). + * - If the same custom properties have at least one `!important` rule, then + * only those properties that do not have this rule will be removed. * @returns {import('postcss').Plugin} */ module.exports = function creator() { diff --git a/packages/docusaurus-logger/README.md b/packages/docusaurus-logger/README.md index 2c4cea3bf5fd..8b173b8bab9a 100644 --- a/packages/docusaurus-logger/README.md +++ b/packages/docusaurus-logger/README.md @@ -8,7 +8,8 @@ It exports a single object as default export: `logger`. `logger` has the followi - Some useful colors. - Formatters. These functions have the same signature as the formatters of `picocolors`. Note that their implementations are not guaranteed. You should only care about their semantics. - - `path`: formats a file path or URL. + - `path`: formats a file path. + - `url`: formats a URL. - `id`: formats an identifier. - `code`: formats a code snippet. - `subdue`: subdues the text. @@ -34,6 +35,7 @@ To buy anything, enter code=${'buy x'} where code=${'x'} is the item's name; to An embedded expression is optionally preceded by a flag in the form `%[a-z]+` (a percentage sign followed by a few lowercase letters). If it's not preceded by any flag, it's printed out as-is. Otherwise, it's formatted with one of the formatters: - `path=`: `path` +- `url=`: `url` - `name=`: `id` - `code=`: `code` - `subdue=`: `subdue` diff --git a/packages/docusaurus-logger/package.json b/packages/docusaurus-logger/package.json index 226de81f6f1e..238e44277965 100644 --- a/packages/docusaurus-logger/package.json +++ b/packages/docusaurus-logger/package.json @@ -1,6 +1,6 @@ { "name": "@docusaurus/logger", - "version": "2.0.0-beta.14", + "version": "2.0.0-beta.18", "description": "An encapsulated logger for semantically formatting console messages.", "main": "./lib/index.js", "repository": { diff --git a/packages/docusaurus-logger/src/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus-logger/src/__tests__/__snapshots__/index.test.ts.snap new file mode 100644 index 000000000000..cf55c3083aea --- /dev/null +++ b/packages/docusaurus-logger/src/__tests__/__snapshots__/index.test.ts.snap @@ -0,0 +1,69 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`error prints objects 1`] = ` +[ + [ + "[ERROR] {\\"a\\":1}", + ], + [ + "[ERROR] undefined", + ], + [ + "[ERROR] 1,2,3", + ], + [ + "[ERROR] Sat Nov 13 2021 00:00:00 GMT+0000 (Coordinated Universal Time)", + ], +] +`; + +exports[`info prints objects 1`] = ` +[ + [ + "[INFO] {\\"a\\":1}", + ], + [ + "[INFO] undefined", + ], + [ + "[INFO] 1,2,3", + ], + [ + "[INFO] Sat Nov 13 2021 00:00:00 GMT+0000 (Coordinated Universal Time)", + ], +] +`; + +exports[`success prints objects 1`] = ` +[ + [ + "[SUCCESS] {\\"a\\":1}", + ], + [ + "[SUCCESS] undefined", + ], + [ + "[SUCCESS] 1,2,3", + ], + [ + "[SUCCESS] Sat Nov 13 2021 00:00:00 GMT+0000 (Coordinated Universal Time)", + ], +] +`; + +exports[`warn prints objects 1`] = ` +[ + [ + "[WARNING] {\\"a\\":1}", + ], + [ + "[WARNING] undefined", + ], + [ + "[WARNING] 1,2,3", + ], + [ + "[WARNING] Sat Nov 13 2021 00:00:00 GMT+0000 (Coordinated Universal Time)", + ], +] +`; diff --git a/packages/docusaurus-logger/src/__tests__/index.test.ts b/packages/docusaurus-logger/src/__tests__/index.test.ts index 5ae55907b774..0b2b710769fd 100644 --- a/packages/docusaurus-logger/src/__tests__/index.test.ts +++ b/packages/docusaurus-logger/src/__tests__/index.test.ts @@ -5,25 +5,32 @@ * LICENSE file in the root directory of this source tree. */ +import {jest} from '@jest/globals'; import logger from '../index'; describe('formatters', () => { - test('path', () => { - expect(logger.path('hey')).toMatchInlineSnapshot(`"hey"`); + it('path', () => { + // cSpell:ignore mhey + expect(logger.path('hey')).toMatchInlineSnapshot(`"\\"hey\\""`); }); - test('id', () => { + it('url', () => { + expect(logger.url('https://docusaurus.io/')).toMatchInlineSnapshot( + `"https://docusaurus.io/"`, + ); + }); + it('id', () => { expect(logger.name('hey')).toMatchInlineSnapshot(`"hey"`); }); - test('code', () => { + it('code', () => { expect(logger.code('hey')).toMatchInlineSnapshot(`"\`hey\`"`); }); - test('subdue', () => { + it('subdue', () => { expect(logger.subdue('hey')).toMatchInlineSnapshot(`"hey"`); }); }); describe('interpolate', () => { - test('should format text with variables & arrays', () => { + it('formats text with variables & arrays', () => { const name = 'Josh'; const items = [1, 'hi', 'Hmmm']; expect(logger.interpolate`Hello ${name}! Here are your goodies:${items}`) @@ -34,14 +41,14 @@ describe('interpolate', () => { - Hmmm" `); }); - test('should recognize valid flags', () => { + it('recognizes valid flags', () => { expect( logger.interpolate`The package at path=${'packages/docusaurus'} has number=${10} files. name=${'Babel'} is exported here subdue=${'(as a preset)'} that you can with code=${"require.resolve('@docusaurus/core/lib/babel/preset')"}`, ).toMatchInlineSnapshot( - `"The package at packages/docusaurus has 10 files. Babel is exported here (as a preset) that you can with \`require.resolve('@docusaurus/core/lib/babel/preset')\`"`, + `"The package at \\"packages/docusaurus\\" has 10 files. Babel is exported here (as a preset) that you can with \`require.resolve('@docusaurus/core/lib/babel/preset')\`"`, ); }); - test('should interpolate arrays with flags', () => { + it('interpolates arrays with flags', () => { expect( logger.interpolate`The following commands are available:code=${[ 'docusaurus start', @@ -55,14 +62,14 @@ describe('interpolate', () => { - \`docusaurus deploy\`" `); }); - test('should print detached flags as-is', () => { + it('prints detached flags as-is', () => { expect( logger.interpolate`You can use placeholders like code= ${'and it will'} be replaced with the succeeding arguments`, ).toMatchInlineSnapshot( `"You can use placeholders like code= and it will be replaced with the succeeding arguments"`, ); }); - test('should throw with bad flags', () => { + it('throws with bad flags', () => { expect( () => logger.interpolate`I mistyped this: cde=${'this code'} and I will be damned`, @@ -74,104 +81,44 @@ describe('interpolate', () => { describe('info', () => { const consoleMock = jest.spyOn(console, 'info').mockImplementation(() => {}); - test('should print objects', () => { + it('prints objects', () => { logger.info({a: 1}); logger.info(undefined); logger.info([1, 2, 3]); logger.info(new Date(2021, 10, 13)); - expect(consoleMock.mock.calls).toMatchInlineSnapshot(` - Array [ - Array [ - "[INFO] {\\"a\\":1}", - ], - Array [ - "[INFO] undefined", - ], - Array [ - "[INFO] 1,2,3", - ], - Array [ - "[INFO] Sat Nov 13 2021 00:00:00 GMT+0000 (Coordinated Universal Time)", - ], - ] - `); + expect(consoleMock.mock.calls).toMatchSnapshot(); }); }); describe('warn', () => { const consoleMock = jest.spyOn(console, 'warn').mockImplementation(() => {}); - test('should print objects', () => { + it('prints objects', () => { logger.warn({a: 1}); logger.warn(undefined); logger.warn([1, 2, 3]); logger.warn(new Date(2021, 10, 13)); - expect(consoleMock.mock.calls).toMatchInlineSnapshot(` - Array [ - Array [ - "[WARNING] {\\"a\\":1}", - ], - Array [ - "[WARNING] undefined", - ], - Array [ - "[WARNING] 1,2,3", - ], - Array [ - "[WARNING] Sat Nov 13 2021 00:00:00 GMT+0000 (Coordinated Universal Time)", - ], - ] - `); + expect(consoleMock.mock.calls).toMatchSnapshot(); }); }); describe('error', () => { const consoleMock = jest.spyOn(console, 'error').mockImplementation(() => {}); - test('should print objects', () => { + it('prints objects', () => { logger.error({a: 1}); logger.error(undefined); logger.error([1, 2, 3]); logger.error(new Date(2021, 10, 13)); - expect(consoleMock.mock.calls).toMatchInlineSnapshot(` - Array [ - Array [ - "[ERROR] {\\"a\\":1}", - ], - Array [ - "[ERROR] undefined", - ], - Array [ - "[ERROR] 1,2,3", - ], - Array [ - "[ERROR] Sat Nov 13 2021 00:00:00 GMT+0000 (Coordinated Universal Time)", - ], - ] - `); + expect(consoleMock.mock.calls).toMatchSnapshot(); }); }); describe('success', () => { const consoleMock = jest.spyOn(console, 'log').mockImplementation(() => {}); - test('should print objects', () => { + it('prints objects', () => { logger.success({a: 1}); logger.success(undefined); logger.success([1, 2, 3]); logger.success(new Date(2021, 10, 13)); - expect(consoleMock.mock.calls).toMatchInlineSnapshot(` - Array [ - Array [ - "[SUCCESS] {\\"a\\":1}", - ], - Array [ - "[SUCCESS] undefined", - ], - Array [ - "[SUCCESS] 1,2,3", - ], - Array [ - "[SUCCESS] Sat Nov 13 2021 00:00:00 GMT+0000 (Coordinated Universal Time)", - ], - ] - `); + expect(consoleMock.mock.calls).toMatchSnapshot(); }); }); diff --git a/packages/docusaurus-logger/src/index.ts b/packages/docusaurus-logger/src/index.ts index 5684cec8fe7d..08e985e77ed4 100644 --- a/packages/docusaurus-logger/src/index.ts +++ b/packages/docusaurus-logger/src/index.ts @@ -9,7 +9,8 @@ import chalk, {type Chalk} from 'chalk'; type InterpolatableValue = string | number | (string | number)[]; -const path = (msg: unknown): string => chalk.cyan(chalk.underline(msg)); +const path = (msg: unknown): string => chalk.cyan(chalk.underline(`"${msg}"`)); +const url = (msg: unknown): string => chalk.cyan(chalk.underline(msg)); const name = (msg: unknown): string => chalk.blue(chalk.bold(msg)); const code = (msg: unknown): string => chalk.cyan(`\`${msg}\``); const subdue: Chalk = chalk.gray; @@ -21,15 +22,17 @@ function interpolate( ): string { let res = ''; values.forEach((value, idx) => { - const flag = msgs[idx].match(/[a-z]+=$/); - res += msgs[idx].replace(/[a-z]+=$/, ''); - const format = (function () { + const flag = msgs[idx]!.match(/[a-z]+=$/); + res += msgs[idx]!.replace(/[a-z]+=$/, ''); + const format = (() => { if (!flag) { return (a: string | number) => a; } switch (flag[0]) { case 'path=': return path; + case 'url=': + return url; case 'number=': return num; case 'name=': @@ -120,6 +123,10 @@ function success(msg: unknown, ...values: InterpolatableValue[]): void { ); } +function newLine(): void { + console.log(); +} + const logger = { red: chalk.red, yellow: chalk.yellow, @@ -127,6 +134,7 @@ const logger = { bold: chalk.bold, dim: chalk.dim, path, + url, name, code, subdue, @@ -136,6 +144,12 @@ const logger = { warn, error, success, + newLine, }; +// TODO remove when migrating to ESM +// logger can only be default-imported in ESM with this +module.exports = logger; +module.exports.default = logger; + export default logger; diff --git a/packages/docusaurus-mdx-loader/package.json b/packages/docusaurus-mdx-loader/package.json index 2dce287c5eae..366bdf8cf5c7 100644 --- a/packages/docusaurus-mdx-loader/package.json +++ b/packages/docusaurus-mdx-loader/package.json @@ -1,6 +1,6 @@ { "name": "@docusaurus/mdx-loader", - "version": "2.0.0-beta.14", + "version": "2.0.0-beta.18", "description": "Docusaurus Loader for MDX", "main": "lib/index.js", "types": "src/mdx-loader.d.ts", @@ -18,32 +18,32 @@ }, "license": "MIT", "dependencies": { - "@babel/parser": "^7.16.4", - "@babel/traverse": "^7.16.3", - "@docusaurus/logger": "2.0.0-beta.14", - "@docusaurus/utils": "2.0.0-beta.14", - "@mdx-js/mdx": "^1.6.21", + "@babel/parser": "^7.17.8", + "@babel/traverse": "^7.17.3", + "@docusaurus/logger": "2.0.0-beta.18", + "@docusaurus/utils": "2.0.0-beta.18", + "@mdx-js/mdx": "^1.6.22", "escape-html": "^1.0.3", "file-loader": "^6.2.0", - "fs-extra": "^10.0.0", + "fs-extra": "^10.0.1", "image-size": "^1.0.1", "mdast-util-to-string": "^2.0.0", - "remark-emoji": "^2.1.0", + "remark-emoji": "^2.2.0", "stringify-object": "^3.3.0", "tslib": "^2.3.1", - "unist-util-visit": "^2.0.2", + "unist-util-visit": "^2.0.3", "url-loader": "^4.1.1", - "webpack": "^5.61.0" + "webpack": "^5.70.0" }, "devDependencies": { - "@docusaurus/types": "2.0.0-beta.14", + "@docusaurus/types": "2.0.0-beta.18", "@types/escape-html": "^1.0.1", - "@types/mdast": "^3.0.7", + "@types/mdast": "^3.0.10", "@types/stringify-object": "^3.3.1", "@types/unist": "^2.0.6", - "remark": "^12.0.0", + "remark": "^12.0.1", "remark-mdx": "^1.6.21", - "to-vfile": "^6.0.0", + "to-vfile": "^6.1.0", "unist-builder": "^2.0.3", "unist-util-remove-position": "^3.0.0" }, diff --git a/packages/docusaurus-mdx-loader/src/index.ts b/packages/docusaurus-mdx-loader/src/index.ts index b3c564222c9a..43dd26d600f3 100644 --- a/packages/docusaurus-mdx-loader/src/index.ts +++ b/packages/docusaurus-mdx-loader/src/index.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import {readFile} from 'fs-extra'; +import fs from 'fs-extra'; import {createCompiler} from '@mdx-js/mdx'; import logger from '@docusaurus/logger'; import emoji from 'remark-emoji'; @@ -21,7 +21,7 @@ import toc from './remark/toc'; import unwrapMdxCodeBlocks from './remark/unwrapMdxCodeBlocks'; import transformImage from './remark/transformImage'; import transformLinks from './remark/transformLinks'; -import type {RemarkAndRehypePluginOptions} from '@docusaurus/mdx-loader'; +import type {MDXOptions} from '@docusaurus/mdx-loader'; import type {LoaderContext} from 'webpack'; import type {Processor} from 'unified'; @@ -35,7 +35,7 @@ const pragma = ` /* @jsxFrag mdx.Fragment */ `; -const DEFAULT_OPTIONS: RemarkAndRehypePluginOptions = { +const DEFAULT_OPTIONS: MDXOptions = { rehypePlugins: [], remarkPlugins: [unwrapMdxCodeBlocks, emoji, headings, toc], beforeDefaultRemarkPlugins: [], @@ -44,7 +44,7 @@ const DEFAULT_OPTIONS: RemarkAndRehypePluginOptions = { const compilerCache = new Map(); -type Options = RemarkAndRehypePluginOptions & { +type Options = MDXOptions & { staticDirs: string[]; siteDir: string; isMDXPartial?: (filePath: string) => boolean; @@ -52,31 +52,36 @@ type Options = RemarkAndRehypePluginOptions & { removeContentTitle?: boolean; metadataPath?: string | ((filePath: string) => string); createAssets?: (metadata: { - frontMatter: Record; - metadata: Record; - }) => Record; + frontMatter: {[key: string]: unknown}; + metadata: {[key: string]: unknown}; + }) => {[key: string]: unknown}; filepath: string; }; -// When this throws, it generally means that there's no metadata file associated with this MDX document -// It can happen when using MDX partials (usually starting with _) -// That's why it's important to provide the "isMDXPartial" function in config +/** + * When this throws, it generally means that there's no metadata file associated + * with this MDX document. It can happen when using MDX partials (usually + * starting with _). That's why it's important to provide the `isMDXPartial` + * function in config + */ async function readMetadataPath(metadataPath: string) { try { - return await readFile(metadataPath, 'utf8'); - } catch (e) { - throw new Error( - `MDX loader can't read MDX metadata file for path ${metadataPath}. Maybe the isMDXPartial option function was not provided?`, - ); + return await fs.readFile(metadataPath, 'utf8'); + } catch (err) { + logger.error`MDX loader can't read MDX metadata file path=${metadataPath}. Maybe the isMDXPartial option function was not provided?`; + throw err; } } -// Converts assets an object with Webpack require calls code -// This is useful for mdx files to reference co-located assets using relative paths -// Those assets should enter the Webpack assets pipeline and be hashed -// For now, we only handle that for images and paths starting with ./ -// {image: "./myImage.png"} => {image: require("./myImage.png")} -function createAssetsExportCode(assets: Record) { +/** + * Converts assets an object with Webpack require calls code. + * This is useful for mdx files to reference co-located assets using relative + * paths. Those assets should enter the Webpack assets pipeline and be hashed. + * For now, we only handle that for images and paths starting with `./`: + * + * `{image: "./myImage.png"}` => `{image: require("./myImage.png")}` + */ +function createAssetsExportCode(assets: {[key: string]: unknown}) { if (Object.keys(assets).length === 0) { return 'undefined'; } @@ -118,7 +123,7 @@ export default async function mdxLoader( ): Promise { const callback = this.async(); const filePath = this.resourcePath; - const reqOptions = this.getOptions() || {}; + const reqOptions = this.getOptions() ?? {}; const {frontMatter, content: contentWithTitle} = parseFrontMatter(fileString); @@ -132,7 +137,7 @@ export default async function mdxLoader( const options: Options = { ...reqOptions, remarkPlugins: [ - ...(reqOptions.beforeDefaultRemarkPlugins || []), + ...(reqOptions.beforeDefaultRemarkPlugins ?? []), ...DEFAULT_OPTIONS.remarkPlugins, [ transformImage, @@ -148,12 +153,12 @@ export default async function mdxLoader( siteDir: reqOptions.siteDir, }, ], - ...(reqOptions.remarkPlugins || []), + ...(reqOptions.remarkPlugins ?? []), ], rehypePlugins: [ - ...(reqOptions.beforeDefaultRehypePlugins || []), + ...(reqOptions.beforeDefaultRehypePlugins ?? []), ...DEFAULT_OPTIONS.rehypePlugins, - ...(reqOptions.rehypePlugins || []), + ...(reqOptions.rehypePlugins ?? []), ], }; compilerCache.set(this.query, [createCompiler(options), options]); @@ -161,19 +166,21 @@ export default async function mdxLoader( const [compiler, options] = compilerCache.get(this.query)!; - let result; + let result: string; try { - result = await compiler.process({ - contents: content, - path: this.resourcePath, - }); + result = ( + await compiler.process({ + contents: content, + path: this.resourcePath, + }) + ).toString(); } catch (err) { return callback(err as Error); } // MDX partials are MDX files starting with _ or in a folder starting with _ - // Partial are not expected to have an associated metadata file or front matter - const isMDXPartial = options.isMDXPartial && options.isMDXPartial(filePath); + // Partial are not expected to have associated metadata files or front matter + const isMDXPartial = options.isMDXPartial?.(filePath); if (isMDXPartial && hasFrontMatter) { const errorMessage = `Docusaurus MDX partial files should not contain FrontMatter. Those partial files use the _ prefix as a convention by default, but this is configurable. @@ -184,9 +191,8 @@ ${JSON.stringify(frontMatter, null, 2)}`; const shouldError = process.env.NODE_ENV === 'test' || process.env.CI; if (shouldError) { return callback(new Error(errorMessage)); - } else { - logger.warn(errorMessage); } + logger.warn(errorMessage); } } diff --git a/packages/docusaurus-mdx-loader/src/mdx-loader.d.ts b/packages/docusaurus-mdx-loader/src/mdx-loader.d.ts index 99d781b4363f..c84168aefdbd 100644 --- a/packages/docusaurus-mdx-loader/src/mdx-loader.d.ts +++ b/packages/docusaurus-mdx-loader/src/mdx-loader.d.ts @@ -7,12 +7,12 @@ import type {Plugin} from 'unified'; -export type RemarkOrRehypePlugin = +export type MDXPlugin = // eslint-disable-next-line @typescript-eslint/no-explicit-any - [Plugin, Record] | Plugin; -export type RemarkAndRehypePluginOptions = { - remarkPlugins: RemarkOrRehypePlugin[]; - rehypePlugins: RemarkOrRehypePlugin[]; - beforeDefaultRemarkPlugins: RemarkOrRehypePlugin[]; - beforeDefaultRehypePlugins: RemarkOrRehypePlugin[]; + [Plugin, any] | Plugin; +export type MDXOptions = { + remarkPlugins: MDXPlugin[]; + rehypePlugins: MDXPlugin[]; + beforeDefaultRemarkPlugins: MDXPlugin[]; + beforeDefaultRehypePlugins: MDXPlugin[]; }; diff --git a/packages/docusaurus-mdx-loader/src/remark/headings/__tests__/index.test.ts b/packages/docusaurus-mdx-loader/src/remark/headings/__tests__/index.test.ts index a317cf4001ed..2f9683e6e8de 100644 --- a/packages/docusaurus-mdx-loader/src/remark/headings/__tests__/index.test.ts +++ b/packages/docusaurus-mdx-loader/src/remark/headings/__tests__/index.test.ts @@ -27,8 +27,8 @@ function heading(label, id) { ); } -describe('headings plugin', () => { - test('should patch `id`s and `data.hProperties.id', () => { +describe('headings remark plugin', () => { + it('patches `id`s and `data.hProperties.id', () => { const result = process('# Normal\n\n## Table of Contents\n\n# Baz\n'); const expected = u('root', [ u( @@ -55,9 +55,9 @@ describe('headings plugin', () => { expect(result).toEqual(expected); }); - test('should not overwrite `data` on headings', () => { + it('does not overwrite `data` on headings', () => { const result = process('# Normal\n', [ - function () { + () => { function transform(tree) { tree.children[0].data = {foo: 'bar'}; } @@ -78,9 +78,9 @@ describe('headings plugin', () => { expect(result).toEqual(expected); }); - test('should not overwrite `data.hProperties` on headings', () => { + it('does not overwrite `data.hProperties` on headings', () => { const result = process('# Normal\n', [ - function () { + () => { function transform(tree) { tree.children[0].data = {hProperties: {className: ['foo']}}; } @@ -101,7 +101,7 @@ describe('headings plugin', () => { expect(result).toEqual(expected); }); - test('should generate `id`s and `hProperties.id`s, based on `hProperties.id` if they exist', () => { + it('generates `id`s and `hProperties.id`s, based on `hProperties.id` if they exist', () => { const result = process( [ '## Something', @@ -110,7 +110,7 @@ describe('headings plugin', () => { '## Something also', ].join('\n\n'), [ - function () { + () => { function transform(tree) { tree.children[1].data = {hProperties: {id: 'here'}}; tree.children[3].data = {hProperties: {id: 'something'}}; @@ -157,7 +157,7 @@ describe('headings plugin', () => { expect(result).toEqual(expected); }); - test('should create GitHub-style headings ids', () => { + it('creates GitHub-style headings ids', () => { const result = process( [ '## I ♥ unicode', @@ -199,7 +199,9 @@ describe('headings plugin', () => { const expected = u('root', [ heading('I ♥ unicode', 'i--unicode'), heading('Dash-dash', 'dash-dash'), + // cSpell:ignore endash heading('en–dash', 'endash'), + // cSpell:ignore emdash heading('em–dash', 'emdash'), heading('😄 unicode emoji', '-unicode-emoji'), heading('😄-😄 unicode emoji', '--unicode-emoji'), @@ -214,6 +216,7 @@ describe('headings plugin', () => { heading(':ok_hand: Single', 'ok_hand-single'), heading( ':ok_hand::hatched_chick: Two in a row with no spaces', + // cSpell:ignore handhatched 'ok_handhatched_chick-two-in-a-row-with-no-spaces', ), heading( @@ -225,7 +228,7 @@ describe('headings plugin', () => { expect(result).toEqual(expected); }); - test('should generate id from only text contents of headings if they contains HTML tags', () => { + it('generates id from only text contents of headings if they contains HTML tags', () => { const result = process('# Normal\n'); const expected = u('root', [ u( @@ -245,17 +248,17 @@ describe('headings plugin', () => { expect(result).toEqual(expected); }); - test('should create custom headings ids', () => { + it('creates custom headings ids', () => { const result = process(` # Heading One {#custom_h1} ## Heading Two {#custom-heading-two} -# With *Bold* {#custom-withbold} +# With *Bold* {#custom-with-bold} -# With *Bold* hello{#custom-withbold-hello} +# With *Bold* hello{#custom-with-bold-hello} -# With *Bold* hello2 {#custom-withbold-hello2} +# With *Bold* hello2 {#custom-with-bold-hello2} # Snake-cased ID {#this_is_custom_id} @@ -281,15 +284,15 @@ describe('headings plugin', () => { text: 'Heading Two', }, { - id: 'custom-withbold', + id: 'custom-with-bold', text: 'With Bold', }, { - id: 'custom-withbold-hello', + id: 'custom-with-bold-hello', text: 'With Bold hello', }, { - id: 'custom-withbold-hello2', + id: 'custom-with-bold-hello2', text: 'With Bold hello2', }, { diff --git a/packages/docusaurus-mdx-loader/src/remark/headings/index.ts b/packages/docusaurus-mdx-loader/src/remark/headings/index.ts index 62b87054a76e..c964b8d266c1 100644 --- a/packages/docusaurus-mdx-loader/src/remark/headings/index.ts +++ b/packages/docusaurus-mdx-loader/src/remark/headings/index.ts @@ -8,17 +8,16 @@ /* Based on remark-slug (https://github.com/remarkjs/remark-slug) and gatsby-remark-autolink-headers (https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-remark-autolink-headers) */ import {parseMarkdownHeadingId, createSlugger} from '@docusaurus/utils'; -import visit, {type Visitor} from 'unist-util-visit'; +import visit from 'unist-util-visit'; import toString from 'mdast-util-to-string'; import type {Transformer} from 'unified'; import type {Parent} from 'unist'; import type {Heading, Text} from 'mdast'; -function headings(): Transformer { - const transformer: Transformer = (ast) => { +export default function plugin(): Transformer { + return (root) => { const slugs = createSlugger(); - - const visitor: Visitor = (headingNode) => { + visit(root, 'heading', (headingNode: Heading) => { const data = headingNode.data || (headingNode.data = {}); const properties = (data.hProperties || (data.hProperties = {})) as { id: string; @@ -44,7 +43,8 @@ function headings(): Transformer { if (parsedHeading.id) { // When there's an id, it is always in the last child node - // Sometimes heading is in multiple "parts" (** syntax creates a child node): + // Sometimes heading is in multiple "parts" (** syntax creates a child + // node): // ## part1 *part2* part3 {#id} const lastNode = headingNode.children[ headingNode.children.length - 1 @@ -68,12 +68,6 @@ function headings(): Transformer { data.id = id; properties.id = id; - }; - - visit(ast, 'heading', visitor); + }); }; - - return transformer; } - -export default headings; diff --git a/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/fixtures/empty-headings.md b/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/__fixtures__/empty-headings.md similarity index 100% rename from packages/docusaurus-mdx-loader/src/remark/toc/__tests__/fixtures/empty-headings.md rename to packages/docusaurus-mdx-loader/src/remark/toc/__tests__/__fixtures__/empty-headings.md diff --git a/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/fixtures/inline-code.md b/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/__fixtures__/inline-code.md similarity index 71% rename from packages/docusaurus-mdx-loader/src/remark/toc/__tests__/fixtures/inline-code.md rename to packages/docusaurus-mdx-loader/src/remark/toc/__tests__/__fixtures__/inline-code.md index 6198b3a2eb08..0136c7997747 100644 --- a/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/fixtures/inline-code.md +++ b/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/__fixtures__/inline-code.md @@ -7,3 +7,5 @@ ## `
Test
` ## `
Test
` + +## [`
Test
`](/some/link) diff --git a/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/fixtures/insert-below-imports.md b/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/__fixtures__/insert-below-imports.md similarity index 100% rename from packages/docusaurus-mdx-loader/src/remark/toc/__tests__/fixtures/insert-below-imports.md rename to packages/docusaurus-mdx-loader/src/remark/toc/__tests__/__fixtures__/insert-below-imports.md diff --git a/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/fixtures/just-content.md b/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/__fixtures__/just-content.md similarity index 84% rename from packages/docusaurus-mdx-loader/src/remark/toc/__tests__/fixtures/just-content.md rename to packages/docusaurus-mdx-loader/src/remark/toc/__tests__/__fixtures__/just-content.md index 982f2df6563a..7f9152f5b851 100644 --- a/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/fixtures/just-content.md +++ b/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/__fixtures__/just-content.md @@ -13,3 +13,5 @@ Lorem ipsum Some content here ## I ♥ unicode. + +export const c = 1; diff --git a/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/fixtures/name-exist.md b/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/__fixtures__/name-exist.md similarity index 100% rename from packages/docusaurus-mdx-loader/src/remark/toc/__tests__/fixtures/name-exist.md rename to packages/docusaurus-mdx-loader/src/remark/toc/__tests__/__fixtures__/name-exist.md diff --git a/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/fixtures/non-text-content.md b/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/__fixtures__/non-text-content.md similarity index 100% rename from packages/docusaurus-mdx-loader/src/remark/toc/__tests__/fixtures/non-text-content.md rename to packages/docusaurus-mdx-loader/src/remark/toc/__tests__/__fixtures__/non-text-content.md diff --git a/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/__snapshots__/index.test.ts.snap index f3d93c058555..fbd3fcc125c8 100644 --- a/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/__snapshots__/index.test.ts.snap +++ b/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/__snapshots__/index.test.ts.snap @@ -1,36 +1,35 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`inline code should be escaped 1`] = ` +exports[`toc remark plugin escapes inline code 1`] = ` "export const toc = [ { value: '<Head />', id: 'head-', - children: [ - { - value: '<Head>Test</Head>', - id: 'headtesthead', - children: [], - level: 3 - } - ], level: 2 }, + { + value: '<Head>Test</Head>', + id: 'headtesthead', + level: 3 + }, { value: '<div />', id: 'div-', - children: [], level: 2 }, { value: '<div> Test </div>', id: 'div-test-div', - children: [], level: 2 }, { value: '<div><i>Test</i></div>', id: 'divitestidiv', - children: [], + level: 2 + }, + { + value: '<div><i>Test</i></div>', + id: 'divitestidiv-1', level: 2 } ]; @@ -44,40 +43,151 @@ exports[`inline code should be escaped 1`] = ` ## \`
Test
\` ## \`
Test
\` + +## [\`
Test
\`](/some/link) +" +`; + +exports[`toc remark plugin exports even with existing name 1`] = ` +"export const toc = [ + { + value: 'Thanos', + id: 'thanos', + level: 2 + }, + { + value: 'Tony Stark', + id: 'tony-stark', + level: 2 + }, + { + value: 'Avengers', + id: 'avengers', + level: 3 + } +]; + +## Thanos + +## Tony Stark + +### Avengers +" +`; + +exports[`toc remark plugin exports with custom name 1`] = ` +"export const customName = [ + { + value: 'Endi', + id: 'endi', + level: 3 + }, + { + value: 'Endi', + id: 'endi-1', + level: 2 + }, + { + value: 'Yangshun', + id: 'yangshun', + level: 3 + }, + { + value: 'I ♥ unicode.', + id: 'i--unicode', + level: 2 + } +]; + +### Endi + +\`\`\`md +## This is ignored +\`\`\` + +## Endi + +Lorem ipsum + +### Yangshun + +Some content here + +## I ♥ unicode. + +export const c = 1; +" +`; + +exports[`toc remark plugin handles empty headings 1`] = ` +"export const toc = []; + +# Ignore this + +## + +## ![](an-image.svg) +" +`; + +exports[`toc remark plugin inserts below imports 1`] = ` +"import something from 'something'; + +import somethingElse from 'something-else'; + +export const toc = [ + { + value: 'Title', + id: 'title', + level: 2 + }, + { + value: 'Test', + id: 'test', + level: 2 + }, + { + value: 'Again', + id: 'again', + level: 3 + } +]; + +## Title + +## Test + +### Again + +Content. " `; -exports[`non text phrasing content 1`] = ` +exports[`toc remark plugin works on non text phrasing content 1`] = ` "export const toc = [ { value: 'Emphasis', id: 'emphasis', - children: [ - { - value: 'Importance', - id: 'importance', - children: [], - level: 3 - } - ], level: 2 }, + { + value: 'Importance', + id: 'importance', + level: 3 + }, { value: 'Strikethrough', id: 'strikethrough', - children: [], level: 2 }, { value: 'HTML', id: 'html', - children: [], level: 2 }, { value: 'inline.code()', id: 'inlinecode', - children: [], level: 2 } ]; @@ -93,3 +203,47 @@ exports[`non text phrasing content 1`] = ` ## \`inline.code()\` " `; + +exports[`toc remark plugin works on text content 1`] = ` +"export const toc = [ + { + value: 'Endi', + id: 'endi', + level: 3 + }, + { + value: 'Endi', + id: 'endi-1', + level: 2 + }, + { + value: 'Yangshun', + id: 'yangshun', + level: 3 + }, + { + value: 'I ♥ unicode.', + id: 'i--unicode', + level: 2 + } +]; + +### Endi + +\`\`\`md +## This is ignored +\`\`\` + +## Endi + +Lorem ipsum + +### Yangshun + +Some content here + +## I ♥ unicode. + +export const c = 1; +" +`; diff --git a/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/index.test.ts b/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/index.test.ts index dd786b95ad73..b3fd7babac76 100644 --- a/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/index.test.ts +++ b/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/index.test.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import {join} from 'path'; +import path from 'path'; import remark from 'remark'; import mdx from 'remark-mdx'; import vfile from 'to-vfile'; @@ -13,8 +13,8 @@ import plugin from '../index'; import headings from '../../headings/index'; const processFixture = async (name, options?) => { - const path = join(__dirname, 'fixtures', `${name}.md`); - const file = await vfile.read(path); + const filePath = path.join(__dirname, '__fixtures__', `${name}.md`); + const file = await vfile.read(filePath); const result = await remark() .use(headings) .use(mdx) @@ -24,203 +24,42 @@ const processFixture = async (name, options?) => { return result.toString(); }; -test('non text phrasing content', async () => { - const result = await processFixture('non-text-content'); - expect(result).toMatchSnapshot(); -}); - -test('inline code should be escaped', async () => { - const result = await processFixture('inline-code'); - expect(result).toMatchSnapshot(); -}); - -test('text content', async () => { - const result = await processFixture('just-content'); - expect(result).toMatchInlineSnapshot(` - "export const toc = [ - { - value: 'Endi', - id: 'endi', - children: [], - level: 3 - }, - { - value: 'Endi', - id: 'endi-1', - children: [ - { - value: 'Yangshun', - id: 'yangshun', - children: [], - level: 3 - } - ], - level: 2 - }, - { - value: 'I ♥ unicode.', - id: 'i--unicode', - children: [], - level: 2 - } - ]; - - ### Endi - - \`\`\`md - ## This is ignored - \`\`\` - - ## Endi - - Lorem ipsum - - ### Yangshun - - Some content here - - ## I ♥ unicode. - " - `); -}); - -test('should export even with existing name', async () => { - const result = await processFixture('name-exist'); - expect(result).toMatchInlineSnapshot(` - "export const toc = [ - { - value: 'Thanos', - id: 'thanos', - children: [], - level: 2 - }, - { - value: 'Tony Stark', - id: 'tony-stark', - children: [ - { - value: 'Avengers', - id: 'avengers', - children: [], - level: 3 - } - ], - level: 2 - } - ]; - - ## Thanos - - ## Tony Stark - - ### Avengers - " - `); -}); - -test('should export with custom name', async () => { - const options = { - name: 'customName', - }; - const result = await processFixture('just-content', options); - expect(result).toMatchInlineSnapshot(` - "export const customName = [ - { - value: 'Endi', - id: 'endi', - children: [], - level: 3 - }, - { - value: 'Endi', - id: 'endi-1', - children: [ - { - value: 'Yangshun', - id: 'yangshun', - children: [], - level: 3 - } - ], - level: 2 - }, - { - value: 'I ♥ unicode.', - id: 'i--unicode', - children: [], - level: 2 - } - ]; - - ### Endi - - \`\`\`md - ## This is ignored - \`\`\` - - ## Endi - - Lorem ipsum - - ### Yangshun - - Some content here - - ## I ♥ unicode. - " - `); -}); - -test('should insert below imports', async () => { - const result = await processFixture('insert-below-imports'); - expect(result).toMatchInlineSnapshot(` - "import something from 'something'; - - import somethingElse from 'something-else'; - - export const toc = [ - { - value: 'Title', - id: 'title', - children: [], - level: 2 - }, - { - value: 'Test', - id: 'test', - children: [ - { - value: 'Again', - id: 'again', - children: [], - level: 3 - } - ], - level: 2 - } - ]; - - ## Title - - ## Test - - ### Again - - Content. - " - `); -}); - -test('empty headings', async () => { - const result = await processFixture('empty-headings'); - expect(result).toMatchInlineSnapshot(` - "export const toc = []; - - # Ignore this - - ## - - ## ![](an-image.svg) - " - `); +describe('toc remark plugin', () => { + it('works on non text phrasing content', async () => { + const result = await processFixture('non-text-content'); + expect(result).toMatchSnapshot(); + }); + + it('escapes inline code', async () => { + const result = await processFixture('inline-code'); + expect(result).toMatchSnapshot(); + }); + + it('works on text content', async () => { + const result = await processFixture('just-content'); + expect(result).toMatchSnapshot(); + }); + + it('exports even with existing name', async () => { + const result = await processFixture('name-exist'); + expect(result).toMatchSnapshot(); + }); + + it('exports with custom name', async () => { + const options = { + name: 'customName', + }; + const result = await processFixture('just-content', options); + expect(result).toMatchSnapshot(); + }); + + it('inserts below imports', async () => { + const result = await processFixture('insert-below-imports'); + expect(result).toMatchSnapshot(); + }); + + it('handles empty headings', async () => { + const result = await processFixture('empty-headings'); + expect(result).toMatchSnapshot(); + }); }); diff --git a/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/search.test.ts b/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/search.test.ts deleted file mode 100644 index 900fbfad3f5d..000000000000 --- a/packages/docusaurus-mdx-loader/src/remark/toc/__tests__/search.test.ts +++ /dev/null @@ -1,182 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import remark from 'remark'; -import mdx from 'remark-mdx'; -import search from '../search'; -import headings from '../../headings/index'; - -const getHeadings = async (mdText: string) => { - const node = remark().parse(mdText); - const result = await remark().use(headings).use(mdx).run(node); - return search(result); -}; - -test('should process all heading levels', async () => { - const md = ` -# Alpha - -## Bravo - -### Charlie - -#### Delta - -##### Echo - -###### Foxtrot - - `; - - await expect(getHeadings(md)).resolves.toEqual([ - { - children: [ - { - children: [ - { - children: [ - { - children: [ - { - children: [], - id: 'foxtrot', - level: 6, - value: 'Foxtrot', - }, - ], - id: 'echo', - level: 5, - value: 'Echo', - }, - ], - id: 'delta', - level: 4, - value: 'Delta', - }, - ], - id: 'charlie', - level: 3, - value: 'Charlie', - }, - ], - id: 'bravo', - level: 2, - value: 'Bravo', - }, - ]); -}); - -test('should process real-world well-formatted md', async () => { - const md = ` -# title - -some text - -## section 1 - -some text - -### subsection 1-1 - -some text - -#### subsection 1-1-1 - -some text - -#### subsection 1-1-2 - -some text - -### subsection 1-2 - -some text - -### subsection 1-3 - -some text - -## section 2 - -some text - -### subsection 2-1 - -some text - -### subsection 2-1 - -some text - -## section 3 - -some text - -### subsection 3-1 - -some text - -### subsection 3-2 - -some text - - `; - - await expect(getHeadings(md)).resolves.toEqual([ - { - children: [ - { - children: [ - { - children: [], - id: 'subsection-1-1-1', - level: 4, - value: 'subsection 1-1-1', - }, - { - children: [], - id: 'subsection-1-1-2', - level: 4, - value: 'subsection 1-1-2', - }, - ], - id: 'subsection-1-1', - level: 3, - value: 'subsection 1-1', - }, - {children: [], id: 'subsection-1-2', level: 3, value: 'subsection 1-2'}, - {children: [], id: 'subsection-1-3', level: 3, value: 'subsection 1-3'}, - ], - id: 'section-1', - level: 2, - value: 'section 1', - }, - { - children: [ - {children: [], id: 'subsection-2-1', level: 3, value: 'subsection 2-1'}, - { - children: [], - id: 'subsection-2-1-1', - level: 3, - value: 'subsection 2-1', - }, - ], - id: 'section-2', - level: 2, - value: 'section 2', - }, - { - children: [ - {children: [], id: 'subsection-3-1', level: 3, value: 'subsection 3-1'}, - {children: [], id: 'subsection-3-2', level: 3, value: 'subsection 3-2'}, - ], - id: 'section-3', - level: 2, - value: 'section 3', - }, - ]); -}); diff --git a/packages/docusaurus-mdx-loader/src/remark/toc/index.ts b/packages/docusaurus-mdx-loader/src/remark/toc/index.ts index c5630b796b16..e733d5ab93a4 100644 --- a/packages/docusaurus-mdx-loader/src/remark/toc/index.ts +++ b/packages/docusaurus-mdx-loader/src/remark/toc/index.ts @@ -9,10 +9,14 @@ import {parse, type ParserOptions} from '@babel/parser'; import type {Identifier} from '@babel/types'; import traverse from '@babel/traverse'; import stringifyObject from 'stringify-object'; -import search from './search'; -import type {Plugin, Transformer} from 'unified'; +import toString from 'mdast-util-to-string'; +import visit from 'unist-util-visit'; +import {toValue} from '../utils'; + +import type {TOCItem} from '@docusaurus/types'; import type {Node, Parent} from 'unist'; -import type {Literal} from 'mdast'; +import type {Heading, Literal} from 'mdast'; +import type {Transformer} from 'unified'; const parseOptions: ParserOptions = { plugins: ['jsx'], @@ -66,22 +70,33 @@ const getOrCreateExistingTargetIndex = (children: Node[], name: string) => { return targetIndex; }; -const plugin: Plugin<[PluginOptions?]> = (options = {}) => { +export default function plugin(options: PluginOptions = {}): Transformer { const name = options.name || 'toc'; - const transformer: Transformer = (node) => { - const headings = search(node); - const {children} = node as Parent; + return (root) => { + const headings: TOCItem[] = []; + + visit(root, 'heading', (child: Heading, index, parent) => { + const value = toString(child); + + // depth:1 headings are titles and not included in the TOC + if (parent !== root || !value || child.depth < 2) { + return; + } + + headings.push({ + value: toValue(child), + id: child.data!.id as string, + level: child.depth, + }); + }); + const {children} = root as Parent; const targetIndex = getOrCreateExistingTargetIndex(children, name); - if (headings && headings.length) { - children[targetIndex].value = `export const ${name} = ${stringifyObject( + if (headings.length) { + children[targetIndex]!.value = `export const ${name} = ${stringifyObject( headings, )};`; } }; - - return transformer; -}; - -export default plugin; +} diff --git a/packages/docusaurus-mdx-loader/src/remark/toc/search.ts b/packages/docusaurus-mdx-loader/src/remark/toc/search.ts deleted file mode 100644 index 4c87132c1e10..000000000000 --- a/packages/docusaurus-mdx-loader/src/remark/toc/search.ts +++ /dev/null @@ -1,80 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import toString from 'mdast-util-to-string'; -import visit from 'unist-util-visit'; -import {toValue} from '../utils'; -import type {TOCItem} from '@docusaurus/types'; -import type {Node} from 'unist'; -import type {Heading} from 'mdast'; - -// Intermediate interface for TOC algorithm -interface SearchItem { - node: TOCItem; - level: number; - parentIndex: number; -} - -/** - * - * Generate a TOC AST from the raw Markdown contents - */ -export default function search(node: Node): TOCItem[] { - const headings: SearchItem[] = []; - - visit(node, 'heading', (child: Heading, _index, parent) => { - const value = toString(child); - - // depth:1 headings are titles and not included in the TOC - if (parent !== node || !value || child.depth < 2) { - return; - } - - headings.push({ - node: { - value: toValue(child), - id: child.data!.id as string, - children: [], - level: child.depth, - }, - level: child.depth, - parentIndex: -1, - }); - }); - - // Keep track of which previous index would be the current heading's direct parent. - // Each entry is the last index of the `headings` array at heading level . - // We will modify these indices as we iterate through all headings. - // e.g. if an ### H3 was last seen at index 2, then prevIndexForLevel[3] === 2 - // indices 0 and 1 will remain unused. - const prevIndexForLevel = Array(7).fill(-1); - - headings.forEach((curr, currIndex) => { - // take the last seen index for each ancestor level. the highest - // index will be the direct ancestor of the current heading. - const ancestorLevelIndexes = prevIndexForLevel.slice(2, curr.level); - curr.parentIndex = Math.max(...ancestorLevelIndexes); - // mark that curr.level was last seen at the current index - prevIndexForLevel[curr.level] = currIndex; - }); - - const rootNodeIndexes: number[] = []; - - // For a given parentIndex, add each Node into that parent's `children` array - headings.forEach((heading, i) => { - if (heading.parentIndex >= 0) { - headings[heading.parentIndex].node.children.push(heading.node); - } else { - rootNodeIndexes.push(i); - } - }); - - const toc = headings - .filter((_, k) => rootNodeIndexes.includes(k)) // only return root nodes - .map((heading) => heading.node); // only return Node, no metadata - return toc; -} diff --git a/packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/img.md b/packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/img.md index e57e4c2948f7..969077544921 100644 --- a/packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/img.md +++ b/packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/img.md @@ -8,6 +8,8 @@ ![img from second static folder](./static2/img2.png) +![img with URL encoded chars](./static2/img2%20copy.png) + ![img](./static/img.png 'Title') ![img](/img.png) ![img with "quotes"](./static/img.png ''Quoted' title') diff --git a/packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/invalid-img.md b/packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/invalid-img.md new file mode 100644 index 000000000000..a41a28b708db --- /dev/null +++ b/packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/invalid-img.md @@ -0,0 +1 @@ +![invalid image](/invalid.png) diff --git a/packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/static/img.png b/packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/static/img.png index e69de29bb2d1..f458149e3c8f 100644 Binary files a/packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/static/img.png and b/packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/static/img.png differ diff --git a/packages/docusaurus/src/server/themes/__tests__/__fixtures__/theme-2/NavbarItem/NestedNavbarItem/index.js b/packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/static/invalid.png similarity index 100% rename from packages/docusaurus/src/server/themes/__tests__/__fixtures__/theme-2/NavbarItem/NestedNavbarItem/index.js rename to packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/static/invalid.png diff --git a/packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/static2/img2 copy.png b/packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/static2/img2 copy.png new file mode 100644 index 000000000000..81923fc56250 Binary files /dev/null and b/packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/static2/img2 copy.png differ diff --git a/packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/static2/img2.png b/packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/static2/img2.png index e69de29bb2d1..81923fc56250 100644 Binary files a/packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/static2/img2.png and b/packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/static2/img2.png differ diff --git a/packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__snapshots__/index.test.ts.snap index bb3cfdabb0c5..e8540eda835d 100644 --- a/packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__snapshots__/index.test.ts.snap +++ b/packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__snapshots__/index.test.ts.snap @@ -1,5 +1,10 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`transformImage plugin does not choke on invalid image 1`] = ` +"{\\"invalid/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static/invalid.png\\").default} /> +" +`; + exports[`transformImage plugin fail if image does not exist 1`] = `"Image packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/static/img/doesNotExist.png or packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/static2/img/doesNotExist.png used in packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/fail.md not found."`; exports[`transformImage plugin fail if image relative path does not exist 1`] = `"Image packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/notFound.png used in packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/fail2.md not found."`; @@ -14,27 +19,29 @@ exports[`transformImage plugin pathname protocol 1`] = ` exports[`transformImage plugin transform md images to 1`] = ` "![img](https://example.com/img.png) - +/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static/img.png\\").default} width=\\"200\\" height=\\"200\\" /> + +{\\"img\\"}/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static/img.png\\").default} width=\\"200\\" height=\\"200\\" /> -{\\"img\\"} +{\\"img/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static2/img2.png\\").default} width=\\"256\\" height=\\"82\\" /> -{\\"img +{\\"img/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static2/img2.png\\").default} width=\\"256\\" height=\\"82\\" /> -{\\"img +{\\"img/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static2/img2 copy.png\\").default} width=\\"256\\" height=\\"82\\" /> -{\\"img\\"} {\\"img\\"} +{\\"img\\"}/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static/img.png\\").default} title=\\"Title\\" width=\\"200\\" height=\\"200\\" /> {\\"img\\"}/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static/img.png\\").default} width=\\"200\\" height=\\"200\\" /> -{\\"img +{\\"img/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static/img.png\\").default} title=\\"'Quoted' title\\" width=\\"200\\" height=\\"200\\" /> -{\\"site +{\\"site/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static/img.png\\").default} width=\\"200\\" height=\\"200\\" /> -{\\"img -{\\"img +{\\"img/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static/img.png\\").default + '#light'} width=\\"200\\" height=\\"200\\" /> +{\\"img/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static/img.png\\").default + '#dark'} width=\\"200\\" height=\\"200\\" /> -{\\"img -{\\"img +{\\"img/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static/img.png?w=10\\").default} width=\\"200\\" height=\\"200\\" /> +{\\"img/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static/img.png?w=10&h=10\\").default} width=\\"200\\" height=\\"200\\" /> -{\\"img +{\\"img/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static/img.png?w=10&h=10\\").default + '#light'} width=\\"200\\" height=\\"200\\" /> ## Heading diff --git a/packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/index.test.ts b/packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/index.test.ts index e56fe47c19cc..15fac36b29cb 100644 --- a/packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/index.test.ts +++ b/packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/index.test.ts @@ -5,6 +5,7 @@ * LICENSE file in the root directory of this source tree. */ +import {jest} from '@jest/globals'; import path from 'path'; import remark from 'remark'; import mdx from 'remark-mdx'; @@ -21,10 +22,7 @@ const processFixture = async (name, options) => { .use(plugin, {...options, filePath}) .process(file); - return result - .toString() - .replace(/\\\\/g, '/') - .replace(new RegExp(process.cwd().replace(/\\/g, '/'), 'g'), '[CWD]'); + return result.toString(); }; const staticDirs = [ @@ -35,29 +33,36 @@ const staticDirs = [ const siteDir = path.join(__dirname, '__fixtures__'); describe('transformImage plugin', () => { - test('fail if image does not exist', async () => { + it('fail if image does not exist', async () => { await expect( processFixture('fail', {staticDirs}), ).rejects.toThrowErrorMatchingSnapshot(); }); - test('fail if image relative path does not exist', async () => { + it('fail if image relative path does not exist', async () => { await expect( processFixture('fail2', {staticDirs}), ).rejects.toThrowErrorMatchingSnapshot(); }); - test('fail if image url is absent', async () => { + it('fail if image url is absent', async () => { await expect( processFixture('noUrl', {staticDirs}), ).rejects.toThrowErrorMatchingSnapshot(); }); - test('transform md images to ', async () => { + it('transform md images to ', async () => { const result = await processFixture('img', {staticDirs, siteDir}); expect(result).toMatchSnapshot(); }); - test('pathname protocol', async () => { + it('pathname protocol', async () => { const result = await processFixture('pathname', {staticDirs}); expect(result).toMatchSnapshot(); }); + + it('does not choke on invalid image', async () => { + const errorMock = jest.spyOn(console, 'warn').mockImplementation(() => {}); + const result = await processFixture('invalid-img', {staticDirs}); + expect(result).toMatchSnapshot(); + expect(errorMock).toBeCalledTimes(1); + }); }); diff --git a/packages/docusaurus-mdx-loader/src/remark/transformImage/index.ts b/packages/docusaurus-mdx-loader/src/remark/transformImage/index.ts index 7644080ae43a..8dc6bbca3e50 100644 --- a/packages/docusaurus-mdx-loader/src/remark/transformImage/index.ts +++ b/packages/docusaurus-mdx-loader/src/remark/transformImage/index.ts @@ -19,7 +19,7 @@ import fs from 'fs-extra'; import escapeHtml from 'escape-html'; import sizeOf from 'image-size'; import {promisify} from 'util'; -import type {Plugin, Transformer} from 'unified'; +import type {Transformer} from 'unified'; import type {Image, Literal} from 'mdast'; import logger from '@docusaurus/logger'; @@ -66,9 +66,13 @@ async function toImageRequireNode( if (size.height) { height = ` height="${size.height}"`; } - } catch (e) { - logger.error`The image at path=${imagePath} can't be read correctly. Please ensure it's a valid image. -${(e as Error).message}`; + } catch (err) { + // Workaround for https://github.com/yarnpkg/berry/pull/3889#issuecomment-1034469784 + // TODO remove this check once fixed in Yarn PnP + if (!process.versions.pnp) { + logger.warn`The image at path=${imagePath} can't be read correctly. Please ensure it's a valid image. +${(err as Error).message}`; + } } Object.keys(jsxNode).forEach( @@ -116,14 +120,13 @@ async function getImageAbsolutePath( } return imageFilePath; } - // We try to convert image urls without protocol to images with require calls - // going through webpack ensures that image assets exist at build time - else { - // relative paths are resolved against the source file's folder - const imageFilePath = path.join(path.dirname(filePath), imagePath); - await ensureImageFileExist(imageFilePath, filePath); - return imageFilePath; - } + // relative paths are resolved against the source file's folder + const imageFilePath = path.join( + path.dirname(filePath), + decodeURIComponent(imagePath), + ); + await ensureImageFileExist(imageFilePath, filePath); + return imageFilePath; } async function processImageNode(node: Image, context: Context) { @@ -137,22 +140,22 @@ async function processImageNode(node: Image, context: Context) { const parsedUrl = url.parse(node.url); if (parsedUrl.protocol || !parsedUrl.pathname) { - // pathname:// is an escape hatch, - // in case user does not want his images to be converted to require calls going through webpack loader - // we don't have to document this for now, - // it's mostly to make next release less risky (2.0.0-alpha.59) + // pathname:// is an escape hatch, in case user does not want her images to + // be converted to require calls going through webpack loader if (parsedUrl.protocol === 'pathname:') { node.url = node.url.replace('pathname://', ''); } return; } + // We try to convert image urls without protocol to images with require calls + // going through webpack ensures that image assets exist at build time const imagePath = await getImageAbsolutePath(parsedUrl.pathname, context); await toImageRequireNode(node, imagePath, context.filePath); } -const plugin: Plugin<[PluginOptions]> = (options) => { - const transformer: Transformer = async (root, vfile) => { +export default function plugin(options: PluginOptions): Transformer { + return async (root, vfile) => { const promises: Promise[] = []; visit(root, 'image', (node: Image) => { promises.push( @@ -161,7 +164,4 @@ const plugin: Plugin<[PluginOptions]> = (options) => { }); await Promise.all(promises); }; - return transformer; -}; - -export default plugin; +} diff --git a/packages/docusaurus/src/server/themes/__tests__/__fixtures__/theme-2/NavbarItem/SiblingNavbarItem.js b/packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/__fixtures__/asset (2).pdf similarity index 100% rename from packages/docusaurus/src/server/themes/__tests__/__fixtures__/theme-2/NavbarItem/SiblingNavbarItem.js rename to packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/__fixtures__/asset (2).pdf diff --git a/packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/__fixtures__/asset.md b/packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/__fixtures__/asset.md index a3808267d20f..a23a16a92832 100644 --- a/packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/__fixtures__/asset.md +++ b/packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/__fixtures__/asset.md @@ -4,6 +4,8 @@ [asset](./asset.pdf) +[asset with URL encoded chars](./asset%20%282%29.pdf) + [asset with hash](./asset.pdf#page=2) [asset](asset.pdf 'Title') @@ -38,4 +40,4 @@ [json](./data.json) -[static json](/staticjson.json) +[static json](/static-json.json) diff --git a/packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/__fixtures__/static/staticjson.json b/packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/__fixtures__/static/static-json.json similarity index 100% rename from packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/__fixtures__/static/staticjson.json rename to packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/__fixtures__/static/static-json.json diff --git a/packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/__snapshots__/index.test.ts.snap index 5f176b716cf3..810a5542ed2b 100644 --- a/packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/__snapshots__/index.test.ts.snap +++ b/packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/__snapshots__/index.test.ts.snap @@ -12,13 +12,15 @@ exports[`transformAsset plugin pathname protocol 1`] = ` exports[`transformAsset plugin transform md links to 1`] = ` "[asset](https://example.com/asset.pdf) - +/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf').default}> -asset +/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf').default}>asset -asset with hash +/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset (2).pdf').default}>asset with URL encoded chars -asset +/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf').default + '#page=2'}>asset with hash + +/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf').default} title=\\"Title\\">asset [page](noUrl.md) @@ -32,24 +34,24 @@ exports[`transformAsset plugin transform md links to 1`] = ` [assets](/github/!file-loader!/assets.pdf) -asset +/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf').default}>asset -asset2 +/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static2/asset2.pdf').default}>asset2 -staticAsset.pdf +/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAsset.pdf').default}>staticAsset.pdf -@site/static/staticAsset.pdf +/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAsset.pdf').default}>@site/static/staticAsset.pdf -@site/static/staticAsset.pdf +/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAsset.pdf').default + '#page=2'} title=\\"Title\\">@site/static/staticAsset.pdf -Just staticAsset.pdf, and awesome staticAsset 2.pdf 'It is really "AWESOME"', but also coded staticAsset 3.pdf +/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAsset.pdf').default}>Just staticAsset.pdf, and /node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAsset.pdf').default}>awesome staticAsset 2.pdf 'It is really "AWESOME"', but also /node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAsset.pdf').default}>coded staticAsset 3.pdf -{\\"Clickable +/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAssetImage.png').default}>{\\"Clickable/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static/staticAssetImage.png\\").default} width=\\"200\\" height=\\"200\\" /> -Stylized link to asset file +/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf').default}>Stylized link to asset file -json +/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./data.json').default}>json -static json +/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/static-json.json').default}>static json " `; diff --git a/packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/index.test.ts b/packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/index.test.ts index a1bf18ceb394..65aac94fb322 100644 --- a/packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/index.test.ts +++ b/packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/index.test.ts @@ -30,31 +30,28 @@ const processFixture = async (name: string, options?) => { }) .process(file); - return result - .toString() - .replace(/\\\\/g, '/') - .replace(new RegExp(process.cwd().replace(/\\/g, '/'), 'g'), '[CWD]'); + return result.toString(); }; describe('transformAsset plugin', () => { - test('fail if asset url is absent', async () => { + it('fail if asset url is absent', async () => { await expect( processFixture('noUrl'), ).rejects.toThrowErrorMatchingSnapshot(); }); - test('fail if asset with site alias does not exist', async () => { + it('fail if asset with site alias does not exist', async () => { await expect( processFixture('nonexistentSiteAlias'), ).rejects.toThrowErrorMatchingSnapshot(); }); - test('transform md links to ', async () => { + it('transform md links to ', async () => { const result = await processFixture('asset'); expect(result).toMatchSnapshot(); }); - test('pathname protocol', async () => { + it('pathname protocol', async () => { const result = await processFixture('pathname'); expect(result).toMatchSnapshot(); }); diff --git a/packages/docusaurus-mdx-loader/src/remark/transformLinks/index.ts b/packages/docusaurus-mdx-loader/src/remark/transformLinks/index.ts index e674bb68695a..b1e7d1467ed9 100644 --- a/packages/docusaurus-mdx-loader/src/remark/transformLinks/index.ts +++ b/packages/docusaurus-mdx-loader/src/remark/transformLinks/index.ts @@ -18,7 +18,7 @@ import url from 'url'; import fs from 'fs-extra'; import escapeHtml from 'escape-html'; import {stringifyContent} from '../utils'; -import type {Plugin, Transformer} from 'unified'; +import type {Transformer} from 'unified'; import type {Link, Literal} from 'mdast'; const { @@ -130,21 +130,21 @@ async function processLinkNode(node: Link, context: Context) { return; } - const assetPath = await getAssetAbsolutePath(parsedUrl.pathname, context); + const assetPath = await getAssetAbsolutePath( + decodeURIComponent(parsedUrl.pathname), + context, + ); if (assetPath) { toAssetRequireNode(node, assetPath, context.filePath); } } -const plugin: Plugin<[PluginOptions]> = (options) => { - const transformer: Transformer = async (root, vfile) => { +export default function plugin(options: PluginOptions): Transformer { + return async (root, vfile) => { const promises: Promise[] = []; visit(root, 'link', (node: Link) => { promises.push(processLinkNode(node, {...options, filePath: vfile.path!})); }); await Promise.all(promises); }; - return transformer; -}; - -export default plugin; +} diff --git a/packages/docusaurus-mdx-loader/src/remark/unwrapMdxCodeBlocks/__tests__/fixtures/has-mdx-code-blocks.mdx b/packages/docusaurus-mdx-loader/src/remark/unwrapMdxCodeBlocks/__tests__/__fixtures__/has-mdx-code-blocks.mdx similarity index 100% rename from packages/docusaurus-mdx-loader/src/remark/unwrapMdxCodeBlocks/__tests__/fixtures/has-mdx-code-blocks.mdx rename to packages/docusaurus-mdx-loader/src/remark/unwrapMdxCodeBlocks/__tests__/__fixtures__/has-mdx-code-blocks.mdx diff --git a/packages/docusaurus-mdx-loader/src/remark/unwrapMdxCodeBlocks/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus-mdx-loader/src/remark/unwrapMdxCodeBlocks/__tests__/__snapshots__/index.test.ts.snap index c234d26fe0d1..35011f30c986 100644 --- a/packages/docusaurus-mdx-loader/src/remark/unwrapMdxCodeBlocks/__tests__/__snapshots__/index.test.ts.snap +++ b/packages/docusaurus-mdx-loader/src/remark/unwrapMdxCodeBlocks/__tests__/__snapshots__/index.test.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`unwrapMdxCodeBlocks should unwrap the mdx code blocks 1`] = ` +exports[`unwrapMdxCodeBlocks remark plugin unwraps the mdx code blocks 1`] = ` "# MDX code blocks test document ## Some basic markdown @@ -95,20 +95,20 @@ cmd /C 'set \\"GIT_USER=\\" && yarn deploy' " `; -exports[`unwrapMdxCodeBlocks should unwrap the mdx code blocks AST 1`] = ` -Object { - "children": Array [ - Object { - "children": Array [ - Object { +exports[`unwrapMdxCodeBlocks remark plugin unwraps the mdx code blocks AST 1`] = ` +{ + "children": [ + { + "children": [ + { "position": Position { - "end": Object { + "end": { "column": 32, "line": 1, "offset": 31, }, - "indent": Array [], - "start": Object { + "indent": [], + "start": { "column": 3, "line": 1, "offset": 2, @@ -120,13 +120,13 @@ Object { ], "depth": 1, "position": Position { - "end": Object { + "end": { "column": 32, "line": 1, "offset": 31, }, - "indent": Array [], - "start": Object { + "indent": [], + "start": { "column": 1, "line": 1, "offset": 0, @@ -134,17 +134,17 @@ Object { }, "type": "heading", }, - Object { - "children": Array [ - Object { + { + "children": [ + { "position": Position { - "end": Object { + "end": { "column": 23, "line": 3, "offset": 55, }, - "indent": Array [], - "start": Object { + "indent": [], + "start": { "column": 4, "line": 3, "offset": 36, @@ -156,13 +156,13 @@ Object { ], "depth": 2, "position": Position { - "end": Object { + "end": { "column": 23, "line": 3, "offset": 55, }, - "indent": Array [], - "start": Object { + "indent": [], + "start": { "column": 1, "line": 3, "offset": 33, @@ -170,17 +170,17 @@ Object { }, "type": "heading", }, - Object { - "children": Array [ - Object { + { + "children": [ + { "position": Position { - "end": Object { + "end": { "column": 5, "line": 5, "offset": 61, }, - "indent": Array [], - "start": Object { + "indent": [], + "start": { "column": 1, "line": 5, "offset": 57, @@ -191,13 +191,13 @@ Object { }, ], "position": Position { - "end": Object { + "end": { "column": 5, "line": 5, "offset": 61, }, - "indent": Array [], - "start": Object { + "indent": [], + "start": { "column": 1, "line": 5, "offset": 57, @@ -205,19 +205,19 @@ Object { }, "type": "paragraph", }, - Object { - "children": Array [ - Object { - "children": Array [ - Object { + { + "children": [ + { + "children": [ + { "position": Position { - "end": Object { + "end": { "column": 6, "line": 7, "offset": 68, }, - "indent": Array [], - "start": Object { + "indent": [], + "start": { "column": 2, "line": 7, "offset": 64, @@ -228,13 +228,13 @@ Object { }, ], "position": Position { - "end": Object { + "end": { "column": 29, "line": 7, "offset": 91, }, - "indent": Array [], - "start": Object { + "indent": [], + "start": { "column": 1, "line": 7, "offset": 63, @@ -246,13 +246,13 @@ Object { }, ], "position": Position { - "end": Object { + "end": { "column": 29, "line": 7, "offset": 91, }, - "indent": Array [], - "start": Object { + "indent": [], + "start": { "column": 1, "line": 7, "offset": 63, @@ -260,19 +260,19 @@ Object { }, "type": "paragraph", }, - Object { - "children": Array [ - Object { - "children": Array [ - Object { + { + "children": [ + { + "children": [ + { "position": Position { - "end": Object { + "end": { "column": 7, "line": 9, "offset": 99, }, - "indent": Array [], - "start": Object { + "indent": [], + "start": { "column": 3, "line": 9, "offset": 95, @@ -283,13 +283,13 @@ Object { }, ], "position": Position { - "end": Object { + "end": { "column": 9, "line": 9, "offset": 101, }, - "indent": Array [], - "start": Object { + "indent": [], + "start": { "column": 1, "line": 9, "offset": 93, @@ -299,13 +299,13 @@ Object { }, ], "position": Position { - "end": Object { + "end": { "column": 9, "line": 9, "offset": 101, }, - "indent": Array [], - "start": Object { + "indent": [], + "start": { "column": 1, "line": 9, "offset": 93, @@ -313,18 +313,18 @@ Object { }, "type": "paragraph", }, - Object { - "children": Array [ - Object { + { + "children": [ + { "alt": "image", "position": Position { - "end": Object { + "end": { "column": 43, "line": 11, "offset": 145, }, - "indent": Array [], - "start": Object { + "indent": [], + "start": { "column": 1, "line": 11, "offset": 103, @@ -336,13 +336,13 @@ Object { }, ], "position": Position { - "end": Object { + "end": { "column": 43, "line": 11, "offset": 145, }, - "indent": Array [], - "start": Object { + "indent": [], + "start": { "column": 1, "line": 11, "offset": 103, @@ -350,17 +350,17 @@ Object { }, "type": "paragraph", }, - Object { - "children": Array [ - Object { + { + "children": [ + { "position": Position { - "end": Object { + "end": { "column": 18, "line": 13, "offset": 164, }, - "indent": Array [], - "start": Object { + "indent": [], + "start": { "column": 4, "line": 13, "offset": 150, @@ -372,13 +372,13 @@ Object { ], "depth": 2, "position": Position { - "end": Object { + "end": { "column": 18, "line": 13, "offset": 164, }, - "indent": Array [], - "start": Object { + "indent": [], + "start": { "column": 1, "line": 13, "offset": 147, @@ -386,15 +386,15 @@ Object { }, "type": "heading", }, - Object { + { "position": Position { - "end": Object { + "end": { "column": 23, "line": 15, "offset": 188, }, - "indent": Array [], - "start": Object { + "indent": [], + "start": { "column": 1, "line": 15, "offset": 166, @@ -403,18 +403,18 @@ Object { "type": "import", "value": "import XYZ from 'xyz';", }, - Object { + { "position": Position { - "end": Object { + "end": { "column": 7, "line": 19, "offset": 287, }, - "indent": Array [ + "indent": [ 1, 1, ], - "start": Object { + "start": { "column": 1, "line": 17, "offset": 190, @@ -425,17 +425,17 @@ Object { Test ", }, - Object { - "children": Array [ - Object { + { + "children": [ + { "position": Position { - "end": Object { + "end": { "column": 29, "line": 21, "offset": 317, }, - "indent": Array [], - "start": Object { + "indent": [], + "start": { "column": 4, "line": 21, "offset": 292, @@ -447,13 +447,13 @@ Object { ], "depth": 2, "position": Position { - "end": Object { + "end": { "column": 29, "line": 21, "offset": 317, }, - "indent": Array [], - "start": Object { + "indent": [], + "start": { "column": 1, "line": 21, "offset": 289, @@ -461,16 +461,16 @@ Object { }, "type": "heading", }, - Object { + { "lang": "mdx-code-block", "meta": null, "position": Position { - "end": Object { + "end": { "column": 4, "line": 29, "offset": 442, }, - "indent": Array [ + "indent": [ 1, 1, 1, @@ -478,7 +478,7 @@ Object { 1, 1, ], - "start": Object { + "start": { "column": 1, "line": 23, "offset": 319, @@ -491,17 +491,17 @@ Object {
Sebastien Lorber
", }, - Object { - "children": Array [ - Object { + { + "children": [ + { "position": Position { - "end": Object { + "end": { "column": 44, "line": 31, "offset": 487, }, - "indent": Array [], - "start": Object { + "indent": [], + "start": { "column": 4, "line": 31, "offset": 447, @@ -513,13 +513,13 @@ Object { ], "depth": 2, "position": Position { - "end": Object { + "end": { "column": 44, "line": 31, "offset": 487, }, - "indent": Array [], - "start": Object { + "indent": [], + "start": { "column": 1, "line": 31, "offset": 444, @@ -527,14 +527,14 @@ Object { }, "type": "heading", }, - Object { + { "position": Position { - "end": Object { + "end": { "column": 25, "line": 40, "offset": 688, }, - "indent": Array [ + "indent": [ 1, 1, 1, @@ -543,7 +543,7 @@ Object { 1, 1, ], - "start": Object { + "start": { "column": 1, "line": 33, "offset": 489, @@ -559,20 +559,20 @@ Object { ]}> ", }, - Object { + { "lang": "bash", "meta": null, "position": Position { - "end": Object { + "end": { "column": 4, "line": 44, "offset": 740, }, - "indent": Array [ + "indent": [ 1, 1, ], - "start": Object { + "start": { "column": 1, "line": 42, "offset": 690, @@ -581,17 +581,17 @@ Object { "type": "code", "value": "GIT_USER= yarn deploy", }, - Object { + { "position": Position { - "end": Object { + "end": { "column": 28, "line": 47, "offset": 782, }, - "indent": Array [ + "indent": [ 1, ], - "start": Object { + "start": { "column": 1, "line": 46, "offset": 742, @@ -601,20 +601,20 @@ Object { "value": " ", }, - Object { + { "lang": null, "meta": null, "position": Position { - "end": Object { + "end": { "column": 8, "line": 51, "offset": 865, }, - "indent": Array [ + "indent": [ 1, 1, ], - "start": Object { + "start": { "column": 1, "line": 49, "offset": 784, @@ -625,17 +625,17 @@ Object { cmd /C \\"set \\"GIT_USER=\\" && yarn deploy\\" \`\`\`", }, - Object { + { "position": Position { - "end": Object { + "end": { "column": 31, "line": 54, "offset": 910, }, - "indent": Array [ + "indent": [ 1, ], - "start": Object { + "start": { "column": 1, "line": 53, "offset": 867, @@ -645,20 +645,20 @@ cmd /C \\"set \\"GIT_USER=\\" && yarn deploy\\" "value": " ", }, - Object { + { "lang": "powershell", "meta": null, "position": Position { - "end": Object { + "end": { "column": 4, "line": 58, "offset": 986, }, - "indent": Array [ + "indent": [ 1, 1, ], - "start": Object { + "start": { "column": 1, "line": 56, "offset": 912, @@ -667,17 +667,17 @@ cmd /C \\"set \\"GIT_USER=\\" && yarn deploy\\" "type": "code", "value": "cmd /C 'set \\"GIT_USER=\\" && yarn deploy'", }, - Object { + { "position": Position { - "end": Object { + "end": { "column": 8, "line": 61, "offset": 1008, }, - "indent": Array [ + "indent": [ 1, ], - "start": Object { + "start": { "column": 1, "line": 60, "offset": 988, @@ -687,17 +687,17 @@ cmd /C \\"set \\"GIT_USER=\\" && yarn deploy\\" "value": " ", }, - Object { - "children": Array [ - Object { + { + "children": [ + { "position": Position { - "end": Object { + "end": { "column": 55, "line": 63, "offset": 1064, }, - "indent": Array [], - "start": Object { + "indent": [], + "start": { "column": 4, "line": 63, "offset": 1013, @@ -709,13 +709,13 @@ cmd /C \\"set \\"GIT_USER=\\" && yarn deploy\\" ], "depth": 2, "position": Position { - "end": Object { + "end": { "column": 55, "line": 63, "offset": 1064, }, - "indent": Array [], - "start": Object { + "indent": [], + "start": { "column": 1, "line": 63, "offset": 1010, @@ -723,16 +723,16 @@ cmd /C \\"set \\"GIT_USER=\\" && yarn deploy\\" }, "type": "heading", }, - Object { + { "lang": "mdx-code-block", "meta": null, "position": Position { - "end": Object { + "end": { "column": 5, "line": 95, "offset": 1585, }, - "indent": Array [ + "indent": [ 1, 1, 1, @@ -764,7 +764,7 @@ cmd /C \\"set \\"GIT_USER=\\" && yarn deploy\\" 1, 1, ], - "start": Object { + "start": { "column": 1, "line": 65, "offset": 1066, @@ -802,13 +802,13 @@ cmd /C 'set \\"GIT_USER=\\" && yarn deploy' ", }, ], - "position": Object { - "end": Object { + "position": { + "end": { "column": 1, "line": 96, "offset": 1586, }, - "start": Object { + "start": { "column": 1, "line": 1, "offset": 0, diff --git a/packages/docusaurus-mdx-loader/src/remark/unwrapMdxCodeBlocks/__tests__/index.test.ts b/packages/docusaurus-mdx-loader/src/remark/unwrapMdxCodeBlocks/__tests__/index.test.ts index 99d3669154b8..13fe031f5b84 100644 --- a/packages/docusaurus-mdx-loader/src/remark/unwrapMdxCodeBlocks/__tests__/index.test.ts +++ b/packages/docusaurus-mdx-loader/src/remark/unwrapMdxCodeBlocks/__tests__/index.test.ts @@ -5,33 +5,31 @@ * LICENSE file in the root directory of this source tree. */ -import {join} from 'path'; +import path from 'path'; import remark from 'remark'; import mdx from 'remark-mdx'; import vfile from 'to-vfile'; import plugin from '..'; -const processFixture = async (name) => { - const path = join(__dirname, 'fixtures', name); - const file = await vfile.read(path); +const processFixture = async (name: string) => { + const file = await vfile.read(path.join(__dirname, '__fixtures__', name)); const result = await remark().use(mdx).use(plugin).process(file); return result.toString(); }; -const processFixtureAST = async (name) => { - const path = join(__dirname, 'fixtures', name); - const file = await vfile.read(path); +const processFixtureAST = async (name: string) => { + const file = await vfile.read(path.join(__dirname, '__fixtures__', name)); return remark().use(mdx).use(plugin).parse(file); }; -describe('unwrapMdxCodeBlocks', () => { - test('should unwrap the mdx code blocks', async () => { +describe('unwrapMdxCodeBlocks remark plugin', () => { + it('unwraps the mdx code blocks', async () => { const result = await processFixture('has-mdx-code-blocks.mdx'); expect(result).toMatchSnapshot(); }); // The AST output should be parsed correctly or the MDX loader won't work! - test('should unwrap the mdx code blocks AST', async () => { + it('unwraps the mdx code blocks AST', async () => { const result = await processFixtureAST('has-mdx-code-blocks.mdx'); expect(result).toMatchSnapshot(); }); diff --git a/packages/docusaurus-mdx-loader/src/remark/unwrapMdxCodeBlocks/index.ts b/packages/docusaurus-mdx-loader/src/remark/unwrapMdxCodeBlocks/index.ts index 101f2552e6b9..07aee3daeb89 100644 --- a/packages/docusaurus-mdx-loader/src/remark/unwrapMdxCodeBlocks/index.ts +++ b/packages/docusaurus-mdx-loader/src/remark/unwrapMdxCodeBlocks/index.ts @@ -10,15 +10,16 @@ import type {Transformer, Processor} from 'unified'; import type {Code, Parent} from 'mdast'; // This plugin is mostly to help integrating Docusaurus with translation systems -// that do not support well MDX embedded JSX syntax (like Crowdin) -// We wrap the JSX syntax in code blocks so that translation tools don't mess-up with the markup -// But the JSX inside such code blocks should still be evaluated as JSX +// that do not support well MDX embedded JSX syntax (like Crowdin). +// We wrap the JSX syntax in code blocks so that translation tools don't mess up +// with the markup, but the JSX inside such code blocks should still be +// evaluated as JSX // See https://github.com/facebook/docusaurus/pull/4278 -function plugin(this: Processor): Transformer { - const transformer: Transformer = (root) => { - visit(root, 'code', (node: Code, _index, parent) => { +export default function plugin(this: Processor): Transformer { + return (root) => { + visit(root, 'code', (node: Code, index, parent) => { if (node.lang === 'mdx-code-block') { - const newChildren = (this!.parse(node.value) as Parent).children; + const newChildren = (this.parse(node.value) as Parent).children; // Replace the mdx code block by its content, parsed parent!.children.splice( @@ -29,8 +30,4 @@ function plugin(this: Processor): Transformer { } }); }; - - return transformer; } - -export default plugin; diff --git a/packages/docusaurus-mdx-loader/src/remark/utils/index.ts b/packages/docusaurus-mdx-loader/src/remark/utils/index.ts index 2309a8dd10ea..b0314d7a2349 100644 --- a/packages/docusaurus-mdx-loader/src/remark/utils/index.ts +++ b/packages/docusaurus-mdx-loader/src/remark/utils/index.ts @@ -8,32 +8,29 @@ import escapeHtml from 'escape-html'; import toString from 'mdast-util-to-string'; import type {Parent} from 'unist'; -import type {StaticPhrasingContent, Heading} from 'mdast'; +import type {PhrasingContent, Heading} from 'mdast'; export function stringifyContent(node: Parent): string { - return ((node.children || []) as StaticPhrasingContent[]) - .map(toValue) - .join(''); + return (node.children as PhrasingContent[]).map(toValue).join(''); } -export function toValue(node: StaticPhrasingContent | Heading): string { - if (node && node.type) { - switch (node.type) { - case 'text': - return escapeHtml(node.value); - case 'heading': - return stringifyContent(node); - case 'inlineCode': - return `${escapeHtml(node.value)}`; - case 'emphasis': - return `${stringifyContent(node)}`; - case 'strong': - return `${stringifyContent(node)}`; - case 'delete': - return `${stringifyContent(node)}`; - default: - } +export function toValue(node: PhrasingContent | Heading): string { + switch (node?.type) { + case 'text': + return escapeHtml(node.value); + case 'heading': + return stringifyContent(node); + case 'inlineCode': + return `${escapeHtml(node.value)}`; + case 'emphasis': + return `${stringifyContent(node)}`; + case 'strong': + return `${stringifyContent(node)}`; + case 'delete': + return `${stringifyContent(node)}`; + case 'link': + return stringifyContent(node); + default: + return toString(node); } - - return toString(node); } diff --git a/packages/docusaurus-migrate/bin/index.js b/packages/docusaurus-migrate/bin/index.mjs similarity index 65% rename from packages/docusaurus-migrate/bin/index.js rename to packages/docusaurus-migrate/bin/index.mjs index 2b79c4d52116..baf72762aeb7 100755 --- a/packages/docusaurus-migrate/bin/index.js +++ b/packages/docusaurus-migrate/bin/index.mjs @@ -8,22 +8,14 @@ // @ts-check -const logger = require('@docusaurus/logger').default; -const semver = require('semver'); -const cli = require('commander'); -const path = require('path'); +import logger from '@docusaurus/logger'; +import semver from 'semver'; +import cli from 'commander'; +import path from 'path'; +import {createRequire} from 'module'; -const requiredVersion = require('../package.json').engines.node; - -const {migrateDocusaurusProject, migrateMDToMDX} = require('../lib'); - -function wrapCommand(fn) { - return (...args) => - fn(...args).catch((err) => { - logger.error(err.stack); - process.exitCode = 1; - }); -} +const moduleRequire = createRequire(import.meta.url); +const requiredVersion = moduleRequire('../package.json').engines.node; if (!semver.satisfies(process.version, requiredVersion)) { logger.error('Minimum Node.js version not met :('); @@ -31,6 +23,10 @@ if (!semver.satisfies(process.version, requiredVersion)) { process.exit(1); } +// See https://github.com/facebook/docusaurus/pull/6860 +const {migrateDocusaurusProject, migrateMDToMDX} = + moduleRequire('../lib/index.js'); + cli .command('migrate [siteDir] [newDir]') .option('--mdx', 'try to migrate MD to MDX too') @@ -39,7 +35,7 @@ cli .action((siteDir = '.', newDir = '.', {mdx, page} = {}) => { const sitePath = path.resolve(siteDir); const newSitePath = path.resolve(newDir); - wrapCommand(migrateDocusaurusProject)(sitePath, newSitePath, mdx, page); + migrateDocusaurusProject(sitePath, newSitePath, mdx, page); }); cli @@ -48,10 +44,16 @@ cli .action((siteDir = '.', newDir = '.') => { const sitePath = path.resolve(siteDir); const newSitePath = path.resolve(newDir); - wrapCommand(migrateMDToMDX)(sitePath, newSitePath); + migrateMDToMDX(sitePath, newSitePath); }); + cli.parse(process.argv); if (!process.argv.slice(2).length) { cli.outputHelp(); } + +process.on('unhandledRejection', (err) => { + logger.error(err); + process.exit(1); +}); diff --git a/packages/docusaurus-migrate/package.json b/packages/docusaurus-migrate/package.json index b008942a2c8d..3dd093f1394f 100644 --- a/packages/docusaurus-migrate/package.json +++ b/packages/docusaurus-migrate/package.json @@ -1,15 +1,14 @@ { "name": "@docusaurus/migrate", - "version": "2.0.0-beta.14", + "version": "2.0.0-beta.18", "description": "A CLI tool to migrate from older versions of Docusaurus.", - "main": "lib/index.js", "license": "MIT", "engines": { "node": ">=14" }, "scripts": { - "build": "tsc", - "watch": "tsc --watch" + "build": "tsc -p tsconfig.build.json", + "watch": "tsc -p tsconfig.build.json --watch" }, "repository": { "type": "git", @@ -20,30 +19,30 @@ "access": "public" }, "bin": { - "docusaurus-migrate": "bin/index.js" + "docusaurus-migrate": "bin/index.mjs" }, "dependencies": { - "@babel/preset-env": "^7.16.4", - "@docusaurus/logger": "2.0.0-beta.14", - "@mapbox/hast-util-to-jsx": "^1.0.0", - "color": "^4.0.1", + "@babel/preset-env": "^7.16.11", + "@docusaurus/logger": "2.0.0-beta.18", + "@docusaurus/utils": "2.0.0-beta.18", + "@mapbox/hast-util-to-jsx": "^2.0.0", + "color": "^4.2.1", "commander": "^5.1.0", - "fs-extra": "^10.0.0", - "glob": "^7.2.0", + "fs-extra": "^10.0.1", "hast-util-to-string": "^1.0.4", "html-tags": "^3.1.0", - "import-fresh": "^3.2.2", - "jscodeshift": "^0.13.0", + "import-fresh": "^3.3.0", + "jscodeshift": "^0.13.1", "rehype-parse": "^7.0.1", "remark-parse": "^8.0.2", "remark-stringify": "^8.1.0", - "semver": "^7.3.4", + "semver": "^7.3.5", "tslib": "^2.3.1", - "unified": "^9.2.1", - "unist-util-visit": "^2.0.2" + "unified": "^9.2.2", + "unist-util-visit": "^2.0.3" }, "devDependencies": { - "@types/color": "^3.0.1", - "@types/jscodeshift": "^0.11.2" + "@types/color": "^3.0.3", + "@types/jscodeshift": "^0.11.3" } } diff --git a/packages/docusaurus-migrate/src/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus-migrate/src/__tests__/__snapshots__/index.test.ts.snap new file mode 100644 index 000000000000..6bb29e50da09 --- /dev/null +++ b/packages/docusaurus-migrate/src/__tests__/__snapshots__/index.test.ts.snap @@ -0,0 +1,565 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`migration CLI migrates complex website: copy 1`] = ` +[ + [ + "/packages/docusaurus-migrate/src/__tests__/__fixtures__/complex_website/website/blog", + "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_complex_site/blog", + ], + [ + "/packages/docusaurus-migrate/src/__tests__/__fixtures__/complex_website/website/pages/en", + "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_complex_site/src/pages", + ], + [ + "/packages/docusaurus-migrate/src/__tests__/__fixtures__/complex_website/website/static", + "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_complex_site/static", + ], +] +`; + +exports[`migration CLI migrates complex website: mkdirp 1`] = ` +[ + [ + "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_complex_site/src/pages", + ], +] +`; + +exports[`migration CLI migrates complex website: mkdirs 1`] = `[]`; + +exports[`migration CLI migrates complex website: write 1`] = ` +[ + [ + "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_complex_site/docusaurus.config.js", + "module.exports={ + \\"title\\": \\"Docusaurus\\", + \\"tagline\\": \\"Easy to Maintain Open Source Documentation Websites\\", + \\"url\\": \\"https://docusaurus.io\\", + \\"baseUrl\\": \\"/\\", + \\"organizationName\\": \\"facebook\\", + \\"projectName\\": \\"docusaurus\\", + \\"noIndex\\": false, + \\"scripts\\": [ + \\"https://buttons.github.io/buttons.js\\", + \\"https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.0/clipboard.min.js\\", + \\"/js/code-blocks-buttons.js\\" + ], + \\"favicon\\": \\"img/docusaurus.ico\\", + \\"customFields\\": { + \\"users\\": { + \\"caption\\": \\"DevSpace\\", + \\"image\\": \\"/img/users/devspace.svg\\", + \\"infoLink\\": \\"https://devspace.cloud/docs/\\", + \\"fbOpenSource\\": false, + \\"pinned\\": false + }, + \\"translationRecruitingLink\\": \\"https://crowdin.com/project/docusaurus\\", + \\"facebookAppId\\": \\"199138890728411\\" + }, + \\"onBrokenLinks\\": \\"log\\", + \\"onBrokenMarkdownLinks\\": \\"log\\", + \\"presets\\": [ + [ + \\"@docusaurus/preset-classic\\", + { + \\"docs\\": { + \\"showLastUpdateAuthor\\": true, + \\"showLastUpdateTime\\": true, + \\"editUrl\\": \\"https://github.com/facebook/docusaurus/edit/main/docs/\\" + }, + \\"blog\\": {}, + \\"theme\\": { + \\"customCss\\": \\"../complex_website/src/css/customTheme.css\\" + }, + \\"googleAnalytics\\": { + \\"trackingID\\": \\"UA-44373548-31\\" + } + } + ] + ], + \\"plugins\\": [], + \\"themeConfig\\": { + \\"navbar\\": { + \\"title\\": \\"Docusaurus\\", + \\"logo\\": { + \\"src\\": \\"img/docusaurus.svg\\" + }, + \\"items\\": [ + { + \\"to\\": \\"docs/installation\\", + \\"label\\": \\"Docs\\", + \\"position\\": \\"left\\" + }, + { + \\"to\\": \\"docs/tutorial-setup\\", + \\"label\\": \\"Tutorial\\", + \\"position\\": \\"left\\" + }, + { + \\"to\\": \\"/users\\", + \\"label\\": \\"Users\\", + \\"position\\": \\"left\\" + }, + { + \\"href\\": \\"https://github.com/facebook/docusaurus\\", + \\"label\\": \\"GitHub\\", + \\"position\\": \\"left\\" + } + ] + }, + \\"image\\": \\"img/docusaurus.png\\", + \\"footer\\": { + \\"links\\": [ + { + \\"title\\": \\"Community\\", + \\"items\\": [ + { + \\"label\\": \\"Twitter\\", + \\"to\\": \\"https://twitter.com/docusaurus\\" + } + ] + } + ], + \\"copyright\\": \\"Copyright © 2022 Facebook Inc.\\", + \\"logo\\": { + \\"src\\": \\"img/docusaurus_monochrome.svg\\" + } + }, + \\"algolia\\": { + \\"apiKey\\": \\"3eb9507824b8be89e7a199ecaa1a9d2c\\", + \\"indexName\\": \\"docusaurus\\", + \\"algoliaOptions\\": { + \\"facetFilters\\": [ + \\"language:LANGUAGE\\", + \\"version:VERSION\\" + ] + } + } + } +}", + ], + [ + "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_complex_site/package.json", + "{ + \\"name\\": \\"docusaurus-1-website\\", + \\"version\\": \\"2.0.0-alpha.58\\", + \\"private\\": true, + \\"scripts\\": { + \\"start\\": \\"docusaurus start\\", + \\"build\\": \\"docusaurus build\\", + \\"publish-gh-pages\\": \\"docusaurus-publish\\", + \\"examples\\": \\"docusaurus-examples\\", + \\"write-translations\\": \\"docusaurus-write-translations\\", + \\"docusaurus-version\\": \\"docusaurus-version\\", + \\"rename-version\\": \\"docusaurus-rename-version\\", + \\"crowdin-upload\\": \\"crowdin --config ../crowdin.yaml upload sources --auto-update -b master\\", + \\"crowdin-download\\": \\"crowdin --config ../crowdin.yaml download -b master\\", + \\"swizzle\\": \\"docusaurus swizzle\\", + \\"deploy\\": \\"docusaurus deploy\\", + \\"docusaurus\\": \\"docusaurus\\" + }, + \\"dependencies\\": { + \\"@docusaurus/core\\": \\"\\", + \\"@docusaurus/preset-classic\\": \\"\\", + \\"clsx\\": \\"^1.1.1\\", + \\"react\\": \\"^17.0.2\\", + \\"react-dom\\": \\"^17.0.2\\" + } +}", + ], + [ + "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_complex_site/src/css/customTheme.css", + ":root{ + --ifm-color-primary-lightest: #3CAD6E; + --ifm-color-primary-lighter: #359962; + --ifm-color-primary-light: #33925D; + --ifm-color-primary: #2E8555; + --ifm-color-primary-dark: #29784C; + --ifm-color-primary-darker: #277148; + --ifm-color-primary-darkest: #205D3B; +} +", + ], +] +`; + +exports[`migration CLI migrates missing versions: copy 1`] = ` +[ + [ + "/packages/docusaurus-migrate/src/__tests__/__fixtures__/missing_version_website/website/blog", + "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_missing_version_site/blog", + ], + [ + "/packages/docusaurus-migrate/src/__tests__/__fixtures__/missing_version_website/website/pages/en", + "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_missing_version_site/src/pages", + ], + [ + "/packages/docusaurus-migrate/src/__tests__/__fixtures__/missing_version_website/website/static", + "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_missing_version_site/static", + ], +] +`; + +exports[`migration CLI migrates missing versions: mkdirp 1`] = ` +[ + [ + "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_missing_version_site/src/pages", + ], +] +`; + +exports[`migration CLI migrates missing versions: mkdirs 1`] = `[]`; + +exports[`migration CLI migrates missing versions: write 1`] = ` +[ + [ + "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_missing_version_site/docusaurus.config.js", + "module.exports={ + \\"title\\": \\"Docusaurus\\", + \\"tagline\\": \\"Easy to Maintain Open Source Documentation Websites\\", + \\"url\\": \\"https://docusaurus.io\\", + \\"baseUrl\\": \\"/\\", + \\"organizationName\\": \\"facebook\\", + \\"projectName\\": \\"docusaurus\\", + \\"noIndex\\": false, + \\"scripts\\": [ + \\"https://buttons.github.io/buttons.js\\", + \\"https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.0/clipboard.min.js\\", + \\"/js/code-blocks-buttons.js\\" + ], + \\"favicon\\": \\"img/docusaurus.ico\\", + \\"customFields\\": { + \\"users\\": { + \\"caption\\": \\"DevSpace\\", + \\"image\\": \\"/img/users/devspace.svg\\", + \\"infoLink\\": \\"https://devspace.cloud/docs/\\", + \\"fbOpenSource\\": false, + \\"pinned\\": false + }, + \\"translationRecruitingLink\\": \\"https://crowdin.com/project/docusaurus\\", + \\"facebookAppId\\": \\"199138890728411\\" + }, + \\"onBrokenLinks\\": \\"log\\", + \\"onBrokenMarkdownLinks\\": \\"log\\", + \\"presets\\": [ + [ + \\"@docusaurus/preset-classic\\", + { + \\"docs\\": { + \\"showLastUpdateAuthor\\": true, + \\"showLastUpdateTime\\": true, + \\"editUrl\\": \\"https://github.com/facebook/docusaurus/edit/main/docs/\\" + }, + \\"blog\\": {}, + \\"theme\\": { + \\"customCss\\": \\"../missing_version_website/src/css/customTheme.css\\" + }, + \\"googleAnalytics\\": { + \\"trackingID\\": \\"UA-44373548-31\\" + } + } + ] + ], + \\"plugins\\": [], + \\"themeConfig\\": { + \\"navbar\\": { + \\"title\\": \\"Docusaurus\\", + \\"logo\\": { + \\"src\\": \\"img/docusaurus.svg\\" + }, + \\"items\\": [ + { + \\"to\\": \\"docs/installation\\", + \\"label\\": \\"Docs\\", + \\"position\\": \\"left\\" + }, + { + \\"to\\": \\"docs/tutorial-setup\\", + \\"label\\": \\"Tutorial\\", + \\"position\\": \\"left\\" + }, + { + \\"to\\": \\"/users\\", + \\"label\\": \\"Users\\", + \\"position\\": \\"left\\" + }, + { + \\"href\\": \\"https://github.com/facebook/docusaurus\\", + \\"label\\": \\"GitHub\\", + \\"position\\": \\"left\\" + } + ] + }, + \\"image\\": \\"img/docusaurus.png\\", + \\"footer\\": { + \\"links\\": [ + { + \\"title\\": \\"Community\\", + \\"items\\": [ + { + \\"label\\": \\"Twitter\\", + \\"to\\": \\"https://twitter.com/docusaurus\\" + } + ] + } + ], + \\"copyright\\": \\"Copyright © 2022 Facebook Inc.\\", + \\"logo\\": { + \\"src\\": \\"img/docusaurus_monochrome.svg\\" + } + }, + \\"algolia\\": { + \\"apiKey\\": \\"3eb9507824b8be89e7a199ecaa1a9d2c\\", + \\"indexName\\": \\"docusaurus\\", + \\"algoliaOptions\\": { + \\"facetFilters\\": [ + \\"language:LANGUAGE\\", + \\"version:VERSION\\" + ] + } + } + } +}", + ], + [ + "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_missing_version_site/package.json", + "{ + \\"name\\": \\"docusaurus-1-website\\", + \\"version\\": \\"2.0.0-alpha.58\\", + \\"private\\": true, + \\"scripts\\": { + \\"start\\": \\"docusaurus start\\", + \\"build\\": \\"docusaurus build\\", + \\"publish-gh-pages\\": \\"docusaurus-publish\\", + \\"examples\\": \\"docusaurus-examples\\", + \\"write-translations\\": \\"docusaurus-write-translations\\", + \\"docusaurus-version\\": \\"docusaurus-version\\", + \\"rename-version\\": \\"docusaurus-rename-version\\", + \\"crowdin-upload\\": \\"crowdin --config ../crowdin.yaml upload sources --auto-update -b master\\", + \\"crowdin-download\\": \\"crowdin --config ../crowdin.yaml download -b master\\", + \\"swizzle\\": \\"docusaurus swizzle\\", + \\"deploy\\": \\"docusaurus deploy\\", + \\"docusaurus\\": \\"docusaurus\\" + }, + \\"dependencies\\": { + \\"@docusaurus/core\\": \\"\\", + \\"@docusaurus/preset-classic\\": \\"\\", + \\"clsx\\": \\"^1.1.1\\", + \\"react\\": \\"^17.0.2\\", + \\"react-dom\\": \\"^17.0.2\\" + } +}", + ], + [ + "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_missing_version_site/src/css/customTheme.css", + ":root{ + --ifm-color-primary-lightest: #3CAD6E; + --ifm-color-primary-lighter: #359962; + --ifm-color-primary-light: #33925D; + --ifm-color-primary: #2E8555; + --ifm-color-primary-dark: #29784C; + --ifm-color-primary-darker: #277148; + --ifm-color-primary-darkest: #205D3B; +} +", + ], +] +`; + +exports[`migration CLI migrates simple website: copy 1`] = ` +[ + [ + "/packages/docusaurus-migrate/src/__tests__/__fixtures__/simple_website/website/pages/en", + "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_simple_site/src/pages", + ], + [ + "/packages/docusaurus-migrate/src/__tests__/__fixtures__/simple_website/website/static", + "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_simple_site/static", + ], +] +`; + +exports[`migration CLI migrates simple website: mkdirp 1`] = ` +[ + [ + "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_simple_site/src/pages", + ], +] +`; + +exports[`migration CLI migrates simple website: mkdirs 1`] = `[]`; + +exports[`migration CLI migrates simple website: write 1`] = ` +[ + [ + "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_simple_site/docusaurus.config.js", + "module.exports={ + \\"title\\": \\"Docusaurus\\", + \\"tagline\\": \\"Easy to Maintain Open Source Documentation Websites\\", + \\"url\\": \\"https://docusaurus.io\\", + \\"baseUrl\\": \\"/\\", + \\"organizationName\\": \\"facebook\\", + \\"projectName\\": \\"docusaurus\\", + \\"noIndex\\": false, + \\"scripts\\": [ + \\"https://buttons.github.io/buttons.js\\", + \\"https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.0/clipboard.min.js\\", + \\"/js/code-blocks-buttons.js\\" + ], + \\"favicon\\": \\"img/docusaurus.ico\\", + \\"customFields\\": { + \\"users\\": { + \\"caption\\": \\"DevSpace\\", + \\"image\\": \\"/img/users/devspace.svg\\", + \\"infoLink\\": \\"https://devspace.cloud/docs/\\", + \\"fbOpenSource\\": false, + \\"pinned\\": false + }, + \\"translationRecruitingLink\\": \\"https://crowdin.com/project/docusaurus\\", + \\"facebookAppId\\": \\"199138890728411\\" + }, + \\"onBrokenLinks\\": \\"log\\", + \\"onBrokenMarkdownLinks\\": \\"log\\", + \\"presets\\": [ + [ + \\"@docusaurus/preset-classic\\", + { + \\"docs\\": { + \\"showLastUpdateAuthor\\": true, + \\"showLastUpdateTime\\": true, + \\"editUrl\\": \\"https://github.com/facebook/docusaurus/edit/main/docs/\\", + \\"path\\": \\"../simple_website/docs\\" + }, + \\"blog\\": {}, + \\"theme\\": { + \\"customCss\\": \\"../simple_website/src/css/customTheme.css\\" + }, + \\"googleAnalytics\\": { + \\"trackingID\\": \\"UA-44373548-31\\" + } + } + ] + ], + \\"plugins\\": [], + \\"themeConfig\\": { + \\"navbar\\": { + \\"title\\": \\"Docusaurus\\", + \\"logo\\": { + \\"src\\": \\"img/docusaurus.svg\\" + }, + \\"items\\": [ + { + \\"to\\": \\"docs/installation\\", + \\"label\\": \\"Docs\\", + \\"position\\": \\"left\\" + }, + { + \\"to\\": \\"docs/tutorial-setup\\", + \\"label\\": \\"Tutorial\\", + \\"position\\": \\"left\\" + }, + { + \\"to\\": \\"/users\\", + \\"label\\": \\"Users\\", + \\"position\\": \\"left\\" + }, + { + \\"href\\": \\"https://github.com/facebook/docusaurus\\", + \\"label\\": \\"GitHub\\", + \\"position\\": \\"left\\" + } + ] + }, + \\"image\\": \\"img/docusaurus.png\\", + \\"footer\\": { + \\"links\\": [ + { + \\"title\\": \\"Community\\", + \\"items\\": [ + { + \\"label\\": \\"Twitter\\", + \\"to\\": \\"https://twitter.com/docusaurus\\" + } + ] + } + ], + \\"copyright\\": \\"Copyright © 2022 Facebook Inc.\\", + \\"logo\\": { + \\"src\\": \\"img/docusaurus_monochrome.svg\\" + } + }, + \\"algolia\\": { + \\"apiKey\\": \\"3eb9507824b8be89e7a199ecaa1a9d2c\\", + \\"indexName\\": \\"docusaurus\\", + \\"algoliaOptions\\": { + \\"facetFilters\\": [ + \\"language:LANGUAGE\\", + \\"version:VERSION\\" + ] + } + } + } +}", + ], + [ + "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_simple_site/package.json", + "{ + \\"name\\": \\"docusaurus-1-website\\", + \\"version\\": \\"2.0.0-alpha.58\\", + \\"private\\": true, + \\"scripts\\": { + \\"start\\": \\"docusaurus start\\", + \\"build\\": \\"docusaurus build\\", + \\"publish-gh-pages\\": \\"docusaurus-publish\\", + \\"examples\\": \\"docusaurus-examples\\", + \\"write-translations\\": \\"docusaurus-write-translations\\", + \\"docusaurus-version\\": \\"docusaurus-version\\", + \\"rename-version\\": \\"docusaurus-rename-version\\", + \\"crowdin-upload\\": \\"crowdin --config ../crowdin.yaml upload sources --auto-update -b master\\", + \\"crowdin-download\\": \\"crowdin --config ../crowdin.yaml download -b master\\", + \\"swizzle\\": \\"docusaurus swizzle\\", + \\"deploy\\": \\"docusaurus deploy\\", + \\"docusaurus\\": \\"docusaurus\\" + }, + \\"dependencies\\": { + \\"@docusaurus/core\\": \\"\\", + \\"@docusaurus/preset-classic\\": \\"\\", + \\"clsx\\": \\"^1.1.1\\", + \\"react\\": \\"^17.0.2\\", + \\"react-dom\\": \\"^17.0.2\\" + } +}", + ], + [ + "/packages/docusaurus-migrate/src/__tests__/__fixtures__/migrated_simple_site/src/css/customTheme.css", + ":root{ + --ifm-color-primary-lightest: #3CAD6E; + --ifm-color-primary-lighter: #359962; + --ifm-color-primary-light: #33925D; + --ifm-color-primary: #2E8555; + --ifm-color-primary-dark: #29784C; + --ifm-color-primary-darker: #277148; + --ifm-color-primary-darkest: #205D3B; +} +", + ], + [ + "/packages/docusaurus-migrate/src/__tests__/__fixtures__/simple_website/docs/api-commands.md", + "--- +id: commands +title: CLI Commands +--- +## Doc +", + ], + [ + "/packages/docusaurus-migrate/src/__tests__/__fixtures__/simple_website/docs/api-doc-markdown.md", + "--- +id: doc-markdown +title: Markdown Features +--- +## Doc +", + ], +] +`; diff --git a/packages/docusaurus-migrate/src/__tests__/frontMatter.test.ts b/packages/docusaurus-migrate/src/__tests__/frontMatter.test.ts index 1359b2b574ab..c75d885f34e5 100644 --- a/packages/docusaurus-migrate/src/__tests__/frontMatter.test.ts +++ b/packages/docusaurus-migrate/src/__tests__/frontMatter.test.ts @@ -7,21 +7,22 @@ import {shouldQuotifyFrontMatter} from '../frontMatter'; -describe('frontMatter', () => { - test('shouldQuotifyFrontMatter', () => { - expect(shouldQuotifyFrontMatter(['id', 'value'])).toEqual(false); +describe('shouldQuotifyFrontMatter', () => { + it('works', () => { + expect(shouldQuotifyFrontMatter(['id', 'value'])).toBe(false); expect( shouldQuotifyFrontMatter([ 'title', + // cSpell:ignore sàáâãäåçèéêëìíîïðòóôõöùúûüýÿ "Some title front matter with allowed special chars like sàáâãäåçèéêëìíîïðòóôõöùúûüýÿ!;,=+-_?'`&#()[]§%€$", ]), - ).toEqual(false); + ).toBe(false); - expect(shouldQuotifyFrontMatter(['title', 'Special char :'])).toEqual(true); + expect(shouldQuotifyFrontMatter(['title', 'Special char :'])).toBe(true); - expect(shouldQuotifyFrontMatter(['title', 'value!'])).toEqual(false); - expect(shouldQuotifyFrontMatter(['title', '!value'])).toEqual(true); + expect(shouldQuotifyFrontMatter(['title', 'value!'])).toBe(false); + expect(shouldQuotifyFrontMatter(['title', '!value'])).toBe(true); - expect(shouldQuotifyFrontMatter(['tags', '[tag1, tag2]'])).toEqual(false); + expect(shouldQuotifyFrontMatter(['tags', '[tag1, tag2]'])).toBe(false); }); }); diff --git a/packages/docusaurus-migrate/src/__tests__/index.test.ts b/packages/docusaurus-migrate/src/__tests__/index.test.ts new file mode 100644 index 000000000000..f4516276d2d9 --- /dev/null +++ b/packages/docusaurus-migrate/src/__tests__/index.test.ts @@ -0,0 +1,65 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {jest} from '@jest/globals'; +import {migrateDocusaurusProject} from '../index'; +import path from 'path'; +import fs from 'fs-extra'; +import {posixPath} from '@docusaurus/utils'; + +async function testMigration(siteDir: string, newDir: string) { + const writeMock = jest.spyOn(fs, 'outputFile').mockImplementation(() => {}); + const mkdirpMock = jest.spyOn(fs, 'mkdirp').mockImplementation(() => {}); + const mkdirsMock = jest.spyOn(fs, 'mkdirs').mockImplementation(() => {}); + const copyMock = jest.spyOn(fs, 'copy').mockImplementation(() => {}); + await migrateDocusaurusProject(siteDir, newDir, true, true); + expect( + writeMock.mock.calls.sort((a, b) => + posixPath(a[0] as string).localeCompare(posixPath(b[0] as string)), + ), + ).toMatchSnapshot('write'); + expect( + mkdirpMock.mock.calls.sort((a, b) => + posixPath(a[0] as string).localeCompare(posixPath(b[0] as string)), + ), + ).toMatchSnapshot('mkdirp'); + expect( + mkdirsMock.mock.calls.sort((a, b) => + posixPath(a[0] as string).localeCompare(posixPath(b[0] as string)), + ), + ).toMatchSnapshot('mkdirs'); + expect( + copyMock.mock.calls.sort((a, b) => + posixPath(a[0] as string).localeCompare(posixPath(b[0] as string)), + ), + ).toMatchSnapshot('copy'); + writeMock.mockRestore(); + mkdirpMock.mockRestore(); + mkdirsMock.mockRestore(); + copyMock.mockRestore(); +} + +describe('migration CLI', () => { + const fixtureDir = path.join(__dirname, '__fixtures__'); + it('migrates simple website', async () => { + const siteDir = path.join(fixtureDir, 'simple_website', 'website'); + const newDir = path.join(fixtureDir, 'migrated_simple_site'); + await testMigration(siteDir, newDir); + }); + + it('migrates complex website', async () => { + const siteDir = path.join(fixtureDir, 'complex_website', 'website'); + const newDir = path.join(fixtureDir, 'migrated_complex_site'); + await testMigration(siteDir, newDir); + }); + + it('migrates missing versions', async () => { + const siteDir = path.join(fixtureDir, 'missing_version_website', 'website'); + const newDir = path.join(fixtureDir, 'migrated_missing_version_site'); + await testMigration(siteDir, newDir); + }); +}); diff --git a/packages/docusaurus-migrate/src/__tests__/migration.test.ts b/packages/docusaurus-migrate/src/__tests__/migration.test.ts deleted file mode 100644 index 4b0397f9e8b2..000000000000 --- a/packages/docusaurus-migrate/src/__tests__/migration.test.ts +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import {migrateDocusaurusProject} from '../index'; -import path from 'path'; -import fs from 'fs-extra'; - -describe('migration test', () => { - test('simple website', async () => { - const siteDir = path.join( - __dirname, - '__fixtures__', - 'simple_website', - 'website', - ); - const newDir = path.join(__dirname, '__fixtures__', 'migrated_simple_site'); - await expect( - migrateDocusaurusProject(siteDir, newDir), - ).resolves.toBeUndefined(); - fs.removeSync(newDir); - }); - test('complex website', async () => { - const siteDir = path.join( - __dirname, - '__fixtures__', - 'complex_website', - 'website', - ); - const newDir = path.join( - __dirname, - '__fixtures__', - 'migrated_complex_site', - ); - await expect( - migrateDocusaurusProject(siteDir, newDir), - ).resolves.toBeUndefined(); - fs.removeSync(newDir); - }); - - test('missing versions', async () => { - const siteDir = path.join( - __dirname, - '__fixtures__', - 'missing_version_website', - 'website', - ); - const newDir = path.join( - __dirname, - '__fixtures__', - 'migrated_missing_version_site', - ); - await expect( - migrateDocusaurusProject(siteDir, newDir), - ).resolves.toBeUndefined(); - fs.removeSync(newDir); - }); -}); diff --git a/packages/docusaurus-migrate/src/__tests__/migrationConfig.test.ts b/packages/docusaurus-migrate/src/__tests__/migrationConfig.test.ts index 2f57bb7cb207..6e77e277e042 100644 --- a/packages/docusaurus-migrate/src/__tests__/migrationConfig.test.ts +++ b/packages/docusaurus-migrate/src/__tests__/migrationConfig.test.ts @@ -10,7 +10,7 @@ import {createConfigFile} from '../index'; import type {VersionOneConfig} from '../types'; describe('create config', () => { - test('simple test', () => { + it('simple test', () => { const v1Config: VersionOneConfig = importFresh( `${__dirname}/__fixtures__/sourceSiteConfig.js`, ); diff --git a/packages/docusaurus-migrate/src/frontMatter.ts b/packages/docusaurus-migrate/src/frontMatter.ts index cd83993f0407..2d03fb0e40e5 100644 --- a/packages/docusaurus-migrate/src/frontMatter.ts +++ b/packages/docusaurus-migrate/src/frontMatter.ts @@ -37,8 +37,8 @@ export default function extractMetadata(content: string): Data { // New line characters => to handle all operating systems. const lines = (both.header ?? '').split(/\r?\n/); - for (let i = 0; i < lines.length - 1; i += 1) { - const keyValue = lines[i].split(':'); + lines.slice(0, -1).forEach((line) => { + const keyValue = line.split(':') as [string, ...string[]]; const key = keyValue[0].trim(); let value = keyValue.slice(1).join(':').trim(); try { @@ -47,7 +47,7 @@ export default function extractMetadata(content: string): Data { // Ignore the error as it means it's not a JSON value. } metadata[key] = value; - } + }); return {metadata, rawContent: both.content}; } @@ -59,7 +59,7 @@ export function shouldQuotifyFrontMatter([key, value]: [ if (key === 'tags') { return false; } - if (String(value).match(/^("|').+("|')$/)) { + if (String(value).match(/^(?["']).+\1$/)) { return false; } // title: !something needs quotes because otherwise it's a YAML tag. @@ -69,6 +69,7 @@ export function shouldQuotifyFrontMatter([key, value]: [ // TODO this is not ideal to have to maintain such a list of allowed chars // maybe we should quotify if gray-matter throws instead? return !String(value).match( - /^([\w .\-sàáâãäåçèéêëìíîïðòóôõöùúûüýÿ!;,=+_?'`&#()[\]§%€$])+$/, + // cSpell:ignore àáâãäåçèéêëìíîïðòóôõöùúûüýÿ + /^[\w .\-àáâãäåçèéêëìíîïðòóôõöùúûüýÿ!;,=+?'`&#()[\]§%€$]+$/, ); } diff --git a/packages/docusaurus-migrate/src/index.ts b/packages/docusaurus-migrate/src/index.ts index d6e45d0ffb4d..764718fc5441 100644 --- a/packages/docusaurus-migrate/src/index.ts +++ b/packages/docusaurus-migrate/src/index.ts @@ -8,11 +8,10 @@ import fs from 'fs-extra'; import importFresh from 'import-fresh'; import logger from '@docusaurus/logger'; -import glob from 'glob'; +import {Globby} from '@docusaurus/utils'; import Color from 'color'; import type { - ClassicPresetEntries, SidebarEntry, SidebarEntries, VersionOneConfig, @@ -26,18 +25,18 @@ import path from 'path'; const DOCUSAURUS_VERSION = (importFresh('../package.json') as {version: string}) .version; -export function walk(dir: string): Array { - let results: Array = []; - const list = fs.readdirSync(dir); - list.forEach((file: string) => { +async function walk(dir: string): Promise { + const results: string[] = []; + const list = await fs.readdir(dir); + for (const file of list) { const fullPath = `${dir}/${file}`; - const stat = fs.statSync(fullPath); - if (stat && stat.isDirectory()) { - results = results.concat(walk(fullPath)); + const stat = await fs.stat(fullPath); + if (stat.isDirectory()) { + results.push(...(await walk(fullPath))); } else { results.push(fullPath); } - }); + } return results; } @@ -46,25 +45,29 @@ function sanitizedFileContent( migrateMDFiles: boolean, ): string { const extractedData = extractMetadata(content); - const extractedMetaData = Object.entries(extractedData.metadata).reduce( - (metaData, [key, value]) => - `${metaData}\n${key}: ${ - shouldQuotifyFrontMatter([key, value]) ? `"${value}"` : value - }`, - '', - ); - const sanitizedData = `---${extractedMetaData}\n---\n${ - migrateMDFiles - ? sanitizeMD(extractedData.rawContent) - : extractedData.rawContent - }`; + const extractedMetaData = Object.entries(extractedData.metadata) + .map( + ([key, value]) => + `${key}: ${ + shouldQuotifyFrontMatter([key, value]) ? `"${value}"` : value + }`, + ) + .join('\n'); + const sanitizedData = `--- +${extractedMetaData} +--- +${ + migrateMDFiles + ? sanitizeMD(extractedData.rawContent) + : extractedData.rawContent +}`; return sanitizedData; } -// TODO refactor this new type should be used everywhere instead of passing many params to each method type MigrationContext = { siteDir: string; newDir: string; + deps: {[key: string]: string}; shouldMigrateMdFiles: boolean; shouldMigratePages: boolean; v1Config: VersionOneConfig; @@ -77,12 +80,20 @@ export async function migrateDocusaurusProject( shouldMigrateMdFiles: boolean = false, shouldMigratePages: boolean = false, ): Promise { - function createMigrationContext(): MigrationContext { + async function createMigrationContext(): Promise { const v1Config = importFresh(`${siteDir}/siteConfig`) as VersionOneConfig; logger.info('Starting migration from v1 to v2...'); + const deps = { + '@docusaurus/core': DOCUSAURUS_VERSION, + '@docusaurus/preset-classic': DOCUSAURUS_VERSION, + clsx: '^1.1.1', + react: '^17.0.2', + 'react-dom': '^17.0.2', + }; const partialMigrationContext = { siteDir, newDir, + deps, shouldMigrateMdFiles, shouldMigratePages, v1Config, @@ -94,102 +105,88 @@ export async function migrateDocusaurusProject( }; } - const migrationContext = createMigrationContext(); - - // TODO need refactor legacy, we pass migrationContext to all methods - const siteConfig = migrationContext.v1Config; - const config = migrationContext.v2Config; - - const classicPreset = migrationContext.v2Config.presets[0][1]; - - const deps: Record = { - '@docusaurus/core': DOCUSAURUS_VERSION, - '@docusaurus/preset-classic': DOCUSAURUS_VERSION, - clsx: '^1.1.1', - react: '^17.0.1', - 'react-dom': '^17.0.1', - }; + const migrationContext = await createMigrationContext(); let errorCount = 0; try { - createClientRedirects(siteConfig, deps, config); + createClientRedirects(migrationContext); logger.success('Created client redirect for non clean URL'); - } catch (e) { - logger.error(`Failed to creating redirects: ${e}`); + } catch (err) { + logger.error(`Failed to creating redirects: ${err}`); errorCount += 1; } if (shouldMigratePages) { try { - createPages(newDir, siteDir); + await createPages(migrationContext); logger.success( 'Created new doc pages (check migration page for more details)', ); - } catch (e) { - logger.error(`Failed to create new doc pages: ${e}`); + } catch (err) { + logger.error(`Failed to create new doc pages: ${err}`); errorCount += 1; } } else { try { - createDefaultLandingPage(newDir); + await createDefaultLandingPage(migrationContext); logger.success( 'Created landing page (check migration page for more details)', ); - } catch (e) { - logger.error(`Failed to create landing page: ${e}`); + } catch (err) { + logger.error(`Failed to create landing page: ${err}`); errorCount += 1; } } try { - migrateStaticFiles(siteDir, newDir); + await migrateStaticFiles(migrationContext); logger.success('Migrated static folder'); - } catch (e) { - logger.error(`Failed to copy static folder: ${e}`); + } catch (err) { + logger.error(`Failed to copy static folder: ${err}`); errorCount += 1; } try { - migrateBlogFiles(siteDir, newDir, classicPreset, shouldMigrateMdFiles); - } catch (e) { - logger.error(`Failed to migrate blogs: ${e}`); + await migrateBlogFiles(migrationContext); + } catch (err) { + logger.error(`Failed to migrate blogs: ${err}`); errorCount += 1; } try { - handleVersioning(siteDir, siteConfig, newDir, config, shouldMigrateMdFiles); - } catch (e) { - logger.error(`Failed to migrate versioned docs: ${e}`); + await handleVersioning(migrationContext); + } catch (err) { + logger.error(`Failed to migrate versioned docs: ${err}`); errorCount += 1; } try { - migrateLatestDocs(siteDir, newDir, shouldMigrateMdFiles, classicPreset); - } catch (e) { - logger.error(`Failed to migrate docs: ${e}`); + await migrateLatestDocs(migrationContext); + } catch (err) { + logger.error(`Failed to migrate docs: ${err}`); errorCount += 1; } try { - migrateLatestSidebar(siteDir, newDir, classicPreset, siteConfig); - } catch (e) { - logger.error(`Failed to migrate sidebar: ${e}`); + await migrateLatestSidebar(migrationContext); + } catch (err) { + logger.error(`Failed to migrate sidebar: ${err}`); errorCount += 1; } try { - fs.writeFileSync( + await fs.outputFile( path.join(newDir, 'docusaurus.config.js'), - `module.exports=${JSON.stringify(config, null, 2)}`, + `module.exports=${JSON.stringify(migrationContext.v2Config, null, 2)}`, ); logger.success( `Created a new config file with new navbar and footer config`, ); - } catch (e) { - logger.error(`Failed to create config file: ${e}`); + } catch (err) { + logger.error(`Failed to create config file: ${err}`); errorCount += 1; } try { - migratePackageFile(siteDir, deps, newDir); - } catch (e) { + await migratePackageFile(migrationContext); + } catch (err) { logger.error( - `Error occurred while creating package.json file for project: ${e}`, + `Error occurred while creating package.json file for project: ${err}`, ); errorCount += 1; } @@ -209,7 +206,7 @@ export function createConfigFile({ 'v1Config' | 'siteDir' | 'newDir' >): VersionTwoConfig { const siteConfig = v1Config; - const customConfigFields: Record = {}; + const customConfigFields: {[key: string]: unknown} = {}; // add fields that are unknown to v2 to customConfigFields Object.keys(siteConfig).forEach((key) => { const knownFields = [ @@ -365,46 +362,45 @@ export function createConfigFile({ }; } -function createClientRedirects( - siteConfig: VersionOneConfig, - deps: {[key: string]: string}, - config: VersionTwoConfig, -): void { - if (!siteConfig.cleanUrl) { - deps['@docusaurus/plugin-client-redirects'] = DOCUSAURUS_VERSION; - config.plugins.push([ +function createClientRedirects(context: MigrationContext): void { + if (!context.v1Config.cleanUrl) { + context.deps['@docusaurus/plugin-client-redirects'] = DOCUSAURUS_VERSION; + context.v2Config.plugins.push([ '@docusaurus/plugin-client-redirects', {fromExtensions: ['html']}, ]); } } -function createPages(newDir: string, siteDir: string): void { - fs.mkdirpSync(path.join(newDir, 'src', 'pages')); - if (fs.existsSync(path.join(siteDir, 'pages', 'en'))) { +async function createPages(context: MigrationContext) { + const {newDir, siteDir} = context; + await fs.mkdirp(path.join(newDir, 'src', 'pages')); + if (await fs.pathExists(path.join(siteDir, 'pages', 'en'))) { try { - fs.copySync( + await fs.copy( path.join(siteDir, 'pages', 'en'), path.join(newDir, 'src', 'pages'), ); - const files = glob.sync('**/*.js', { + const files = await Globby('**/*.js', { cwd: path.join(newDir, 'src', 'pages'), }); - files.forEach((file) => { - const filePath = path.join(newDir, 'src', 'pages', file); - const content = String(fs.readFileSync(filePath)); - fs.writeFileSync(filePath, migratePage(content)); - }); - } catch (e) { - logger.error(`Unable to migrate Pages: ${e}`); - createDefaultLandingPage(newDir); + await Promise.all( + files.map(async (file) => { + const filePath = path.join(newDir, 'src', 'pages', file); + const content = await fs.readFile(filePath, 'utf-8'); + await fs.outputFile(filePath, migratePage(content)); + }), + ); + } catch (err) { + logger.error(`Unable to migrate Pages: ${err}`); + await createDefaultLandingPage(context); } } else { logger.info('Ignoring Pages'); } } -function createDefaultLandingPage(newDir: string) { +async function createDefaultLandingPage({newDir}: MigrationContext) { const indexPage = `import Layout from "@theme/Layout"; import React from "react"; @@ -412,65 +408,53 @@ function createDefaultLandingPage(newDir: string) { return ; }; `; - fs.mkdirpSync(`${newDir}/src/pages/`); - fs.writeFileSync(`${newDir}/src/pages/index.js`, indexPage); + await fs.outputFile(`${newDir}/src/pages/index.js`, indexPage); } -function migrateStaticFiles(siteDir: string, newDir: string): void { - if (fs.existsSync(path.join(siteDir, 'static'))) { - fs.copySync(path.join(siteDir, 'static'), path.join(newDir, 'static')); +async function migrateStaticFiles({siteDir, newDir}: MigrationContext) { + if (await fs.pathExists(path.join(siteDir, 'static'))) { + await fs.copy(path.join(siteDir, 'static'), path.join(newDir, 'static')); } else { - fs.mkdirSync(path.join(newDir, 'static')); + await fs.mkdir(path.join(newDir, 'static')); } } -function migrateBlogFiles( - siteDir: string, - newDir: string, - classicPreset: ClassicPresetEntries, - migrateMDFiles: boolean, -): void { - if (fs.existsSync(path.join(siteDir, 'blog'))) { - fs.copySync(path.join(siteDir, 'blog'), path.join(newDir, 'blog')); - const files = walk(path.join(newDir, 'blog')); - files.forEach((file) => { - const content = String(fs.readFileSync(file)); - fs.writeFileSync(file, sanitizedFileContent(content, migrateMDFiles)); - }); - classicPreset.blog.path = 'blog'; +async function migrateBlogFiles(context: MigrationContext) { + const {siteDir, newDir, shouldMigrateMdFiles} = context; + if (await fs.pathExists(path.join(siteDir, 'blog'))) { + await fs.copy(path.join(siteDir, 'blog'), path.join(newDir, 'blog')); + const files = await walk(path.join(newDir, 'blog')); + await Promise.all( + files.map(async (file) => { + const content = await fs.readFile(file, 'utf-8'); + await fs.outputFile( + file, + sanitizedFileContent(content, shouldMigrateMdFiles), + ); + }), + ); + context.v2Config.presets[0][1].blog.path = 'blog'; logger.success('Migrated blogs to version 2 with change in front matter'); } else { logger.warn('Blog not found. Skipping migration for blog'); } } -function handleVersioning( - siteDir: string, - siteConfig: VersionOneConfig, - newDir: string, - config: VersionTwoConfig, - migrateMDFiles: boolean, -): void { - if (fs.existsSync(path.join(siteDir, 'versions.json'))) { - const loadedVersions: Array = JSON.parse( - String(fs.readFileSync(path.join(siteDir, 'versions.json'))), +async function handleVersioning(context: MigrationContext) { + const {siteDir, newDir} = context; + if (await fs.pathExists(path.join(siteDir, 'versions.json'))) { + const loadedVersions: string[] = JSON.parse( + await fs.readFile(path.join(siteDir, 'versions.json'), 'utf-8'), ); - fs.copyFileSync( + await fs.copyFile( path.join(siteDir, 'versions.json'), path.join(newDir, 'versions.json'), ); const versions = loadedVersions.reverse(); - const versionRegex = new RegExp(`version-(${versions.join('|')})-`, 'mgi'); - migrateVersionedSidebar(siteDir, newDir, versions, versionRegex, config); - fs.mkdirpSync(path.join(newDir, 'versioned_docs')); - migrateVersionedDocs( - siteConfig, - versions, - siteDir, - newDir, - versionRegex, - migrateMDFiles, - ); + const versionRegex = new RegExp(`version-(${versions.join('|')})-`, 'gim'); + await migrateVersionedSidebar(context, versions, versionRegex); + await fs.mkdirp(path.join(newDir, 'versioned_docs')); + await migrateVersionedDocs(context, versions, versionRegex); logger.success`Migrated version docs and sidebar. The following doc versions have been created:name=${loadedVersions}`; } else { logger.warn( @@ -479,69 +463,78 @@ function handleVersioning( } } -function migrateVersionedDocs( - siteConfig: VersionOneConfig, +async function migrateVersionedDocs( + context: MigrationContext, versions: string[], - siteDir: string, - newDir: string, versionRegex: RegExp, - migrateMDFiles: boolean, -): void { - versions.reverse().forEach((version, index) => { - if (index === 0) { - fs.copySync( - path.join(siteDir, '..', siteConfig.customDocsPath || 'docs'), - path.join(newDir, 'versioned_docs', `version-${version}`), - ); - fs.copySync( - path.join(siteDir, 'versioned_docs', `version-${version}`), - path.join(newDir, 'versioned_docs', `version-${version}`), - ); - return; - } - try { - fs.mkdirsSync(path.join(newDir, 'versioned_docs', `version-${version}`)); - fs.copySync( - path.join(newDir, 'versioned_docs', `version-${versions[index - 1]}`), - path.join(newDir, 'versioned_docs', `version-${version}`), - ); - fs.copySync( - path.join(siteDir, 'versioned_docs', `version-${version}`), - path.join(newDir, 'versioned_docs', `version-${version}`), - ); - } catch { - fs.copySync( - path.join(newDir, 'versioned_docs', `version-${versions[index - 1]}`), - path.join(newDir, 'versioned_docs', `version-${version}`), - ); - } - }); - const files = walk(path.join(newDir, 'versioned_docs')); - files.forEach((pathToFile) => { - if (path.extname(pathToFile) === '.md') { - const content = fs.readFileSync(pathToFile).toString(); - fs.writeFileSync( - pathToFile, - sanitizedFileContent(content.replace(versionRegex, ''), migrateMDFiles), - ); - } - }); +) { + const {siteDir, newDir, shouldMigrateMdFiles} = context; + await Promise.all( + versions.reverse().map(async (version, index) => { + if (index === 0) { + await fs.copy( + path.join(siteDir, '..', context.v1Config.customDocsPath || 'docs'), + path.join(newDir, 'versioned_docs', `version-${version}`), + ); + await fs.copy( + path.join(siteDir, 'versioned_docs', `version-${version}`), + path.join(newDir, 'versioned_docs', `version-${version}`), + ); + return; + } + try { + await fs.mkdirs( + path.join(newDir, 'versioned_docs', `version-${version}`), + ); + await fs.copy( + path.join(newDir, 'versioned_docs', `version-${versions[index - 1]}`), + path.join(newDir, 'versioned_docs', `version-${version}`), + ); + await fs.copy( + path.join(siteDir, 'versioned_docs', `version-${version}`), + path.join(newDir, 'versioned_docs', `version-${version}`), + ); + } catch { + await fs.copy( + path.join(newDir, 'versioned_docs', `version-${versions[index - 1]}`), + path.join(newDir, 'versioned_docs', `version-${version}`), + ); + } + }), + ); + const files = await walk(path.join(newDir, 'versioned_docs')); + await Promise.all( + files.map(async (pathToFile) => { + if (path.extname(pathToFile) === '.md') { + const content = await fs.readFile(pathToFile, 'utf-8'); + await fs.outputFile( + pathToFile, + sanitizedFileContent( + content.replace(versionRegex, ''), + shouldMigrateMdFiles, + ), + ); + } + }), + ); } -function migrateVersionedSidebar( - siteDir: string, - newDir: string, +async function migrateVersionedSidebar( + context: MigrationContext, versions: string[], versionRegex: RegExp, - config: VersionTwoConfig, -): void { - if (fs.existsSync(path.join(siteDir, 'versioned_sidebars'))) { - fs.mkdirpSync(path.join(newDir, 'versioned_sidebars')); +) { + const {siteDir, newDir} = context; + if (await fs.pathExists(path.join(siteDir, 'versioned_sidebars'))) { + await fs.mkdirp(path.join(newDir, 'versioned_sidebars')); const sidebars: { entries: SidebarEntries; version: string; }[] = []; - versions.forEach((version, index) => { + // Order matters: if a sidebar file doesn't exist, we have to use the + // previous version's + for (let i = 0; i < versions.length; i += 1) { + const version = versions[i]!; let sidebarEntries: SidebarEntries; const sidebarPath = path.join( siteDir, @@ -549,80 +542,75 @@ function migrateVersionedSidebar( `version-${version}-sidebars.json`, ); try { - fs.statSync(sidebarPath); - sidebarEntries = JSON.parse(String(fs.readFileSync(sidebarPath))); + sidebarEntries = JSON.parse(await fs.readFile(sidebarPath, 'utf-8')); } catch { - sidebars.push({version, entries: sidebars[index - 1].entries}); + sidebars.push({version, entries: sidebars[i - 1]!.entries}); return; } const newSidebar = Object.entries(sidebarEntries).reduce( (topLevel: SidebarEntries, value) => { const key = value[0].replace(versionRegex, ''); - topLevel[key] = Object.entries(value[1]).reduce( - ( - acc: {[key: string]: Array | string>}, - val, - ) => { - acc[val[0].replace(versionRegex, '')] = ( - val[1] as Array - ).map((item) => { - if (typeof item === 'string') { - return item.replace(versionRegex, ''); - } - return { - type: 'category', - label: item.label, - ids: item.ids.map((id) => id.replace(versionRegex, '')), - }; - }); - return acc; - }, - {}, - ); - return topLevel; - }, - {}, - ); - sidebars.push({version, entries: newSidebar}); - }); - sidebars.forEach((sidebar) => { - const newSidebar = Object.entries(sidebar.entries).reduce( - (acc: SidebarEntries, val) => { - const key = `version-${sidebar.version}/${val[0]}`; - acc[key] = Object.entries(val[1]).map((value) => ({ - type: 'category', - label: value[0], - items: (value[1] as Array).map((sidebarItem) => { - if (typeof sidebarItem === 'string') { - return { - type: 'doc', - id: `version-${sidebar.version}/${sidebarItem}`, - }; + topLevel[key] = Object.entries(value[1]).reduce((acc, val) => { + acc[val[0].replace(versionRegex, '')] = ( + val[1] as SidebarEntry[] + ).map((item) => { + if (typeof item === 'string') { + return item.replace(versionRegex, ''); } return { type: 'category', - label: sidebarItem.label, - items: sidebarItem.ids.map((id: string) => ({ - type: 'doc', - id: `version-${sidebar.version}/${id}`, - })), + label: item.label, + ids: item.ids.map((id) => id.replace(versionRegex, '')), }; - }), - })); - return acc; + }); + return acc; + }, {} as {[key: string]: Array}); + return topLevel; }, {}, ); - fs.writeFileSync( - path.join( - newDir, - 'versioned_sidebars', - `version-${sidebar.version}-sidebars.json`, - ), - JSON.stringify(newSidebar, null, 2), - ); - }); - config.themeConfig.navbar.items.push({ + sidebars.push({version, entries: newSidebar}); + } + await Promise.all( + sidebars.map(async (sidebar) => { + const newSidebar = Object.entries(sidebar.entries).reduce( + (acc, val) => { + const key = `version-${sidebar.version}/${val[0]}`; + acc[key] = Object.entries(val[1]).map((value) => ({ + type: 'category', + label: value[0], + items: (value[1] as SidebarEntry[]).map((sidebarItem) => { + if (typeof sidebarItem === 'string') { + return { + type: 'doc', + id: `version-${sidebar.version}/${sidebarItem}`, + }; + } + return { + type: 'category', + label: sidebarItem.label, + items: sidebarItem.ids.map((id) => ({ + type: 'doc', + id: `version-${sidebar.version}/${id}`, + })), + }; + }), + })); + return acc; + }, + {} as SidebarEntries, + ); + await fs.outputFile( + path.join( + newDir, + 'versioned_sidebars', + `version-${sidebar.version}-sidebars.json`, + ), + JSON.stringify(newSidebar, null, 2), + ); + }), + ); + context.v2Config.themeConfig.navbar.items.push({ label: 'Version', to: 'docs', position: 'right', @@ -649,80 +637,74 @@ function migrateVersionedSidebar( } } -function migrateLatestSidebar( - siteDir: string, - newDir: string, - classicPreset: ClassicPresetEntries, - siteConfig: VersionOneConfig, -): void { +async function migrateLatestSidebar(context: MigrationContext) { + const {siteDir, newDir} = context; try { - fs.copyFileSync( + await fs.copyFile( path.join(siteDir, 'sidebars.json'), path.join(newDir, 'sidebars.json'), ); - classicPreset.docs.sidebarPath = path.join( + context.v2Config.presets[0][1].docs.sidebarPath = path.join( path.relative(newDir, siteDir), 'sidebars.json', ); } catch { logger.warn('Sidebar not found. Skipping migration for sidebar'); } - if (siteConfig.colors) { - const primaryColor = Color(siteConfig.colors.primaryColor); + if (context.v1Config.colors) { + const primaryColor = Color(context.v1Config.colors.primaryColor); const css = `:root{ --ifm-color-primary-lightest: ${primaryColor.darken(-0.3).hex()}; --ifm-color-primary-lighter: ${primaryColor.darken(-0.15).hex()}; --ifm-color-primary-light: ${primaryColor.darken(-0.1).hex()}; - --ifm-color-primary: ${siteConfig.colors.primaryColor}; + --ifm-color-primary: ${primaryColor.hex()}; --ifm-color-primary-dark: ${primaryColor.darken(0.1).hex()}; --ifm-color-primary-darker: ${primaryColor.darken(0.15).hex()}; --ifm-color-primary-darkest: ${primaryColor.darken(0.3).hex()}; } `; - fs.mkdirpSync(path.join(newDir, 'src', 'css')); - fs.writeFileSync(path.join(newDir, 'src', 'css', 'customTheme.css'), css); - classicPreset.theme.customCss = path.join( + await fs.outputFile( + path.join(newDir, 'src', 'css', 'customTheme.css'), + css, + ); + context.v2Config.presets[0][1].theme.customCss = path.join( path.relative(newDir, path.join(siteDir, '..')), - 'src', - 'css', - 'customTheme.css', + 'src/css/customTheme.css', ); } } -function migrateLatestDocs( - siteDir: string, - newDir: string, - migrateMDFiles: boolean, - classicPreset: ClassicPresetEntries, -): void { - if (fs.existsSync(path.join(siteDir, '..', 'docs'))) { - classicPreset.docs.path = path.join( +async function migrateLatestDocs(context: MigrationContext) { + const {siteDir, newDir, shouldMigrateMdFiles} = context; + if (await fs.pathExists(path.join(siteDir, '..', 'docs'))) { + context.v2Config.presets[0][1].docs.path = path.join( path.relative(newDir, path.join(siteDir, '..')), 'docs', ); - const files = walk(path.join(siteDir, '..', 'docs')); - files.forEach((file) => { - if (path.extname(file) === '.md') { - const content = fs.readFileSync(file).toString(); - fs.writeFileSync(file, sanitizedFileContent(content, migrateMDFiles)); - } - }); + const files = await walk(path.join(siteDir, '..', 'docs')); + await Promise.all( + files.map(async (file) => { + if (path.extname(file) === '.md') { + const content = await fs.readFile(file, 'utf-8'); + await fs.outputFile( + file, + sanitizedFileContent(content, shouldMigrateMdFiles), + ); + } + }), + ); logger.success('Migrated docs to version 2'); } else { logger.warn('Docs folder not found. Skipping migration for docs'); } } -function migratePackageFile( - siteDir: string, - deps: {[key: string]: string}, - newDir: string, -): void { +async function migratePackageFile(context: MigrationContext): Promise { + const {deps, siteDir, newDir} = context; const packageFile = importFresh(`${siteDir}/package.json`) as { - scripts?: Record; - dependencies?: Record; - devDependencies?: Record; + scripts?: {[key: string]: string}; + dependencies?: {[key: string]: string}; + devDependencies?: {[key: string]: string}; [otherKey: string]: unknown; }; packageFile.scripts = { @@ -744,7 +726,7 @@ function migratePackageFile( ...packageFile.dependencies, ...deps, }; - fs.writeFileSync( + await fs.outputFile( path.join(newDir, 'package.json'), JSON.stringify(packageFile, null, 2), ); @@ -755,14 +737,16 @@ export async function migrateMDToMDX( siteDir: string, newDir: string, ): Promise { - fs.mkdirpSync(newDir); - fs.copySync(siteDir, newDir); - const files = walk(newDir); - files.forEach((filePath) => { - if (path.extname(filePath) === '.md') { - const content = fs.readFileSync(filePath).toString(); - fs.writeFileSync(filePath, sanitizedFileContent(content, true)); - } - }); + await fs.mkdirp(newDir); + await fs.copy(siteDir, newDir); + const files = await walk(newDir); + await Promise.all( + files.map(async (filePath) => { + if (path.extname(filePath) === '.md') { + const content = await fs.readFile(filePath, 'utf-8'); + await fs.outputFile(filePath, sanitizedFileContent(content, true)); + } + }), + ); logger.success`Successfully migrated path=${siteDir} to path=${newDir}`; } diff --git a/packages/docusaurus-migrate/src/sanitizeMD.ts b/packages/docusaurus-migrate/src/sanitizeMD.ts index 1ebecb01c339..55bd803aea51 100644 --- a/packages/docusaurus-migrate/src/sanitizeMD.ts +++ b/packages/docusaurus-migrate/src/sanitizeMD.ts @@ -14,6 +14,7 @@ import remarkStringify from 'remark-stringify'; import htmlTags from 'html-tags'; import toText from 'hast-util-to-string'; import type {Code, InlineCode} from 'mdast'; +import type {Element, Text} from 'hast'; const tags = htmlTags.reduce((acc: {[key: string]: boolean}, tag) => { acc[tag] = true; @@ -34,12 +35,14 @@ export default function sanitizeMD(code: string): string { .stringify(markdownTree); const htmlTree = unified().use(parse).parse(markdownString); - visit(htmlTree, 'element', (node: any) => { + + visit(htmlTree, 'element', (node: Element) => { if (!tags[node.tagName as string]) { - node.type = 'text'; - node.value = node.tagName + toText(node); - delete node.children; - delete node.tagName; + (node as Element | Text).type = 'text'; + (node as Element & Partial>).value = + node.tagName + toText(node); + delete (node as Partial).children; + delete (node as Partial).tagName; } }); return toJsx(htmlTree) diff --git a/packages/docusaurus-migrate/src/transform.ts b/packages/docusaurus-migrate/src/transform.ts index f8a840531696..24820cf7e9b5 100644 --- a/packages/docusaurus-migrate/src/transform.ts +++ b/packages/docusaurus-migrate/src/transform.ts @@ -12,6 +12,9 @@ import jscodeshift, { type Collection, type TemplateElement, VariableDeclarator, + type CallExpression, + type MemberExpression, + type Identifier, } from 'jscodeshift'; const empty = () => @@ -29,9 +32,15 @@ const property = (key: string, value: ArrowFunctionExpression) => jscodeshift.objectProperty(jscodeshift.identifier(key), value); const processCallExpression = (node: ASTPath) => { - const args = (node?.value?.init as any)?.arguments[0]; + const args = (node?.value?.init as CallExpression)?.arguments[0]; + if (!args) { + return; + } if (args.type === 'Literal') { - if (args.value.includes('../../core/CompLibrary')) { + if ( + typeof args.value === 'string' && + args.value.includes('../../core/CompLibrary') + ) { const newDeclarator = jscodeshift.variableDeclarator( node.value.id, jscodeshift.objectExpression([ @@ -60,7 +69,14 @@ const processCallExpression = (node: ASTPath) => { }; const processMemberExpression = (node: ASTPath) => { - const args = (node?.value?.init as any)?.object?.arguments[0]; + const object = (node?.value?.init as MemberExpression)?.object; + if (!(object.type === 'CallExpression')) { + return; + } + const args = object.arguments[0]; + if (!args) { + return; + } if (args.type === 'Literal') { if (args.value === '../../core/CompLibrary.js') { const newDeclarator = jscodeshift.variableDeclarator( @@ -72,7 +88,7 @@ const processMemberExpression = (node: ASTPath) => { ]), ); jscodeshift(node).replaceWith(newDeclarator); - } else if (args.value.match(/server/)) { + } else if (typeof args.value === 'string' && args.value.match(/server/)) { const newDeclarator = jscodeshift.variableDeclarator( node.value.id, empty(), @@ -107,7 +123,7 @@ export default function transformer(file: string): string { } }); if (r[r.length - 1]) { - jscodeshift(r[r.length - 1].parent).insertAfter( + jscodeshift(r[r.length - 1]!.parent).insertAfter( jscodeshift.importDeclaration( [jscodeshift.importDefaultSpecifier(jscodeshift.identifier('Layout'))], jscodeshift.literal('@theme/Layout'), @@ -146,7 +162,7 @@ export default function transformer(file: string): string { [ jscodeshift.jsxElement( jscodeshift.jsxOpeningElement( - jscodeshift.jsxIdentifier((p.value.right as any).name), + jscodeshift.jsxIdentifier((p.value.right as Identifier).name), [ jscodeshift.jsxSpreadAttribute( jscodeshift.identifier('props'), diff --git a/packages/docusaurus-migrate/src/types.ts b/packages/docusaurus-migrate/src/types.ts index 1da33918953c..8a3583fcfad2 100644 --- a/packages/docusaurus-migrate/src/types.ts +++ b/packages/docusaurus-migrate/src/types.ts @@ -33,8 +33,8 @@ export type SidebarEntry = export type SidebarEntries = { [key: string]: - | Record - | Array | string>; + | {[key: string]: unknown} + | Array<{[key: string]: unknown} | string>; }; export interface VersionTwoConfig { @@ -58,7 +58,7 @@ export interface VersionTwoConfig { logo?: { src?: string; }; - items: Array | null>; + items: Array<{[key: string]: unknown} | null>; }; image?: string; footer: { @@ -74,7 +74,7 @@ export interface VersionTwoConfig { src?: string; }; }; - algolia?: Record; + algolia?: {[key: string]: unknown}; }; customFields: { [key: string]: unknown; @@ -111,16 +111,16 @@ export type VersionOneConfig = { copyright?: string; editUrl?: string; customDocsPath?: string; - users?: Array>; + users?: Array<{[key: string]: unknown}>; disableHeaderTitle?: string; disableTitleTagline?: string; - separateCss?: Array>; + separateCss?: Array<{[key: string]: unknown}>; footerIcon?: string; translationRecruitingLink?: string; - algolia?: Record; + algolia?: {[key: string]: unknown}; gaTrackingId?: string; gaGtag?: boolean; - highlight?: Record; + highlight?: {[key: string]: unknown}; markdownPlugins?: Array<() => void>; scripts?: Array<{src: string; [key: string]: unknown} | string>; stylesheets?: Array<{href: string; [key: string]: unknown} | string>; @@ -133,5 +133,5 @@ export type VersionOneConfig = { ogImage?: string; cleanUrl?: boolean; scrollToTop?: boolean; - scrollToTopOptions?: Record; + scrollToTopOptions?: {[key: string]: unknown}; }; diff --git a/packages/docusaurus-migrate/tsconfig.build.json b/packages/docusaurus-migrate/tsconfig.build.json new file mode 100644 index 000000000000..358fdb6f6e60 --- /dev/null +++ b/packages/docusaurus-migrate/tsconfig.build.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "incremental": true, + "tsBuildInfoFile": "./lib/.tsbuildinfo", + "rootDir": "src", + "outDir": "lib" + }, + "include": ["src"] +} diff --git a/packages/docusaurus-migrate/tsconfig.json b/packages/docusaurus-migrate/tsconfig.json index e893590d8925..419de04867a2 100644 --- a/packages/docusaurus-migrate/tsconfig.json +++ b/packages/docusaurus-migrate/tsconfig.json @@ -1,10 +1,11 @@ +// For editor typechecking; includes bin { - "extends": "../../tsconfig.json", + "extends": "./tsconfig.build.json", "compilerOptions": { - "incremental": true, - "tsBuildInfoFile": "./lib/.tsbuildinfo", - "rootDir": "src", - "outDir": "lib", - "typeRoots": ["./external_types", "./node_modules/@types"] - } + "noEmit": true, + "module": "esnext", + "allowJs": true, + "rootDir": "." + }, + "include": ["src", "bin"] } diff --git a/packages/docusaurus-module-type-aliases/package.json b/packages/docusaurus-module-type-aliases/package.json index b8d66ef1cc20..f15a3a6c7685 100644 --- a/packages/docusaurus-module-type-aliases/package.json +++ b/packages/docusaurus-module-type-aliases/package.json @@ -1,6 +1,6 @@ { "name": "@docusaurus/module-type-aliases", - "version": "2.0.0-beta.14", + "version": "2.0.0-beta.18", "description": "Docusaurus module type aliases.", "types": "./src/index.d.ts", "publishConfig": { @@ -12,11 +12,15 @@ "directory": "packages/docusaurus-module-type-aliases" }, "dependencies": { - "@docusaurus/types": "2.0.0-beta.14", + "@docusaurus/types": "2.0.0-beta.18", "@types/react": "*", - "@types/react-helmet": "*", "@types/react-router-config": "*", - "@types/react-router-dom": "*" + "@types/react-router-dom": "*", + "react-helmet-async": "*" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" }, "license": "MIT" } diff --git a/packages/docusaurus-module-type-aliases/src/index.d.ts b/packages/docusaurus-module-type-aliases/src/index.d.ts index ed9a3e4a5f33..0e4b69c68759 100644 --- a/packages/docusaurus-module-type-aliases/src/index.d.ts +++ b/packages/docusaurus-module-type-aliases/src/index.d.ts @@ -6,8 +6,9 @@ */ declare module '@generated/client-modules' { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const clientModules: readonly any[]; + import type {ClientModule} from '@docusaurus/types'; + + const clientModules: readonly (ClientModule & {default: ClientModule})[]; export default clientModules; } @@ -19,69 +20,59 @@ declare module '@generated/docusaurus.config' { } declare module '@generated/site-metadata' { - import type {DocusaurusSiteMetadata} from '@docusaurus/types'; + import type {SiteMetadata} from '@docusaurus/types'; - const siteMetadata: DocusaurusSiteMetadata; + const siteMetadata: SiteMetadata; export = siteMetadata; } declare module '@generated/registry' { - const registry: { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - readonly [key: string]: [() => Promise, string, string]; - }; + import type {Registry} from '@docusaurus/types'; + + const registry: Registry; export default registry; } declare module '@generated/routes' { - import type {RouteConfig} from 'react-router-config'; + import type {RouteConfig as RRRouteConfig} from 'react-router-config'; - type Route = { - readonly path: string; - readonly component: RouteConfig['component']; - readonly exact?: boolean; - readonly routes?: Route[]; + type RouteConfig = RRRouteConfig & { + path: string; }; - const routes: Route[]; + const routes: RouteConfig[]; export default routes; } declare module '@generated/routesChunkNames' { - import type {RouteChunksTree} from '@docusaurus/types'; + import type {RouteChunkNames} from '@docusaurus/types'; - const routesChunkNames: Record; + const routesChunkNames: RouteChunkNames; export = routesChunkNames; } declare module '@generated/globalData' { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const globalData: Record; + import type {GlobalData} from '@docusaurus/types'; + + const globalData: GlobalData; export = globalData; } declare module '@generated/i18n' { - const i18n: { - defaultLocale: string; - locales: [string, ...string[]]; - currentLocale: string; - localeConfigs: Record< - string, - { - label: string; - direction: string; - htmlLang: string; - } - >; - }; + import type {I18n} from '@docusaurus/types'; + + const i18n: I18n; export = i18n; } declare module '@generated/codeTranslations' { - const codeTranslations: Record; + import type {CodeTranslations} from '@docusaurus/types'; + + const codeTranslations: CodeTranslations; export = codeTranslations; } declare module '@theme-original/*'; +declare module '@theme-init/*'; declare module '@theme/Error' { export interface Props { @@ -96,8 +87,6 @@ declare module '@theme/Layout' { export interface Props { readonly children?: ReactNode; - readonly title?: string; - readonly description?: string; } export default function Layout(props: Props): JSX.Element; } @@ -121,6 +110,10 @@ declare module '@theme/Root' { export default function Root({children}: Props): JSX.Element; } +declare module '@theme/SiteMetadata' { + export default function SiteMetadata(): JSX.Element; +} + declare module '@docusaurus/constants' { export const DEFAULT_PLUGIN_ID: 'default'; } @@ -137,20 +130,19 @@ declare module '@docusaurus/ErrorBoundary' { } declare module '@docusaurus/Head' { - import type {HelmetProps} from 'react-helmet'; + import type {HelmetProps} from 'react-helmet-async'; import type {ReactNode} from 'react'; - export type HeadProps = HelmetProps & {children: ReactNode}; + export type Props = HelmetProps & {children: ReactNode}; - const Head: (props: HeadProps) => JSX.Element; - export default Head; + export default function Head(props: Props): JSX.Element; } declare module '@docusaurus/Link' { import type {CSSProperties, ComponentProps} from 'react'; type NavLinkProps = Partial; - export type LinkProps = NavLinkProps & + export type Props = NavLinkProps & ComponentProps<'a'> & { readonly className?: string; readonly style?: CSSProperties; @@ -159,11 +151,12 @@ declare module '@docusaurus/Link' { readonly href?: string; readonly autoAddBaseUrl?: boolean; - // escape hatch in case broken links check is annoying for a specific link + /** + * escape hatch in case broken links check is annoying for a specific link + */ readonly 'data-noBrokenLinkCheck'?: boolean; }; - const Link: (props: LinkProps) => JSX.Element; - export default Link; + export default function Link(props: Props): JSX.Element; } declare module '@docusaurus/Interpolate' { @@ -174,18 +167,17 @@ declare module '@docusaurus/Interpolate' { ? Key | ExtractInterpolatePlaceholders : never; - export type InterpolateValues< - Str extends string, - Value extends ReactNode, - > = Record, Value>; + export type InterpolateValues = { + [key in ExtractInterpolatePlaceholders]: Value; + }; - // TS function overload: if all the values are plain strings, then interpolate returns a simple string + // If all the values are plain strings, interpolate returns a simple string export function interpolate( text: Str, values?: InterpolateValues, ): string; - // If values contain any ReactNode, then the return is a ReactNode + // If values contain any ReactNode, the return is a ReactNode export function interpolate( text: Str, values?: InterpolateValues, @@ -241,11 +233,7 @@ declare module '@docusaurus/Translate' { declare module '@docusaurus/router' { // eslint-disable-next-line import/no-extraneous-dependencies - export * from 'react-router-dom'; -} -declare module '@docusaurus/history' { - // eslint-disable-next-line import/no-extraneous-dependencies - export * from 'history'; + export {useHistory, useLocation, Redirect, matchPath} from 'react-router-dom'; } declare module '@docusaurus/useDocusaurusContext' { @@ -254,6 +242,12 @@ declare module '@docusaurus/useDocusaurusContext' { export default function useDocusaurusContext(): DocusaurusContext; } +declare module '@docusaurus/useRouteContext' { + import type {PluginRouteContext} from '@docusaurus/types'; + + export default function useRouteContext(): PluginRouteContext; +} + declare module '@docusaurus/useIsBrowser' { export default function useIsBrowser(): boolean; } @@ -289,11 +283,10 @@ declare module '@docusaurus/ExecutionEnvironment' { declare module '@docusaurus/ComponentCreator' { import type Loadable from 'react-loadable'; - function ComponentCreator( + export default function ComponentCreator( path: string, hash: string, ): ReturnType; - export default ComponentCreator; } declare module '@docusaurus/BrowserOnly' { @@ -301,8 +294,7 @@ declare module '@docusaurus/BrowserOnly' { readonly children?: () => JSX.Element; readonly fallback?: JSX.Element; } - const BrowserOnly: (props: Props) => JSX.Element | null; - export default BrowserOnly; + export default function BrowserOnly(props: Props): JSX.Element | null; } declare module '@docusaurus/isInternalUrl' { @@ -322,18 +314,18 @@ declare module '@docusaurus/renderRoutes' { } declare module '@docusaurus/useGlobalData' { - export function useAllPluginInstancesData( + import type {GlobalData} from '@docusaurus/types'; + + export function useAllPluginInstancesData( pluginName: string, - ): Record; + ): GlobalData[string]; - export function usePluginData( + export function usePluginData( pluginName: string, pluginId?: string, - ): T; + ): GlobalData[string][string]; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - function useGlobalData(): Record; - export default useGlobalData; + export default function useGlobalData(): GlobalData; } declare module '*.svg' { diff --git a/packages/docusaurus-plugin-client-redirects/package.json b/packages/docusaurus-plugin-client-redirects/package.json index e6f1b91a62e8..3bee353ebe93 100644 --- a/packages/docusaurus-plugin-client-redirects/package.json +++ b/packages/docusaurus-plugin-client-redirects/package.json @@ -1,6 +1,6 @@ { "name": "@docusaurus/plugin-client-redirects", - "version": "2.0.0-beta.14", + "version": "2.0.0-beta.18", "description": "Client redirects plugin for Docusaurus.", "main": "lib/index.js", "types": "src/plugin-client-redirects.d.ts", @@ -18,18 +18,18 @@ }, "license": "MIT", "dependencies": { - "@docusaurus/core": "2.0.0-beta.14", - "@docusaurus/logger": "2.0.0-beta.14", - "@docusaurus/utils": "2.0.0-beta.14", - "@docusaurus/utils-common": "2.0.0-beta.14", - "@docusaurus/utils-validation": "2.0.0-beta.14", + "@docusaurus/core": "2.0.0-beta.18", + "@docusaurus/logger": "2.0.0-beta.18", + "@docusaurus/utils": "2.0.0-beta.18", + "@docusaurus/utils-common": "2.0.0-beta.18", + "@docusaurus/utils-validation": "2.0.0-beta.18", "eta": "^1.12.3", - "fs-extra": "^10.0.0", - "lodash": "^4.17.20", + "fs-extra": "^10.0.1", + "lodash": "^4.17.21", "tslib": "^2.3.1" }, "devDependencies": { - "@docusaurus/types": "2.0.0-beta.14" + "@docusaurus/types": "2.0.0-beta.18" }, "peerDependencies": { "react": "^16.8.4 || ^17.0.0", diff --git a/packages/docusaurus-plugin-client-redirects/src/__tests__/__snapshots__/collectRedirects.test.ts.snap b/packages/docusaurus-plugin-client-redirects/src/__tests__/__snapshots__/collectRedirects.test.ts.snap index 785f7e3478dc..7c9cd4ee9de9 100644 --- a/packages/docusaurus-plugin-client-redirects/src/__tests__/__snapshots__/collectRedirects.test.ts.snap +++ b/packages/docusaurus-plugin-client-redirects/src/__tests__/__snapshots__/collectRedirects.test.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`collectRedirects should throw if plugin option redirects contain invalid to paths 1`] = ` +exports[`collectRedirects throw if plugin option redirects contain invalid to paths 1`] = ` "You are trying to create client-side redirections to paths that do not exist: - /this/path/does/not/exist2 - /this/path/does/not/exist2 @@ -12,13 +12,13 @@ Valid paths you can redirect to: " `; -exports[`collectRedirects should throw if redirect creator creates array of array redirect 1`] = ` +exports[`collectRedirects throws if redirect creator creates array of array redirect 1`] = ` "Some created redirects are invalid: - {\\"from\\":[\\"/fromPath\\"],\\"to\\":\\"/\\"} => Validation error: \\"from\\" must be a string " `; -exports[`collectRedirects should throw if redirect creator creates invalid redirects 1`] = ` +exports[`collectRedirects throws if redirect creator creates invalid redirects 1`] = ` "Some created redirects are invalid: - {\\"from\\":\\"https://google.com/\\",\\"to\\":\\"/\\"} => Validation error: \\"from\\" is not a valid pathname. Pathname should start with slash and not contain any domain or query string. - {\\"from\\":\\"//abc\\",\\"to\\":\\"/\\"} => Validation error: \\"from\\" is not a valid pathname. Pathname should start with slash and not contain any domain or query string. diff --git a/packages/docusaurus-plugin-client-redirects/src/__tests__/__snapshots__/createRedirectPageContent.test.ts.snap b/packages/docusaurus-plugin-client-redirects/src/__tests__/__snapshots__/createRedirectPageContent.test.ts.snap index 7fa3afa57de0..7882ae877ad0 100644 --- a/packages/docusaurus-plugin-client-redirects/src/__tests__/__snapshots__/createRedirectPageContent.test.ts.snap +++ b/packages/docusaurus-plugin-client-redirects/src/__tests__/__snapshots__/createRedirectPageContent.test.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`createRedirectPageContent should encode uri special chars 1`] = ` +exports[`createRedirectPageContent encodes uri special chars 1`] = ` " @@ -14,7 +14,7 @@ exports[`createRedirectPageContent should encode uri special chars 1`] = ` " `; -exports[`createRedirectPageContent should match snapshot 1`] = ` +exports[`createRedirectPageContent works 1`] = ` " diff --git a/packages/docusaurus-plugin-client-redirects/src/__tests__/__snapshots__/normalizePluginOptions.test.ts.snap b/packages/docusaurus-plugin-client-redirects/src/__tests__/__snapshots__/normalizePluginOptions.test.ts.snap deleted file mode 100644 index 0bfd440bfedc..000000000000 --- a/packages/docusaurus-plugin-client-redirects/src/__tests__/__snapshots__/normalizePluginOptions.test.ts.snap +++ /dev/null @@ -1,35 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`normalizePluginOptions should reject bad createRedirects user inputs 1`] = ` -"Invalid @docusaurus/plugin-client-redirects options: \\"createRedirects\\" must be of type function - { - \\"createRedirects\\": [ - \\"bad\\", - \\"value\\" - ] -}" -`; - -exports[`normalizePluginOptions should reject bad fromExtensions user inputs 1`] = ` -"Invalid @docusaurus/plugin-client-redirects options: \\"fromExtensions[0]\\" contains an invalid value - { - \\"fromExtensions\\": [ - null, - null, - 123, - true - ] -}" -`; - -exports[`normalizePluginOptions should reject bad toExtensions user inputs 1`] = ` -"Invalid @docusaurus/plugin-client-redirects options: \\"toExtensions[0]\\" contains an invalid value - { - \\"toExtensions\\": [ - null, - null, - 123, - true - ] -}" -`; diff --git a/packages/docusaurus-plugin-client-redirects/src/__tests__/__snapshots__/options.test.ts.snap b/packages/docusaurus-plugin-client-redirects/src/__tests__/__snapshots__/options.test.ts.snap new file mode 100644 index 000000000000..83f23180a018 --- /dev/null +++ b/packages/docusaurus-plugin-client-redirects/src/__tests__/__snapshots__/options.test.ts.snap @@ -0,0 +1,7 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`normalizePluginOptions rejects bad createRedirects user inputs 1`] = `"\\"createRedirects\\" must be of type function"`; + +exports[`normalizePluginOptions rejects bad fromExtensions user inputs 1`] = `"\\"fromExtensions[0]\\" contains an invalid value"`; + +exports[`normalizePluginOptions rejects bad toExtensions user inputs 1`] = `"\\"toExtensions[0]\\" contains an invalid value"`; diff --git a/packages/docusaurus-plugin-client-redirects/src/__tests__/__snapshots__/redirectValidation.test.ts.snap b/packages/docusaurus-plugin-client-redirects/src/__tests__/__snapshots__/redirectValidation.test.ts.snap index 62b3059d0e7d..0153b77a23dd 100644 --- a/packages/docusaurus-plugin-client-redirects/src/__tests__/__snapshots__/redirectValidation.test.ts.snap +++ b/packages/docusaurus-plugin-client-redirects/src/__tests__/__snapshots__/redirectValidation.test.ts.snap @@ -8,4 +8,4 @@ exports[`validateRedirect throw for bad redirects 3`] = `"{\\"from\\":\\"/fromSo exports[`validateRedirect throw for bad redirects 4`] = `"{\\"from\\":null,\\"to\\":\\"/toSomePath?queryString=xyz\\"} => Validation error: \\"from\\" must be a string"`; -exports[`validateRedirect throw for bad redirects 5`] = `"{\\"from\\":[\\"heyho\\"],\\"to\\":\\"/toSomePath?queryString=xyz\\"} => Validation error: \\"from\\" must be a string"`; +exports[`validateRedirect throw for bad redirects 5`] = `"{\\"from\\":[\\"hey\\"],\\"to\\":\\"/toSomePath?queryString=xyz\\"} => Validation error: \\"from\\" must be a string"`; diff --git a/packages/docusaurus-plugin-client-redirects/src/__tests__/__snapshots__/writeRedirectFiles.test.ts.snap b/packages/docusaurus-plugin-client-redirects/src/__tests__/__snapshots__/writeRedirectFiles.test.ts.snap index a57e33c0c21f..fa9472e3b172 100644 --- a/packages/docusaurus-plugin-client-redirects/src/__tests__/__snapshots__/writeRedirectFiles.test.ts.snap +++ b/packages/docusaurus-plugin-client-redirects/src/__tests__/__snapshots__/writeRedirectFiles.test.ts.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`toRedirectFilesMetadata should create appropriate metadata for empty baseUrl: fileContent baseUrl=empty 1`] = ` -Array [ +exports[`toRedirectFilesMetadata creates appropriate metadata for empty baseUrl: fileContent baseUrl=empty 1`] = ` +[ " @@ -16,8 +16,8 @@ Array [ ] `; -exports[`toRedirectFilesMetadata should create appropriate metadata for root baseUrl: fileContent baseUrl=/ 1`] = ` -Array [ +exports[`toRedirectFilesMetadata creates appropriate metadata for root baseUrl: fileContent baseUrl=/ 1`] = ` +[ " @@ -32,8 +32,8 @@ Array [ ] `; -exports[`toRedirectFilesMetadata should create appropriate metadata trailingSlash=false: fileContent 1`] = ` -Array [ +exports[`toRedirectFilesMetadata creates appropriate metadata trailingSlash=false: fileContent 1`] = ` +[ " @@ -70,8 +70,8 @@ Array [ ] `; -exports[`toRedirectFilesMetadata should create appropriate metadata trailingSlash=true: fileContent 1`] = ` -Array [ +exports[`toRedirectFilesMetadata creates appropriate metadata trailingSlash=true: fileContent 1`] = ` +[ " @@ -108,8 +108,8 @@ Array [ ] `; -exports[`toRedirectFilesMetadata should create appropriate metadata trailingSlash=undefined: fileContent 1`] = ` -Array [ +exports[`toRedirectFilesMetadata creates appropriate metadata trailingSlash=undefined: fileContent 1`] = ` +[ " diff --git a/packages/docusaurus-plugin-client-redirects/src/__tests__/collectRedirects.test.ts b/packages/docusaurus-plugin-client-redirects/src/__tests__/collectRedirects.test.ts index 5219bc25bd32..3e7f47e5f853 100644 --- a/packages/docusaurus-plugin-client-redirects/src/__tests__/collectRedirects.test.ts +++ b/packages/docusaurus-plugin-client-redirects/src/__tests__/collectRedirects.test.ts @@ -7,8 +7,9 @@ import type {PluginContext} from '../types'; import collectRedirects from '../collectRedirects'; -import normalizePluginOptions from '../normalizePluginOptions'; +import {validateOptions} from '../options'; import {removeTrailingSlash} from '@docusaurus/utils'; +import {normalizePluginOptions} from '@docusaurus/utils-validation'; import type {Options} from '@docusaurus/plugin-client-redirects'; function createTestPluginContext( @@ -19,12 +20,12 @@ function createTestPluginContext( outDir: '/tmp', baseUrl: 'https://docusaurus.io', relativeRoutesPaths, - options: normalizePluginOptions(options), + options: validateOptions({validate: normalizePluginOptions, options}), }; } describe('collectRedirects', () => { - test('should collect no redirect for undefined config', () => { + it('collects no redirect for undefined config', () => { expect( collectRedirects( createTestPluginContext(undefined, ['/', '/path']), @@ -33,13 +34,13 @@ describe('collectRedirects', () => { ).toEqual([]); }); - test('should collect no redirect for empty config', () => { + it('collects no redirect for empty config', () => { expect(collectRedirects(createTestPluginContext({}), undefined)).toEqual( [], ); }); - test('should collect redirects from html/exe extension', () => { + it('collects redirects from html/exe extension', () => { expect( collectRedirects( createTestPluginContext( @@ -62,7 +63,7 @@ describe('collectRedirects', () => { ]); }); - test('should collect redirects to html/exe extension', () => { + it('collects redirects to html/exe extension', () => { expect( collectRedirects( createTestPluginContext( @@ -81,7 +82,7 @@ describe('collectRedirects', () => { ]); }); - test('should collect redirects from plugin option redirects', () => { + it('collects redirects from plugin option redirects', () => { expect( collectRedirects( createTestPluginContext( @@ -117,7 +118,7 @@ describe('collectRedirects', () => { ]); }); - test('should collect redirects from plugin option redirects with trailingSlash=true', () => { + it('collects redirects from plugin option redirects with trailingSlash=true', () => { expect( collectRedirects( createTestPluginContext( @@ -153,7 +154,7 @@ describe('collectRedirects', () => { ]); }); - test('should collect redirects from plugin option redirects with trailingSlash=false', () => { + it('collects redirects from plugin option redirects with trailingSlash=false', () => { expect( collectRedirects( createTestPluginContext( @@ -189,7 +190,7 @@ describe('collectRedirects', () => { ]); }); - test('should throw if plugin option redirects contain invalid to paths', () => { + it('throw if plugin option redirects contain invalid to paths', () => { expect(() => collectRedirects( createTestPluginContext( @@ -216,7 +217,7 @@ describe('collectRedirects', () => { ).toThrowErrorMatchingSnapshot(); }); - test('should collect redirects with custom redirect creator', () => { + it('collects redirects with custom redirect creator', () => { expect( collectRedirects( createTestPluginContext( @@ -226,7 +227,7 @@ describe('collectRedirects', () => { `${removeTrailingSlash(routePath)}/some/other/path/suffix2`, ], }, - ['/', '/testpath', '/otherPath.html'], + ['/', '/testPath', '/otherPath.html'], ), undefined, ), @@ -241,12 +242,12 @@ describe('collectRedirects', () => { }, { - from: '/testpath/some/path/suffix1', - to: '/testpath', + from: '/testPath/some/path/suffix1', + to: '/testPath', }, { - from: '/testpath/some/other/path/suffix2', - to: '/testpath', + from: '/testPath/some/other/path/suffix2', + to: '/testPath', }, { @@ -260,7 +261,7 @@ describe('collectRedirects', () => { ]); }); - test('should allow returning string / undefined', () => { + it('allows returning string / undefined', () => { expect( collectRedirects( createTestPluginContext( @@ -272,14 +273,14 @@ describe('collectRedirects', () => { return undefined; }, }, - ['/', '/testpath', '/otherPath.html'], + ['/', '/testPath', '/otherPath.html'], ), undefined, ), ).toEqual([{from: '/foo', to: '/'}]); }); - test('should throw if redirect creator creates invalid redirects', () => { + it('throws if redirect creator creates invalid redirects', () => { expect(() => collectRedirects( createTestPluginContext( @@ -302,7 +303,7 @@ describe('collectRedirects', () => { ).toThrowErrorMatchingSnapshot(); }); - test('should throw if redirect creator creates array of array redirect', () => { + it('throws if redirect creator creates array of array redirect', () => { expect(() => collectRedirects( createTestPluginContext( @@ -321,7 +322,7 @@ describe('collectRedirects', () => { ).toThrowErrorMatchingSnapshot(); }); - test('should filter unwanted redirects', () => { + it('filters unwanted redirects', () => { expect( collectRedirects( createTestPluginContext( diff --git a/packages/docusaurus-plugin-client-redirects/src/__tests__/createRedirectPageContent.test.ts b/packages/docusaurus-plugin-client-redirects/src/__tests__/createRedirectPageContent.test.ts index e30bd762d441..a65449b0c5de 100644 --- a/packages/docusaurus-plugin-client-redirects/src/__tests__/createRedirectPageContent.test.ts +++ b/packages/docusaurus-plugin-client-redirects/src/__tests__/createRedirectPageContent.test.ts @@ -8,13 +8,13 @@ import createRedirectPageContent from '../createRedirectPageContent'; describe('createRedirectPageContent', () => { - test('should match snapshot', () => { + it('works', () => { expect( createRedirectPageContent({toUrl: 'https://docusaurus.io/'}), ).toMatchSnapshot(); }); - test('should encode uri special chars', () => { + it('encodes uri special chars', () => { const result = createRedirectPageContent({ toUrl: 'https://docusaurus.io/gr/σελιδας/', }); diff --git a/packages/docusaurus-plugin-client-redirects/src/__tests__/extensionRedirects.test.ts b/packages/docusaurus-plugin-client-redirects/src/__tests__/extensionRedirects.test.ts index dcaa6bca3442..59f60c679e89 100644 --- a/packages/docusaurus-plugin-client-redirects/src/__tests__/extensionRedirects.test.ts +++ b/packages/docusaurus-plugin-client-redirects/src/__tests__/extensionRedirects.test.ts @@ -11,7 +11,7 @@ import { } from '../extensionRedirects'; describe('createToExtensionsRedirects', () => { - test('should reject empty extensions', () => { + it('rejects empty extensions', () => { expect(() => { createToExtensionsRedirects(['/'], ['']); }).toThrowErrorMatchingInlineSnapshot(` @@ -20,7 +20,7 @@ describe('createToExtensionsRedirects', () => { `); }); - test('should reject extensions with .', () => { + it('rejects extensions with "."', () => { expect(() => { createToExtensionsRedirects(['/'], ['.html']); }).toThrowErrorMatchingInlineSnapshot(` @@ -29,7 +29,7 @@ describe('createToExtensionsRedirects', () => { `); }); - test('should reject extensions with /', () => { + it('rejects extensions with /', () => { expect(() => { createToExtensionsRedirects(['/'], ['ht/ml']); }).toThrowErrorMatchingInlineSnapshot(` @@ -38,7 +38,7 @@ describe('createToExtensionsRedirects', () => { `); }); - test('should reject extensions with illegal url char', () => { + it('rejects extensions with illegal url char', () => { expect(() => { createToExtensionsRedirects(['/'], [',']); }).toThrowErrorMatchingInlineSnapshot(` @@ -47,7 +47,7 @@ describe('createToExtensionsRedirects', () => { `); }); - test('should create redirects from html/htm extensions', () => { + it('creates redirects from html/htm extensions', () => { const ext = ['html', 'htm']; expect(createToExtensionsRedirects([''], ext)).toEqual([]); expect(createToExtensionsRedirects(['/'], ext)).toEqual([]); @@ -60,13 +60,13 @@ describe('createToExtensionsRedirects', () => { expect(createToExtensionsRedirects(['/abc.xyz'], ext)).toEqual([]); }); - test('should create "to" redirects when relativeRoutesPath contains a prefix', () => { + it('creates "to" redirects when relativeRoutesPath contains a prefix', () => { expect( createToExtensionsRedirects(['/prefix/file.html'], ['html']), ).toEqual([{from: '/prefix/file', to: '/prefix/file.html'}]); }); - test('should not create redirection for an empty extension array', () => { + it('does not create redirection for an empty extension array', () => { const ext: string[] = []; expect(createToExtensionsRedirects([''], ext)).toEqual([]); expect(createToExtensionsRedirects(['/'], ext)).toEqual([]); @@ -75,7 +75,7 @@ describe('createToExtensionsRedirects', () => { }); describe('createFromExtensionsRedirects', () => { - test('should reject empty extensions', () => { + it('rejects empty extensions', () => { expect(() => { createFromExtensionsRedirects(['/'], ['.html']); }).toThrowErrorMatchingInlineSnapshot(` @@ -84,7 +84,7 @@ describe('createFromExtensionsRedirects', () => { `); }); - test('should reject extensions with .', () => { + it('rejects extensions with "."', () => { expect(() => { createFromExtensionsRedirects(['/'], ['.html']); }).toThrowErrorMatchingInlineSnapshot(` @@ -93,7 +93,7 @@ describe('createFromExtensionsRedirects', () => { `); }); - test('should reject extensions with /', () => { + it('rejects extensions with /', () => { expect(() => { createFromExtensionsRedirects(['/'], ['ht/ml']); }).toThrowErrorMatchingInlineSnapshot(` @@ -102,7 +102,7 @@ describe('createFromExtensionsRedirects', () => { `); }); - test('should reject extensions with illegal url char', () => { + it('rejects extensions with illegal url char', () => { expect(() => { createFromExtensionsRedirects(['/'], [',']); }).toThrowErrorMatchingInlineSnapshot(` @@ -111,7 +111,7 @@ describe('createFromExtensionsRedirects', () => { `); }); - test('should create redirects from html/htm extensions', () => { + it('creates redirects from html/htm extensions', () => { const ext = ['html', 'htm']; expect(createFromExtensionsRedirects([''], ext)).toEqual([]); expect(createFromExtensionsRedirects(['/'], ext)).toEqual([]); @@ -126,13 +126,13 @@ describe('createFromExtensionsRedirects', () => { ]); }); - test('should create "from" redirects when relativeRoutesPath contains a prefix', () => { + it('creates "from" redirects when relativeRoutesPath contains a prefix', () => { expect(createFromExtensionsRedirects(['/prefix/file'], ['html'])).toEqual([ {from: '/prefix/file.html', to: '/prefix/file'}, ]); }); - test('should not create redirection for an empty extension array', () => { + it('does not create redirection for an empty extension array', () => { const ext: string[] = []; expect(createFromExtensionsRedirects([''], ext)).toEqual([]); expect(createFromExtensionsRedirects(['/'], ext)).toEqual([]); diff --git a/packages/docusaurus-plugin-client-redirects/src/__tests__/normalizePluginOptions.test.ts b/packages/docusaurus-plugin-client-redirects/src/__tests__/normalizePluginOptions.test.ts deleted file mode 100644 index 68f29cbf485b..000000000000 --- a/packages/docusaurus-plugin-client-redirects/src/__tests__/normalizePluginOptions.test.ts +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import normalizePluginOptions, { - DefaultPluginOptions, -} from '../normalizePluginOptions'; -import type {CreateRedirectsFnOption} from '../types'; - -describe('normalizePluginOptions', () => { - test('should return default options for undefined user options', () => { - expect(normalizePluginOptions()).toEqual(DefaultPluginOptions); - }); - - test('should return default options for empty user options', () => { - expect(normalizePluginOptions()).toEqual(DefaultPluginOptions); - }); - - test('should override one default options with valid user options', () => { - expect( - normalizePluginOptions({ - toExtensions: ['html'], - }), - ).toEqual({...DefaultPluginOptions, toExtensions: ['html']}); - }); - - test('should override all default options with valid user options', () => { - const createRedirects: CreateRedirectsFnOption = (_routePath: string) => []; - expect( - normalizePluginOptions({ - fromExtensions: ['exe', 'zip'], - toExtensions: ['html'], - createRedirects, - redirects: [{from: '/x', to: '/y'}], - }), - ).toEqual({ - id: 'default', - fromExtensions: ['exe', 'zip'], - toExtensions: ['html'], - createRedirects, - redirects: [{from: '/x', to: '/y'}], - }); - }); - - test('should reject bad fromExtensions user inputs', () => { - expect(() => - normalizePluginOptions({ - fromExtensions: [null, undefined, 123, true] as unknown as string[], - }), - ).toThrowErrorMatchingSnapshot(); - }); - - test('should reject bad toExtensions user inputs', () => { - expect(() => - normalizePluginOptions({ - toExtensions: [null, undefined, 123, true] as unknown as string[], - }), - ).toThrowErrorMatchingSnapshot(); - }); - - test('should reject bad createRedirects user inputs', () => { - expect(() => - normalizePluginOptions({ - createRedirects: ['bad', 'value'] as unknown as CreateRedirectsFnOption, - }), - ).toThrowErrorMatchingSnapshot(); - }); -}); diff --git a/packages/docusaurus-plugin-client-redirects/src/__tests__/options.test.ts b/packages/docusaurus-plugin-client-redirects/src/__tests__/options.test.ts new file mode 100644 index 000000000000..0354dcfe995a --- /dev/null +++ b/packages/docusaurus-plugin-client-redirects/src/__tests__/options.test.ts @@ -0,0 +1,80 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {validateOptions, DEFAULT_OPTIONS} from '../options'; +import {normalizePluginOptions} from '@docusaurus/utils-validation'; +import type {Options} from '@docusaurus/plugin-client-redirects'; + +function testValidate(options: Options) { + return validateOptions({validate: normalizePluginOptions, options}); +} + +describe('normalizePluginOptions', () => { + it('returns default options for undefined user options', () => { + expect(testValidate(undefined)).toEqual({ + ...DEFAULT_OPTIONS, + id: 'default', + }); + }); + + it('returns default options for empty user options', () => { + expect(testValidate({})).toEqual({...DEFAULT_OPTIONS, id: 'default'}); + }); + + it('overrides one default options with valid user options', () => { + expect( + testValidate({ + toExtensions: ['html'], + }), + ).toEqual({...DEFAULT_OPTIONS, id: 'default', toExtensions: ['html']}); + }); + + it('overrides all default options with valid user options', () => { + const createRedirects: Options['createRedirects'] = () => []; + expect( + testValidate({ + fromExtensions: ['exe', 'zip'], + toExtensions: ['html'], + createRedirects, + redirects: [{from: '/x', to: '/y'}], + }), + ).toEqual({ + id: 'default', + fromExtensions: ['exe', 'zip'], + toExtensions: ['html'], + createRedirects, + redirects: [{from: '/x', to: '/y'}], + }); + }); + + it('rejects bad fromExtensions user inputs', () => { + expect(() => + testValidate({ + // @ts-expect-error: for test + fromExtensions: [null, undefined, 123, true], + }), + ).toThrowErrorMatchingSnapshot(); + }); + + it('rejects bad toExtensions user inputs', () => { + expect(() => + testValidate({ + // @ts-expect-error: for test + toExtensions: [null, undefined, 123, true], + }), + ).toThrowErrorMatchingSnapshot(); + }); + + it('rejects bad createRedirects user inputs', () => { + expect(() => + testValidate({ + // @ts-expect-error: for test + createRedirects: ['bad', 'value'], + }), + ).toThrowErrorMatchingSnapshot(); + }); +}); diff --git a/packages/docusaurus-plugin-client-redirects/src/__tests__/redirectValidation.test.ts b/packages/docusaurus-plugin-client-redirects/src/__tests__/redirectValidation.test.ts index 2b0b9b59af39..2d357985ba04 100644 --- a/packages/docusaurus-plugin-client-redirects/src/__tests__/redirectValidation.test.ts +++ b/packages/docusaurus-plugin-client-redirects/src/__tests__/redirectValidation.test.ts @@ -8,7 +8,7 @@ import {validateRedirect} from '../redirectValidation'; describe('validateRedirect', () => { - test('validate good redirects without throwing', () => { + it('validate good redirects without throwing', () => { expect(() => { validateRedirect({ from: '/fromSomePath', @@ -29,7 +29,7 @@ describe('validateRedirect', () => { }).not.toThrow(); }); - test('throw for bad redirects', () => { + it('throw for bad redirects', () => { expect(() => validateRedirect({ from: 'https://fb.com/fromSomePath', @@ -60,7 +60,7 @@ describe('validateRedirect', () => { expect(() => validateRedirect({ - from: ['heyho'] as unknown as string, + from: ['hey'] as unknown as string, to: '/toSomePath?queryString=xyz', }), ).toThrowErrorMatchingSnapshot(); diff --git a/packages/docusaurus-plugin-client-redirects/src/__tests__/writeRedirectFiles.test.ts b/packages/docusaurus-plugin-client-redirects/src/__tests__/writeRedirectFiles.test.ts index f01181d97ad2..215d968b7399 100644 --- a/packages/docusaurus-plugin-client-redirects/src/__tests__/writeRedirectFiles.test.ts +++ b/packages/docusaurus-plugin-client-redirects/src/__tests__/writeRedirectFiles.test.ts @@ -17,33 +17,33 @@ import writeRedirectFiles, { // - https://github.com/facebook/docusaurus/issues/3886 // - https://github.com/facebook/docusaurus/issues/3925 describe('createToUrl', () => { - test('should create appropriate redirect urls', async () => { - expect(createToUrl('/', '/docs/something/else')).toEqual( + it('creates appropriate redirect urls', async () => { + expect(createToUrl('/', '/docs/something/else')).toBe( '/docs/something/else', ); - expect(createToUrl('/', '/docs/something/else/')).toEqual( + expect(createToUrl('/', '/docs/something/else/')).toBe( '/docs/something/else/', ); - expect(createToUrl('/', 'docs/something/else')).toEqual( + expect(createToUrl('/', 'docs/something/else')).toBe( '/docs/something/else', ); }); - test('should create appropriate redirect urls with baseUrl', async () => { - expect(createToUrl('/baseUrl/', '/docs/something/else')).toEqual( + it('creates appropriate redirect urls with baseUrl', async () => { + expect(createToUrl('/baseUrl/', '/docs/something/else')).toBe( '/baseUrl/docs/something/else', ); - expect(createToUrl('/baseUrl/', '/docs/something/else/')).toEqual( + expect(createToUrl('/baseUrl/', '/docs/something/else/')).toBe( '/baseUrl/docs/something/else/', ); - expect(createToUrl('/baseUrl/', 'docs/something/else')).toEqual( + expect(createToUrl('/baseUrl/', 'docs/something/else')).toBe( '/baseUrl/docs/something/else', ); }); }); describe('toRedirectFilesMetadata', () => { - test('should create appropriate metadata trailingSlash=undefined', async () => { + it('creates appropriate metadata trailingSlash=undefined', async () => { const pluginContext = { outDir: '/tmp/someFixedOutDir', baseUrl: 'https://docusaurus.io', @@ -70,7 +70,7 @@ describe('toRedirectFilesMetadata', () => { ); }); - test('should create appropriate metadata trailingSlash=true', async () => { + it('creates appropriate metadata trailingSlash=true', async () => { const pluginContext = { outDir: '/tmp/someFixedOutDir', baseUrl: 'https://docusaurus.io', @@ -97,7 +97,7 @@ describe('toRedirectFilesMetadata', () => { ); }); - test('should create appropriate metadata trailingSlash=false', async () => { + it('creates appropriate metadata trailingSlash=false', async () => { const pluginContext = { outDir: '/tmp/someFixedOutDir', baseUrl: 'https://docusaurus.io', @@ -114,7 +114,9 @@ describe('toRedirectFilesMetadata', () => { ); expect(redirectFiles.map((f) => f.fileAbsolutePath)).toEqual([ - // path.join(pluginContext.outDir, '/abc.html/index.html'), // Can't be used because /abc.html already exists, and file/folder can't share same name on Unix! + // Can't be used because /abc.html already exists, and file/folder can't + // share same name on Unix! + // path.join(pluginContext.outDir, '/abc.html/index.html'), path.join(pluginContext.outDir, '/abc.html.html'), // Weird but on purpose! path.join(pluginContext.outDir, '/def/index.html'), path.join(pluginContext.outDir, '/xyz/index.html'), @@ -125,7 +127,7 @@ describe('toRedirectFilesMetadata', () => { ); }); - test('should create appropriate metadata for root baseUrl', async () => { + it('creates appropriate metadata for root baseUrl', async () => { const pluginContext = { outDir: '/tmp/someFixedOutDir', baseUrl: '/', @@ -140,7 +142,7 @@ describe('toRedirectFilesMetadata', () => { ); }); - test('should create appropriate metadata for empty baseUrl', async () => { + it('creates appropriate metadata for empty baseUrl', async () => { const pluginContext = { outDir: '/tmp/someFixedOutDir', baseUrl: '', @@ -157,7 +159,7 @@ describe('toRedirectFilesMetadata', () => { }); describe('writeRedirectFiles', () => { - test('write the files', async () => { + it('write the files', async () => { const outDir = `/tmp/docusaurus_tests_${Math.random()}`; const filesMetadata = [ @@ -175,14 +177,14 @@ describe('writeRedirectFiles', () => { await expect( fs.readFile(filesMetadata[0].fileAbsolutePath, 'utf8'), - ).resolves.toEqual('content 1'); + ).resolves.toBe('content 1'); await expect( fs.readFile(filesMetadata[1].fileAbsolutePath, 'utf8'), - ).resolves.toEqual('content 2'); + ).resolves.toBe('content 2'); }); - test('avoid overwriting existing files', async () => { + it('avoid overwriting existing files', async () => { const outDir = `/tmp/docusaurus_tests_${Math.random()}`; const filesMetadata = [ @@ -192,8 +194,7 @@ describe('writeRedirectFiles', () => { }, ]; - await fs.ensureDir(path.dirname(filesMetadata[0].fileAbsolutePath)); - await fs.writeFile( + await fs.outputFile( filesMetadata[0].fileAbsolutePath, 'file already exists!', ); diff --git a/packages/docusaurus-plugin-client-redirects/src/collectRedirects.ts b/packages/docusaurus-plugin-client-redirects/src/collectRedirects.ts index 6c53e817d806..1ffb8e568eca 100644 --- a/packages/docusaurus-plugin-client-redirects/src/collectRedirects.ts +++ b/packages/docusaurus-plugin-client-redirects/src/collectRedirects.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import {uniqBy, difference, groupBy} from 'lodash'; +import _ from 'lodash'; import type { PluginOptions, RedirectOption, @@ -39,9 +39,12 @@ export default function collectRedirects( } // If users wants to redirect to=/abc and they enable trailingSlash=true then -// => we don't want to reject the to=/abc (as only /abc/ is an existing/valid path now) -// => we want to redirect to=/abc/ without the user having to change all its redirect plugin options -// It should be easy to toggle siteConfig.trailingSlash option without having to change other configs +// => we don't want to reject the to=/abc (as only /abc/ is an existing/valid +// path now) +// => we want to redirect to=/abc/ without the user having to change all its +// redirect plugin options +// It should be easy to toggle siteConfig.trailingSlash option without having to +// change other configs function applyRedirectsTrailingSlash( redirects: RedirectMetadata[], params: ApplyTrailingSlashParams, @@ -61,8 +64,8 @@ function validateCollectedRedirects( try { validateRedirect(redirect); return undefined; - } catch (e) { - return (e as Error).message; + } catch (err) { + return (err as Error).message; } }) .filter(Boolean); @@ -76,7 +79,7 @@ function validateCollectedRedirects( const allowedToPaths = pluginContext.relativeRoutesPaths; const toPaths = redirects.map((redirect) => redirect.to); - const illegalToPaths = difference(toPaths, allowedToPaths); + const illegalToPaths = _.difference(toPaths, allowedToPaths); if (illegalToPaths.length > 0) { throw new Error( `You are trying to create client-side redirections to paths that do not exist: @@ -95,7 +98,7 @@ function filterUnwantedRedirects( ): RedirectMetadata[] { // we don't want to create twice the same redirect // that would lead to writing twice the same html redirection file - Object.entries(groupBy(redirects, (redirect) => redirect.from)).forEach( + Object.entries(_.groupBy(redirects, (redirect) => redirect.from)).forEach( ([from, groupedFromRedirects]) => { if (groupedFromRedirects.length > 1) { logger.error`name=${'@docusaurus/plugin-client-redirects'}: multiple redirects are created with the same "from" pathname: path=${from} @@ -105,7 +108,7 @@ It is not possible to redirect the same pathname to multiple destinations: ${gro } }, ); - const collectedRedirects = uniqBy(redirects, (redirect) => redirect.from); + const collectedRedirects = _.uniqBy(redirects, (redirect) => redirect.from); // We don't want to override an already existing route with a redirect file! const redirectsOverridingExistingPath = collectedRedirects.filter( @@ -163,9 +166,7 @@ function createCreateRedirectsOptionRedirects( createRedirects: PluginOptions['createRedirects'], ): RedirectMetadata[] { function createPathRedirects(path: string): RedirectMetadata[] { - const fromsMixed: string | string[] = createRedirects - ? createRedirects(path) || [] - : []; + const fromsMixed: string | string[] = createRedirects?.(path) ?? []; const froms: string[] = typeof fromsMixed === 'string' ? [fromsMixed] : fromsMixed; diff --git a/packages/docusaurus-plugin-client-redirects/src/createRedirectPageContent.ts b/packages/docusaurus-plugin-client-redirects/src/createRedirectPageContent.ts index f55a6e0e5687..b7bdd7f61c6d 100644 --- a/packages/docusaurus-plugin-client-redirects/src/createRedirectPageContent.ts +++ b/packages/docusaurus-plugin-client-redirects/src/createRedirectPageContent.ts @@ -7,24 +7,22 @@ import * as eta from 'eta'; import redirectPageTemplate from './templates/redirectPage.template.html'; -import {memoize} from 'lodash'; +import _ from 'lodash'; -type CreateRedirectPageOptions = { - toUrl: string; -}; - -const getCompiledRedirectPageTemplate = memoize(() => +const getCompiledRedirectPageTemplate = _.memoize(() => eta.compile(redirectPageTemplate.trim()), ); -function renderRedirectPageTemplate(data: Record) { +function renderRedirectPageTemplate(data: {toUrl: string}) { const compiled = getCompiledRedirectPageTemplate(); return compiled(data, eta.defaultConfig); } export default function createRedirectPageContent({ toUrl, -}: CreateRedirectPageOptions): string { +}: { + toUrl: string; +}): string { return renderRedirectPageTemplate({ toUrl: encodeURI(toUrl), }); diff --git a/packages/docusaurus-plugin-client-redirects/src/extensionRedirects.ts b/packages/docusaurus-plugin-client-redirects/src/extensionRedirects.ts index 0ef566060287..a72452a7528c 100644 --- a/packages/docusaurus-plugin-client-redirects/src/extensionRedirects.ts +++ b/packages/docusaurus-plugin-client-redirects/src/extensionRedirects.ts @@ -81,19 +81,12 @@ export function createFromExtensionsRedirects( if (path === '' || path === '/' || alreadyEndsWithAnExtension(path)) { return []; } - - // /path => /path.html - // /path/ => /path.html/ - function getFrom(ext: string) { - if (path.endsWith('/')) { - return addTrailingSlash(`${removeTrailingSlash(path)}.${ext}`); - } else { - return `${path}.${ext}`; - } - } - return extensions.map((ext) => ({ - from: getFrom(ext), + // /path => /path.html + // /path/ => /path.html/ + from: path.endsWith('/') + ? addTrailingSlash(`${removeTrailingSlash(path)}.${ext}`) + : `${path}.${ext}`, to: path, })); }; diff --git a/packages/docusaurus-plugin-client-redirects/src/index.ts b/packages/docusaurus-plugin-client-redirects/src/index.ts index 2fc739c4840a..21ec1195f373 100644 --- a/packages/docusaurus-plugin-client-redirects/src/index.ts +++ b/packages/docusaurus-plugin-client-redirects/src/index.ts @@ -5,11 +5,10 @@ * LICENSE file in the root directory of this source tree. */ -import type {LoadContext, Plugin, Props} from '@docusaurus/types'; +import type {LoadContext, Plugin} from '@docusaurus/types'; import type {PluginContext, RedirectMetadata} from './types'; import type {PluginOptions} from '@docusaurus/plugin-client-redirects'; -import normalizePluginOptions from './normalizePluginOptions'; import collectRedirects from './collectRedirects'; import writeRedirectFiles, { toRedirectFilesMetadata, @@ -19,15 +18,13 @@ import {removePrefix, addLeadingSlash} from '@docusaurus/utils'; export default function pluginClientRedirectsPages( context: LoadContext, - opts: PluginOptions, + options: PluginOptions, ): Plugin { const {trailingSlash} = context.siteConfig; - const options = normalizePluginOptions(opts); - return { name: 'docusaurus-plugin-client-redirects', - async postBuild(props: Props) { + async postBuild(props) { const pluginContext: PluginContext = { relativeRoutesPaths: props.routesPaths.map( (path) => `${addLeadingSlash(removePrefix(path, props.baseUrl))}`, @@ -53,3 +50,5 @@ export default function pluginClientRedirectsPages( }, }; } + +export {validateOptions} from './options'; diff --git a/packages/docusaurus-plugin-client-redirects/src/normalizePluginOptions.ts b/packages/docusaurus-plugin-client-redirects/src/normalizePluginOptions.ts deleted file mode 100644 index f3d2cfb82a55..000000000000 --- a/packages/docusaurus-plugin-client-redirects/src/normalizePluginOptions.ts +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import type { - PluginOptions, - Options as UserPluginOptions, - RedirectOption, -} from '@docusaurus/plugin-client-redirects'; -import {Joi, PathnameSchema} from '@docusaurus/utils-validation'; -import {DEFAULT_PLUGIN_ID} from '@docusaurus/utils'; - -export const DefaultPluginOptions: PluginOptions = { - id: DEFAULT_PLUGIN_ID, // TODO temporary - fromExtensions: [], - toExtensions: [], - redirects: [], -}; - -const RedirectPluginOptionValidation = Joi.object({ - to: PathnameSchema.required(), - from: Joi.alternatives().try( - PathnameSchema.required(), - Joi.array().items(PathnameSchema.required()), - ), -}); - -const isString = Joi.string().required().not(null); - -const UserOptionsSchema = Joi.object({ - id: Joi.string().optional(), // TODO remove once validation migrated to new system - fromExtensions: Joi.array().items(isString), - toExtensions: Joi.array().items(isString), - redirects: Joi.array().items(RedirectPluginOptionValidation), - createRedirects: Joi.function().arity(1), -}); - -function validateUserOptions(userOptions: UserPluginOptions) { - const {error} = UserOptionsSchema.validate(userOptions, { - abortEarly: true, - allowUnknown: false, - }); - if (error) { - throw new Error( - `Invalid @docusaurus/plugin-client-redirects options: ${error.message} - ${JSON.stringify(userOptions, null, 2)}`, - ); - } -} - -export default function normalizePluginOptions( - userPluginOptions: UserPluginOptions = {}, -): PluginOptions { - validateUserOptions(userPluginOptions); - return {...DefaultPluginOptions, ...userPluginOptions}; -} diff --git a/packages/docusaurus-plugin-client-redirects/src/options.ts b/packages/docusaurus-plugin-client-redirects/src/options.ts new file mode 100644 index 000000000000..d81b535482b8 --- /dev/null +++ b/packages/docusaurus-plugin-client-redirects/src/options.ts @@ -0,0 +1,50 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import type { + PluginOptions, + Options, + RedirectOption, +} from '@docusaurus/plugin-client-redirects'; +import type {OptionValidationContext} from '@docusaurus/types'; +import {Joi, PathnameSchema} from '@docusaurus/utils-validation'; + +export const DEFAULT_OPTIONS: Partial = { + fromExtensions: [], + toExtensions: [], + redirects: [], +}; + +const RedirectPluginOptionValidation = Joi.object({ + to: PathnameSchema.required(), + from: Joi.alternatives().try( + PathnameSchema.required(), + Joi.array().items(PathnameSchema.required()), + ), +}); + +const isString = Joi.string().required().not(null); + +const UserOptionsSchema = Joi.object({ + fromExtensions: Joi.array() + .items(isString) + .default(DEFAULT_OPTIONS.fromExtensions), + toExtensions: Joi.array() + .items(isString) + .default(DEFAULT_OPTIONS.toExtensions), + redirects: Joi.array() + .items(RedirectPluginOptionValidation) + .default(DEFAULT_OPTIONS.redirects), + createRedirects: Joi.function().maxArity(1), +}).default(DEFAULT_OPTIONS); + +export function validateOptions({ + validate, + options: userOptions, +}: OptionValidationContext): PluginOptions { + return validate(UserOptionsSchema, userOptions); +} diff --git a/packages/docusaurus-plugin-client-redirects/src/plugin-client-redirects.d.ts b/packages/docusaurus-plugin-client-redirects/src/plugin-client-redirects.d.ts index fb580bae6ad6..a943eace5e8f 100644 --- a/packages/docusaurus-plugin-client-redirects/src/plugin-client-redirects.d.ts +++ b/packages/docusaurus-plugin-client-redirects/src/plugin-client-redirects.d.ts @@ -10,18 +10,23 @@ export type RedirectOption = { from: string | string[]; }; -// For a given existing route path, -// return all the paths from which we should redirect from -export type CreateRedirectsFnOption = ( - path: string, -) => string[] | string | null | undefined; - export type PluginOptions = { + /** Plugin ID. */ id: string; + /** The extensions to be removed from the route after redirecting. */ fromExtensions: string[]; + /** The extensions to be appended to the route after redirecting. */ toExtensions: string[]; + /** The list of redirect rules, each one with multiple `from`s → one `to`. */ redirects: RedirectOption[]; - createRedirects?: CreateRedirectsFnOption; + /** + * A callback to create a redirect rule. + * @returns All the paths from which we should redirect to `path` + */ + createRedirects?: ( + /** An existing Docusaurus route path */ + path: string, + ) => string[] | string | null | undefined; }; export type Options = Partial; diff --git a/packages/docusaurus-plugin-client-redirects/src/types.ts b/packages/docusaurus-plugin-client-redirects/src/types.ts index 2affd0c6c5d3..ca0f61a6fb01 100644 --- a/packages/docusaurus-plugin-client-redirects/src/types.ts +++ b/packages/docusaurus-plugin-client-redirects/src/types.ts @@ -8,16 +8,22 @@ import type {Props} from '@docusaurus/types'; import type {PluginOptions} from '@docusaurus/plugin-client-redirects'; -// The minimal infos the plugin needs to work +/** + * The minimal infos the plugin needs to work + */ export type PluginContext = Pick & { options: PluginOptions; relativeRoutesPaths: string[]; }; -// In-memory representation of redirects we want: easier to test -// /!\ easy to be confused: "from" is the new page we should create, -// that redirects to "to": the existing Docusaurus page +/** + * In-memory representation of redirects we want: easier to test + * /!\ easy to be confused: "from" is the new page we should create, + * that redirects to "to": the existing Docusaurus page + */ export type RedirectMetadata = { - from: string; // pathname - to: string; // pathname + /** Pathname of the new page we should create */ + from: string; + /** Pathname of an existing Docusaurus page */ + to: string; }; diff --git a/packages/docusaurus-plugin-client-redirects/src/writeRedirectFiles.ts b/packages/docusaurus-plugin-client-redirects/src/writeRedirectFiles.ts index 8dcb5a14a74b..e0a8cabd7ac4 100644 --- a/packages/docusaurus-plugin-client-redirects/src/writeRedirectFiles.ts +++ b/packages/docusaurus-plugin-client-redirects/src/writeRedirectFiles.ts @@ -7,7 +7,7 @@ import fs from 'fs-extra'; import path from 'path'; -import {memoize} from 'lodash'; +import _ from 'lodash'; import type {PluginContext, RedirectMetadata} from './types'; import createRedirectPageContent from './createRedirectPageContent'; @@ -25,7 +25,8 @@ export function createToUrl(baseUrl: string, to: string): string { } // Create redirect file path -// Make sure this path has lower precedence over the original file path when served by host providers! +// Make sure this path has lower precedence over the original file path when +// served by host providers! // Otherwise it can produce infinite redirect loops! // // See https://github.com/facebook/docusaurus/issues/5055 @@ -39,17 +40,19 @@ function getRedirectFilePath( const filePath = path.dirname(fromPath); // Edge case for https://github.com/facebook/docusaurus/pull/5102 // If the redirect source path is /xyz, with file /xyz.html - // We can't write the redirect file at /xyz.html/index.html because for Unix FS, a file/folder can't have the same name "xyz.html" - // The only possible solution for a redirect file is thus /xyz.html.html (I know, looks suspicious) + // We can't write the redirect file at /xyz.html/index.html because for Unix + // FS, a file/folder can't have the same name "xyz.html" + // The only possible solution for a redirect file is thus /xyz.html.html (I + // know, looks suspicious) if (trailingSlash === false && fileName.endsWith('.html')) { return path.join(filePath, `${fileName}.html`); } - // If the target path is /xyz, with file /xyz/index.html, we don't want the redirect file to be /xyz.html - // otherwise it would be picked in priority and the redirect file would redirect to itself - // We prefer the redirect file to be /xyz.html/index.html, served with lower priority for most static hosting tools - else { - return path.join(filePath, `${fileName}/index.html`); - } + // If the target path is /xyz, with file /xyz/index.html, we don't want the + // redirect file to be /xyz.html, otherwise it would be picked in priority and + // the redirect file would redirect to itself. We prefer the redirect file to + // be /xyz.html/index.html, served with lower priority for most static hosting + // tools + return path.join(filePath, `${fileName}/index.html`); } export function toRedirectFilesMetadata( @@ -60,7 +63,7 @@ export function toRedirectFilesMetadata( // Perf: avoid rendering the template twice with the exact same "props" // We might create multiple redirect pages for the same destination url // note: the first fn arg is the cache key! - const createPageContentMemoized = memoize((toUrl: string) => + const createPageContentMemoized = _.memoize((toUrl: string) => createRedirectPageContent({toUrl}), ); @@ -89,8 +92,7 @@ export async function writeRedirectFile( 'The redirect plugin is not supposed to override existing files.', ); } - await fs.ensureDir(path.dirname(file.fileAbsolutePath)); - await fs.writeFile( + await fs.outputFile( file.fileAbsolutePath, file.fileContent, // Hard security to prevent file overrides diff --git a/packages/docusaurus-plugin-content-blog/package.json b/packages/docusaurus-plugin-content-blog/package.json index 5c5dd3543f3f..4dffa1a94040 100644 --- a/packages/docusaurus-plugin-content-blog/package.json +++ b/packages/docusaurus-plugin-content-blog/package.json @@ -1,6 +1,6 @@ { "name": "@docusaurus/plugin-content-blog", - "version": "2.0.0-beta.14", + "version": "2.0.0-beta.18", "description": "Blog plugin for Docusaurus.", "main": "lib/index.js", "types": "src/plugin-content-blog.d.ts", @@ -18,24 +18,24 @@ }, "license": "MIT", "dependencies": { - "@docusaurus/core": "2.0.0-beta.14", - "@docusaurus/logger": "2.0.0-beta.14", - "@docusaurus/mdx-loader": "2.0.0-beta.14", - "@docusaurus/utils": "2.0.0-beta.14", - "@docusaurus/utils-common": "2.0.0-beta.14", - "@docusaurus/utils-validation": "2.0.0-beta.14", + "@docusaurus/core": "2.0.0-beta.18", + "@docusaurus/logger": "2.0.0-beta.18", + "@docusaurus/mdx-loader": "2.0.0-beta.18", + "@docusaurus/utils": "2.0.0-beta.18", + "@docusaurus/utils-common": "2.0.0-beta.18", + "@docusaurus/utils-validation": "2.0.0-beta.18", "cheerio": "^1.0.0-rc.10", "feed": "^4.2.2", - "fs-extra": "^10.0.0", - "lodash": "^4.17.20", + "fs-extra": "^10.0.1", + "lodash": "^4.17.21", "reading-time": "^1.5.0", "remark-admonitions": "^1.2.1", "tslib": "^2.3.1", "utility-types": "^3.10.0", - "webpack": "^5.61.0" + "webpack": "^5.70.0" }, "devDependencies": { - "@docusaurus/types": "2.0.0-beta.14", + "@docusaurus/types": "2.0.0-beta.18", "escape-string-regexp": "^4.0.0" }, "peerDependencies": { diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/authorsMapFiles/authors.json b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/authorsMapFiles/authors.json index 1cca9c9f0a1c..3c5009233655 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/authorsMapFiles/authors.json +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/authorsMapFiles/authors.json @@ -11,7 +11,8 @@ "title": "Docusaurus maintainer", "url": "https://sebastienlorber.com", "image_url": "https://github.com/slorber.png", - "twitter": "sebastienlorber" + "twitter": "sebastienlorber", + "email": "lorber.sebastien@gmail.com" }, "yangshun": { "name": "Yangshun Tay", diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/authorsMapFiles/authors.yml b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/authorsMapFiles/authors.yml index 5bce126cef98..ac09421385d9 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/authorsMapFiles/authors.yml +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/authorsMapFiles/authors.yml @@ -12,6 +12,7 @@ slorber: url: https://sebastienlorber.com image_url: https://github.com/slorber.png twitter: sebastienlorber + email: lorber.sebastien@gmail.com yangshun: name: Yangshun Tay diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-tags/blog/another-simple-slug-with-tags.md b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-tags/blog/another-simple-slug-with-tags.md new file mode 100644 index 000000000000..8cfe5ab9459d --- /dev/null +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-tags/blog/another-simple-slug-with-tags.md @@ -0,0 +1,13 @@ +--- +slug: /simple/slug/another +title: Another Simple Slug +date: 2020-08-15 + +author: Sébastien Lorber +author_title: Docusaurus maintainer +author_url: https://sebastienlorber.com + +tags: [tag1] +--- + +simple url slug diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-tags/blog/another-with-tags.md b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-tags/blog/another-with-tags.md new file mode 100644 index 000000000000..888efec53571 --- /dev/null +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-tags/blog/another-with-tags.md @@ -0,0 +1,9 @@ +--- +slug: /another/tags +title: Another With Tag +date: 2020-08-15 + +tags: [tag1, tag2] +--- + +with tag diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-tags/blog/another-with-tags2.md b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-tags/blog/another-with-tags2.md new file mode 100644 index 000000000000..b69e2e9e17bf --- /dev/null +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website-blog-with-tags/blog/another-with-tags2.md @@ -0,0 +1,9 @@ +--- +slug: /another/tags2 +title: Another With Tag +date: 2020-08-15 + +tags: [tag1, tag2] +--- + +with tag diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/authors.yml b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/authors.yml index cb6c296b212a..a704e9e9841b 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/authors.yml +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/blog/authors.yml @@ -1,4 +1,5 @@ - slorber: name: Sébastien Lorber title: Docusaurus maintainer + email: lorber.sebastien@gmail.com + url: https://sebastienlorber.com diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/mdx-blog-post/index.html b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/mdx-blog-post/index.html index 921732b87755..0b8cd0bbf476 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/mdx-blog-post/index.html +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/mdx-blog-post/index.html @@ -11,8 +11,7 @@
-

Full Blog Sample

· One min read

HTML Heading 1

HTML Heading 2

HTML Paragraph

Import DOM

Heading 1

Heading 2

Heading 3

Heading 4

Heading 5
  • list1
  • list2
  • list3
  • list1
  • list2
  • list3

Normal Text Italics Text Bold Text

link -image

+

Full Blog Sample

· One min read

HTML Heading 1

HTML Heading 2

HTML Paragraph

Import DOM

Heading 1

Heading 2

Heading 3

Heading 4

Heading 5
  • list1
  • list2
  • list3
  • list1
  • list2
  • list3

Normal Text Italics Text Bold Text

linkimage

diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/i18n/en/docusaurus-plugin-content-blog/authors.yml b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/i18n/en/docusaurus-plugin-content-blog/authors.yml index f42af6257f93..f509f4ff45ee 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/i18n/en/docusaurus-plugin-content-blog/authors.yml +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/i18n/en/docusaurus-plugin-content-blog/authors.yml @@ -1,5 +1,4 @@ - - slorber: name: Sébastien Lorber (translated) title: Docusaurus maintainer (translated) + email: lorber.sebastien@gmail.com diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/blogUtils.test.ts.snap b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/blogUtils.test.ts.snap new file mode 100644 index 000000000000..6137538dad52 --- /dev/null +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/blogUtils.test.ts.snap @@ -0,0 +1,159 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`linkify reports broken markdown links 1`] = ` +"--- +title: This post links to another one! +--- + +[Good link 1](/blog/2018/12/14/Happy-First-Birthday-Slash) + +[Good link 2](/blog/2018/12/14/Happy-First-Birthday-Slash) + +[Bad link 1](postNotExist1.md) + +[Bad link 1](./postNotExist2.mdx) +" +`; + +exports[`linkify transforms to correct link 1`] = ` +"--- +title: This post links to another one! +--- + +[Linked post](/blog/2018/12/14/Happy-First-Birthday-Slash)" +`; + +exports[`paginateBlogPosts generates right pages 1`] = ` +[ + { + "items": [ + "post1", + "post2", + ], + "metadata": { + "blogDescription": "Blog Description", + "blogTitle": "Blog Title", + "nextPage": "/blog/page/2", + "page": 1, + "permalink": "/blog", + "postsPerPage": 2, + "previousPage": undefined, + "totalCount": 5, + "totalPages": 3, + }, + }, + { + "items": [ + "post3", + "post4", + ], + "metadata": { + "blogDescription": "Blog Description", + "blogTitle": "Blog Title", + "nextPage": "/blog/page/3", + "page": 2, + "permalink": "/blog/page/2", + "postsPerPage": 2, + "previousPage": "/blog", + "totalCount": 5, + "totalPages": 3, + }, + }, + { + "items": [ + "post5", + ], + "metadata": { + "blogDescription": "Blog Description", + "blogTitle": "Blog Title", + "nextPage": undefined, + "page": 3, + "permalink": "/blog/page/3", + "postsPerPage": 2, + "previousPage": "/blog/page/2", + "totalCount": 5, + "totalPages": 3, + }, + }, +] +`; + +exports[`paginateBlogPosts generates right pages 2`] = ` +[ + { + "items": [ + "post1", + "post2", + ], + "metadata": { + "blogDescription": "Blog Description", + "blogTitle": "Blog Title", + "nextPage": "/page/2", + "page": 1, + "permalink": "/", + "postsPerPage": 2, + "previousPage": undefined, + "totalCount": 5, + "totalPages": 3, + }, + }, + { + "items": [ + "post3", + "post4", + ], + "metadata": { + "blogDescription": "Blog Description", + "blogTitle": "Blog Title", + "nextPage": "/page/3", + "page": 2, + "permalink": "/page/2", + "postsPerPage": 2, + "previousPage": "/", + "totalCount": 5, + "totalPages": 3, + }, + }, + { + "items": [ + "post5", + ], + "metadata": { + "blogDescription": "Blog Description", + "blogTitle": "Blog Title", + "nextPage": undefined, + "page": 3, + "permalink": "/page/3", + "postsPerPage": 2, + "previousPage": "/page/2", + "totalCount": 5, + "totalPages": 3, + }, + }, +] +`; + +exports[`paginateBlogPosts generates right pages 3`] = ` +[ + { + "items": [ + "post1", + "post2", + "post3", + "post4", + "post5", + ], + "metadata": { + "blogDescription": "Blog Description", + "blogTitle": "Blog Title", + "nextPage": undefined, + "page": 1, + "permalink": "/", + "postsPerPage": 10, + "previousPage": undefined, + "totalCount": 5, + "totalPages": 1, + }, + }, +] +`; diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/feed.test.ts.snap b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/feed.test.ts.snap index 28d2e5b29413..5fa35968ed25 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/feed.test.ts.snap +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/feed.test.ts.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`blogFeed atom shows feed item for each post 1`] = ` -Array [ +exports[`atom has feed item for each post 1`] = ` +[ " https://docusaurus.io/myBaseUrl/blog @@ -26,8 +26,7 @@ Array [ 2021-03-05T00:00:00.000Z - HTML Heading 1

HTML Heading 2

HTML Paragraph

Import DOM

Heading 1

Heading 2

Heading 3

Heading 4

Heading 5
  • list1
  • list2
  • list3
  • list1
  • list2
  • list3

Normal Text Italics Text Bold Text

link -\\"image\\"

]]>
+ HTML Heading 1

HTML Heading 2

HTML Paragraph

Import DOM

Heading 1

Heading 2

Heading 3

Heading 4

Heading 5
  • list1
  • list2
  • list3
  • list1
  • list2
  • list3

Normal Text Italics Text Bold Text

link\\"image\\"

]]>
<![CDATA[Complex Slug]]> @@ -78,14 +77,15 @@ Array [ Sébastien Lorber (translated) + lorber.sebastien@gmail.com
", ] `; -exports[`blogFeed json shows feed item for each post 1`] = ` -Array [ +exports[`json has feed item for each post 1`] = ` +[ "{ \\"version\\": \\"https://jsonfeed.org/version/1\\", \\"title\\": \\"Hello Blog\\", @@ -103,7 +103,7 @@ Array [ }, { \\"id\\": \\"/mdx-blog-post\\", - \\"content_html\\": \\"

HTML Heading 1

HTML Heading 2

HTML Paragraph

Import DOM

Heading 1

Heading 2

Heading 3

Heading 4

Heading 5
  • list1
  • list2
  • list3
  • list1
  • list2
  • list3

Normal Text Italics Text Bold Text

link\\\\n\\\\\\"image\\\\\\"

\\", + \\"content_html\\": \\"

HTML Heading 1

HTML Heading 2

HTML Paragraph

Import DOM

Heading 1

Heading 2

Heading 3

Heading 4

Heading 5
  • list1
  • list2
  • list3
  • list1
  • list2
  • list3

Normal Text Italics Text Bold Text

link\\\\\\"image\\\\\\"

\\", \\"url\\": \\"https://docusaurus.io/myBaseUrl/blog/mdx-blog-post\\", \\"title\\": \\"Full Blog Sample\\", \\"summary\\": \\"HTML Heading 1\\", @@ -171,8 +171,8 @@ Array [ ] `; -exports[`blogFeed rss shows feed item for each post 1`] = ` -Array [ +exports[`rss has feed item for each post 1`] = ` +[ " @@ -182,6 +182,7 @@ Array [ Sat, 06 Mar 2021 00:00:00 GMT https://validator.w3.org/feed/docs/rss2.html https://github.com/jpmonette/feed + en Copyright <![CDATA[MDX Blog Sample with require calls]]> @@ -197,8 +198,7 @@ Array [ /mdx-blog-post Fri, 05 Mar 2021 00:00:00 GMT - HTML Heading 1

HTML Heading 2

HTML Paragraph

Import DOM

Heading 1

Heading 2

Heading 3

Heading 4

Heading 5
  • list1
  • list2
  • list3
  • list1
  • list2
  • list3

Normal Text Italics Text Bold Text

link -\\"image\\"

]]>
+ HTML Heading 1

HTML Heading 2

HTML Paragraph

Import DOM

Heading 1

Heading 2

Heading 3

Heading 4

Heading 5
  • list1
  • list2
  • list3
  • list1
  • list2
  • list3

Normal Text Italics Text Bold Text

link\\"image\\"

]]>
<![CDATA[Complex Slug]]> @@ -240,6 +240,7 @@ Array [ Fri, 14 Dec 2018 00:00:00 GMT Happy birthday!

]]>
+ lorber.sebastien@gmail.com (Sébastien Lorber (translated))
", diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/index.test.ts.snap new file mode 100644 index 000000000000..1eab791d4450 --- /dev/null +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/index.test.ts.snap @@ -0,0 +1,138 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`blog plugin works on blog tags without pagination 1`] = ` +{ + "/blog/tags/tag-1": { + "items": [ + "/simple/slug/another", + "/another/tags", + "/another/tags2", + ], + "name": "tag1", + "pages": [ + { + "items": [ + "/simple/slug/another", + "/another/tags", + "/another/tags2", + ], + "metadata": { + "blogDescription": "Blog", + "blogTitle": "Blog", + "nextPage": undefined, + "page": 1, + "permalink": "/blog/tags/tag-1", + "postsPerPage": 3, + "previousPage": undefined, + "totalCount": 3, + "totalPages": 1, + }, + }, + ], + "permalink": "/blog/tags/tag-1", + }, + "/blog/tags/tag-2": { + "items": [ + "/another/tags", + "/another/tags2", + ], + "name": "tag2", + "pages": [ + { + "items": [ + "/another/tags", + "/another/tags2", + ], + "metadata": { + "blogDescription": "Blog", + "blogTitle": "Blog", + "nextPage": undefined, + "page": 1, + "permalink": "/blog/tags/tag-2", + "postsPerPage": 2, + "previousPage": undefined, + "totalCount": 2, + "totalPages": 1, + }, + }, + ], + "permalink": "/blog/tags/tag-2", + }, +} +`; + +exports[`blog plugin works with blog tags 1`] = ` +{ + "/blog/tags/tag-1": { + "items": [ + "/simple/slug/another", + "/another/tags", + "/another/tags2", + ], + "name": "tag1", + "pages": [ + { + "items": [ + "/simple/slug/another", + "/another/tags", + ], + "metadata": { + "blogDescription": "Blog", + "blogTitle": "Blog", + "nextPage": "/blog/tags/tag-1/page/2", + "page": 1, + "permalink": "/blog/tags/tag-1", + "postsPerPage": 2, + "previousPage": undefined, + "totalCount": 3, + "totalPages": 2, + }, + }, + { + "items": [ + "/another/tags2", + ], + "metadata": { + "blogDescription": "Blog", + "blogTitle": "Blog", + "nextPage": undefined, + "page": 2, + "permalink": "/blog/tags/tag-1/page/2", + "postsPerPage": 2, + "previousPage": "/blog/tags/tag-1", + "totalCount": 3, + "totalPages": 2, + }, + }, + ], + "permalink": "/blog/tags/tag-1", + }, + "/blog/tags/tag-2": { + "items": [ + "/another/tags", + "/another/tags2", + ], + "name": "tag2", + "pages": [ + { + "items": [ + "/another/tags", + "/another/tags2", + ], + "metadata": { + "blogDescription": "Blog", + "blogTitle": "Blog", + "nextPage": undefined, + "page": 1, + "permalink": "/blog/tags/tag-2", + "postsPerPage": 2, + "previousPage": undefined, + "totalCount": 2, + "totalPages": 1, + }, + }, + ], + "permalink": "/blog/tags/tag-2", + }, +} +`; diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/linkify.test.ts.snap b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/linkify.test.ts.snap deleted file mode 100644 index f1c28fabbb0f..000000000000 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/linkify.test.ts.snap +++ /dev/null @@ -1,24 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`report broken markdown links 1`] = ` -"--- -title: This post links to another one! ---- - -[Good link 1](/blog/2018/12/14/Happy-First-Birthday-Slash) - -[Good link 2](/blog/2018/12/14/Happy-First-Birthday-Slash) - -[Bad link 1](postNotExist1.md) - -[Bad link 1](./postNotExist2.mdx) -" -`; - -exports[`transform to correct link 1`] = ` -"--- -title: This post links to another one! ---- - -[Linked post](/blog/2018/12/14/Happy-First-Birthday-Slash)" -`; diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/options.test.ts.snap b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/options.test.ts.snap new file mode 100644 index 000000000000..76f0a53c41b7 --- /dev/null +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/options.test.ts.snap @@ -0,0 +1,5 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`validateOptions throws Error in case of invalid feed type 1`] = `"\\"feedOptions.type\\" does not match any of the allowed types"`; + +exports[`validateOptions throws Error in case of invalid options 1`] = `"\\"postsPerPage\\" must be greater than or equal to 1"`; diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/pluginOptionSchema.test.ts.snap b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/pluginOptionSchema.test.ts.snap deleted file mode 100644 index 0a95617bd7bf..000000000000 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/pluginOptionSchema.test.ts.snap +++ /dev/null @@ -1,5 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should throw Error in case of invalid feedtype 1`] = `[ValidationError: "feedOptions.type" does not match any of the allowed types]`; - -exports[`should throw Error in case of invalid options 1`] = `[ValidationError: "postsPerPage" must be greater than or equal to 1]`; diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/translations.test.ts.snap b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/translations.test.ts.snap index ba9ff2b59bdd..beb31a882566 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/translations.test.ts.snap +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/translations.test.ts.snap @@ -1,18 +1,18 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`getContentTranslationFiles should return translation files matching snapshot 1`] = ` -Array [ - Object { - "content": Object { - "description": Object { +exports[`getContentTranslationFiles returns translation files matching snapshot 1`] = ` +[ + { + "content": { + "description": { "description": "The description for the blog used in SEO", "message": "Someone's random blog", }, - "sidebar.title": Object { + "sidebar.title": { "description": "The label for the left sidebar", "message": "All my posts", }, - "title": Object { + "title": { "description": "The title for the blog used in SEO", "message": "My blog", }, @@ -22,43 +22,84 @@ Array [ ] `; -exports[`translateContent should return translated loaded content matching snapshot 1`] = ` -Object { - "blogListPaginated": Array [ - Object { - "items": Array [ +exports[`translateContent falls back when translation is incomplete 1`] = ` +{ + "blogListPaginated": [ + { + "items": [ "hello", ], - "metadata": Object { + "metadata": { + "blogDescription": "Someone's random blog", + "blogTitle": "My blog", + "nextPage": undefined, + "page": 1, + "permalink": "/", + "postsPerPage": 10, + "previousPage": undefined, + "totalCount": 1, + "totalPages": 1, + }, + }, + ], + "blogPosts": [ + { + "id": "hello", + "metadata": { + "date": 2021-07-19T00:00:00.000Z, + "description": "/blog/2021/06/19/hello", + "formattedDate": "June 19, 2021", + "permalink": "/blog/2021/06/19/hello", + "source": "/blog/2021/06/19/hello", + "tags": [], + "title": "Hello", + "truncated": true, + }, + }, + ], + "blogSidebarTitle": "All my posts", + "blogTags": {}, + "blogTagsListPath": "/tags", +} +`; + +exports[`translateContent returns translated loaded 1`] = ` +{ + "blogListPaginated": [ + { + "items": [ + "hello", + ], + "metadata": { "blogDescription": "Someone's random blog (translated)", "blogTitle": "My blog (translated)", - "nextPage": null, + "nextPage": undefined, "page": 1, "permalink": "/", "postsPerPage": 10, - "previousPage": null, + "previousPage": undefined, "totalCount": 1, "totalPages": 1, }, }, ], - "blogPosts": Array [ - Object { + "blogPosts": [ + { "id": "hello", - "metadata": Object { + "metadata": { "date": 2021-07-19T00:00:00.000Z, "description": "/blog/2021/06/19/hello", "formattedDate": "June 19, 2021", "permalink": "/blog/2021/06/19/hello", "source": "/blog/2021/06/19/hello", - "tags": Array [], + "tags": [], "title": "Hello", "truncated": true, }, }, ], "blogSidebarTitle": "All my posts (translated)", - "blogTags": Object {}, + "blogTags": {}, "blogTagsListPath": "/tags", } `; diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/authors.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/authors.test.ts index 78152420283e..0fde188e85af 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/authors.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/authors.test.ts @@ -14,7 +14,7 @@ import { import path from 'path'; describe('getBlogPostAuthors', () => { - test('can read no authors', () => { + it('can read no authors', () => { expect( getBlogPostAuthors({ frontMatter: {}, @@ -31,7 +31,7 @@ describe('getBlogPostAuthors', () => { ).toEqual([]); }); - test('can read author from legacy front matter', () => { + it('can read author from legacy front matter', () => { expect( getBlogPostAuthors({ frontMatter: { @@ -79,7 +79,7 @@ describe('getBlogPostAuthors', () => { ]); }); - test('can read authors string', () => { + it('can read authors string', () => { expect( getBlogPostAuthors({ frontMatter: { @@ -90,7 +90,7 @@ describe('getBlogPostAuthors', () => { ).toEqual([{key: 'slorber', name: 'Sébastien Lorber'}]); }); - test('can read authors string[]', () => { + it('can read authors string[]', () => { expect( getBlogPostAuthors({ frontMatter: { @@ -107,7 +107,7 @@ describe('getBlogPostAuthors', () => { ]); }); - test('can read authors Author', () => { + it('can read authors Author', () => { expect( getBlogPostAuthors({ frontMatter: { @@ -118,7 +118,7 @@ describe('getBlogPostAuthors', () => { ).toEqual([{name: 'Sébastien Lorber', title: 'maintainer'}]); }); - test('can read authors Author[]', () => { + it('can read authors Author[]', () => { expect( getBlogPostAuthors({ frontMatter: { @@ -135,7 +135,7 @@ describe('getBlogPostAuthors', () => { ]); }); - test('can read authors complex (string | Author)[] setup with keys and local overrides', () => { + it('can read authors complex (string | Author)[] setup with keys and local overrides', () => { expect( getBlogPostAuthors({ frontMatter: { @@ -166,7 +166,7 @@ describe('getBlogPostAuthors', () => { ]); }); - test('throw when using author key with no authorsMap', () => { + it('throw when using author key with no authorsMap', () => { expect(() => getBlogPostAuthors({ frontMatter: { @@ -180,7 +180,7 @@ describe('getBlogPostAuthors', () => { `); }); - test('throw when using author key with empty authorsMap', () => { + it('throw when using author key with empty authorsMap', () => { expect(() => getBlogPostAuthors({ frontMatter: { @@ -194,7 +194,7 @@ describe('getBlogPostAuthors', () => { `); }); - test('throw when using bad author key in string', () => { + it('throw when using bad author key in string', () => { expect(() => getBlogPostAuthors({ frontMatter: { @@ -213,7 +213,7 @@ describe('getBlogPostAuthors', () => { `); }); - test('throw when using bad author key in string[]', () => { + it('throw when using bad author key in string[]', () => { expect(() => getBlogPostAuthors({ frontMatter: { @@ -232,7 +232,7 @@ describe('getBlogPostAuthors', () => { `); }); - test('throw when using bad author key in Author[].key', () => { + it('throw when using bad author key in Author[].key', () => { expect(() => getBlogPostAuthors({ frontMatter: { @@ -251,7 +251,7 @@ describe('getBlogPostAuthors', () => { `); }); - test('throw when mixing legacy/new authors front matter', () => { + it('throw when mixing legacy/new authors front matter', () => { expect(() => getBlogPostAuthors({ frontMatter: { @@ -287,7 +287,7 @@ describe('getAuthorsMap', () => { contentPath: fixturesDir, }; - test('getAuthorsMap can read yml file', async () => { + it('getAuthorsMap can read yml file', async () => { await expect( getAuthorsMap({ contentPaths, @@ -296,7 +296,7 @@ describe('getAuthorsMap', () => { ).resolves.toBeDefined(); }); - test('getAuthorsMap can read json file', async () => { + it('getAuthorsMap can read json file', async () => { await expect( getAuthorsMap({ contentPaths, @@ -305,7 +305,7 @@ describe('getAuthorsMap', () => { ).resolves.toBeDefined(); }); - test('getAuthorsMap can return undefined if yaml file not found', async () => { + it('getAuthorsMap can return undefined if yaml file not found', async () => { await expect( getAuthorsMap({ contentPaths, @@ -316,7 +316,7 @@ describe('getAuthorsMap', () => { }); describe('validateAuthorsMap', () => { - test('accept valid authors map', () => { + it('accept valid authors map', () => { const authorsMap: AuthorsMap = { slorber: { name: 'Sébastien Lorber', @@ -338,7 +338,7 @@ describe('validateAuthorsMap', () => { expect(validateAuthorsMap(authorsMap)).toEqual(authorsMap); }); - test('rename snake case image_url to camelCase imageURL', () => { + it('rename snake case image_url to camelCase imageURL', () => { const authorsMap: AuthorsMap = { slorber: { name: 'Sébastien Lorber', @@ -353,7 +353,7 @@ describe('validateAuthorsMap', () => { }); }); - test('accept author with only image', () => { + it('accept author with only image', () => { const authorsMap: AuthorsMap = { slorber: { imageURL: 'https://github.com/slorber.png', @@ -363,7 +363,7 @@ describe('validateAuthorsMap', () => { expect(validateAuthorsMap(authorsMap)).toEqual(authorsMap); }); - test('reject author without name or image', () => { + it('reject author without name or image', () => { const authorsMap: AuthorsMap = { slorber: { title: 'foo', @@ -376,49 +376,49 @@ describe('validateAuthorsMap', () => { ); }); - test('reject undefined author', () => { + it('reject undefined author', () => { expect(() => validateAuthorsMap({ slorber: undefined, }), - ).toThrowErrorMatchingInlineSnapshot(`"\\"slorber\\" is required"`); + ).toThrowErrorMatchingInlineSnapshot( + `"\\"slorber\\" cannot be undefined. It should be an author object containing properties like name, title, and imageURL."`, + ); }); - test('reject null author', () => { + it('reject null author', () => { expect(() => validateAuthorsMap({ slorber: null, }), ).toThrowErrorMatchingInlineSnapshot( - `"\\"slorber\\" must be of type object"`, + `"\\"slorber\\" should be an author object containing properties like name, title, and imageURL."`, ); }); - test('reject array author', () => { + it('reject array author', () => { expect(() => validateAuthorsMap({slorber: []}), ).toThrowErrorMatchingInlineSnapshot( - `"\\"slorber\\" must be of type object"`, + `"\\"slorber\\" should be an author object containing properties like name, title, and imageURL."`, ); }); - test('reject array content', () => { + it('reject array content', () => { expect(() => validateAuthorsMap([])).toThrowErrorMatchingInlineSnapshot( - // TODO improve this error message - `"\\"value\\" must be of type object"`, + `"The authors map file should contain an object where each entry contains an author key and the corresponding author's data."`, ); }); - test('reject flat author', () => { + it('reject flat author', () => { expect(() => validateAuthorsMap({name: 'Sébastien'}), ).toThrowErrorMatchingInlineSnapshot( - // TODO improve this error message - `"\\"name\\" must be of type object"`, + `"\\"name\\" should be an author object containing properties like name, title, and imageURL."`, ); }); - test('reject non-map author', () => { + it('reject non-map author', () => { const authorsMap: AuthorsMap = { // @ts-expect-error: for tests slorber: [], @@ -426,7 +426,7 @@ describe('validateAuthorsMap', () => { expect(() => validateAuthorsMap(authorsMap), ).toThrowErrorMatchingInlineSnapshot( - `"\\"slorber\\" must be of type object"`, + `"\\"slorber\\" should be an author object containing properties like name, title, and imageURL."`, ); }); }); diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/blogUtils.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/blogUtils.test.ts index 4a8874dfb8ce..5460a4440f9f 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/blogUtils.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/blogUtils.test.ts @@ -5,27 +5,82 @@ * LICENSE file in the root directory of this source tree. */ -import {truncate, parseBlogFileName} from '../blogUtils'; +import {jest} from '@jest/globals'; +import { + truncate, + parseBlogFileName, + linkify, + getSourceToPermalink, + paginateBlogPosts, + type LinkifyParams, +} from '../blogUtils'; +import fs from 'fs-extra'; +import path from 'path'; +import type { + BlogBrokenMarkdownLink, + BlogContentPaths, + BlogPost, +} from '../types'; describe('truncate', () => { - test('truncates texts', () => { + it('truncates texts', () => { expect( - truncate('aaa\n\nbbb\nccc', //), - ).toEqual('aaa\n'); + truncate('aaa\n\nbbb\n ccc', //), + ).toBe('aaa\n'); expect( - truncate('\n\nbbb\nccc', //), - ).toEqual('\n'); + truncate('\n\nbbb\n ccc', //), + ).toBe('\n'); }); - test('leaves texts without markers', () => { - expect(truncate('aaa\nbbb\nccc', //)).toEqual( - 'aaa\nbbb\nccc', + + it('leaves texts without markers', () => { + expect(truncate('aaa\nbbb\n ccc', //)).toBe( + 'aaa\nbbb\n ccc', ); - expect(truncate('', //)).toEqual(''); + expect(truncate('', //)).toBe(''); + }); +}); + +describe('paginateBlogPosts', () => { + it('generates right pages', () => { + const blogPosts = [ + {id: 'post1', metadata: {}, content: 'Foo 1'}, + {id: 'post2', metadata: {}, content: 'Foo 2'}, + {id: 'post3', metadata: {}, content: 'Foo 3'}, + {id: 'post4', metadata: {}, content: 'Foo 4'}, + {id: 'post5', metadata: {}, content: 'Foo 5'}, + ] as BlogPost[]; + expect( + paginateBlogPosts({ + blogPosts, + basePageUrl: '/blog', + blogTitle: 'Blog Title', + blogDescription: 'Blog Description', + postsPerPageOption: 2, + }), + ).toMatchSnapshot(); + expect( + paginateBlogPosts({ + blogPosts, + basePageUrl: '/', + blogTitle: 'Blog Title', + blogDescription: 'Blog Description', + postsPerPageOption: 2, + }), + ).toMatchSnapshot(); + expect( + paginateBlogPosts({ + blogPosts, + basePageUrl: '/', + blogTitle: 'Blog Title', + blogDescription: 'Blog Description', + postsPerPageOption: 10, + }), + ).toMatchSnapshot(); }); }); describe('parseBlogFileName', () => { - test('parse file', () => { + it('parses file', () => { expect(parseBlogFileName('some-post.md')).toEqual({ date: undefined, text: 'some-post', @@ -33,7 +88,7 @@ describe('parseBlogFileName', () => { }); }); - test('parse folder', () => { + it('parses folder', () => { expect(parseBlogFileName('some-post/index.md')).toEqual({ date: undefined, text: 'some-post', @@ -41,7 +96,7 @@ describe('parseBlogFileName', () => { }); }); - test('parse nested file', () => { + it('parses nested file', () => { expect(parseBlogFileName('some-post/some-file.md')).toEqual({ date: undefined, text: 'some-post/some-file', @@ -49,7 +104,7 @@ describe('parseBlogFileName', () => { }); }); - test('parse nested folder', () => { + it('parses nested folder', () => { expect(parseBlogFileName('some-post/some-subfolder/index.md')).toEqual({ date: undefined, text: 'some-post/some-subfolder', @@ -57,7 +112,7 @@ describe('parseBlogFileName', () => { }); }); - test('parse file respecting date convention', () => { + it('parses file respecting date convention', () => { expect( parseBlogFileName('2021-05-12-announcing-docusaurus-two-beta.md'), ).toEqual({ @@ -67,7 +122,7 @@ describe('parseBlogFileName', () => { }); }); - test('parse folder name respecting date convention', () => { + it('parses folder name respecting date convention', () => { expect( parseBlogFileName('2021-05-12-announcing-docusaurus-two-beta/index.md'), ).toEqual({ @@ -77,7 +132,7 @@ describe('parseBlogFileName', () => { }); }); - test('parse folder tree respecting date convention', () => { + it('parses folder tree respecting date convention', () => { expect( parseBlogFileName('2021/05/12/announcing-docusaurus-two-beta/index.md'), ).toEqual({ @@ -87,7 +142,7 @@ describe('parseBlogFileName', () => { }); }); - test('parse folder name/tree (mixed) respecting date convention', () => { + it('parses folder name/tree (mixed) respecting date convention', () => { expect( parseBlogFileName('2021/05-12-announcing-docusaurus-two-beta/index.md'), ).toEqual({ @@ -97,19 +152,19 @@ describe('parseBlogFileName', () => { }); }); - test('parse nested folder tree respecting date convention', () => { + it('parses nested folder tree respecting date convention', () => { expect( parseBlogFileName( - '2021/05/12/announcing-docusaurus-two-beta/subfolder/subfile.md', + '2021/05/12/announcing-docusaurus-two-beta/subfolder/file.md', ), ).toEqual({ date: new Date('2021-05-12Z'), - text: 'announcing-docusaurus-two-beta/subfolder/subfile', - slug: '/2021/05/12/announcing-docusaurus-two-beta/subfolder/subfile', + text: 'announcing-docusaurus-two-beta/subfolder/file', + slug: '/2021/05/12/announcing-docusaurus-two-beta/subfolder/file', }); }); - test('parse date in the middle of path', () => { + it('parses date in the middle of path', () => { expect( parseBlogFileName('team-a/2021/05/12/announcing-docusaurus-two-beta.md'), ).toEqual({ @@ -119,7 +174,7 @@ describe('parseBlogFileName', () => { }); }); - test('parse date in the middle of a folder name', () => { + it('parses date in the middle of a folder name', () => { expect( parseBlogFileName( 'team-a-2021-05-12-hey/announcing-docusaurus-two-beta.md', @@ -131,3 +186,88 @@ describe('parseBlogFileName', () => { }); }); }); + +describe('linkify', () => { + const siteDir = path.join(__dirname, '__fixtures__', 'website'); + const contentPaths: BlogContentPaths = { + contentPath: path.join(siteDir, 'blog-with-ref'), + contentPathLocalized: path.join(siteDir, 'blog-with-ref-localized'), + }; + const pluginDir = 'blog-with-ref'; + + const blogPosts: BlogPost[] = [ + { + id: 'Happy 1st Birthday Slash!', + metadata: { + permalink: '/blog/2018/12/14/Happy-First-Birthday-Slash', + source: path.posix.join( + '@site', + pluginDir, + '2018-12-14-Happy-First-Birthday-Slash.md', + ), + title: 'Happy 1st Birthday Slash!', + description: `pattern name`, + date: new Date('2018-12-14'), + tags: [], + prevItem: { + permalink: '/blog/2019/01/01/date-matter', + title: 'date-matter', + }, + truncated: false, + }, + }, + ]; + + async function transform(filePath: string, options?: Partial) { + const fileContent = await fs.readFile(filePath, 'utf-8'); + const transformedContent = linkify({ + filePath, + fileString: fileContent, + siteDir, + contentPaths, + sourceToPermalink: getSourceToPermalink(blogPosts), + onBrokenMarkdownLink: (brokenMarkdownLink) => { + throw new Error( + `Broken markdown link found: ${JSON.stringify(brokenMarkdownLink)}`, + ); + }, + ...options, + }); + return [fileContent, transformedContent]; + } + + it('transforms to correct link', async () => { + const post = path.join(contentPaths.contentPath, 'post.md'); + const [content, transformedContent] = await transform(post); + expect(transformedContent).toMatchSnapshot(); + expect(transformedContent).toContain( + '](/blog/2018/12/14/Happy-First-Birthday-Slash', + ); + expect(transformedContent).not.toContain( + '](2018-12-14-Happy-First-Birthday-Slash.md)', + ); + expect(content).not.toEqual(transformedContent); + }); + + it('reports broken markdown links', async () => { + const filePath = 'post-with-broken-links.md'; + const folderPath = contentPaths.contentPath; + const postWithBrokenLinks = path.join(folderPath, filePath); + const onBrokenMarkdownLink = jest.fn(); + const [, transformedContent] = await transform(postWithBrokenLinks, { + onBrokenMarkdownLink, + }); + expect(transformedContent).toMatchSnapshot(); + expect(onBrokenMarkdownLink).toHaveBeenCalledTimes(2); + expect(onBrokenMarkdownLink).toHaveBeenNthCalledWith(1, { + filePath: path.resolve(folderPath, filePath), + contentPaths, + link: 'postNotExist1.md', + } as BlogBrokenMarkdownLink); + expect(onBrokenMarkdownLink).toHaveBeenNthCalledWith(2, { + filePath: path.resolve(folderPath, filePath), + contentPaths, + link: './postNotExist2.mdx', + } as BlogBrokenMarkdownLink); + }); +}); diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts index bd77b29115fd..facf967523db 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts @@ -5,12 +5,13 @@ * LICENSE file in the root directory of this source tree. */ +import {jest} from '@jest/globals'; import path from 'path'; import fs from 'fs-extra'; import {createBlogFeedFiles} from '../feed'; import type {LoadContext, I18n} from '@docusaurus/types'; import type {BlogContentPaths} from '../types'; -import {DEFAULT_OPTIONS} from '../pluginOptionSchema'; +import {DEFAULT_OPTIONS} from '../options'; import {generateBlogPosts} from '../blogUtils'; import type {PluginOptions} from '@docusaurus/plugin-content-blog'; @@ -48,88 +49,85 @@ async function testGenerateFeeds( options, siteConfig: context.siteConfig, outDir: context.outDir, + locale: 'en', }); } -describe('blogFeed', () => { - (['atom', 'rss', 'json'] as const).forEach((feedType) => { - describe(`${feedType}`, () => { - const fsMock = jest.spyOn(fs, 'outputFile').mockImplementation(() => {}); +describe.each(['atom', 'rss', 'json'])('%s', (feedType) => { + const fsMock = jest.spyOn(fs, 'outputFile').mockImplementation(() => {}); - test('should not show feed without posts', async () => { - const siteDir = __dirname; - const siteConfig = { - title: 'Hello', - baseUrl: '/', - url: 'https://docusaurus.io', - favicon: 'image/favicon.ico', - }; - const outDir = path.join(siteDir, 'build-snap'); + it('does not get generated without posts', async () => { + const siteDir = __dirname; + const siteConfig = { + title: 'Hello', + baseUrl: '/', + url: 'https://docusaurus.io', + favicon: 'image/favicon.ico', + }; + const outDir = path.join(siteDir, 'build-snap'); - await testGenerateFeeds( - { - siteDir, - siteConfig, - i18n: DefaultI18N, - outDir, - } as LoadContext, - { - path: 'invalid-blog-path', - routeBasePath: 'blog', - tagsBasePath: 'tags', - authorsMapPath: 'authors.yml', - include: ['*.md', '*.mdx'], - feedOptions: { - type: [feedType], - copyright: 'Copyright', - }, - readingTime: ({content, defaultReadingTime}) => - defaultReadingTime({content}), - } as PluginOptions, - ); + await testGenerateFeeds( + { + siteDir, + siteConfig, + i18n: DefaultI18N, + outDir, + } as LoadContext, + { + path: 'invalid-blog-path', + routeBasePath: 'blog', + tagsBasePath: 'tags', + authorsMapPath: 'authors.yml', + include: ['*.md', '*.mdx'], + feedOptions: { + type: [feedType], + copyright: 'Copyright', + }, + readingTime: ({content, defaultReadingTime}) => + defaultReadingTime({content}), + } as PluginOptions, + ); - expect(fsMock).toBeCalledTimes(0); - fsMock.mockClear(); - }); + expect(fsMock).toBeCalledTimes(0); + fsMock.mockClear(); + }); - test('shows feed item for each post', async () => { - const siteDir = path.join(__dirname, '__fixtures__', 'website'); - const outDir = path.join(siteDir, 'build-snap'); - const siteConfig = { - title: 'Hello', - baseUrl: '/myBaseUrl/', - url: 'https://docusaurus.io', - favicon: 'image/favicon.ico', - }; + it('has feed item for each post', async () => { + const siteDir = path.join(__dirname, '__fixtures__', 'website'); + const outDir = path.join(siteDir, 'build-snap'); + const siteConfig = { + title: 'Hello', + baseUrl: '/myBaseUrl/', + url: 'https://docusaurus.io', + favicon: 'image/favicon.ico', + }; - // Build is quite difficult to mock, so we built the blog beforehand and - // copied the output to the fixture... - await testGenerateFeeds( - { - siteDir, - siteConfig, - i18n: DefaultI18N, - outDir, - } as LoadContext, - { - path: 'blog', - routeBasePath: 'blog', - tagsBasePath: 'tags', - authorsMapPath: 'authors.yml', - include: DEFAULT_OPTIONS.include, - exclude: DEFAULT_OPTIONS.exclude, - feedOptions: { - type: [feedType], - copyright: 'Copyright', - }, - readingTime: ({content, defaultReadingTime}) => - defaultReadingTime({content}), - } as PluginOptions, - ); + // Build is quite difficult to mock, so we built the blog beforehand and + // copied the output to the fixture... + await testGenerateFeeds( + { + siteDir, + siteConfig, + i18n: DefaultI18N, + outDir, + } as LoadContext, + { + path: 'blog', + routeBasePath: 'blog', + tagsBasePath: 'tags', + authorsMapPath: 'authors.yml', + include: DEFAULT_OPTIONS.include, + exclude: DEFAULT_OPTIONS.exclude, + feedOptions: { + type: [feedType], + copyright: 'Copyright', + }, + readingTime: ({content, defaultReadingTime}) => + defaultReadingTime({content}), + } as PluginOptions, + ); - expect(fsMock.mock.calls.map((call) => call[1])).toMatchSnapshot(); - fsMock.mockClear(); - }); - }); + expect(fsMock.mock.calls.map((call) => call[1])).toMatchSnapshot(); + fsMock.mockClear(); }); }); diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/blogFrontMatter.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/frontMatter.test.ts similarity index 94% rename from packages/docusaurus-plugin-content-blog/src/__tests__/blogFrontMatter.test.ts rename to packages/docusaurus-plugin-content-blog/src/__tests__/frontMatter.test.ts index bc3aaf174cb0..99e3c694bfbd 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/blogFrontMatter.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/frontMatter.test.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import {validateBlogPostFrontMatter} from '../blogFrontMatter'; +import {validateBlogPostFrontMatter} from '../frontMatter'; import escapeStringRegexp from 'escape-string-regexp'; import type {BlogPostFrontMatter} from '@docusaurus/plugin-content-blog'; @@ -15,22 +15,22 @@ function testField(params: { fieldName: keyof BlogPostFrontMatter; validFrontMatters: BlogPostFrontMatter[]; convertibleFrontMatter?: [ - ConvertableFrontMatter: Record, + ConvertibleFrontMatter: {[key: string]: unknown}, ConvertedFrontMatter: BlogPostFrontMatter, ][]; invalidFrontMatters?: [ - InvalidFrontMatter: Record, + InvalidFrontMatter: {[key: string]: unknown}, ErrorMessage: string, ][]; }) { describe(`"${params.fieldName}" field`, () => { - test('accept valid values', () => { + it('accept valid values', () => { params.validFrontMatters.forEach((frontMatter) => { expect(validateBlogPostFrontMatter(frontMatter)).toEqual(frontMatter); }); }); - test('convert valid values', () => { + it('convert valid values', () => { params.convertibleFrontMatter?.forEach( ([convertibleFrontMatter, convertedFrontMatter]) => { expect(validateBlogPostFrontMatter(convertibleFrontMatter)).toEqual( @@ -40,7 +40,7 @@ function testField(params: { ); }); - test('throw error for values', () => { + it('throw error for values', () => { params.invalidFrontMatters?.forEach(([frontMatter, message]) => { try { validateBlogPostFrontMatter(frontMatter); @@ -54,9 +54,9 @@ function testField(params: { )}`, ), ); - } catch (e) { + } catch (err) { // eslint-disable-next-line jest/no-conditional-expect - expect(e.message).toMatch(new RegExp(escapeStringRegexp(message))); + expect(err.message).toMatch(new RegExp(escapeStringRegexp(message))); } }); }); @@ -64,12 +64,12 @@ function testField(params: { } describe('validateBlogPostFrontMatter', () => { - test('accept empty object', () => { + it('accept empty object', () => { const frontMatter = {}; expect(validateBlogPostFrontMatter(frontMatter)).toEqual(frontMatter); }); - test('accept unknown field', () => { + it('accept unknown field', () => { const frontMatter = {abc: '1'}; expect(validateBlogPostFrontMatter(frontMatter)).toEqual(frontMatter); }); @@ -106,7 +106,7 @@ describe('validateBlogPostFrontMatter id', () => { }); describe('validateBlogPostFrontMatter handles legacy/new author front matter', () => { - test('allow legacy author front matter', () => { + it('allow legacy author front matter', () => { const frontMatter: BlogPostFrontMatter = { author: 'Sebastien', author_url: 'https://sebastienlorber.com', @@ -116,7 +116,7 @@ describe('validateBlogPostFrontMatter handles legacy/new author front matter', ( expect(validateBlogPostFrontMatter(frontMatter)).toEqual(frontMatter); }); - test('allow new authors front matter', () => { + it('allow new authors front matter', () => { const frontMatter: BlogPostFrontMatter = { authors: [ 'slorber', diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts index c6f1a8ca38da..2a197366835e 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts @@ -5,14 +5,14 @@ * LICENSE file in the root directory of this source tree. */ -import fs from 'fs-extra'; +import {jest} from '@jest/globals'; import path from 'path'; import pluginContentBlog from '../index'; import type {DocusaurusConfig, LoadContext, I18n} from '@docusaurus/types'; -import {PluginOptionSchema} from '../pluginOptionSchema'; +import {validateOptions} from '../options'; import type {BlogPost} from '../types'; -import type {Joi} from '@docusaurus/utils-validation'; -import {posixPath} from '@docusaurus/utils'; +import {normalizePluginOptions} from '@docusaurus/utils-validation'; +import {posixPath, getFileCommitDate} from '@docusaurus/utils'; import type { PluginOptions, EditUrlFunction, @@ -24,6 +24,7 @@ function findByTitle( ): BlogPost | undefined { return blogPosts.find((v) => v.metadata.title === title); } + function getByTitle(blogPosts: BlogPost[], title: string): BlogPost { const post = findByTitle(blogPosts, title); if (!post) { @@ -46,60 +47,61 @@ function getI18n(locale: string): I18n { const DefaultI18N: I18n = getI18n('en'); -function validateAndNormalize( - schema: Joi.ObjectSchema, - options: Partial, -) { - const {value, error} = schema.validate(options); - if (error) { - throw error; - } else { - return value; - } -} +const PluginPath = 'blog'; + +const BaseEditUrl = 'https://baseEditUrl.com/edit'; -describe('loadBlog', () => { - const PluginPath = 'blog'; - - const BaseEditUrl = 'https://baseEditUrl.com/edit'; - - const getPlugin = async ( - siteDir: string, - pluginOptions: Partial = {}, - i18n: I18n = DefaultI18N, - ) => { - const generatedFilesDir: string = path.resolve(siteDir, '.docusaurus'); - const siteConfig = { - title: 'Hello', - baseUrl: '/', - url: 'https://docusaurus.io', - } as DocusaurusConfig; - return pluginContentBlog( - { - siteDir, - siteConfig, - generatedFilesDir, - i18n, - } as LoadContext, - validateAndNormalize(PluginOptionSchema, { +const getPlugin = async ( + siteDir: string, + pluginOptions: Partial = {}, + i18n: I18n = DefaultI18N, +) => { + const generatedFilesDir: string = path.resolve(siteDir, '.docusaurus'); + const siteConfig = { + title: 'Hello', + baseUrl: '/', + url: 'https://docusaurus.io', + } as DocusaurusConfig; + return pluginContentBlog( + { + siteDir, + siteConfig, + generatedFilesDir, + i18n, + } as LoadContext, + validateOptions({ + validate: normalizePluginOptions, + options: { path: PluginPath, editUrl: BaseEditUrl, ...pluginOptions, - }), - ); - }; + }, + }) as PluginOptions, + ); +}; - const getBlogPosts = async ( - siteDir: string, - pluginOptions: Partial = {}, - i18n: I18n = DefaultI18N, - ) => { - const plugin = await getPlugin(siteDir, pluginOptions, i18n); - const {blogPosts} = (await plugin.loadContent!())!; - return blogPosts; - }; +const getBlogPosts = async ( + siteDir: string, + pluginOptions: Partial = {}, + i18n: I18n = DefaultI18N, +) => { + const plugin = await getPlugin(siteDir, pluginOptions, i18n); + const {blogPosts} = (await plugin.loadContent!())!; + return blogPosts; +}; + +const getBlogTags = async ( + siteDir: string, + pluginOptions: Partial = {}, + i18n: I18n = DefaultI18N, +) => { + const plugin = await getPlugin(siteDir, pluginOptions, i18n); + const {blogTags} = (await plugin.loadContent!())!; + return blogTags; +}; - test('getPathsToWatch', async () => { +describe('blog plugin', () => { + it('getPathsToWatch returns right files', async () => { const siteDir = path.join(__dirname, '__fixtures__', 'website'); const plugin = await getPlugin(siteDir); const pathsToWatch = plugin.getPathsToWatch!(); @@ -113,7 +115,7 @@ describe('loadBlog', () => { ]); }); - test('simple website', async () => { + it('builds a simple website', async () => { const siteDir = path.join(__dirname, '__fixtures__', 'website'); const blogPosts = await getBlogPosts(siteDir); @@ -167,6 +169,7 @@ describe('loadBlog', () => { name: 'Yangshun Tay (translated)', }, { + email: 'lorber.sebastien@gmail.com', key: 'slorber', name: 'Sébastien Lorber (translated)', title: 'Docusaurus maintainer (translated)', @@ -291,7 +294,7 @@ describe('loadBlog', () => { }); }); - test('simple website blog dates localized', async () => { + it('builds simple website blog with localized dates', async () => { const siteDir = path.join(__dirname, '__fixtures__', 'website'); const blogPostsFrench = await getBlogPosts(siteDir, {}, getI18n('fr')); expect(blogPostsFrench).toHaveLength(8); @@ -321,7 +324,7 @@ describe('loadBlog', () => { ); }); - test('edit url with editLocalizedBlogs true', async () => { + it('handles edit URL with editLocalizedBlogs: true', async () => { const siteDir = path.join(__dirname, '__fixtures__', 'website'); const blogPosts = await getBlogPosts(siteDir, {editLocalizedFiles: true}); @@ -329,12 +332,12 @@ describe('loadBlog', () => { (v) => v.metadata.title === 'Happy 1st Birthday Slash! (translated)', )!; - expect(localizedBlogPost.metadata.editUrl).toEqual( + expect(localizedBlogPost.metadata.editUrl).toBe( `${BaseEditUrl}/i18n/en/docusaurus-plugin-content-blog/2018-12-14-Happy-First-Birthday-Slash.md`, ); }); - test('edit url with editUrl function', async () => { + it('handles edit URL with editUrl function', async () => { const siteDir = path.join(__dirname, '__fixtures__', 'website'); const hardcodedEditUrl = 'hardcoded-edit-url'; @@ -398,15 +401,18 @@ describe('loadBlog', () => { }); }); - test('draft blog post not exists in production build', async () => { - process.env.NODE_ENV = 'production'; + it('excludes draft blog post from production build', async () => { + const originalEnv = process.env; + jest.resetModules(); + process.env = {...originalEnv, NODE_ENV: 'production'}; const siteDir = path.join(__dirname, '__fixtures__', 'website'); const blogPosts = await getBlogPosts(siteDir); expect(blogPosts.find((v) => v.metadata.title === 'draft')).toBeUndefined(); + process.env = originalEnv; }); - test('create blog post without date', async () => { + it('creates blog post without date', async () => { const siteDir = path.join( __dirname, '__fixtures__', @@ -414,14 +420,15 @@ describe('loadBlog', () => { ); const blogPosts = await getBlogPosts(siteDir); const noDateSource = path.posix.join('@site', PluginPath, 'no date.md'); - const noDateSourceBirthTime = ( - await fs.stat(noDateSource.replace('@site', siteDir)) - ).birthtime; + const noDateSourceFile = path.posix.join(siteDir, PluginPath, 'no date.md'); + // we know the file exist and we know we have git + const result = getFileCommitDate(noDateSourceFile, {age: 'oldest'}); + const noDateSourceTime = result.date; const formattedDate = Intl.DateTimeFormat('en', { day: 'numeric', month: 'long', year: 'numeric', - }).format(noDateSourceBirthTime); + }).format(noDateSourceTime); expect({ ...getByTitle(blogPosts, 'no date').metadata, @@ -434,7 +441,7 @@ describe('loadBlog', () => { title: 'no date', description: `no date`, authors: [], - date: noDateSourceBirthTime, + date: noDateSourceTime, formattedDate, frontMatter: {}, tags: [], @@ -444,7 +451,7 @@ describe('loadBlog', () => { }); }); - test('test ascending sort direction of blog post', async () => { + it('can sort blog posts in ascending order', async () => { const siteDir = path.join(__dirname, '__fixtures__', 'website'); const normalOrder = await getBlogPosts(siteDir); const reversedOrder = await getBlogPosts(siteDir, { @@ -454,4 +461,31 @@ describe('loadBlog', () => { reversedOrder.map((x) => x.metadata.date), ); }); + + it('works with blog tags', async () => { + const siteDir = path.join( + __dirname, + '__fixtures__', + 'website-blog-with-tags', + ); + const blogTags = await getBlogTags(siteDir, { + postsPerPage: 2, + }); + + expect(Object.keys(blogTags)).toHaveLength(2); + expect(blogTags).toMatchSnapshot(); + }); + + it('works on blog tags without pagination', async () => { + const siteDir = path.join( + __dirname, + '__fixtures__', + 'website-blog-with-tags', + ); + const blogTags = await getBlogTags(siteDir, { + postsPerPage: 'ALL', + }); + + expect(blogTags).toMatchSnapshot(); + }); }); diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/linkify.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/linkify.test.ts deleted file mode 100644 index 9fe9a5149011..000000000000 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/linkify.test.ts +++ /dev/null @@ -1,97 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import fs from 'fs-extra'; -import path from 'path'; -import {linkify, type LinkifyParams, getSourceToPermalink} from '../blogUtils'; -import type { - BlogBrokenMarkdownLink, - BlogContentPaths, - BlogPost, -} from '../types'; - -const siteDir = path.join(__dirname, '__fixtures__', 'website'); -const contentPaths: BlogContentPaths = { - contentPath: path.join(siteDir, 'blog-with-ref'), - contentPathLocalized: path.join(siteDir, 'blog-with-ref-localized'), -}; -const pluginDir = 'blog-with-ref'; -const blogPosts: BlogPost[] = [ - { - id: 'Happy 1st Birthday Slash!', - metadata: { - permalink: '/blog/2018/12/14/Happy-First-Birthday-Slash', - source: path.posix.join( - '@site', - pluginDir, - '2018-12-14-Happy-First-Birthday-Slash.md', - ), - title: 'Happy 1st Birthday Slash!', - description: `pattern name`, - date: new Date('2018-12-14'), - tags: [], - prevItem: { - permalink: '/blog/2019/01/01/date-matter', - title: 'date-matter', - }, - truncated: false, - }, - }, -]; - -const transform = (filePath: string, options?: Partial) => { - const fileContent = fs.readFileSync(filePath, 'utf-8'); - const transformedContent = linkify({ - filePath, - fileString: fileContent, - siteDir, - contentPaths, - sourceToPermalink: getSourceToPermalink(blogPosts), - onBrokenMarkdownLink: (brokenMarkdownLink) => { - throw new Error( - `Broken markdown link found: ${JSON.stringify(brokenMarkdownLink)}`, - ); - }, - ...options, - }); - return [fileContent, transformedContent]; -}; - -test('transform to correct link', () => { - const post = path.join(contentPaths.contentPath, 'post.md'); - const [content, transformedContent] = transform(post); - expect(transformedContent).toMatchSnapshot(); - expect(transformedContent).toContain( - '](/blog/2018/12/14/Happy-First-Birthday-Slash', - ); - expect(transformedContent).not.toContain( - '](2018-12-14-Happy-First-Birthday-Slash.md)', - ); - expect(content).not.toEqual(transformedContent); -}); - -test('report broken markdown links', () => { - const filePath = 'post-with-broken-links.md'; - const folderPath = contentPaths.contentPath; - const postWithBrokenLinks = path.join(folderPath, filePath); - const onBrokenMarkdownLink = jest.fn(); - const [, transformedContent] = transform(postWithBrokenLinks, { - onBrokenMarkdownLink, - }); - expect(transformedContent).toMatchSnapshot(); - expect(onBrokenMarkdownLink).toHaveBeenCalledTimes(2); - expect(onBrokenMarkdownLink).toHaveBeenNthCalledWith(1, { - filePath: path.resolve(folderPath, filePath), - contentPaths, - link: 'postNotExist1.md', - } as BlogBrokenMarkdownLink); - expect(onBrokenMarkdownLink).toHaveBeenNthCalledWith(2, { - filePath: path.resolve(folderPath, filePath), - contentPaths, - link: './postNotExist2.mdx', - } as BlogBrokenMarkdownLink); -}); diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/options.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/options.test.ts new file mode 100644 index 000000000000..738884dd0297 --- /dev/null +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/options.test.ts @@ -0,0 +1,160 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {validateOptions, DEFAULT_OPTIONS} from '../options'; +import {normalizePluginOptions} from '@docusaurus/utils-validation'; +import type {Options} from '@docusaurus/plugin-content-blog'; + +function testValidate(options: Options) { + return validateOptions({validate: normalizePluginOptions, options}); +} + +// the type of remark/rehype plugins can be either function, object or array +const markdownPluginsFunctionStub = () => {}; +const markdownPluginsObjectStub = {}; + +const defaultOptions = {...DEFAULT_OPTIONS, id: 'default'}; + +describe('validateOptions', () => { + it('returns default options for undefined user options', () => { + expect(testValidate(undefined)).toEqual(defaultOptions); + }); + + it('returns default options for empty user options', () => { + expect(testValidate({})).toEqual(defaultOptions); + }); + + it('accepts correctly defined user options', () => { + const userOptions = { + ...defaultOptions, + feedOptions: {type: 'rss' as const, title: 'myTitle'}, + path: 'not_blog', + routeBasePath: 'myBlog', + postsPerPage: 5, + include: ['api/*', 'docs/*'], + }; + expect(testValidate(userOptions)).toEqual({ + ...userOptions, + feedOptions: {type: ['rss'], title: 'myTitle', copyright: ''}, + }); + }); + + it('accepts valid user options', () => { + const userOptions = { + ...defaultOptions, + routeBasePath: 'myBlog', + beforeDefaultRemarkPlugins: [], + beforeDefaultRehypePlugins: [markdownPluginsFunctionStub], + remarkPlugins: [[markdownPluginsFunctionStub, {option1: '42'}]], + rehypePlugins: [ + markdownPluginsObjectStub, + [markdownPluginsFunctionStub, {option1: '42'}], + ], + }; + expect(testValidate(userOptions)).toEqual(userOptions); + }); + + it('throws Error in case of invalid options', () => { + expect(() => + testValidate({ + path: 'not_blog', + postsPerPage: -1, + include: ['api/*', 'docs/*'], + routeBasePath: 'not_blog', + }), + ).toThrowErrorMatchingSnapshot(); + }); + + it('throws Error in case of invalid feed type', () => { + expect(() => + testValidate({ + feedOptions: { + type: 'none', + }, + }), + ).toThrowErrorMatchingSnapshot(); + }); + + it('converts all feed type to array with other feed type', () => { + expect( + testValidate({ + feedOptions: {type: 'all'}, + }), + ).toEqual({ + ...defaultOptions, + feedOptions: {type: ['rss', 'atom', 'json'], copyright: ''}, + }); + }); + + it('accepts null type and return same', () => { + expect( + testValidate({ + feedOptions: {type: null}, + }), + ).toEqual({ + ...defaultOptions, + feedOptions: {type: null}, + }); + }); + + it('contains array with rss + atom for missing feed type', () => { + expect( + testValidate({ + feedOptions: {}, + }), + ).toEqual(defaultOptions); + }); + + it('has array with rss + atom, title for missing feed type', () => { + expect( + testValidate({ + feedOptions: {title: 'title'}, + }), + ).toEqual({ + ...defaultOptions, + feedOptions: {type: ['rss', 'atom'], title: 'title', copyright: ''}, + }); + }); + + it('accepts 0 sidebar count', () => { + const userOptions = {blogSidebarCount: 0}; + expect(testValidate(userOptions)).toEqual({ + ...defaultOptions, + ...userOptions, + }); + }); + + it('accepts "ALL" sidebar count', () => { + const userOptions = {blogSidebarCount: 'ALL' as const}; + expect(testValidate(userOptions)).toEqual({ + ...defaultOptions, + ...userOptions, + }); + }); + + it('rejects "abcdef" sidebar count', () => { + const userOptions = {blogSidebarCount: 'abcdef'}; + expect(() => testValidate(userOptions)).toThrowErrorMatchingInlineSnapshot( + `"\\"blogSidebarCount\\" must be one of [ALL, number]"`, + ); + }); + + it('accepts "all posts" sidebar title', () => { + const userOptions = {blogSidebarTitle: 'all posts'}; + expect(testValidate(userOptions)).toEqual({ + ...defaultOptions, + ...userOptions, + }); + }); + + it('rejects 42 sidebar title', () => { + const userOptions = {blogSidebarTitle: 42}; + expect(() => testValidate(userOptions)).toThrowErrorMatchingInlineSnapshot( + `"\\"blogSidebarTitle\\" must be a string"`, + ); + }); +}); diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/pluginOptionSchema.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/pluginOptionSchema.test.ts deleted file mode 100644 index 4c0fdef73f16..000000000000 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/pluginOptionSchema.test.ts +++ /dev/null @@ -1,150 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import {PluginOptionSchema, DEFAULT_OPTIONS} from '../pluginOptionSchema'; - -// the type of remark/rehype plugins can be either function, object or array -const markdownPluginsFunctionStub = () => {}; -const markdownPluginsObjectStub = {}; - -test('should normalize options', () => { - const {value, error} = PluginOptionSchema.validate({}); - expect(value).toEqual(DEFAULT_OPTIONS); - expect(error).toBe(undefined); -}); - -test('should accept correctly defined user options', () => { - const userOptions = { - ...DEFAULT_OPTIONS, - feedOptions: {type: 'rss', title: 'myTitle'}, - path: 'not_blog', - routeBasePath: 'myBlog', - postsPerPage: 5, - include: ['api/*', 'docs/*'], - }; - const {value, error} = PluginOptionSchema.validate(userOptions); - expect(value).toEqual({ - ...userOptions, - feedOptions: {type: ['rss'], title: 'myTitle', copyright: ''}, - }); - expect(error).toBe(undefined); -}); - -test('should accept valid user options', async () => { - const userOptions = { - ...DEFAULT_OPTIONS, - routeBasePath: 'myBlog', - beforeDefaultRemarkPlugins: [], - beforeDefaultRehypePlugins: [markdownPluginsFunctionStub], - remarkPlugins: [[markdownPluginsFunctionStub, {option1: '42'}]], - rehypePlugins: [ - markdownPluginsObjectStub, - [markdownPluginsFunctionStub, {option1: '42'}], - ], - }; - const {value, error} = await PluginOptionSchema.validate(userOptions); - expect(value).toEqual(userOptions); - expect(error).toBe(undefined); -}); - -test('should throw Error in case of invalid options', () => { - const {error} = PluginOptionSchema.validate({ - path: 'not_blog', - postsPerPage: -1, - include: ['api/*', 'docs/*'], - routeBasePath: 'not_blog', - }); - - expect(error).toMatchSnapshot(); -}); - -test('should throw Error in case of invalid feedtype', () => { - const {error} = PluginOptionSchema.validate({ - feedOptions: { - type: 'none', - }, - }); - - expect(error).toMatchSnapshot(); -}); - -test('should convert all feed type to array with other feed type', () => { - const {value} = PluginOptionSchema.validate({ - feedOptions: {type: 'all'}, - }); - expect(value).toEqual({ - ...DEFAULT_OPTIONS, - feedOptions: {type: ['rss', 'atom', 'json'], copyright: ''}, - }); -}); - -test('should accept null type and return same', () => { - const {value, error} = PluginOptionSchema.validate({ - feedOptions: {type: null}, - }); - expect(value).toEqual({ - ...DEFAULT_OPTIONS, - feedOptions: {type: null}, - }); - expect(error).toBe(undefined); -}); - -test('should contain array with rss + atom for missing feed type', () => { - const {value} = PluginOptionSchema.validate({ - feedOptions: {}, - }); - expect(value).toEqual(DEFAULT_OPTIONS); -}); - -test('should have array with rss + atom, title for missing feed type', () => { - const {value} = PluginOptionSchema.validate({ - feedOptions: {title: 'title'}, - }); - expect(value).toEqual({ - ...DEFAULT_OPTIONS, - feedOptions: {type: ['rss', 'atom'], title: 'title', copyright: ''}, - }); -}); - -describe('blog sidebar', () => { - test('should accept 0 sidebar count', () => { - const userOptions = {blogSidebarCount: 0}; - const {value, error} = PluginOptionSchema.validate(userOptions); - expect(value).toEqual({...DEFAULT_OPTIONS, ...userOptions}); - expect(error).toBe(undefined); - }); - - test('should accept "ALL" sidebar count', () => { - const userOptions = {blogSidebarCount: 'ALL'}; - const {value, error} = PluginOptionSchema.validate(userOptions); - expect(value).toEqual({...DEFAULT_OPTIONS, ...userOptions}); - expect(error).toBe(undefined); - }); - - test('should reject "abcdef" sidebar count', () => { - const userOptions = {blogSidebarCount: 'abcdef'}; - const {error} = PluginOptionSchema.validate(userOptions); - expect(error).toMatchInlineSnapshot( - `[ValidationError: "blogSidebarCount" must be one of [ALL, number]]`, - ); - }); - - test('should accept "all posts" sidebar title', () => { - const userOptions = {blogSidebarTitle: 'all posts'}; - const {value, error} = PluginOptionSchema.validate(userOptions); - expect(value).toEqual({...DEFAULT_OPTIONS, ...userOptions}); - expect(error).toBe(undefined); - }); - - test('should reject 42 sidebar title', () => { - const userOptions = {blogSidebarTitle: 42}; - const {error} = PluginOptionSchema.validate(userOptions); - expect(error).toMatchInlineSnapshot( - `[ValidationError: "blogSidebarTitle" must be a string]`, - ); - }); -}); diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/translations.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/translations.test.ts index 9844c4cab990..840a3af4d359 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/translations.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/translations.test.ts @@ -7,7 +7,7 @@ import type {BlogPost, BlogContent} from '../types'; import {getTranslationFiles, translateContent} from '../translations'; -import {DEFAULT_OPTIONS} from '../pluginOptionSchema'; +import {DEFAULT_OPTIONS} from '../options'; import {updateTranslationFileMessages} from '@docusaurus/utils'; import type {PluginOptions} from '@docusaurus/plugin-content-blog'; @@ -45,8 +45,8 @@ const sampleBlogContent: BlogContent = { postsPerPage: 10, totalPages: 1, totalCount: 1, - previousPage: null, - nextPage: null, + previousPage: undefined, + nextPage: undefined, blogTitle: sampleBlogOptions.blogTitle, blogDescription: sampleBlogOptions.blogDescription, }, @@ -71,20 +71,26 @@ function getSampleTranslationFilesTranslated() { } describe('getContentTranslationFiles', () => { - test('should return translation files matching snapshot', async () => { + it('returns translation files matching snapshot', async () => { expect(getSampleTranslationFiles()).toMatchSnapshot(); }); }); describe('translateContent', () => { - test('should not translate anything if translation files are untranslated', () => { + it('falls back when translation is incomplete', () => { + expect( + translateContent(sampleBlogContent, [{path: 'foo', content: {}}]), + ).toMatchSnapshot(); + }); + + it('does not translate anything if translation files are untranslated', () => { const translationFiles = getSampleTranslationFiles(); expect(translateContent(sampleBlogContent, translationFiles)).toEqual( sampleBlogContent, ); }); - test('should return translated loaded content matching snapshot', () => { + it('returns translated loaded', () => { const translationFiles = getSampleTranslationFilesTranslated(); expect( translateContent(sampleBlogContent, translationFiles), diff --git a/packages/docusaurus-plugin-content-blog/src/authors.ts b/packages/docusaurus-plugin-content-blog/src/authors.ts index 85c01c4a557f..e8e2be323127 100644 --- a/packages/docusaurus-plugin-content-blog/src/authors.ts +++ b/packages/docusaurus-plugin-content-blog/src/authors.ts @@ -15,21 +15,33 @@ import type { BlogPostFrontMatterAuthors, } from '@docusaurus/plugin-content-blog'; -export type AuthorsMap = Record; - -const AuthorsMapSchema = Joi.object().pattern( - Joi.string(), - Joi.object({ - name: Joi.string(), - url: URISchema, - imageURL: URISchema, - title: Joi.string(), - }) - .rename('image_url', 'imageURL') - .or('name', 'imageURL') - .unknown() - .required(), -); +export type AuthorsMap = {[authorKey: string]: Author}; + +const AuthorsMapSchema = Joi.object() + .pattern( + Joi.string(), + Joi.object({ + name: Joi.string(), + url: URISchema, + imageURL: URISchema, + title: Joi.string(), + email: Joi.string(), + }) + .rename('image_url', 'imageURL') + .or('name', 'imageURL') + .unknown() + .required() + .messages({ + 'object.base': + '{#label} should be an author object containing properties like name, title, and imageURL.', + 'any.required': + '{#label} cannot be undefined. It should be an author object containing properties like name, title, and imageURL.', + }), + ) + .messages({ + 'object.base': + "The authors map file should contain an object where each entry contains an author key and the corresponding author's data.", + }); export function validateAuthorsMap(content: unknown): AuthorsMap { return Joi.attempt(content, AuthorsMapSchema); @@ -58,7 +70,7 @@ type AuthorsParam = { // We may want to deprecate those in favor of using only frontMatter.authors function getFrontMatterAuthorLegacy( frontMatter: BlogPostFrontMatter, -): BlogPostFrontMatterAuthor | undefined { +): Author | undefined { const name = frontMatter.author; const title = frontMatter.author_title ?? frontMatter.authorTitle; const url = frontMatter.author_url ?? frontMatter.authorURL; @@ -80,12 +92,12 @@ function normalizeFrontMatterAuthors( frontMatterAuthors: BlogPostFrontMatterAuthors = [], ): BlogPostFrontMatterAuthor[] { function normalizeAuthor( - authorInput: string | BlogPostFrontMatterAuthor, + authorInput: string | Author, ): BlogPostFrontMatterAuthor { if (typeof authorInput === 'string') { - // Technically, we could allow users to provide an author's name here - // IMHO it's better to only support keys here - // Reason: a typo in a key would fallback to becoming a name and may end-up un-noticed + // Technically, we could allow users to provide an author's name here, but + // we only support keys, otherwise, a typo in a key would fallback to + // becoming a name and may end up unnoticed return {key: authorInput}; } return authorInput; @@ -137,7 +149,8 @@ export function getBlogPostAuthors(params: AuthorsParam): Author[] { const authors = getFrontMatterAuthors(params); if (authorLegacy) { - // Technically, we could allow mixing legacy/authors front matter, but do we really want to? + // Technically, we could allow mixing legacy/authors front matter, but do we + // really want to? if (authors.length > 0) { throw new Error( `To declare blog post authors, use the 'authors' front matter in priority. diff --git a/packages/docusaurus-plugin-content-blog/src/blogUtils.ts b/packages/docusaurus-plugin-content-blog/src/blogUtils.ts index ddda9dc01684..b1823bb6d68d 100644 --- a/packages/docusaurus-plugin-content-blog/src/blogUtils.ts +++ b/packages/docusaurus-plugin-content-blog/src/blogUtils.ts @@ -8,12 +8,13 @@ import fs from 'fs-extra'; import path from 'path'; import readingTime from 'reading-time'; -import {keyBy, mapValues} from 'lodash'; +import _ from 'lodash'; import type { BlogPost, BlogContentPaths, BlogMarkdownLoaderOptions, BlogTags, + BlogPaginated, } from './types'; import { parseMarkdownString, @@ -26,10 +27,11 @@ import { Globby, normalizeFrontMatterTags, groupTaggedItems, + getFileCommitDate, getContentPathList, } from '@docusaurus/utils'; import type {LoadContext} from '@docusaurus/types'; -import {validateBlogPostFrontMatter} from './blogFrontMatter'; +import {validateBlogPostFrontMatter} from './frontMatter'; import {type AuthorsMap, getAuthorsMap, getBlogPostAuthors} from './authors'; import logger from '@docusaurus/logger'; import type { @@ -41,29 +43,90 @@ export function truncate(fileString: string, truncateMarker: RegExp): string { return fileString.split(truncateMarker, 1).shift()!; } -export function getSourceToPermalink( - blogPosts: BlogPost[], -): Record { - return mapValues( - keyBy(blogPosts, (item) => item.metadata.source), - (v) => v.metadata.permalink, +export function getSourceToPermalink(blogPosts: BlogPost[]): { + [aliasedPath: string]: string; +} { + return Object.fromEntries( + blogPosts.map(({metadata: {source, permalink}}) => [source, permalink]), ); } -export function getBlogTags(blogPosts: BlogPost[]): BlogTags { +export function paginateBlogPosts({ + blogPosts, + basePageUrl, + blogTitle, + blogDescription, + postsPerPageOption, +}: { + blogPosts: BlogPost[]; + basePageUrl: string; + blogTitle: string; + blogDescription: string; + postsPerPageOption: number | 'ALL'; +}): BlogPaginated[] { + const totalCount = blogPosts.length; + const postsPerPage = + postsPerPageOption === 'ALL' ? totalCount : postsPerPageOption; + const numberOfPages = Math.ceil(totalCount / postsPerPage); + + const pages: BlogPaginated[] = []; + + function permalink(page: number) { + return page > 0 + ? normalizeUrl([basePageUrl, `page/${page + 1}`]) + : basePageUrl; + } + + for (let page = 0; page < numberOfPages; page += 1) { + pages.push({ + items: blogPosts + .slice(page * postsPerPage, (page + 1) * postsPerPage) + .map((item) => item.id), + metadata: { + permalink: permalink(page), + page: page + 1, + postsPerPage, + totalPages: numberOfPages, + totalCount, + previousPage: page !== 0 ? permalink(page - 1) : undefined, + nextPage: page < numberOfPages - 1 ? permalink(page + 1) : undefined, + blogDescription, + blogTitle, + }, + }); + } + + return pages; +} + +export function getBlogTags({ + blogPosts, + ...params +}: { + blogPosts: BlogPost[]; + blogTitle: string; + blogDescription: string; + postsPerPageOption: number | 'ALL'; +}): BlogTags { const groups = groupTaggedItems( blogPosts, (blogPost) => blogPost.metadata.tags, ); - return mapValues(groups, (group) => ({ - name: group.tag.label, - items: group.items.map((item) => item.id), - permalink: group.tag.permalink, + + return _.mapValues(groups, ({tag, items: tagBlogPosts}) => ({ + name: tag.label, + items: tagBlogPosts.map((item) => item.id), + permalink: tag.permalink, + pages: paginateBlogPosts({ + blogPosts: tagBlogPosts, + basePageUrl: tag.permalink, + ...params, + }), })); } const DATE_FILENAME_REGEX = - /^(?.*)(?\d{4}[-/]\d{1,2}[-/]\d{1,2})[-/]?(?.*?)(\/index)?.mdx?$/; + /^(?.*)(?\d{4}[-/]\d{1,2}[-/]\d{1,2})[-/]?(?.*?)(?:\/index)?.mdx?$/; type ParsedBlogFileName = { date: Date | undefined; @@ -78,15 +141,14 @@ export function parseBlogFileName( if (dateFilenameMatch) { const {folder, text, date: dateString} = dateFilenameMatch.groups!; // Always treat dates as UTC by adding the `Z` - const date = new Date(`${dateString}Z`); - const slugDate = dateString.replace(/-/g, '/'); - const slug = `/${slugDate}/${folder}${text}`; - return {date, text, slug}; - } else { - const text = blogSourceRelative.replace(/(\/index)?\.mdx?$/, ''); - const slug = `/${text}`; - return {date: undefined, text, slug}; + const date = new Date(`${dateString!}Z`); + const slugDate = dateString!.replace(/-/g, '/'); + const slug = `/${slugDate}/${folder!}${text!}`; + return {date, text: text!, slug}; } + const text = blogSourceRelative.replace(/(?:\/index)?\.mdx?$/, ''); + const slug = `/${text}`; + return {date: undefined, text, slug}; } function formatBlogPostDate(locale: string, date: Date): string { @@ -97,8 +159,9 @@ function formatBlogPostDate(locale: string, date: Date): string { year: 'numeric', timeZone: 'UTC', }).format(date); - } catch (e) { - throw new Error(`Can't format blog post date "${date}"`); + } catch (err) { + logger.error`Can't format blog post date "${String(date)}"`; + throw err; } } @@ -112,12 +175,9 @@ async function parseBlogPostMarkdownFile(blogSourceAbsolute: string) { ...result, frontMatter: validateBlogPostFrontMatter(result.frontMatter), }; - } catch (e) { - throw new Error( - `Error while parsing blog post file ${blogSourceAbsolute}: "${ - (e as Error).message - }".`, - ); + } catch (err) { + logger.error`Error while parsing blog post file path=${blogSourceAbsolute}.`; + throw err; } } @@ -179,8 +239,17 @@ async function processBlogSourceFile( } else if (parsedBlogFileName.date) { return parsedBlogFileName.date; } - // Fallback to file create time - return (await fs.stat(blogSourceAbsolute)).birthtime; + + try { + const result = getFileCommitDate(blogSourceAbsolute, { + age: 'oldest', + includeAuthor: false, + }); + return result.date; + } catch (err) { + logger.warn(err); + return (await fs.stat(blogSourceAbsolute)).birthtime; + } } const date = await getDate(); @@ -263,7 +332,7 @@ export async function generateBlogPosts( ): Promise { const {include, exclude} = options; - if (!fs.existsSync(contentPaths.contentPath)) { + if (!(await fs.pathExists(contentPaths.contentPath))) { return []; } @@ -288,9 +357,9 @@ export async function generateBlogPosts( options, authorsMap, ); - } catch (e) { - logger.error`Processing of blog source file failed for path path=${blogSourceFile}.`; - throw e; + } catch (err) { + logger.error`Processing of blog source file path=${blogSourceFile} failed.`; + throw err; } }), ) diff --git a/packages/docusaurus-plugin-content-blog/src/deps.d.ts b/packages/docusaurus-plugin-content-blog/src/deps.d.ts index c9976d8a584b..6b8b33906b54 100644 --- a/packages/docusaurus-plugin-content-blog/src/deps.d.ts +++ b/packages/docusaurus-plugin-content-blog/src/deps.d.ts @@ -6,7 +6,7 @@ */ declare module 'remark-admonitions' { - type Options = Record; + type Options = {[key: string]: unknown}; const plugin: (options?: Options) => void; export = plugin; diff --git a/packages/docusaurus-plugin-content-blog/src/feed.ts b/packages/docusaurus-plugin-content-blog/src/feed.ts index 1357135e60ba..fa546fbf0f18 100644 --- a/packages/docusaurus-plugin-content-blog/src/feed.ts +++ b/packages/docusaurus-plugin-content-blog/src/feed.ts @@ -9,11 +9,10 @@ import {Feed, type Author as FeedAuthor, type Item as FeedItem} from 'feed'; import type {BlogPost} from './types'; import { normalizeUrl, - posixPath, mapAsyncSequential, readOutputHTMLFile, } from '@docusaurus/utils'; -import cheerio from 'cheerio'; +import {load as cheerioLoad} from 'cheerio'; import type {DocusaurusConfig} from '@docusaurus/types'; import path from 'path'; import fs from 'fs-extra'; @@ -29,11 +28,13 @@ async function generateBlogFeed({ options, siteConfig, outDir, + locale, }: { blogPosts: BlogPost[]; options: PluginOptions; siteConfig: DocusaurusConfig; outDir: string; + locale: string; }): Promise { if (!blogPosts.length) { return null; @@ -43,23 +44,21 @@ async function generateBlogFeed({ const {url: siteUrl, baseUrl, title, favicon} = siteConfig; const blogBaseUrl = normalizeUrl([siteUrl, baseUrl, routeBasePath]); - const updated = blogPosts[0] && blogPosts[0].metadata.date; + const updated = blogPosts[0]?.metadata.date; const feed = new Feed({ id: blogBaseUrl, - title: feedOptions.title || `${title} Blog`, + title: feedOptions.title ?? `${title} Blog`, updated, - language: feedOptions.language, + language: feedOptions.language ?? locale, link: blogBaseUrl, - description: feedOptions.description || `${siteConfig.title} Blog`, + description: feedOptions.description ?? `${siteConfig.title} Blog`, favicon: favicon ? normalizeUrl([siteUrl, baseUrl, favicon]) : undefined, copyright: feedOptions.copyright, }); function toFeedAuthor(author: Author): FeedAuthor { - // TODO ask author emails? - // RSS feed requires email to render authors - return {name: author.name, link: author.url}; + return {name: author.name, link: author.url, email: author.email}; } await mapAsyncSequential(blogPosts, async (post) => { @@ -80,7 +79,7 @@ async function generateBlogFeed({ outDir, siteConfig.trailingSlash, ); - const $ = cheerio.load(content); + const $ = cheerioLoad(content); const feedItem: FeedItem = { title: metadataTitle, @@ -128,10 +127,7 @@ async function createBlogFeedFile({ } })(); try { - await fs.outputFile( - posixPath(path.join(generatePath, feedPath)), - feedContent, - ); + await fs.outputFile(path.join(generatePath, feedPath), feedContent); } catch (err) { throw new Error(`Generating ${feedType} feed failed: ${err}.`); } @@ -142,17 +138,20 @@ export async function createBlogFeedFiles({ options, siteConfig, outDir, + locale, }: { blogPosts: BlogPost[]; options: PluginOptions; siteConfig: DocusaurusConfig; outDir: string; + locale: string; }): Promise { const feed = await generateBlogFeed({ blogPosts, options, siteConfig, outDir, + locale, }); const feedTypes = options.feedOptions.type; diff --git a/packages/docusaurus-plugin-content-blog/src/blogFrontMatter.ts b/packages/docusaurus-plugin-content-blog/src/frontMatter.ts similarity index 95% rename from packages/docusaurus-plugin-content-blog/src/blogFrontMatter.ts rename to packages/docusaurus-plugin-content-blog/src/frontMatter.ts index 49a872b6507e..417bf1df00fb 100644 --- a/packages/docusaurus-plugin-content-blog/src/blogFrontMatter.ts +++ b/packages/docusaurus-plugin-content-blog/src/frontMatter.ts @@ -74,8 +74,8 @@ const BlogFrontMatterSchema = Joi.object({ '{#label} blog frontMatter field is deprecated. Please use {#alternative} instead.', }); -export function validateBlogPostFrontMatter( - frontMatter: Record, -): BlogPostFrontMatter { +export function validateBlogPostFrontMatter(frontMatter: { + [key: string]: unknown; +}): BlogPostFrontMatter { return validateFrontMatter(frontMatter, BlogFrontMatterSchema); } diff --git a/packages/docusaurus-plugin-content-blog/src/index.ts b/packages/docusaurus-plugin-content-blog/src/index.ts index 14729a2fadb6..c1b8d82089ef 100644 --- a/packages/docusaurus-plugin-content-blog/src/index.ts +++ b/packages/docusaurus-plugin-content-blog/src/index.ts @@ -23,35 +23,27 @@ import { import {translateContent, getTranslationFiles} from './translations'; import type { + BlogTag, BlogTags, BlogContent, - BlogItemsToMetadata, - TagsModule, BlogPaginated, BlogContentPaths, BlogMarkdownLoaderOptions, - MetaData, } from './types'; -import {PluginOptionSchema} from './pluginOptionSchema'; -import type { - LoadContext, - ConfigureWebpackUtils, - Plugin, - HtmlTags, - OptionValidationContext, - ValidationResult, -} from '@docusaurus/types'; -import type {Configuration} from 'webpack'; +import type {LoadContext, Plugin, HtmlTags} from '@docusaurus/types'; import { generateBlogPosts, getSourceToPermalink, getBlogTags, + paginateBlogPosts, } from './blogUtils'; import {createBlogFeedFiles} from './feed'; import type { PluginOptions, BlogPostFrontMatter, + BlogPostMetadata, Assets, + TagModule, } from '@docusaurus/plugin-content-blog'; export default async function pluginContentBlog( @@ -134,6 +126,7 @@ export default async function pluginContentBlog( blogListPaginated: [], blogTags: {}, blogTagsListPath: null, + blogTagsPaginated: [], }; } @@ -157,45 +150,22 @@ export default async function pluginContentBlog( } }); - // Blog pagination routes. - // Example: `/blog`, `/blog/page/1`, `/blog/page/2` - const totalCount = blogPosts.length; - const postsPerPage = - postsPerPageOption === 'ALL' ? totalCount : postsPerPageOption; - const numberOfPages = Math.ceil(totalCount / postsPerPage); const baseBlogUrl = normalizeUrl([baseUrl, routeBasePath]); - const blogListPaginated: BlogPaginated[] = []; - - function blogPaginationPermalink(page: number) { - return page > 0 - ? normalizeUrl([baseBlogUrl, `page/${page + 1}`]) - : baseBlogUrl; - } - - for (let page = 0; page < numberOfPages; page += 1) { - blogListPaginated.push({ - metadata: { - permalink: blogPaginationPermalink(page), - page: page + 1, - postsPerPage, - totalPages: numberOfPages, - totalCount, - previousPage: page !== 0 ? blogPaginationPermalink(page - 1) : null, - nextPage: - page < numberOfPages - 1 - ? blogPaginationPermalink(page + 1) - : null, - blogDescription, - blogTitle, - }, - items: blogPosts - .slice(page * postsPerPage, (page + 1) * postsPerPage) - .map((item) => item.id), - }); - } + const blogListPaginated: BlogPaginated[] = paginateBlogPosts({ + blogPosts, + blogTitle, + blogDescription, + postsPerPageOption, + basePageUrl: baseBlogUrl, + }); - const blogTags: BlogTags = getBlogTags(blogPosts); + const blogTags: BlogTags = getBlogTags({ + blogPosts, + postsPerPageOption, + blogDescription, + blogTitle, + }); const tagsPath = normalizeUrl([baseBlogUrl, tagsBasePath]); @@ -221,6 +191,7 @@ export default async function pluginContentBlog( blogPostComponent, blogTagsListComponent, blogTagsPostsComponent, + blogArchiveComponent, routeBasePath, archiveBasePath, } = options; @@ -234,14 +205,14 @@ export default async function pluginContentBlog( blogTagsListPath, } = blogContents; - const blogItemsToMetadata: BlogItemsToMetadata = {}; + const blogItemsToMetadata: {[postId: string]: BlogPostMetadata} = {}; const sidebarBlogPosts = options.blogSidebarCount === 'ALL' ? blogPosts : blogPosts.slice(0, options.blogSidebarCount); - if (archiveBasePath) { + if (archiveBasePath && blogPosts.length) { const archiveUrl = normalizeUrl([ baseUrl, routeBasePath, @@ -254,7 +225,7 @@ export default async function pluginContentBlog( ); addRoute({ path: archiveUrl, - component: '@theme/BlogArchivePage', + component: blogArchiveComponent, exact: true, modules: { archive: aliasedSource(archiveProp), @@ -321,18 +292,15 @@ export default async function pluginContentBlog( exact: true, modules: { sidebar: aliasedSource(sidebarProp), - items: items.map((postID) => - // To tell routes.js this is an import and not a nested object to recurse. - ({ - content: { - __import: true, - path: blogItemsToMetadata[postID].source, - query: { - truncated: true, - }, + items: items.map((postID) => ({ + content: { + __import: true, + path: blogItemsToMetadata[postID]!.source, + query: { + truncated: true, }, - }), - ), + }, + })), metadata: aliasedSource(pageMetadataPath), }, }); @@ -344,50 +312,60 @@ export default async function pluginContentBlog( return; } - const tagsModule: TagsModule = {}; - - await Promise.all( - Object.keys(blogTags).map(async (tag) => { - const {name, items, permalink} = blogTags[tag]; - - // Refactor all this, see docs implementation - tagsModule[tag] = { + const tagsModule: {[tagName: string]: TagModule} = Object.fromEntries( + Object.entries(blogTags).map(([, tag]) => { + const tagModule: TagModule = { allTagsPath: blogTagsListPath, - slug: tag, - name, - count: items.length, - permalink, + name: tag.name, + count: tag.items.length, + permalink: tag.permalink, }; - - const tagsMetadataPath = await createData( - `${docuHash(permalink)}.json`, - JSON.stringify(tagsModule[tag], null, 2), - ); - - addRoute({ - path: permalink, - component: blogTagsPostsComponent, - exact: true, - modules: { - sidebar: aliasedSource(sidebarProp), - items: items.map((postID) => { - const metadata = blogItemsToMetadata[postID]; - return { - content: { - __import: true, - path: metadata.source, - query: { - truncated: true, - }, - }, - }; - }), - metadata: aliasedSource(tagsMetadataPath), - }, - }); + return [tag.name, tagModule]; }), ); + async function createTagRoutes(tag: BlogTag): Promise { + await Promise.all( + tag.pages.map(async (blogPaginated) => { + const {metadata, items} = blogPaginated; + const tagsMetadataPath = await createData( + `${docuHash(metadata.permalink)}.json`, + JSON.stringify(tagsModule[tag.name], null, 2), + ); + + const listMetadataPath = await createData( + `${docuHash(metadata.permalink)}-list.json`, + JSON.stringify(metadata, null, 2), + ); + + addRoute({ + path: metadata.permalink, + component: blogTagsPostsComponent, + exact: true, + modules: { + sidebar: aliasedSource(sidebarProp), + items: items.map((postID) => { + const blogPostMetadata = blogItemsToMetadata[postID]!; + return { + content: { + __import: true, + path: blogPostMetadata.source, + query: { + truncated: true, + }, + }, + }; + }), + metadata: aliasedSource(tagsMetadataPath), + listMetadata: aliasedSource(listMetadataPath), + }, + }); + }), + ); + } + + await Promise.all(Object.values(blogTags).map(createTagRoutes)); + // Only create /tags page if there are tags. if (Object.keys(blogTags).length > 0) { const tagsListPath = await createData( @@ -411,12 +389,7 @@ export default async function pluginContentBlog( return translateContent(content, translationFiles); }, - configureWebpack( - _config: Configuration, - isServer: boolean, - {getJSLoader}: ConfigureWebpackUtils, - content, - ) { + configureWebpack(_config, isServer, {getJSLoader}, content) { const { rehypePlugins, remarkPlugins, @@ -451,7 +424,7 @@ export default async function pluginContentBlog( module: { rules: [ { - test: /(\.mdx?)$/, + test: /\.mdx?$/i, include: contentDirs // Trailing slash is important, see https://github.com/facebook/docusaurus/pull/3970 .map(addTrailingPathSeparator), @@ -485,13 +458,14 @@ export default async function pluginContentBlog( // Blog posts title are rendered separately removeContentTitle: true, - // Assets allow to convert some relative images paths to require() calls + // Assets allow to convert some relative images paths to + // require() calls createAssets: ({ frontMatter, metadata, }: { frontMatter: BlogPostFrontMatter; - metadata: MetaData; + metadata: BlogPostMetadata; }): Assets => ({ image: frontMatter.image, authorsImageUrls: metadata.authors.map( @@ -524,6 +498,7 @@ export default async function pluginContentBlog( options, outDir, siteConfig, + locale: currentLocale, }); }, @@ -558,13 +533,11 @@ export default async function pluginContentBlog( const headTags: HtmlTags = []; feedTypes.forEach((feedType) => { - const feedConfig = feedsConfig[feedType] || {}; - - if (!feedsConfig) { - return; - } - - const {type, path: feedConfigPath, title: feedConfigTitle} = feedConfig; + const { + type, + path: feedConfigPath, + title: feedConfigTitle, + } = feedsConfig[feedType]; headTags.push({ tagName: 'link', @@ -588,10 +561,4 @@ export default async function pluginContentBlog( }; } -export function validateOptions({ - validate, - options, -}: OptionValidationContext): ValidationResult { - const validatedOptions = validate(PluginOptionSchema, options); - return validatedOptions; -} +export {validateOptions} from './options'; diff --git a/packages/docusaurus-plugin-content-blog/src/markdownLoader.ts b/packages/docusaurus-plugin-content-blog/src/markdownLoader.ts index 55d2cda689dc..a5f3d4929889 100644 --- a/packages/docusaurus-plugin-content-blog/src/markdownLoader.ts +++ b/packages/docusaurus-plugin-content-blog/src/markdownLoader.ts @@ -34,5 +34,5 @@ export default function markdownLoader( finalContent = truncate(finalContent, markdownLoaderOptions.truncateMarker); } - return callback && callback(null, finalContent); + return callback?.(null, finalContent); } diff --git a/packages/docusaurus-plugin-content-blog/src/pluginOptionSchema.ts b/packages/docusaurus-plugin-content-blog/src/options.ts similarity index 87% rename from packages/docusaurus-plugin-content-blog/src/pluginOptionSchema.ts rename to packages/docusaurus-plugin-content-blog/src/options.ts index d72d9bea6e2f..c4c344116324 100644 --- a/packages/docusaurus-plugin-content-blog/src/pluginOptionSchema.ts +++ b/packages/docusaurus-plugin-content-blog/src/options.ts @@ -13,14 +13,15 @@ import { URISchema, } from '@docusaurus/utils-validation'; import {GlobExcludeDefault} from '@docusaurus/utils'; -import type {PluginOptions} from '@docusaurus/plugin-content-blog'; +import type {PluginOptions, Options} from '@docusaurus/plugin-content-blog'; +import type {OptionValidationContext} from '@docusaurus/types'; export const DEFAULT_OPTIONS: PluginOptions = { feedOptions: {type: ['rss', 'atom'], copyright: ''}, beforeDefaultRehypePlugins: [], beforeDefaultRemarkPlugins: [], admonitions: {}, - truncateMarker: //, + truncateMarker: //, rehypePlugins: [], remarkPlugins: [], showReadingTime: true, @@ -28,6 +29,7 @@ export const DEFAULT_OPTIONS: PluginOptions = { blogTagsListComponent: '@theme/BlogTagsListPage', blogPostComponent: '@theme/BlogPostPage', blogListComponent: '@theme/BlogListPage', + blogArchiveComponent: '@theme/BlogArchivePage', blogDescription: 'Blog', blogTitle: 'Blog', blogSidebarCount: 5, @@ -45,7 +47,7 @@ export const DEFAULT_OPTIONS: PluginOptions = { sortPosts: 'descending', }; -export const PluginOptionSchema = Joi.object({ +const PluginOptionSchema = Joi.object({ path: Joi.string().default(DEFAULT_OPTIONS.path), archiveBasePath: Joi.string() .default(DEFAULT_OPTIONS.archiveBasePath) @@ -68,6 +70,9 @@ export const PluginOptionSchema = Joi.object({ blogTagsPostsComponent: Joi.string().default( DEFAULT_OPTIONS.blogTagsPostsComponent, ), + blogArchiveComponent: Joi.string().default( + DEFAULT_OPTIONS.blogArchiveComponent, + ), blogTitle: Joi.string().allow('').default(DEFAULT_OPTIONS.blogTitle), blogDescription: Joi.string() .allow('') @@ -121,4 +126,15 @@ export const PluginOptionSchema = Joi.object({ sortPosts: Joi.string() .valid('descending', 'ascending') .default(DEFAULT_OPTIONS.sortPosts), -}); +}).default(DEFAULT_OPTIONS); + +export function validateOptions({ + validate, + options, +}: OptionValidationContext): PluginOptions { + const validatedOptions = validate( + PluginOptionSchema, + options, + ) as PluginOptions; + return validatedOptions; +} diff --git a/packages/docusaurus-plugin-content-blog/src/plugin-content-blog.d.ts b/packages/docusaurus-plugin-content-blog/src/plugin-content-blog.d.ts index cd90496a00ba..e7fd5a65ab88 100644 --- a/packages/docusaurus-plugin-content-blog/src/plugin-content-blog.d.ts +++ b/packages/docusaurus-plugin-content-blog/src/plugin-content-blog.d.ts @@ -6,277 +6,556 @@ */ declare module '@docusaurus/plugin-content-blog' { - import type {RemarkAndRehypePluginOptions} from '@docusaurus/mdx-loader'; - import type {FrontMatterTag} from '@docusaurus/utils'; + import type {MDXOptions} from '@docusaurus/mdx-loader'; + import type {FrontMatterTag, Tag} from '@docusaurus/utils'; import type {Overwrite} from 'utility-types'; export interface Assets { + /** + * If `metadata.image` is a collocated image path, this entry will be the + * bundler-generated image path. Otherwise, it's empty, and the image URL + * should be accessed through `frontMatter.image`. + */ image?: string; - authorsImageUrls: (string | undefined)[]; // Array of same size as the original MetaData.authors array + /** + * Array where each item is 1-1 correlated with the `metadata.authors` array + * so that client can render the correct author images. If the author's + * image is a local file path, the slot will be filled with the bundler- + * generated image path; otherwise, it's empty, and the author's image URL + * should be accessed through `authors.imageURL`. + */ + authorsImageUrls: (string | undefined)[]; } - // We allow passing custom fields to authors, e.g., twitter + /** + * Unknown keys are allowed, so that we can pass custom fields to authors, + * e.g., `twitter`. + */ export interface Author extends Record { + /** + * If `name` doesn't exist, an `imageURL` is expected. + */ name?: string; + /** + * The image path could be collocated, in which case + * `metadata.assets.authorsImageUrls` should be used instead. If `imageURL` + * doesn't exist, a `name` is expected. + */ imageURL?: string; + /** + * Used to generate the author's link. + */ url?: string; + /** + * Used as a subtitle for the author, e.g. "maintainer of Docusaurus" + */ title?: string; + /** + * Mainly used for RSS feeds; if `url` doesn't exist, `email` can be used + * to generate a fallback `mailto:` URL. + */ + email?: string; } + /** + * Everything is partial/unnormalized, because front matter is always + * preserved as-is. Default values will be applied when generating metadata + */ export type BlogPostFrontMatter = { + /** + * @deprecated Use `slug` instead. + */ id?: string; + /** + * Will override the default title collected from h1 heading. + * @see {@link BlogPostMetadata.title} + */ title?: string; + /** + * Will override the default excerpt. + * @see {@link BlogPostMetadata.description} + */ description?: string; + /** + * Front matter tags, unnormalized. + * @see {@link BlogPostMetadata.tags} + */ tags?: FrontMatterTag[]; + /** Custom slug appended after `///` */ slug?: string; + /** + * Marks the post as draft and excludes it from the production build. + */ draft?: boolean; - date?: Date | string; // Yaml automatically convert some string patterns as Date, but not all - + /** + * Will override the default publish date inferred from git/filename. Yaml + * only converts standard yyyy-MM-dd format to dates, so this may stay as a + * plain string. + * @see {@link BlogPostMetadata.date} + */ + date?: Date | string; + /** + * Authors, unnormalized. + * @see {@link BlogPostMetadata.authors} + */ authors?: BlogPostFrontMatterAuthors; - - // We may want to deprecate those older author front matter fields later: + /** + * To be deprecated + * @see {@link BlogPostFrontMatterAuthor.name} + */ author?: string; + /** + * To be deprecated + * @see {@link BlogPostFrontMatterAuthor.title} + */ author_title?: string; + /** + * To be deprecated + * @see {@link BlogPostFrontMatterAuthor.url} + */ author_url?: string; + /** + * To be deprecated + * @see {@link BlogPostFrontMatterAuthor.imageURL} + */ author_image_url?: string; - /** @deprecated */ + /** @deprecated v1 legacy */ authorTitle?: string; - /** @deprecated */ + /** @deprecated v1 legacy */ authorURL?: string; - /** @deprecated */ + /** @deprecated v1 legacy */ authorImageURL?: string; + /** Used in the head meta. Should use `assets.image` in priority. */ image?: string; + /** Used in the head meta. */ keywords?: string[]; + /** Hide the right TOC. */ hide_table_of_contents?: boolean; + /** + * Minimum TOC heading level. Must be between 2 and 6 and lower or equal to + * the max value. + */ toc_min_heading_level?: number; + /** Maximum TOC heading level. Must be between 2 and 6. */ toc_max_heading_level?: number; }; - export type BlogPostFrontMatterAuthor = Record & { + export type BlogPostFrontMatterAuthor = Author & { + /** + * Will be normalized into the `imageURL` prop. + */ + image_url?: string; + /** + * References an existing author in the authors map. + */ key?: string; - name?: string; - imageURL?: string; - url?: string; - title?: string; }; - // All the possible variants that the user can use for convenience + /** + * Blog post authors can be declared in front matter as a string key + * (referencing an author in authors map), an object (partially overriding the + * data in authors map, or a completely new author), or an array of a mix of + * both. + */ export type BlogPostFrontMatterAuthors = | string | BlogPostFrontMatterAuthor | (string | BlogPostFrontMatterAuthor)[]; + export type BlogPostMetadata = { + /** Path to the Markdown source, with `@site` alias. */ + readonly source: string; + /** + * Used to generate the page h1 heading, tab title, and pagination title. + */ + readonly title: string; + /** + * The publish date of the post. On client side, this will be serialized + * into a string. + */ + readonly date: Date; + /** + * Publish date formatted according to the locale, so that the client can + * render the date regardless of the existence of `Intl.DateTimeFormat`. + */ + readonly formattedDate: string; + /** Full link including base URL. */ + readonly permalink: string; + /** + * Description used in the meta. Could be an empty string (empty content) + */ + readonly description: string; + /** + * Absolute URL to the editing page of the post. Undefined if the post + * shouldn't be edited. + */ + readonly editUrl?: string; + /** + * Reading time in minutes calculated based on word count. + */ + readonly readingTime?: number; + /** + * Whether the truncate marker exists in the post's content. + */ + readonly truncated?: boolean; + /** + * Used in pagination. Generated after the other metadata, so not readonly. + * Content is just a subset of another post's metadata. + */ + nextItem?: {readonly title: string; readonly permalink: string}; + /** + * Used in pagination. Generated after the other metadata, so not readonly. + * Content is just a subset of another post's metadata. + */ + prevItem?: {readonly title: string; readonly permalink: string}; + /** + * Author metadata, normalized. Should be used in joint with + * `assets.authorsImageUrls` on client side. + */ + readonly authors: Author[]; + /** Front matter, as-is. */ + readonly frontMatter: BlogPostFrontMatter & {[key: string]: unknown}; + /** Tags, normalized. */ + readonly tags: Tag[]; + }; + /** + * @returns The edit URL that's directly plugged into metadata. + */ export type EditUrlFunction = (editUrlParams: { + /** + * The root content directory containing this post file, relative to the + * site path. Usually the same as `options.path` but can be localized + */ blogDirPath: string; + /** Path to this post file, relative to `blogDirPath`. */ blogPath: string; + /** @see {@link BlogPostMetadata.permalink} */ permalink: string; + /** Locale name. */ locale: string; }) => string | undefined; export type FeedType = 'rss' | 'atom' | 'json'; + /** + * Normalized feed options used within code. + */ export type FeedOptions = { + /** If `null`, no feed is generated. */ type?: FeedType[] | null; + /** Title of generated feed. */ title?: string; + /** Description of generated feed. */ description?: string; + /** Copyright notice. Required because the feed library marked it that. */ copyright: string; + /** Language of the feed. */ language?: string; }; - // Feed options, as provided by user config - export type UserFeedOptions = Overwrite< - Partial, - {type?: FeedOptions['type'] | 'all'} // Handle the type: "all" shortcut - >; - // Duplicate from ngryman/reading-time to keep stability of API + /** + * Duplicate from ngryman/reading-time to keep stability of API. + */ type ReadingTimeOptions = { wordsPerMinute?: number; + /** + * @param char The character to be matched. + * @returns `true` if this character is a word bound. + */ wordBound?: (char: string) => boolean; }; + /** + * Represents the default reading time implementation. + * @returns The reading time directly plugged into metadata. + */ export type ReadingTimeFunction = (params: { + /** Markdown content. */ content: string; - frontMatter?: BlogPostFrontMatter & Record; + /** Front matter. */ + frontMatter?: BlogPostFrontMatter & {[key: string]: unknown}; + /** Options accepted by ngryman/reading-time. */ options?: ReadingTimeOptions; }) => number; + /** + * @returns The reading time directly plugged into metadata. `undefined` to + * hide reading time for a specific post. + */ export type ReadingTimeFunctionOption = ( + /** + * The `options` is not provided by the caller; the user can inject their + * own option values into `defaultReadingTime` + */ params: Required[0], 'options'>> & { + /** + * The default reading time implementation from ngryman/reading-time. + */ defaultReadingTime: ReadingTimeFunction; }, ) => number | undefined; - - export type PluginOptions = RemarkAndRehypePluginOptions & { + /** + * Plugin options after normalization. + */ + export type PluginOptions = MDXOptions & { + /** Plugin ID. */ id?: string; + /** + * Path to the blog content directory on the file system, relative to site + * directory. + */ path: string; + /** + * URL route for the blog section of your site. **DO NOT** include a + * trailing slash. Use `/` to put the blog at root path. + */ routeBasePath: string; + /** + * URL route for the tags section of your blog. Will be appended to + * `routeBasePath`. **DO NOT** include a trailing slash. + */ tagsBasePath: string; + /** + * URL route for the archive section of your blog. Will be appended to + * `routeBasePath`. **DO NOT** include a trailing slash. Use `null` to + * disable generation of archive. + */ archiveBasePath: string | null; + /** + * Array of glob patterns matching Markdown files to be built, relative to + * the content path. + */ include: string[]; + /** + * Array of glob patterns matching Markdown files to be excluded. Serves as + * refinement based on the `include` option. + */ exclude: string[]; + /** + * Number of posts to show per page in the listing page. Use `'ALL'` to + * display all posts on one listing page. + */ postsPerPage: number | 'ALL'; + /** Root component of the blog listing page. */ blogListComponent: string; + /** Root component of each blog post page. */ blogPostComponent: string; + /** Root component of the tags list page. */ blogTagsListComponent: string; + /** Root component of the "posts containing tag" page. */ blogTagsPostsComponent: string; + /** Root component of the blog archive page. */ + blogArchiveComponent: string; + /** Blog page title for better SEO. */ blogTitle: string; + /** Blog page meta description for better SEO. */ blogDescription: string; + /** + * Number of blog post elements to show in the blog sidebar. `'ALL'` to show + * all blog posts; `0` to disable. + */ blogSidebarCount: number | 'ALL'; + /** Title of the blog sidebar. */ blogSidebarTitle: string; + /** Truncate marker marking where the summary ends. */ truncateMarker: RegExp; + /** Show estimated reading time for the blog post. */ showReadingTime: boolean; - feedOptions: { - type?: FeedType[] | null; - title?: string; - description?: string; - copyright: string; - language?: string; - }; + /** Blog feed. */ + feedOptions: FeedOptions; + /** + * Base URL to edit your site. The final URL is computed by `editUrl + + * relativePostPath`. Using a function allows more nuanced control for each + * file. Omitting this variable entirely will disable edit links. + */ editUrl?: string | EditUrlFunction; + /** + * The edit URL will target the localized file, instead of the original + * unlocalized file. Ignored when `editUrl` is a function. + */ editLocalizedFiles?: boolean; - admonitions: Record; + admonitions: {[key: string]: unknown}; + /** Path to the authors map file, relative to the blog content directory. */ authorsMapPath: string; + /** A callback to customize the reading time number displayed. */ readingTime: ReadingTimeFunctionOption; + /** Governs the direction of blog post sorting. */ sortPosts: 'ascending' | 'descending'; }; - // Options, as provided in the user config (before normalization) + + /** + * Feed options, as provided by user config. `type` accepts `all` as shortcut + */ + export type UserFeedOptions = Overwrite< + Partial, + { + /** Type of feed to be generated. Use `null` to disable generation. */ + type?: FeedOptions['type'] | 'all' | FeedType; + } + >; + /** + * Options as provided in the user config (before normalization) + */ export type Options = Overwrite< Partial, - {feedOptions?: UserFeedOptions} + { + /** Blog feed. */ + feedOptions?: UserFeedOptions; + } >; -} -declare module '@theme/BlogSidebar' { - export type BlogSidebarItem = {title: string; permalink: string}; + export type TagModule = { + /** Permalink of the tag's own page. */ + permalink: string; + /** Name of the tag. */ + name: string; + /** Number of posts with this tag. */ + count: number; + /** The tags list page. */ + allTagsPath: string; + }; + export type BlogSidebar = { title: string; - items: BlogSidebarItem[]; + items: {title: string; permalink: string}[]; }; - - export interface Props { - readonly sidebar: BlogSidebar; - } - - const BlogSidebar: (props: Props) => JSX.Element; - export default BlogSidebar; } declare module '@theme/BlogPostPage' { - import type {BlogSidebar} from '@theme/BlogSidebar'; import type {TOCItem} from '@docusaurus/types'; import type { BlogPostFrontMatter, - Author, + BlogPostMetadata, Assets, + BlogSidebar, } from '@docusaurus/plugin-content-blog'; + import type {Overwrite} from 'utility-types'; export type FrontMatter = BlogPostFrontMatter; - export type Metadata = { - readonly title: string; - readonly date: string; - readonly formattedDate: string; - readonly permalink: string; - readonly description?: string; - readonly editUrl?: string; - readonly readingTime?: number; - readonly truncated?: string; - readonly nextItem?: {readonly title: string; readonly permalink: string}; - readonly prevItem?: {readonly title: string; readonly permalink: string}; - readonly authors: Author[]; - readonly frontMatter: FrontMatter & Record; - readonly tags: readonly { - readonly label: string; - readonly permalink: string; - }[]; - }; + export type Metadata = Overwrite< + BlogPostMetadata, + { + /** The publish date of the post. Serialized from the `Date` object. */ + date: string; + } + >; export type Content = { + // TODO remove this. `metadata.frontMatter` is preferred because it can be + // accessed in enhanced plugins + /** Same as `metadata.frontMatter` */ readonly frontMatter: FrontMatter; + /** + * Usually image assets that may be collocated like `./img/thumbnail.png`. + * The loader would also bundle these assets and the client should use these + * in priority. + */ readonly assets: Assets; + /** Metadata of the post. */ readonly metadata: Metadata; + /** A list of TOC items (headings). */ readonly toc: readonly TOCItem[]; + /** Renders the actual MDX content. */ (): JSX.Element; }; export interface Props { + /** Blog sidebar. */ readonly sidebar: BlogSidebar; + /** Content of this post as an MDX component, with useful metadata. */ readonly content: Content; } - const BlogPostPage: (props: Props) => JSX.Element; - export default BlogPostPage; + export default function BlogPostPage(props: Props): JSX.Element; } declare module '@theme/BlogListPage' { import type {Content} from '@theme/BlogPostPage'; - import type {BlogSidebar} from '@theme/BlogSidebar'; + import type {BlogSidebar} from '@docusaurus/plugin-content-blog'; export type Metadata = { + /** Title of the entire blog. */ readonly blogTitle: string; + /** Blog description. */ readonly blogDescription: string; + /** Permalink to the next list page. */ readonly nextPage?: string; - readonly page: number; + /** Permalink of the current page. */ readonly permalink: string; - readonly postsPerPage: number; + /** Permalink to the previous list page. */ readonly previousPage?: string; + /** Index of the current page, 1-based. */ + readonly page: number; + /** Posts displayed on each list page. */ + readonly postsPerPage: number; + /** Total number of posts in the entire blog. */ readonly totalCount: number; + /** Total number of list pages. */ readonly totalPages: number; }; export interface Props { + /** Blog sidebar. */ readonly sidebar: BlogSidebar; + /** Metadata of the current listing page. */ readonly metadata: Metadata; + /** + * Array of blog posts included on this page. Every post's metadata is also + * available. + */ readonly items: readonly {readonly content: Content}[]; } - const BlogListPage: (props: Props) => JSX.Element; - export default BlogListPage; + export default function BlogListPage(props: Props): JSX.Element; } declare module '@theme/BlogTagsListPage' { - import type {BlogSidebar} from '@theme/BlogSidebar'; - - export type Tag = { - permalink: string; - name: string; - count: number; - allTagsPath: string; - slug: string; - }; + import type {BlogSidebar, TagModule} from '@docusaurus/plugin-content-blog'; export interface Props { + /** Blog sidebar. */ readonly sidebar: BlogSidebar; - readonly tags: Readonly>; + /** A map from tag names to the full tag module. */ + readonly tags: Readonly<{[tagName: string]: TagModule}>; } - const BlogTagsListPage: (props: Props) => JSX.Element; - export default BlogTagsListPage; + export default function BlogTagsListPage(props: Props): JSX.Element; } declare module '@theme/BlogTagsPostsPage' { - import type {BlogSidebar} from '@theme/BlogSidebar'; - import type {Tag} from '@theme/BlogTagsListPage'; + import type {BlogSidebar, TagModule} from '@docusaurus/plugin-content-blog'; import type {Content} from '@theme/BlogPostPage'; + import type {Metadata} from '@theme/BlogListPage'; export interface Props { + /** Blog sidebar. */ readonly sidebar: BlogSidebar; - readonly metadata: Tag; + /** Metadata of this tag. */ + readonly metadata: TagModule; + /** Looks exactly the same as the posts list page */ + readonly listMetadata: Metadata; + /** + * Array of blog posts included on this page. Every post's metadata is also + * available. + */ readonly items: readonly {readonly content: Content}[]; } - const BlogTagsPostsPage: (props: Props) => JSX.Element; - export default BlogTagsPostsPage; + export default function BlogTagsPostsPage(props: Props): JSX.Element; } declare module '@theme/BlogArchivePage' { import type {Content} from '@theme/BlogPostPage'; + /** We may add extra metadata or prune some metadata from here */ export type ArchiveBlogPost = Content; export interface Props { + /** The entirety of the blog's data. */ readonly archive: { + /** All posts. Can select any useful data/metadata to render. */ readonly blogPosts: readonly ArchiveBlogPost[]; }; } diff --git a/packages/docusaurus-plugin-content-blog/src/translations.ts b/packages/docusaurus-plugin-content-blog/src/translations.ts index 09598e59ab0f..28c233dd99c8 100644 --- a/packages/docusaurus-plugin-content-blog/src/translations.ts +++ b/packages/docusaurus-plugin-content-blog/src/translations.ts @@ -6,7 +6,7 @@ */ import type {BlogContent, BlogPaginated} from './types'; -import type {TranslationFileContent, TranslationFiles} from '@docusaurus/types'; +import type {TranslationFileContent, TranslationFile} from '@docusaurus/types'; import type {PluginOptions} from '@docusaurus/plugin-content-blog'; function translateListPage( @@ -19,14 +19,15 @@ function translateListPage( items, metadata: { ...metadata, - blogTitle: translations.title.message, - blogDescription: translations.description.message, + blogTitle: translations.title?.message ?? page.metadata.blogTitle, + blogDescription: + translations.description?.message ?? page.metadata.blogDescription, }, }; }); } -export function getTranslationFiles(options: PluginOptions): TranslationFiles { +export function getTranslationFiles(options: PluginOptions): TranslationFile[] { return [ { path: 'options', @@ -50,12 +51,13 @@ export function getTranslationFiles(options: PluginOptions): TranslationFiles { export function translateContent( content: BlogContent, - translationFiles: TranslationFiles, + translationFiles: TranslationFile[], ): BlogContent { - const [{content: optionsTranslations}] = translationFiles; + const {content: optionsTranslations} = translationFiles[0]!; return { ...content, - blogSidebarTitle: optionsTranslations['sidebar.title'].message, + blogSidebarTitle: + optionsTranslations['sidebar.title']?.message ?? content.blogSidebarTitle, blogListPaginated: translateListPage( content.blogListPaginated, optionsTranslations, diff --git a/packages/docusaurus-plugin-content-blog/src/types.ts b/packages/docusaurus-plugin-content-blog/src/types.ts index ed909f2b3c0a..a96390f71d51 100644 --- a/packages/docusaurus-plugin-content-blog/src/types.ts +++ b/packages/docusaurus-plugin-content-blog/src/types.ts @@ -5,15 +5,9 @@ * LICENSE file in the root directory of this source tree. */ -import type {Tag} from '@docusaurus/utils'; -import type { - BrokenMarkdownLink, - ContentPaths, -} from '@docusaurus/utils/lib/markdownLinks'; -import type { - BlogPostFrontMatter, - Author, -} from '@docusaurus/plugin-content-blog'; +import type {BrokenMarkdownLink, ContentPaths} from '@docusaurus/utils'; +import type {BlogPostMetadata} from '@docusaurus/plugin-content-blog'; +import type {Metadata as BlogPaginatedMetadata} from '@theme/BlogListPage'; export type BlogContentPaths = ContentPaths; @@ -26,74 +20,29 @@ export interface BlogContent { } export interface BlogTags { - [key: string]: BlogTag; + // TODO, the key is the tag slug/permalink + // This is due to legacy frontmatter: tags: + // [{label: "xyz", permalink: "/1"}, {label: "xyz", permalink: "/2"}] + // Soon we should forbid declaring permalink through frontmatter + [tagKey: string]: BlogTag; } export interface BlogTag { name: string; - items: string[]; + items: string[]; // blog post permalinks permalink: string; + pages: BlogPaginated[]; } export interface BlogPost { id: string; - metadata: MetaData; + metadata: BlogPostMetadata; content: string; } -export interface BlogPaginatedMetadata { - permalink: string; - page: number; - postsPerPage: number; - totalPages: number; - totalCount: number; - previousPage: string | null; - nextPage: string | null; - blogTitle: string; - blogDescription: string; -} - export interface BlogPaginated { metadata: BlogPaginatedMetadata; - items: string[]; -} - -export interface MetaData { - permalink: string; - source: string; - description: string; - date: Date; - formattedDate: string; - tags: Tag[]; - title: string; - readingTime?: number; - prevItem?: Paginator; - nextItem?: Paginator; - truncated: boolean; - editUrl?: string; - authors: Author[]; - frontMatter: BlogPostFrontMatter & Record; -} - -export interface Paginator { - title: string; - permalink: string; -} - -export interface BlogItemsToMetadata { - [key: string]: MetaData; -} - -export interface TagsModule { - [key: string]: TagModule; -} - -export interface TagModule { - allTagsPath: string; - slug: string; - name: string; - count: number; - permalink: string; + items: string[]; // blog post permalinks } export type BlogBrokenMarkdownLink = BrokenMarkdownLink; @@ -101,6 +50,6 @@ export type BlogMarkdownLoaderOptions = { siteDir: string; contentPaths: BlogContentPaths; truncateMarker: RegExp; - sourceToPermalink: Record; + sourceToPermalink: {[aliasedPath: string]: string}; onBrokenMarkdownLink: (brokenMarkdownLink: BlogBrokenMarkdownLink) => void; }; diff --git a/packages/docusaurus-plugin-content-docs/package.json b/packages/docusaurus-plugin-content-docs/package.json index c456105e1b8c..c080891df07f 100644 --- a/packages/docusaurus-plugin-content-docs/package.json +++ b/packages/docusaurus-plugin-content-docs/package.json @@ -1,10 +1,11 @@ { "name": "@docusaurus/plugin-content-docs", - "version": "2.0.0-beta.14", + "version": "2.0.0-beta.18", "description": "Docs plugin for Docusaurus.", "main": "lib/index.js", "exports": { "./client": "./lib/client/index.js", + "./server": "./lib/server-export.js", ".": "./lib/index.js" }, "types": "src/plugin-content-docs.d.ts", @@ -22,30 +23,30 @@ }, "license": "MIT", "dependencies": { - "@docusaurus/core": "2.0.0-beta.14", - "@docusaurus/logger": "2.0.0-beta.14", - "@docusaurus/mdx-loader": "2.0.0-beta.14", - "@docusaurus/utils": "2.0.0-beta.14", - "@docusaurus/utils-validation": "2.0.0-beta.14", + "@docusaurus/core": "2.0.0-beta.18", + "@docusaurus/logger": "2.0.0-beta.18", + "@docusaurus/mdx-loader": "2.0.0-beta.18", + "@docusaurus/utils": "2.0.0-beta.18", + "@docusaurus/utils-validation": "2.0.0-beta.18", "combine-promises": "^1.1.0", - "fs-extra": "^10.0.0", - "import-fresh": "^3.2.2", - "js-yaml": "^4.0.0", - "lodash": "^4.17.20", + "fs-extra": "^10.0.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", "remark-admonitions": "^1.2.1", - "shelljs": "^0.8.4", "tslib": "^2.3.1", "utility-types": "^3.10.0", - "webpack": "^5.61.0" + "webpack": "^5.70.0" }, "devDependencies": { - "@docusaurus/module-type-aliases": "2.0.0-beta.14", - "@docusaurus/types": "2.0.0-beta.14", - "@types/js-yaml": "^4.0.0", - "@types/picomatch": "^2.2.1", + "@docusaurus/module-type-aliases": "2.0.0-beta.18", + "@docusaurus/types": "2.0.0-beta.18", + "@types/js-yaml": "^4.0.5", + "@types/picomatch": "^2.3.0", "commander": "^5.1.0", "escape-string-regexp": "^4.0.0", - "picomatch": "^2.1.1", + "picomatch": "^2.3.1", + "shelljs": "^0.8.5", "utility-types": "^3.10.0" }, "peerDependencies": { diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/docs/foo/baz.md b/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/docs/foo/baz.md index 95d048670453..3407ccd1b033 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/docs/foo/baz.md +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/docs/foo/baz.md @@ -70,6 +70,6 @@ Term 2 ~ Definition 2a ~ Definition 2b This is HTML abbreviation example. -It converts "HTML", but keep intact partial entries like "xxxHTMLyyy" and so on. +It converts "HTML", but keep intact partial entries like "xxxHTMLxxx" and so on. \*[HTML]: Hyper Text Markup Language diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/wrong-sidebars.json b/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/wrong-sidebars.json index 6f56ce2c0b5a..37dd62535414 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/wrong-sidebars.json +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/wrong-sidebars.json @@ -1,7 +1,7 @@ { "docs": { "Test": [ - "goku" + "nonExistent" ] } } diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/cli.test.ts.snap b/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/cli.test.ts.snap index 5536e5ca09c8..2f4d4e94fa34 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/cli.test.ts.snap +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/cli.test.ts.snap @@ -1,43 +1,43 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`docsVersion first time versioning 1`] = ` -Object { - "docs": Object { - "Guides": Array [ +{ + "docs": { + "Guides": [ "hello", ], - "Test": Array [ - Object { - "items": Array [ + "Test": [ + { + "items": [ "foo/bar", "foo/baz", ], "label": "foo", "type": "category", }, - Object { - "items": Array [ + { + "items": [ "rootAbsoluteSlug", "rootRelativeSlug", "rootResolvedSlug", "rootTryToEscapeSlug", ], "label": "Slugs", - "link": Object { + "link": { "type": "generated-index", }, "type": "category", }, - Object { + { "id": "headingAsTitle", "type": "doc", }, - Object { + { "href": "https://github.com", "label": "Github", "type": "link", }, - Object { + { "id": "hello", "type": "ref", }, @@ -47,12 +47,12 @@ Object { `; exports[`docsVersion not the first time versioning 1`] = ` -Object { - "docs": Object { - "Guides": Array [ +{ + "docs": { + "Guides": [ "hello", ], - "Test": Array [ + "Test": [ "foo/bar", ], }, @@ -60,8 +60,8 @@ Object { `; exports[`docsVersion second docs instance versioning 1`] = ` -Object { - "community": Array [ +{ + "community": [ "team", ], } diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/docs.test.ts.snap b/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/docs.test.ts.snap index d8ad2b8a3ee8..eaed1c90bbbb 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/docs.test.ts.snap +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/docs.test.ts.snap @@ -1,144 +1,244 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`simple site custom pagination 1`] = ` -Array [ - Array [ - undefined, - Object { - "permalink": "/docs/headingAsTitle", - "title": "My heading as title", +{ + "pagination": [ + { + "id": "doc with space", + "next": { + "permalink": "/docs/foo/bar", + "title": "Bar", + }, + "prev": undefined, + }, + { + "id": "foo/bar", + "next": undefined, + "prev": undefined, + }, + { + "id": "foo/baz", + "next": { + "permalink": "/docs/headingAsTitle", + "title": "My heading as title", + }, + "prev": { + "permalink": "/docs/foo/bar", + "title": "Bar", + }, + }, + { + "id": "headingAsTitle", + "next": { + "permalink": "/docs/", + "title": "Hello sidebar_label", + }, + "prev": { + "permalink": "/docs/foo/bazSlug.html", + "title": "baz pagination_label", + }, + }, + { + "id": "hello", + "next": { + "permalink": "/docs/ipsum", + "title": "ipsum", + }, + "prev": { + "permalink": "/docs/headingAsTitle", + "title": "My heading as title", + }, + }, + { + "id": "ipsum", + "next": { + "permalink": "/docs/lorem", + "title": "lorem", + }, + "prev": { + "permalink": "/docs/", + "title": "Hello sidebar_label", + }, + }, + { + "id": "lorem", + "next": { + "permalink": "/docs/rootAbsoluteSlug", + "title": "rootAbsoluteSlug", + }, + "prev": { + "permalink": "/docs/ipsum", + "title": "ipsum", + }, + }, + { + "id": "rootAbsoluteSlug", + "next": { + "permalink": "/docs/headingAsTitle", + "title": "My heading as title", + }, + "prev": { + "permalink": "/docs/foo/bazSlug.html", + "title": "baz pagination_label", + }, + }, + { + "id": "rootRelativeSlug", + "next": { + "permalink": "/docs/headingAsTitle", + "title": "My heading as title", + }, + "prev": { + "permalink": "/docs/foo/bazSlug.html", + "title": "baz pagination_label", + }, + }, + { + "id": "rootResolvedSlug", + "next": { + "permalink": "/docs/headingAsTitle", + "title": "My heading as title", + }, + "prev": { + "permalink": "/docs/foo/bazSlug.html", + "title": "baz pagination_label", + }, + }, + { + "id": "rootTryToEscapeSlug", + "next": { + "permalink": "/docs/headingAsTitle", + "title": "My heading as title", + }, + "prev": { + "permalink": "/docs/foo/bazSlug.html", + "title": "baz pagination_label", + }, + }, + { + "id": "slugs/absoluteSlug", + "next": { + "permalink": "/docs/slugs/relativeSlug", + "title": "relativeSlug", + }, + "prev": { + "permalink": "/docs/rootTryToEscapeSlug", + "title": "rootTryToEscapeSlug", + }, + }, + { + "id": "slugs/relativeSlug", + "next": { + "permalink": "/docs/slugs/hey/resolvedSlug", + "title": "resolvedSlug", + }, + "prev": { + "permalink": "/docs/absoluteSlug", + "title": "absoluteSlug", + }, + }, + { + "id": "slugs/resolvedSlug", + "next": { + "permalink": "/docs/tryToEscapeSlug", + "title": "tryToEscapeSlug", + }, + "prev": { + "permalink": "/docs/slugs/relativeSlug", + "title": "relativeSlug", + }, + }, + { + "id": "slugs/tryToEscapeSlug", + "next": undefined, + "prev": { + "permalink": "/docs/slugs/hey/resolvedSlug", + "title": "resolvedSlug", + }, }, ], - Array [ - undefined, - undefined, - ], - Array [ - Object { - "permalink": "/docs/foo/bar", - "title": "Bar", - }, - Object { - "permalink": "/docs/absoluteSlug", - "title": "absoluteSlug", - }, - ], - Array [ - Object { - "permalink": "/docs/doc with space", - "title": "Hoo hoo, if this path tricks you...", - }, - Object { - "permalink": "/docs/", - "title": "Hello sidebar_label", - }, - ], - Array [ - Object { - "permalink": "/docs/headingAsTitle", - "title": "My heading as title", - }, - Object { - "permalink": "/docs/ipsum", - "title": "ipsum", - }, - ], - Array [ - Object { - "permalink": "/docs/", - "title": "Hello sidebar_label", - }, - Object { - "permalink": "/docs/lorem", - "title": "lorem", - }, - ], - Array [ - Object { - "permalink": "/docs/ipsum", - "title": "ipsum", - }, - Object { - "permalink": "/docs/rootAbsoluteSlug", - "title": "rootAbsoluteSlug", - }, - ], - Array [ - Object { - "permalink": "/docs/foo/bazSlug.html", - "title": "baz pagination_label", - }, - Object { - "permalink": "/docs/headingAsTitle", - "title": "My heading as title", - }, - ], - Array [ - Object { - "permalink": "/docs/foo/bazSlug.html", - "title": "baz pagination_label", - }, - Object { - "permalink": "/docs/headingAsTitle", - "title": "My heading as title", - }, - ], - Array [ - Object { - "permalink": "/docs/foo/bazSlug.html", - "title": "baz pagination_label", - }, - Object { - "permalink": "/docs/headingAsTitle", - "title": "My heading as title", - }, - ], - Array [ - Object { - "permalink": "/docs/foo/bazSlug.html", - "title": "baz pagination_label", - }, - Object { - "permalink": "/docs/headingAsTitle", - "title": "My heading as title", - }, - ], - Array [ - Object { - "permalink": "/docs/foo/bazSlug.html", - "title": "baz pagination_label", - }, - Object { - "permalink": "/docs/slugs/relativeSlug", - "title": "relativeSlug", - }, - ], - Array [ - Object { - "permalink": "/docs/absoluteSlug", - "title": "absoluteSlug", - }, - Object { - "permalink": "/docs/slugs/hey/resolvedSlug", - "title": "resolvedSlug", - }, - ], - Array [ - Object { - "permalink": "/docs/slugs/relativeSlug", - "title": "relativeSlug", - }, - Object { - "permalink": "/docs/tryToEscapeSlug", - "title": "tryToEscapeSlug", - }, - ], - Array [ - Object { - "permalink": "/docs/slugs/hey/resolvedSlug", - "title": "resolvedSlug", - }, - undefined, - ], -] + "sidebars": { + "defaultSidebar": [ + { + "id": "doc with space", + "type": "doc", + }, + { + "collapsed": false, + "collapsible": true, + "items": [ + { + "id": "foo/bar", + "type": "doc", + }, + { + "id": "foo/baz", + "type": "doc", + }, + ], + "label": "foo", + "link": undefined, + "type": "category", + }, + { + "id": "headingAsTitle", + "type": "doc", + }, + { + "id": "hello", + "label": "Hello sidebar_label", + "type": "doc", + }, + { + "id": "ipsum", + "type": "doc", + }, + { + "id": "lorem", + "type": "doc", + }, + { + "id": "rootAbsoluteSlug", + "type": "doc", + }, + { + "id": "rootRelativeSlug", + "type": "doc", + }, + { + "id": "rootResolvedSlug", + "type": "doc", + }, + { + "id": "rootTryToEscapeSlug", + "type": "doc", + }, + { + "collapsed": false, + "collapsible": true, + "items": [ + { + "id": "slugs/absoluteSlug", + "type": "doc", + }, + { + "id": "slugs/relativeSlug", + "type": "doc", + }, + { + "id": "slugs/resolvedSlug", + "type": "doc", + }, + { + "id": "slugs/tryToEscapeSlug", + "type": "doc", + }, + ], + "label": "slugs", + "link": undefined, + "type": "category", + }, + ], + }, +} `; diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/globalData.test.ts.snap b/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/globalData.test.ts.snap new file mode 100644 index 000000000000..a646e0241e43 --- /dev/null +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/globalData.test.ts.snap @@ -0,0 +1,43 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`toGlobalDataVersion generates the right docs, sidebars, and metadata 1`] = ` +{ + "docs": [ + { + "id": "main", + "path": "/current/main", + "sidebar": "tutorial", + }, + { + "id": "doc", + "path": "/current/doc", + "sidebar": "tutorial", + }, + { + "id": "/current/generated", + "path": "/current/generated", + "sidebar": "tutorial", + }, + ], + "isLast": true, + "label": "Label", + "mainDocId": "main", + "name": "current", + "path": "/current", + "sidebars": { + "another": { + "link": { + "label": "Generated", + "path": "/current/generated", + }, + }, + "links": {}, + "tutorial": { + "link": { + "label": "main", + "path": "/current/main", + }, + }, + }, +} +`; diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/index.test.ts.snap index 0d601a5d2414..44346d589b45 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/index.test.ts.snap +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/index.test.ts.snap @@ -1,9 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`sidebar site with undefined sidebar 1`] = ` +{ + "defaultSidebar": [ + { + "id": "hello-1", + "type": "doc", + }, + { + "id": "hello-2", + "label": "Hello 2 From Doc", + "type": "doc", + }, + ], +} +`; + exports[`sidebar site with wrong sidebar content 1`] = ` "Invalid sidebar file at \\"packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/simple-site/wrong-sidebars.json\\". These sidebar document ids do not exist: -- goku +- nonExistent Available document ids are: - doc with space @@ -24,18 +40,18 @@ Available document ids are: `; exports[`simple website content 1`] = ` -Object { +{ "description": "Images", "editUrl": undefined, "formattedLastUpdatedAt": undefined, - "frontMatter": Object { + "frontMatter": { "id": "baz", "pagination_label": "baz pagination_label", "slug": "bazSlug.html", - "tags": Array [ + "tags": [ "tag 1", "tag-1", - Object { + { "label": "tag 2", "permalink": "tag2-custom-permalink", }, @@ -45,12 +61,12 @@ Object { "id": "foo/baz", "lastUpdatedAt": undefined, "lastUpdatedBy": undefined, - "next": Object { + "next": { "permalink": "/docs/category/slugs", "title": "Slugs", }, "permalink": "/docs/foo/bazSlug.html", - "previous": Object { + "previous": { "permalink": "/docs/foo/bar", "title": "Bar", }, @@ -59,12 +75,12 @@ Object { "slug": "/foo/bazSlug.html", "source": "@site/docs/foo/baz.md", "sourceDirName": "foo", - "tags": Array [ - Object { + "tags": [ + { "label": "tag 1", "permalink": "/docs/tags/tag-1", }, - Object { + { "label": "tag 2", "permalink": "/docs/tags/tag2-custom-permalink", }, @@ -76,15 +92,15 @@ Object { `; exports[`simple website content 2`] = ` -Object { +{ "description": "Hi, Endilie here :)", "editUrl": undefined, "formattedLastUpdatedAt": undefined, - "frontMatter": Object { + "frontMatter": { "id": "hello", "sidebar_label": "Hello sidebar_label", "slug": "/", - "tags": Array [ + "tags": [ "tag-1", "tag 3", ], @@ -95,7 +111,7 @@ Object { "lastUpdatedBy": undefined, "next": undefined, "permalink": "/docs/", - "previous": Object { + "previous": { "permalink": "/docs/headingAsTitle", "title": "My heading as title", }, @@ -104,12 +120,12 @@ Object { "slug": "/", "source": "@site/docs/hello.md", "sourceDirName": ".", - "tags": Array [ - Object { + "tags": [ + { "label": "tag-1", "permalink": "/docs/tags/tag-1", }, - Object { + { "label": "tag 3", "permalink": "/docs/tags/tag-3", }, @@ -121,11 +137,11 @@ Object { `; exports[`simple website content 3`] = ` -Object { +{ "description": "This is custom description", "editUrl": undefined, "formattedLastUpdatedAt": undefined, - "frontMatter": Object { + "frontMatter": { "description": "This is custom description", "id": "bar", "pagination_next": null, @@ -143,7 +159,7 @@ Object { "slug": "/foo/bar", "source": "@site/docs/foo/bar.md", "sourceDirName": "foo", - "tags": Array [], + "tags": [], "title": "Bar", "unversionedId": "foo/bar", "version": "current", @@ -151,21 +167,21 @@ Object { `; exports[`simple website content 4`] = ` -Object { - "docs": Array [ - Object { +{ + "docs": [ + { "collapsed": true, "collapsible": true, - "items": Array [ - Object { + "items": [ + { "collapsed": true, "collapsible": true, - "items": Array [ - Object { + "items": [ + { "id": "foo/bar", "type": "doc", }, - Object { + { "id": "foo/baz", "type": "doc", }, @@ -174,45 +190,45 @@ Object { "link": undefined, "type": "category", }, - Object { + { "collapsed": true, "collapsible": true, - "items": Array [ - Object { + "items": [ + { "id": "rootAbsoluteSlug", "type": "doc", }, - Object { + { "id": "rootRelativeSlug", "type": "doc", }, - Object { + { "id": "rootResolvedSlug", "type": "doc", }, - Object { + { "id": "rootTryToEscapeSlug", "type": "doc", }, ], "label": "Slugs", - "link": Object { + "link": { "permalink": "/docs/category/slugs", "slug": "/category/slugs", "type": "generated-index", }, "type": "category", }, - Object { + { "id": "headingAsTitle", "type": "doc", }, - Object { + { "href": "https://github.com", "label": "Github", "type": "link", }, - Object { + { "id": "hello", "type": "ref", }, @@ -221,11 +237,11 @@ Object { "link": undefined, "type": "category", }, - Object { + { "collapsed": true, "collapsible": true, - "items": Array [ - Object { + "items": [ + { "id": "hello", "type": "doc", }, @@ -239,89 +255,90 @@ Object { `; exports[`simple website content 5`] = ` -Object { - "pluginName": Object { - "pluginId": Object { +{ + "pluginName": { + "pluginId": { + "breadcrumbs": true, "path": "/docs", - "versions": Array [ - Object { - "docs": Array [ - Object { + "versions": [ + { + "docs": [ + { "id": "doc with space", "path": "/docs/doc with space", "sidebar": undefined, }, - Object { + { "id": "foo/bar", "path": "/docs/foo/bar", "sidebar": "docs", }, - Object { + { "id": "foo/baz", "path": "/docs/foo/bazSlug.html", "sidebar": "docs", }, - Object { + { "id": "headingAsTitle", "path": "/docs/headingAsTitle", "sidebar": "docs", }, - Object { + { "id": "hello", "path": "/docs/", "sidebar": "docs", }, - Object { + { "id": "ipsum", "path": "/docs/ipsum", "sidebar": undefined, }, - Object { + { "id": "lorem", "path": "/docs/lorem", "sidebar": undefined, }, - Object { + { "id": "rootAbsoluteSlug", "path": "/docs/rootAbsoluteSlug", "sidebar": "docs", }, - Object { + { "id": "rootRelativeSlug", "path": "/docs/rootRelativeSlug", "sidebar": "docs", }, - Object { + { "id": "rootResolvedSlug", "path": "/docs/hey/rootResolvedSlug", "sidebar": "docs", }, - Object { + { "id": "rootTryToEscapeSlug", "path": "/docs/rootTryToEscapeSlug", "sidebar": "docs", }, - Object { + { "id": "slugs/absoluteSlug", "path": "/docs/absoluteSlug", "sidebar": undefined, }, - Object { + { "id": "slugs/relativeSlug", "path": "/docs/slugs/relativeSlug", "sidebar": undefined, }, - Object { + { "id": "slugs/resolvedSlug", "path": "/docs/slugs/hey/resolvedSlug", "sidebar": undefined, }, - Object { + { "id": "slugs/tryToEscapeSlug", "path": "/docs/tryToEscapeSlug", "sidebar": undefined, }, - Object { + { "id": "/category/slugs", "path": "/docs/category/slugs", "sidebar": "docs", @@ -332,9 +349,9 @@ Object { "mainDocId": "hello", "name": "current", "path": "/docs", - "sidebars": Object { - "docs": Object { - "link": Object { + "sidebars": { + "docs": { + "link": { "label": "foo/bar", "path": "/docs/foo/bar", }, @@ -348,7 +365,7 @@ Object { `; exports[`simple website content: data 1`] = ` -Object { +{ "category-docs-docs-category-slugs-0fe.json": "{ \\"title\\": \\"Slugs\\", \\"slug\\": \\"/category/slugs\\", @@ -770,8 +787,6 @@ Object { \\"docs\\": [ { \\"type\\": \\"category\\", - \\"collapsed\\": true, - \\"collapsible\\": true, \\"label\\": \\"Test\\", \\"items\\": [ { @@ -791,8 +806,8 @@ Object { \\"docId\\": \\"foo/baz\\" } ], - \\"collapsible\\": true, - \\"collapsed\\": true + \\"collapsed\\": true, + \\"collapsible\\": true }, { \\"type\\": \\"category\\", @@ -823,8 +838,8 @@ Object { \\"docId\\": \\"rootTryToEscapeSlug\\" } ], - \\"collapsible\\": true, \\"collapsed\\": true, + \\"collapsible\\": true, \\"href\\": \\"/docs/category/slugs\\" }, { @@ -844,12 +859,12 @@ Object { \\"href\\": \\"/docs/\\", \\"docId\\": \\"hello\\" } - ] + ], + \\"collapsed\\": true, + \\"collapsible\\": true }, { \\"type\\": \\"category\\", - \\"collapsed\\": true, - \\"collapsible\\": true, \\"label\\": \\"Guides\\", \\"items\\": [ { @@ -858,7 +873,9 @@ Object { \\"href\\": \\"/docs/\\", \\"docId\\": \\"hello\\" } - ] + ], + \\"collapsed\\": true, + \\"collapsible\\": true } ] }, @@ -952,89 +969,90 @@ Object { `; exports[`simple website content: global data 1`] = ` -Object { - "pluginName": Object { - "pluginId": Object { +{ + "pluginName": { + "pluginId": { + "breadcrumbs": true, "path": "/docs", - "versions": Array [ - Object { - "docs": Array [ - Object { + "versions": [ + { + "docs": [ + { "id": "doc with space", "path": "/docs/doc with space", "sidebar": undefined, }, - Object { + { "id": "foo/bar", "path": "/docs/foo/bar", "sidebar": "docs", }, - Object { + { "id": "foo/baz", "path": "/docs/foo/bazSlug.html", "sidebar": "docs", }, - Object { + { "id": "headingAsTitle", "path": "/docs/headingAsTitle", "sidebar": "docs", }, - Object { + { "id": "hello", "path": "/docs/", "sidebar": "docs", }, - Object { + { "id": "ipsum", "path": "/docs/ipsum", "sidebar": undefined, }, - Object { + { "id": "lorem", "path": "/docs/lorem", "sidebar": undefined, }, - Object { + { "id": "rootAbsoluteSlug", "path": "/docs/rootAbsoluteSlug", "sidebar": "docs", }, - Object { + { "id": "rootRelativeSlug", "path": "/docs/rootRelativeSlug", "sidebar": "docs", }, - Object { + { "id": "rootResolvedSlug", "path": "/docs/hey/rootResolvedSlug", "sidebar": "docs", }, - Object { + { "id": "rootTryToEscapeSlug", "path": "/docs/rootTryToEscapeSlug", "sidebar": "docs", }, - Object { + { "id": "slugs/absoluteSlug", "path": "/docs/absoluteSlug", "sidebar": undefined, }, - Object { + { "id": "slugs/relativeSlug", "path": "/docs/slugs/relativeSlug", "sidebar": undefined, }, - Object { + { "id": "slugs/resolvedSlug", "path": "/docs/slugs/hey/resolvedSlug", "sidebar": undefined, }, - Object { + { "id": "slugs/tryToEscapeSlug", "path": "/docs/tryToEscapeSlug", "sidebar": undefined, }, - Object { + { "id": "/category/slugs", "path": "/docs/category/slugs", "sidebar": "docs", @@ -1045,9 +1063,9 @@ Object { "mainDocId": "hello", "name": "current", "path": "/docs", - "sidebars": Object { - "docs": Object { - "link": Object { + "sidebars": { + "docs": { + "link": { "label": "foo/bar", "path": "/docs/foo/bar", }, @@ -1061,181 +1079,181 @@ Object { `; exports[`simple website content: route config 1`] = ` -Array [ - Object { +[ + { "component": "@theme/DocTagsListPage", "exact": true, - "modules": Object { + "modules": { "tags": "~docs/tags-list-current-prop-15a.json", }, "path": "/docs/tags", }, - Object { + { "component": "@theme/DocTagDocListPage", "exact": true, - "modules": Object { + "modules": { "tag": "~docs/tag-docs-tags-tag-1-b3f.json", }, "path": "/docs/tags/tag-1", }, - Object { + { "component": "@theme/DocTagDocListPage", "exact": true, - "modules": Object { + "modules": { "tag": "~docs/tag-docs-tags-tag-3-ab5.json", }, "path": "/docs/tags/tag-3", }, - Object { + { "component": "@theme/DocTagDocListPage", "exact": true, - "modules": Object { + "modules": { "tag": "~docs/tag-docs-tags-tag-2-custom-permalink-825.json", }, "path": "/docs/tags/tag2-custom-permalink", }, - Object { + { "component": "@theme/DocPage", "exact": false, - "modules": Object { + "modules": { "versionMetadata": "~docs/version-current-metadata-prop-751.json", }, "path": "/docs", "priority": -1, - "routes": Array [ - Object { + "routes": [ + { "component": "@theme/DocItem", "exact": true, - "modules": Object { + "modules": { "content": "@site/docs/hello.md", }, "path": "/docs/", "sidebar": "docs", }, - Object { + { "component": "@theme/DocItem", "exact": true, - "modules": Object { + "modules": { "content": "@site/docs/slugs/absoluteSlug.md", }, "path": "/docs/absoluteSlug", }, - Object { + { "component": "@theme/DocCategoryGeneratedIndexPage", "exact": true, - "modules": Object { + "modules": { "categoryGeneratedIndex": "~docs/category-docs-docs-category-slugs-0fe.json", }, "path": "/docs/category/slugs", "sidebar": "docs", }, - Object { + { "component": "@theme/DocItem", "exact": true, - "modules": Object { + "modules": { "content": "@site/docs/doc with space.md", }, "path": "/docs/doc with space", }, - Object { + { "component": "@theme/DocItem", "exact": true, - "modules": Object { + "modules": { "content": "@site/docs/foo/bar.md", }, "path": "/docs/foo/bar", "sidebar": "docs", }, - Object { + { "component": "@theme/DocItem", "exact": true, - "modules": Object { + "modules": { "content": "@site/docs/foo/baz.md", }, "path": "/docs/foo/bazSlug.html", "sidebar": "docs", }, - Object { + { "component": "@theme/DocItem", "exact": true, - "modules": Object { + "modules": { "content": "@site/docs/headingAsTitle.md", }, "path": "/docs/headingAsTitle", "sidebar": "docs", }, - Object { + { "component": "@theme/DocItem", "exact": true, - "modules": Object { + "modules": { "content": "@site/docs/rootResolvedSlug.md", }, "path": "/docs/hey/rootResolvedSlug", "sidebar": "docs", }, - Object { + { "component": "@theme/DocItem", "exact": true, - "modules": Object { + "modules": { "content": "@site/docs/ipsum.md", }, "path": "/docs/ipsum", }, - Object { + { "component": "@theme/DocItem", "exact": true, - "modules": Object { + "modules": { "content": "@site/docs/lorem.md", }, "path": "/docs/lorem", }, - Object { + { "component": "@theme/DocItem", "exact": true, - "modules": Object { + "modules": { "content": "@site/docs/rootAbsoluteSlug.md", }, "path": "/docs/rootAbsoluteSlug", "sidebar": "docs", }, - Object { + { "component": "@theme/DocItem", "exact": true, - "modules": Object { + "modules": { "content": "@site/docs/rootRelativeSlug.md", }, "path": "/docs/rootRelativeSlug", "sidebar": "docs", }, - Object { + { "component": "@theme/DocItem", "exact": true, - "modules": Object { + "modules": { "content": "@site/docs/rootTryToEscapeSlug.md", }, "path": "/docs/rootTryToEscapeSlug", "sidebar": "docs", }, - Object { + { "component": "@theme/DocItem", "exact": true, - "modules": Object { + "modules": { "content": "@site/docs/slugs/resolvedSlug.md", }, "path": "/docs/slugs/hey/resolvedSlug", }, - Object { + { "component": "@theme/DocItem", "exact": true, - "modules": Object { + "modules": { "content": "@site/docs/slugs/relativeSlug.md", }, "path": "/docs/slugs/relativeSlug", }, - Object { + { "component": "@theme/DocItem", "exact": true, - "modules": Object { + "modules": { "content": "@site/docs/slugs/tryToEscapeSlug.md", }, "path": "/docs/tryToEscapeSlug", @@ -1245,14 +1263,23 @@ Array [ ] `; +exports[`simple website getPathToWatch 1`] = ` +[ + "sidebars.json", + "i18n/en/docusaurus-plugin-content-docs/current/**/*.{md,mdx}", + "docs/**/*.{md,mdx}", + "docs/**/_category_.{json,yml,yaml}", +] +`; + exports[`site with custom sidebar items generator sidebar is autogenerated according to a custom sidebarItemsGenerator 1`] = ` -Object { - "defaultSidebar": Array [ - Object { +{ + "defaultSidebar": [ + { "id": "API/api-overview", "type": "doc", }, - Object { + { "id": "API/api-end", "type": "doc", }, @@ -1261,25 +1288,25 @@ Object { `; exports[`site with custom sidebar items generator sidebarItemsGenerator can wrap/enhance/sort/reverse the default sidebar generator 1`] = ` -Object { - "defaultSidebar": Array [ - Object { +{ + "defaultSidebar": [ + { "collapsed": true, "collapsible": true, - "items": Array [ - Object { + "items": [ + { "id": "API/api-end", "type": "doc", }, - Object { + { "collapsed": true, "collapsible": true, - "items": Array [ - Object { + "items": [ + { "id": "API/Extension APIs/Theme API", "type": "doc", }, - Object { + { "id": "API/Extension APIs/Plugin API", "type": "doc", }, @@ -1288,15 +1315,15 @@ Object { "link": undefined, "type": "category", }, - Object { + { "collapsed": true, "collapsible": true, - "items": Array [ - Object { + "items": [ + { "id": "API/Core APIs/Server API", "type": "doc", }, - Object { + { "id": "API/Core APIs/Client API", "type": "doc", }, @@ -1305,7 +1332,7 @@ Object { "link": undefined, "type": "category", }, - Object { + { "id": "API/api-overview", "type": "doc", }, @@ -1314,31 +1341,31 @@ Object { "link": undefined, "type": "category", }, - Object { + { "collapsed": true, "collapsible": true, - "items": Array [ - Object { + "items": [ + { "id": "Guides/guide5", "type": "doc", }, - Object { + { "id": "Guides/guide4", "type": "doc", }, - Object { + { "id": "Guides/guide3", "type": "doc", }, - Object { + { "id": "Guides/guide2.5", "type": "doc", }, - Object { + { "id": "Guides/guide2", "type": "doc", }, - Object { + { "id": "Guides/guide1", "type": "doc", }, @@ -1347,11 +1374,11 @@ Object { "link": undefined, "type": "category", }, - Object { + { "id": "installation", "type": "doc", }, - Object { + { "id": "getting-started", "type": "doc", }, @@ -1360,59 +1387,76 @@ Object { `; exports[`site with custom sidebar items generator sidebarItemsGenerator is called with appropriate data 1`] = ` -Object { +{ + "categoriesMetadata": { + "3-API": { + "label": "API (label from _category_.json)", + }, + "3-API/02_Extension APIs": { + "label": "Extension APIs (label from _category_.yml)", + }, + "Guides": { + "position": 2, + }, + }, "defaultSidebarItemsGenerator": [Function], - "docs": Array [ - Object { - "frontMatter": Object {}, + "docs": [ + { + "frontMatter": {}, "id": "API/Core APIs/Client API", "sidebarPosition": 0, "source": "@site/docs/3-API/01_Core APIs/0 --- Client API.md", "sourceDirName": "3-API/01_Core APIs", + "title": "Client API", "unversionedId": "API/Core APIs/Client API", }, - Object { - "frontMatter": Object {}, + { + "frontMatter": {}, "id": "API/Core APIs/Server API", "sidebarPosition": 1, "source": "@site/docs/3-API/01_Core APIs/1 --- Server API.md", "sourceDirName": "3-API/01_Core APIs", + "title": "Server API", "unversionedId": "API/Core APIs/Server API", }, - Object { - "frontMatter": Object {}, + { + "frontMatter": {}, "id": "API/Extension APIs/Plugin API", "sidebarPosition": 0, "source": "@site/docs/3-API/02_Extension APIs/0. Plugin API.md", "sourceDirName": "3-API/02_Extension APIs", + "title": "Plugin API", "unversionedId": "API/Extension APIs/Plugin API", }, - Object { - "frontMatter": Object {}, + { + "frontMatter": {}, "id": "API/Extension APIs/Theme API", "sidebarPosition": 1, "source": "@site/docs/3-API/02_Extension APIs/1. Theme API.md", "sourceDirName": "3-API/02_Extension APIs", + "title": "Theme API", "unversionedId": "API/Extension APIs/Theme API", }, - Object { - "frontMatter": Object {}, + { + "frontMatter": {}, "id": "API/api-end", "sidebarPosition": 3, "source": "@site/docs/3-API/03_api-end.md", "sourceDirName": "3-API", + "title": "API End", "unversionedId": "API/api-end", }, - Object { - "frontMatter": Object {}, + { + "frontMatter": {}, "id": "API/api-overview", "sidebarPosition": 0, "source": "@site/docs/3-API/00_api-overview.md", "sourceDirName": "3-API", + "title": "API Overview", "unversionedId": "API/api-overview", }, - Object { - "frontMatter": Object { + { + "frontMatter": { "id": "guide1", "sidebar_position": 1, }, @@ -1420,20 +1464,22 @@ Object { "sidebarPosition": 1, "source": "@site/docs/Guides/z-guide1.md", "sourceDirName": "Guides", + "title": "Guide 1", "unversionedId": "Guides/guide1", }, - Object { - "frontMatter": Object { + { + "frontMatter": { "id": "guide2", }, "id": "Guides/guide2", "sidebarPosition": 2, "source": "@site/docs/Guides/02-guide2.md", "sourceDirName": "Guides", + "title": "Guide 2", "unversionedId": "Guides/guide2", }, - Object { - "frontMatter": Object { + { + "frontMatter": { "id": "guide2.5", "sidebar_position": 2.5, }, @@ -1441,10 +1487,11 @@ Object { "sidebarPosition": 2.5, "source": "@site/docs/Guides/0-guide2.5.md", "sourceDirName": "Guides", + "title": "Guide 2.5", "unversionedId": "Guides/guide2.5", }, - Object { - "frontMatter": Object { + { + "frontMatter": { "id": "guide3", "sidebar_position": 3, }, @@ -1452,56 +1499,57 @@ Object { "sidebarPosition": 3, "source": "@site/docs/Guides/guide3.md", "sourceDirName": "Guides", + "title": "Guide 3", "unversionedId": "Guides/guide3", }, - Object { - "frontMatter": Object { + { + "frontMatter": { "id": "guide4", }, "id": "Guides/guide4", "sidebarPosition": undefined, "source": "@site/docs/Guides/a-guide4.md", "sourceDirName": "Guides", + "title": "Guide 4", "unversionedId": "Guides/guide4", }, - Object { - "frontMatter": Object { + { + "frontMatter": { "id": "guide5", }, "id": "Guides/guide5", "sidebarPosition": undefined, "source": "@site/docs/Guides/b-guide5.md", "sourceDirName": "Guides", + "title": "Guide 5", "unversionedId": "Guides/guide5", }, - Object { - "frontMatter": Object {}, + { + "frontMatter": {}, "id": "getting-started", "sidebarPosition": 0, "source": "@site/docs/0-getting-started.md", "sourceDirName": ".", + "title": "Getting Started", "unversionedId": "getting-started", }, - Object { - "frontMatter": Object {}, + { + "frontMatter": {}, "id": "installation", "sidebarPosition": 1, "source": "@site/docs/1-installation.md", "sourceDirName": ".", + "title": "Installation", "unversionedId": "installation", }, ], "isCategoryIndex": [Function], - "item": Object { + "item": { "dirName": ".", "type": "autogenerated", }, "numberPrefixParser": [Function], - "options": Object { - "sidebarCollapsed": true, - "sidebarCollapsible": true, - }, - "version": Object { + "version": { "contentPath": "docs", "versionName": "current", }, @@ -1509,15 +1557,15 @@ Object { `; exports[`site with full autogenerated sidebar docs in fully generated sidebar have correct metadata 1`] = ` -Object { +{ "description": "Getting started text", "editUrl": undefined, "formattedLastUpdatedAt": undefined, - "frontMatter": Object {}, + "frontMatter": {}, "id": "getting-started", "lastUpdatedAt": undefined, "lastUpdatedBy": undefined, - "next": Object { + "next": { "permalink": "/docs/installation", "title": "Installation", }, @@ -1528,7 +1576,7 @@ Object { "slug": "/getting-started", "source": "@site/docs/0-getting-started.md", "sourceDirName": ".", - "tags": Array [], + "tags": [], "title": "Getting Started", "unversionedId": "getting-started", "version": "current", @@ -1536,20 +1584,20 @@ Object { `; exports[`site with full autogenerated sidebar docs in fully generated sidebar have correct metadata 2`] = ` -Object { +{ "description": "Installation text", "editUrl": undefined, "formattedLastUpdatedAt": undefined, - "frontMatter": Object {}, + "frontMatter": {}, "id": "installation", "lastUpdatedAt": undefined, "lastUpdatedBy": undefined, - "next": Object { + "next": { "permalink": "/docs/Guides/guide1", "title": "Guide 1", }, "permalink": "/docs/installation", - "previous": Object { + "previous": { "permalink": "/docs/getting-started", "title": "Getting Started", }, @@ -1558,7 +1606,7 @@ Object { "slug": "/installation", "source": "@site/docs/1-installation.md", "sourceDirName": ".", - "tags": Array [], + "tags": [], "title": "Installation", "unversionedId": "installation", "version": "current", @@ -1566,23 +1614,23 @@ Object { `; exports[`site with full autogenerated sidebar docs in fully generated sidebar have correct metadata 3`] = ` -Object { +{ "description": "Guide 1 text", "editUrl": undefined, "formattedLastUpdatedAt": undefined, - "frontMatter": Object { + "frontMatter": { "id": "guide1", "sidebar_position": 1, }, "id": "Guides/guide1", "lastUpdatedAt": undefined, "lastUpdatedBy": undefined, - "next": Object { + "next": { "permalink": "/docs/Guides/guide2", "title": "Guide 2", }, "permalink": "/docs/Guides/guide1", - "previous": Object { + "previous": { "permalink": "/docs/installation", "title": "Installation", }, @@ -1591,7 +1639,7 @@ Object { "slug": "/Guides/guide1", "source": "@site/docs/Guides/z-guide1.md", "sourceDirName": "Guides", - "tags": Array [], + "tags": [], "title": "Guide 1", "unversionedId": "Guides/guide1", "version": "current", @@ -1599,22 +1647,22 @@ Object { `; exports[`site with full autogenerated sidebar docs in fully generated sidebar have correct metadata 4`] = ` -Object { +{ "description": "Guide 2 text", "editUrl": undefined, "formattedLastUpdatedAt": undefined, - "frontMatter": Object { + "frontMatter": { "id": "guide2", }, "id": "Guides/guide2", "lastUpdatedAt": undefined, "lastUpdatedBy": undefined, - "next": Object { + "next": { "permalink": "/docs/Guides/guide2.5", "title": "Guide 2.5", }, "permalink": "/docs/Guides/guide2", - "previous": Object { + "previous": { "permalink": "/docs/Guides/guide1", "title": "Guide 1", }, @@ -1623,7 +1671,7 @@ Object { "slug": "/Guides/guide2", "source": "@site/docs/Guides/02-guide2.md", "sourceDirName": "Guides", - "tags": Array [], + "tags": [], "title": "Guide 2", "unversionedId": "Guides/guide2", "version": "current", @@ -1631,23 +1679,23 @@ Object { `; exports[`site with full autogenerated sidebar docs in fully generated sidebar have correct metadata 5`] = ` -Object { +{ "description": "Guide 2.5 text", "editUrl": undefined, "formattedLastUpdatedAt": undefined, - "frontMatter": Object { + "frontMatter": { "id": "guide2.5", "sidebar_position": 2.5, }, "id": "Guides/guide2.5", "lastUpdatedAt": undefined, "lastUpdatedBy": undefined, - "next": Object { + "next": { "permalink": "/docs/Guides/guide3", "title": "Guide 3", }, "permalink": "/docs/Guides/guide2.5", - "previous": Object { + "previous": { "permalink": "/docs/Guides/guide2", "title": "Guide 2", }, @@ -1656,7 +1704,7 @@ Object { "slug": "/Guides/guide2.5", "source": "@site/docs/Guides/0-guide2.5.md", "sourceDirName": "Guides", - "tags": Array [], + "tags": [], "title": "Guide 2.5", "unversionedId": "Guides/guide2.5", "version": "current", @@ -1664,23 +1712,23 @@ Object { `; exports[`site with full autogenerated sidebar docs in fully generated sidebar have correct metadata 6`] = ` -Object { +{ "description": "Guide 3 text", "editUrl": undefined, "formattedLastUpdatedAt": undefined, - "frontMatter": Object { + "frontMatter": { "id": "guide3", "sidebar_position": 3, }, "id": "Guides/guide3", "lastUpdatedAt": undefined, "lastUpdatedBy": undefined, - "next": Object { + "next": { "permalink": "/docs/Guides/guide4", "title": "Guide 4", }, "permalink": "/docs/Guides/guide3", - "previous": Object { + "previous": { "permalink": "/docs/Guides/guide2.5", "title": "Guide 2.5", }, @@ -1689,7 +1737,7 @@ Object { "slug": "/Guides/guide3", "source": "@site/docs/Guides/guide3.md", "sourceDirName": "Guides", - "tags": Array [], + "tags": [], "title": "Guide 3", "unversionedId": "Guides/guide3", "version": "current", @@ -1697,22 +1745,22 @@ Object { `; exports[`site with full autogenerated sidebar docs in fully generated sidebar have correct metadata 7`] = ` -Object { +{ "description": "Guide 4 text", "editUrl": undefined, "formattedLastUpdatedAt": undefined, - "frontMatter": Object { + "frontMatter": { "id": "guide4", }, "id": "Guides/guide4", "lastUpdatedAt": undefined, "lastUpdatedBy": undefined, - "next": Object { + "next": { "permalink": "/docs/Guides/guide5", "title": "Guide 5", }, "permalink": "/docs/Guides/guide4", - "previous": Object { + "previous": { "permalink": "/docs/Guides/guide3", "title": "Guide 3", }, @@ -1721,7 +1769,7 @@ Object { "slug": "/Guides/guide4", "source": "@site/docs/Guides/a-guide4.md", "sourceDirName": "Guides", - "tags": Array [], + "tags": [], "title": "Guide 4", "unversionedId": "Guides/guide4", "version": "current", @@ -1729,22 +1777,22 @@ Object { `; exports[`site with full autogenerated sidebar docs in fully generated sidebar have correct metadata 8`] = ` -Object { +{ "description": "Guide 5 text", "editUrl": undefined, "formattedLastUpdatedAt": undefined, - "frontMatter": Object { + "frontMatter": { "id": "guide5", }, "id": "Guides/guide5", "lastUpdatedAt": undefined, "lastUpdatedBy": undefined, - "next": Object { + "next": { "permalink": "/docs/API/api-overview", "title": "API Overview", }, "permalink": "/docs/Guides/guide5", - "previous": Object { + "previous": { "permalink": "/docs/Guides/guide4", "title": "Guide 4", }, @@ -1753,7 +1801,7 @@ Object { "slug": "/Guides/guide5", "source": "@site/docs/Guides/b-guide5.md", "sourceDirName": "Guides", - "tags": Array [], + "tags": [], "title": "Guide 5", "unversionedId": "Guides/guide5", "version": "current", @@ -1761,20 +1809,20 @@ Object { `; exports[`site with full autogenerated sidebar docs in fully generated sidebar have correct metadata 9`] = ` -Object { +{ "description": "API Overview text", "editUrl": undefined, "formattedLastUpdatedAt": undefined, - "frontMatter": Object {}, + "frontMatter": {}, "id": "API/api-overview", "lastUpdatedAt": undefined, "lastUpdatedBy": undefined, - "next": Object { + "next": { "permalink": "/docs/API/Core APIs/Client API", "title": "Client API", }, "permalink": "/docs/API/api-overview", - "previous": Object { + "previous": { "permalink": "/docs/Guides/guide5", "title": "Guide 5", }, @@ -1783,7 +1831,7 @@ Object { "slug": "/API/api-overview", "source": "@site/docs/3-API/00_api-overview.md", "sourceDirName": "3-API", - "tags": Array [], + "tags": [], "title": "API Overview", "unversionedId": "API/api-overview", "version": "current", @@ -1791,20 +1839,20 @@ Object { `; exports[`site with full autogenerated sidebar docs in fully generated sidebar have correct metadata 10`] = ` -Object { +{ "description": "Client API text", "editUrl": undefined, "formattedLastUpdatedAt": undefined, - "frontMatter": Object {}, + "frontMatter": {}, "id": "API/Core APIs/Client API", "lastUpdatedAt": undefined, "lastUpdatedBy": undefined, - "next": Object { + "next": { "permalink": "/docs/API/Core APIs/Server API", "title": "Server API", }, "permalink": "/docs/API/Core APIs/Client API", - "previous": Object { + "previous": { "permalink": "/docs/API/api-overview", "title": "API Overview", }, @@ -1813,7 +1861,7 @@ Object { "slug": "/API/Core APIs/Client API", "source": "@site/docs/3-API/01_Core APIs/0 --- Client API.md", "sourceDirName": "3-API/01_Core APIs", - "tags": Array [], + "tags": [], "title": "Client API", "unversionedId": "API/Core APIs/Client API", "version": "current", @@ -1821,20 +1869,20 @@ Object { `; exports[`site with full autogenerated sidebar docs in fully generated sidebar have correct metadata 11`] = ` -Object { +{ "description": "Server API text", "editUrl": undefined, "formattedLastUpdatedAt": undefined, - "frontMatter": Object {}, + "frontMatter": {}, "id": "API/Core APIs/Server API", "lastUpdatedAt": undefined, "lastUpdatedBy": undefined, - "next": Object { + "next": { "permalink": "/docs/API/Extension APIs/Plugin API", "title": "Plugin API", }, "permalink": "/docs/API/Core APIs/Server API", - "previous": Object { + "previous": { "permalink": "/docs/API/Core APIs/Client API", "title": "Client API", }, @@ -1843,7 +1891,7 @@ Object { "slug": "/API/Core APIs/Server API", "source": "@site/docs/3-API/01_Core APIs/1 --- Server API.md", "sourceDirName": "3-API/01_Core APIs", - "tags": Array [], + "tags": [], "title": "Server API", "unversionedId": "API/Core APIs/Server API", "version": "current", @@ -1851,20 +1899,20 @@ Object { `; exports[`site with full autogenerated sidebar docs in fully generated sidebar have correct metadata 12`] = ` -Object { +{ "description": "Plugin API text", "editUrl": undefined, "formattedLastUpdatedAt": undefined, - "frontMatter": Object {}, + "frontMatter": {}, "id": "API/Extension APIs/Plugin API", "lastUpdatedAt": undefined, "lastUpdatedBy": undefined, - "next": Object { + "next": { "permalink": "/docs/API/Extension APIs/Theme API", "title": "Theme API", }, "permalink": "/docs/API/Extension APIs/Plugin API", - "previous": Object { + "previous": { "permalink": "/docs/API/Core APIs/Server API", "title": "Server API", }, @@ -1873,7 +1921,7 @@ Object { "slug": "/API/Extension APIs/Plugin API", "source": "@site/docs/3-API/02_Extension APIs/0. Plugin API.md", "sourceDirName": "3-API/02_Extension APIs", - "tags": Array [], + "tags": [], "title": "Plugin API", "unversionedId": "API/Extension APIs/Plugin API", "version": "current", @@ -1881,20 +1929,20 @@ Object { `; exports[`site with full autogenerated sidebar docs in fully generated sidebar have correct metadata 13`] = ` -Object { +{ "description": "Theme API text", "editUrl": undefined, "formattedLastUpdatedAt": undefined, - "frontMatter": Object {}, + "frontMatter": {}, "id": "API/Extension APIs/Theme API", "lastUpdatedAt": undefined, "lastUpdatedBy": undefined, - "next": Object { + "next": { "permalink": "/docs/API/api-end", "title": "API End", }, "permalink": "/docs/API/Extension APIs/Theme API", - "previous": Object { + "previous": { "permalink": "/docs/API/Extension APIs/Plugin API", "title": "Plugin API", }, @@ -1903,7 +1951,7 @@ Object { "slug": "/API/Extension APIs/Theme API", "source": "@site/docs/3-API/02_Extension APIs/1. Theme API.md", "sourceDirName": "3-API/02_Extension APIs", - "tags": Array [], + "tags": [], "title": "Theme API", "unversionedId": "API/Extension APIs/Theme API", "version": "current", @@ -1911,17 +1959,17 @@ Object { `; exports[`site with full autogenerated sidebar docs in fully generated sidebar have correct metadata 14`] = ` -Object { +{ "description": "API End text", "editUrl": undefined, "formattedLastUpdatedAt": undefined, - "frontMatter": Object {}, + "frontMatter": {}, "id": "API/api-end", "lastUpdatedAt": undefined, "lastUpdatedBy": undefined, "next": undefined, "permalink": "/docs/API/api-end", - "previous": Object { + "previous": { "permalink": "/docs/API/Extension APIs/Theme API", "title": "Theme API", }, @@ -1930,7 +1978,7 @@ Object { "slug": "/API/api-end", "source": "@site/docs/3-API/03_api-end.md", "sourceDirName": "3-API", - "tags": Array [], + "tags": [], "title": "API End", "unversionedId": "API/api-end", "version": "current", @@ -1938,41 +1986,41 @@ Object { `; exports[`site with full autogenerated sidebar sidebar is fully autogenerated 1`] = ` -Object { - "defaultSidebar": Array [ - Object { +{ + "defaultSidebar": [ + { "id": "getting-started", "type": "doc", }, - Object { + { "id": "installation", "type": "doc", }, - Object { + { "collapsed": true, "collapsible": true, - "items": Array [ - Object { + "items": [ + { "id": "Guides/guide1", "type": "doc", }, - Object { + { "id": "Guides/guide2", "type": "doc", }, - Object { + { "id": "Guides/guide2.5", "type": "doc", }, - Object { + { "id": "Guides/guide3", "type": "doc", }, - Object { + { "id": "Guides/guide4", "type": "doc", }, - Object { + { "id": "Guides/guide5", "type": "doc", }, @@ -1981,23 +2029,23 @@ Object { "link": undefined, "type": "category", }, - Object { + { "collapsed": true, "collapsible": true, - "items": Array [ - Object { + "items": [ + { "id": "API/api-overview", "type": "doc", }, - Object { + { "collapsed": true, "collapsible": true, - "items": Array [ - Object { + "items": [ + { "id": "API/Core APIs/Client API", "type": "doc", }, - Object { + { "id": "API/Core APIs/Server API", "type": "doc", }, @@ -2006,15 +2054,15 @@ Object { "link": undefined, "type": "category", }, - Object { + { "collapsed": true, "collapsible": true, - "items": Array [ - Object { + "items": [ + { "id": "API/Extension APIs/Plugin API", "type": "doc", }, - Object { + { "id": "API/Extension APIs/Theme API", "type": "doc", }, @@ -2023,7 +2071,7 @@ Object { "link": undefined, "type": "category", }, - Object { + { "id": "API/api-end", "type": "doc", }, @@ -2037,25 +2085,25 @@ Object { `; exports[`site with partial autogenerated sidebars 2 (fix #4638) sidebar is partially autogenerated 1`] = ` -Object { - "someSidebar": Array [ - Object { +{ + "someSidebar": [ + { "id": "API/api-end", "type": "doc", }, - Object { + { "id": "API/api-overview", "type": "doc", }, - Object { + { "collapsed": true, "collapsible": true, - "items": Array [ - Object { + "items": [ + { "id": "API/Core APIs/Client API", "type": "doc", }, - Object { + { "id": "API/Core APIs/Server API", "type": "doc", }, @@ -2064,15 +2112,15 @@ Object { "link": undefined, "type": "category", }, - Object { + { "collapsed": true, "collapsible": true, - "items": Array [ - Object { + "items": [ + { "id": "API/Extension APIs/Plugin API", "type": "doc", }, - Object { + { "id": "API/Extension APIs/Theme API", "type": "doc", }, @@ -2081,7 +2129,7 @@ Object { "link": undefined, "type": "category", }, - Object { + { "id": "API/api-end", "type": "doc", }, @@ -2090,15 +2138,15 @@ Object { `; exports[`site with partial autogenerated sidebars docs in partially generated sidebar have correct metadata 1`] = ` -Object { +{ "description": "API End text", "editUrl": undefined, "formattedLastUpdatedAt": undefined, - "frontMatter": Object {}, + "frontMatter": {}, "id": "API/api-end", "lastUpdatedAt": undefined, "lastUpdatedBy": undefined, - "next": Object { + "next": { "permalink": "/docs/API/api-overview", "title": "API Overview", }, @@ -2109,7 +2157,7 @@ Object { "slug": "/API/api-end", "source": "@site/docs/3-API/03_api-end.md", "sourceDirName": "3-API", - "tags": Array [], + "tags": [], "title": "API End", "unversionedId": "API/api-end", "version": "current", @@ -2117,20 +2165,20 @@ Object { `; exports[`site with partial autogenerated sidebars docs in partially generated sidebar have correct metadata 2`] = ` -Object { +{ "description": "API Overview text", "editUrl": undefined, "formattedLastUpdatedAt": undefined, - "frontMatter": Object {}, + "frontMatter": {}, "id": "API/api-overview", "lastUpdatedAt": undefined, "lastUpdatedBy": undefined, - "next": Object { + "next": { "permalink": "/docs/API/Extension APIs/Plugin API", "title": "Plugin API", }, "permalink": "/docs/API/api-overview", - "previous": Object { + "previous": { "permalink": "/docs/API/api-end", "title": "API End", }, @@ -2139,7 +2187,7 @@ Object { "slug": "/API/api-overview", "source": "@site/docs/3-API/00_api-overview.md", "sourceDirName": "3-API", - "tags": Array [], + "tags": [], "title": "API Overview", "unversionedId": "API/api-overview", "version": "current", @@ -2147,20 +2195,20 @@ Object { `; exports[`site with partial autogenerated sidebars docs in partially generated sidebar have correct metadata 3`] = ` -Object { +{ "description": "Plugin API text", "editUrl": undefined, "formattedLastUpdatedAt": undefined, - "frontMatter": Object {}, + "frontMatter": {}, "id": "API/Extension APIs/Plugin API", "lastUpdatedAt": undefined, "lastUpdatedBy": undefined, - "next": Object { + "next": { "permalink": "/docs/API/Extension APIs/Theme API", "title": "Theme API", }, "permalink": "/docs/API/Extension APIs/Plugin API", - "previous": Object { + "previous": { "permalink": "/docs/API/api-overview", "title": "API Overview", }, @@ -2169,7 +2217,7 @@ Object { "slug": "/API/Extension APIs/Plugin API", "source": "@site/docs/3-API/02_Extension APIs/0. Plugin API.md", "sourceDirName": "3-API/02_Extension APIs", - "tags": Array [], + "tags": [], "title": "Plugin API", "unversionedId": "API/Extension APIs/Plugin API", "version": "current", @@ -2177,17 +2225,17 @@ Object { `; exports[`site with partial autogenerated sidebars docs in partially generated sidebar have correct metadata 4`] = ` -Object { +{ "description": "Theme API text", "editUrl": undefined, "formattedLastUpdatedAt": undefined, - "frontMatter": Object {}, + "frontMatter": {}, "id": "API/Extension APIs/Theme API", "lastUpdatedAt": undefined, "lastUpdatedBy": undefined, "next": undefined, "permalink": "/docs/API/Extension APIs/Theme API", - "previous": Object { + "previous": { "permalink": "/docs/API/Extension APIs/Plugin API", "title": "Plugin API", }, @@ -2196,7 +2244,7 @@ Object { "slug": "/API/Extension APIs/Theme API", "source": "@site/docs/3-API/02_Extension APIs/1. Theme API.md", "sourceDirName": "3-API/02_Extension APIs", - "tags": Array [], + "tags": [], "title": "Theme API", "unversionedId": "API/Extension APIs/Theme API", "version": "current", @@ -2204,25 +2252,25 @@ Object { `; exports[`site with partial autogenerated sidebars sidebar is partially autogenerated 1`] = ` -Object { - "someSidebar": Array [ - Object { +{ + "someSidebar": [ + { "id": "API/api-end", "type": "doc", }, - Object { + { "collapsed": true, "collapsible": true, - "items": Array [ - Object { + "items": [ + { "id": "API/api-overview", "type": "doc", }, - Object { + { "id": "API/Extension APIs/Plugin API", "type": "doc", }, - Object { + { "id": "API/Extension APIs/Theme API", "type": "doc", }, @@ -2236,11 +2284,11 @@ Object { `; exports[`versioned website (community) content 1`] = ` -Object { +{ "description": "Team current version (translated)", "editUrl": undefined, "formattedLastUpdatedAt": undefined, - "frontMatter": Object { + "frontMatter": { "title": "Team title translated", }, "id": "team", @@ -2254,7 +2302,7 @@ Object { "slug": "/team", "source": "@site/i18n/en/docusaurus-plugin-content-docs-community/current/team.md", "sourceDirName": ".", - "tags": Array [], + "tags": [], "title": "Team title translated", "unversionedId": "team", "version": "current", @@ -2262,11 +2310,11 @@ Object { `; exports[`versioned website (community) content 2`] = ` -Object { +{ "description": "Team 1.0.0", "editUrl": undefined, "formattedLastUpdatedAt": undefined, - "frontMatter": Object {}, + "frontMatter": {}, "id": "version-1.0.0/team", "lastUpdatedAt": undefined, "lastUpdatedBy": undefined, @@ -2278,7 +2326,7 @@ Object { "slug": "/team", "source": "@site/community_versioned_docs/version-1.0.0/team.md", "sourceDirName": ".", - "tags": Array [], + "tags": [], "title": "team", "unversionedId": "team", "version": "1.0.0", @@ -2286,9 +2334,9 @@ Object { `; exports[`versioned website (community) content: 100 version sidebars 1`] = ` -Object { - "version-1.0.0/community": Array [ - Object { +{ + "version-1.0.0/community": [ + { "id": "version-1.0.0/team", "type": "doc", }, @@ -2297,9 +2345,9 @@ Object { `; exports[`versioned website (community) content: current version sidebars 1`] = ` -Object { - "community": Array [ - Object { +{ + "community": [ + { "id": "team", "type": "doc", }, @@ -2308,7 +2356,7 @@ Object { `; exports[`versioned website (community) content: data 1`] = ` -Object { +{ "site-community-versioned-docs-version-1-0-0-team-md-359.json": "{ \\"unversionedId\\": \\"team\\", \\"id\\": \\"version-1.0.0/team\\", @@ -2397,14 +2445,15 @@ Object { `; exports[`versioned website (community) content: global data 1`] = ` -Object { - "pluginName": Object { - "pluginId": Object { +{ + "pluginName": { + "pluginId": { + "breadcrumbs": true, "path": "/community", - "versions": Array [ - Object { - "docs": Array [ - Object { + "versions": [ + { + "docs": [ + { "id": "team", "path": "/community/next/team", "sidebar": "community", @@ -2415,18 +2464,18 @@ Object { "mainDocId": "team", "name": "current", "path": "/community/next", - "sidebars": Object { - "community": Object { - "link": Object { + "sidebars": { + "community": { + "link": { "label": "team", "path": "/community/next/team", }, }, }, }, - Object { - "docs": Array [ - Object { + { + "docs": [ + { "id": "team", "path": "/community/team", "sidebar": "version-1.0.0/community", @@ -2437,9 +2486,9 @@ Object { "mainDocId": "team", "name": "1.0.0", "path": "/community", - "sidebars": Object { - "version-1.0.0/community": Object { - "link": Object { + "sidebars": { + "version-1.0.0/community": { + "link": { "label": "version-1.0.0/team", "path": "/community/team", }, @@ -2453,20 +2502,20 @@ Object { `; exports[`versioned website (community) content: route config 1`] = ` -Array [ - Object { +[ + { "component": "@theme/DocPage", "exact": false, - "modules": Object { + "modules": { "versionMetadata": "~docs/version-current-metadata-prop-751.json", }, "path": "/community/next", "priority": undefined, - "routes": Array [ - Object { + "routes": [ + { "component": "@theme/DocItem", "exact": true, - "modules": Object { + "modules": { "content": "@site/i18n/en/docusaurus-plugin-content-docs-community/current/team.md", }, "path": "/community/next/team", @@ -2474,19 +2523,19 @@ Array [ }, ], }, - Object { + { "component": "@theme/DocPage", "exact": false, - "modules": Object { + "modules": { "versionMetadata": "~docs/version-1-0-0-metadata-prop-608.json", }, "path": "/community", "priority": -1, - "routes": Array [ - Object { + "routes": [ + { "component": "@theme/DocItem", "exact": true, - "modules": Object { + "modules": { "content": "@site/community_versioned_docs/version-1.0.0/team.md", }, "path": "/community/team", @@ -2497,17 +2546,30 @@ Array [ ] `; +exports[`versioned website (community) getPathToWatch 1`] = ` +[ + "community_sidebars.json", + "i18n/en/docusaurus-plugin-content-docs-community/current/**/*.{md,mdx}", + "community/**/*.{md,mdx}", + "community/**/_category_.{json,yml,yaml}", + "community_versioned_sidebars/version-1.0.0-sidebars.json", + "i18n/en/docusaurus-plugin-content-docs-community/version-1.0.0/**/*.{md,mdx}", + "community_versioned_docs/version-1.0.0/**/*.{md,mdx}", + "community_versioned_docs/version-1.0.0/**/_category_.{json,yml,yaml}", +] +`; + exports[`versioned website content 1`] = ` -Object { +{ "description": "This is next version of bar.", "editUrl": undefined, "formattedLastUpdatedAt": undefined, - "frontMatter": Object { + "frontMatter": { "slug": "barSlug", - "tags": Array [ + "tags": [ "barTag 1", "barTag-2", - Object { + { "label": "barTag 3", "permalink": "barTag-3-permalink", }, @@ -2516,7 +2578,7 @@ Object { "id": "foo/bar", "lastUpdatedAt": undefined, "lastUpdatedBy": undefined, - "next": Object { + "next": { "permalink": "/docs/next/", "title": "hello", }, @@ -2527,16 +2589,16 @@ Object { "slug": "/foo/barSlug", "source": "@site/docs/foo/bar.md", "sourceDirName": "foo", - "tags": Array [ - Object { + "tags": [ + { "label": "barTag 1", "permalink": "/docs/next/tags/bar-tag-1", }, - Object { + { "label": "barTag-2", "permalink": "/docs/next/tags/bar-tag-2", }, - Object { + { "label": "barTag 3", "permalink": "/docs/next/tags/barTag-3-permalink", }, @@ -2548,15 +2610,15 @@ Object { `; exports[`versioned website content 2`] = ` -Object { +{ "description": "Bar 1.0.1 !", "editUrl": undefined, "formattedLastUpdatedAt": undefined, - "frontMatter": Object {}, + "frontMatter": {}, "id": "version-1.0.1/foo/bar", "lastUpdatedAt": undefined, "lastUpdatedBy": undefined, - "next": Object { + "next": { "permalink": "/docs/", "title": "hello", }, @@ -2567,7 +2629,7 @@ Object { "slug": "/foo/bar", "source": "@site/versioned_docs/version-1.0.1/foo/bar.md", "sourceDirName": "foo", - "tags": Array [], + "tags": [], "title": "bar", "unversionedId": "foo/bar", "version": "1.0.1", @@ -2575,11 +2637,11 @@ Object { `; exports[`versioned website content 3`] = ` -Object { +{ "description": "Hello next !", "editUrl": undefined, "formattedLastUpdatedAt": undefined, - "frontMatter": Object { + "frontMatter": { "slug": "/", }, "id": "hello", @@ -2587,7 +2649,7 @@ Object { "lastUpdatedBy": undefined, "next": undefined, "permalink": "/docs/next/", - "previous": Object { + "previous": { "permalink": "/docs/next/foo/barSlug", "title": "bar", }, @@ -2596,7 +2658,7 @@ Object { "slug": "/", "source": "@site/docs/hello.md", "sourceDirName": ".", - "tags": Array [], + "tags": [], "title": "hello", "unversionedId": "hello", "version": "current", @@ -2604,11 +2666,11 @@ Object { `; exports[`versioned website content 4`] = ` -Object { +{ "description": "Hello 1.0.1 !", "editUrl": undefined, "formattedLastUpdatedAt": undefined, - "frontMatter": Object { + "frontMatter": { "slug": "/", }, "id": "version-1.0.1/hello", @@ -2616,7 +2678,7 @@ Object { "lastUpdatedBy": undefined, "next": undefined, "permalink": "/docs/", - "previous": Object { + "previous": { "permalink": "/docs/foo/bar", "title": "bar", }, @@ -2625,7 +2687,7 @@ Object { "slug": "/", "source": "@site/versioned_docs/version-1.0.1/hello.md", "sourceDirName": ".", - "tags": Array [], + "tags": [], "title": "hello", "unversionedId": "hello", "version": "1.0.1", @@ -2633,20 +2695,20 @@ Object { `; exports[`versioned website content 5`] = ` -Object { +{ "description": "Baz 1.0.0 ! This will be deleted in next subsequent versions.", "editUrl": undefined, "formattedLastUpdatedAt": undefined, - "frontMatter": Object {}, + "frontMatter": {}, "id": "version-1.0.0/foo/baz", "lastUpdatedAt": undefined, "lastUpdatedBy": undefined, - "next": Object { + "next": { "permalink": "/docs/1.0.0/", "title": "hello", }, "permalink": "/docs/1.0.0/foo/baz", - "previous": Object { + "previous": { "permalink": "/docs/1.0.0/foo/barSlug", "title": "bar", }, @@ -2655,7 +2717,7 @@ Object { "slug": "/foo/baz", "source": "@site/versioned_docs/version-1.0.0/foo/baz.md", "sourceDirName": "foo", - "tags": Array [], + "tags": [], "title": "baz", "unversionedId": "foo/baz", "version": "1.0.0", @@ -2663,17 +2725,17 @@ Object { `; exports[`versioned website content: 100 version sidebars 1`] = ` -Object { - "version-1.0.0/docs": Array [ - Object { +{ + "version-1.0.0/docs": [ + { "collapsed": true, "collapsible": true, - "items": Array [ - Object { + "items": [ + { "id": "version-1.0.0/foo/bar", "type": "doc", }, - Object { + { "id": "version-1.0.0/foo/baz", "type": "doc", }, @@ -2682,11 +2744,11 @@ Object { "link": undefined, "type": "category", }, - Object { + { "collapsed": true, "collapsible": true, - "items": Array [ - Object { + "items": [ + { "id": "version-1.0.0/hello", "type": "doc", }, @@ -2700,13 +2762,13 @@ Object { `; exports[`versioned website content: 101 version sidebars 1`] = ` -Object { - "VersionedSideBarNameDoesNotMatter/docs": Array [ - Object { +{ + "VersionedSideBarNameDoesNotMatter/docs": [ + { "collapsed": true, "collapsible": true, - "items": Array [ - Object { + "items": [ + { "id": "foo/bar", "type": "doc", }, @@ -2715,11 +2777,11 @@ Object { "link": undefined, "type": "category", }, - Object { + { "collapsed": true, "collapsible": true, - "items": Array [ - Object { + "items": [ + { "id": "version-1.0.1/hello", "type": "doc", }, @@ -2733,13 +2795,13 @@ Object { `; exports[`versioned website content: current version sidebars 1`] = ` -Object { - "docs": Array [ - Object { +{ + "docs": [ + { "collapsed": true, "collapsible": true, - "items": Array [ - Object { + "items": [ + { "id": "foo/bar", "type": "doc", }, @@ -2748,11 +2810,11 @@ Object { "link": undefined, "type": "category", }, - Object { + { "collapsed": true, "collapsible": true, - "items": Array [ - Object { + "items": [ + { "id": "hello", "type": "doc", }, @@ -2766,7 +2828,7 @@ Object { `; exports[`versioned website content: data 1`] = ` -Object { +{ "site-docs-foo-bar-md-8c2.json": "{ \\"unversionedId\\": \\"foo/bar\\", \\"id\\": \\"foo/bar\\", @@ -3177,8 +3239,6 @@ Object { \\"version-1.0.0/docs\\": [ { \\"type\\": \\"category\\", - \\"collapsed\\": true, - \\"collapsible\\": true, \\"label\\": \\"Test\\", \\"items\\": [ { @@ -3193,12 +3253,12 @@ Object { \\"href\\": \\"/docs/1.0.0/foo/baz\\", \\"docId\\": \\"foo/baz\\" } - ] + ], + \\"collapsed\\": true, + \\"collapsible\\": true }, { \\"type\\": \\"category\\", - \\"collapsed\\": true, - \\"collapsible\\": true, \\"label\\": \\"Guides\\", \\"items\\": [ { @@ -3207,7 +3267,9 @@ Object { \\"href\\": \\"/docs/1.0.0/\\", \\"docId\\": \\"hello\\" } - ] + ], + \\"collapsed\\": true, + \\"collapsible\\": true } ] }, @@ -3244,8 +3306,6 @@ Object { \\"VersionedSideBarNameDoesNotMatter/docs\\": [ { \\"type\\": \\"category\\", - \\"collapsed\\": true, - \\"collapsible\\": true, \\"label\\": \\"Test\\", \\"items\\": [ { @@ -3254,12 +3314,12 @@ Object { \\"href\\": \\"/docs/foo/bar\\", \\"docId\\": \\"foo/bar\\" } - ] + ], + \\"collapsed\\": true, + \\"collapsible\\": true }, { \\"type\\": \\"category\\", - \\"collapsed\\": true, - \\"collapsible\\": true, \\"label\\": \\"Guides\\", \\"items\\": [ { @@ -3268,7 +3328,9 @@ Object { \\"href\\": \\"/docs/\\", \\"docId\\": \\"hello\\" } - ] + ], + \\"collapsed\\": true, + \\"collapsible\\": true } ] }, @@ -3299,8 +3361,6 @@ Object { \\"docs\\": [ { \\"type\\": \\"category\\", - \\"collapsed\\": true, - \\"collapsible\\": true, \\"label\\": \\"Test\\", \\"items\\": [ { @@ -3309,12 +3369,12 @@ Object { \\"href\\": \\"/docs/next/foo/barSlug\\", \\"docId\\": \\"foo/bar\\" } - ] + ], + \\"collapsed\\": true, + \\"collapsible\\": true }, { \\"type\\": \\"category\\", - \\"collapsed\\": true, - \\"collapsible\\": true, \\"label\\": \\"Guides\\", \\"items\\": [ { @@ -3323,7 +3383,9 @@ Object { \\"href\\": \\"/docs/next/\\", \\"docId\\": \\"hello\\" } - ] + ], + \\"collapsed\\": true, + \\"collapsible\\": true } ] }, @@ -3374,8 +3436,6 @@ Object { \\"version-1.0.1/docs\\": [ { \\"type\\": \\"category\\", - \\"collapsed\\": true, - \\"collapsible\\": true, \\"label\\": \\"Test\\", \\"items\\": [ { @@ -3384,7 +3444,9 @@ Object { \\"href\\": \\"/docs/withSlugs/rootAbsoluteSlug\\", \\"docId\\": \\"rootAbsoluteSlug\\" } - ] + ], + \\"collapsed\\": true, + \\"collapsible\\": true } ] }, @@ -3436,39 +3498,40 @@ Object { `; exports[`versioned website content: global data 1`] = ` -Object { - "pluginName": Object { - "pluginId": Object { +{ + "pluginName": { + "pluginId": { + "breadcrumbs": true, "path": "/docs", - "versions": Array [ - Object { - "docs": Array [ - Object { + "versions": [ + { + "docs": [ + { "id": "foo/bar", "path": "/docs/next/foo/barSlug", "sidebar": "docs", }, - Object { + { "id": "hello", "path": "/docs/next/", "sidebar": "docs", }, - Object { + { "id": "slugs/absoluteSlug", "path": "/docs/next/absoluteSlug", "sidebar": undefined, }, - Object { + { "id": "slugs/relativeSlug", "path": "/docs/next/slugs/relativeSlug", "sidebar": undefined, }, - Object { + { "id": "slugs/resolvedSlug", "path": "/docs/next/slugs/hey/resolvedSlug", "sidebar": undefined, }, - Object { + { "id": "slugs/tryToEscapeSlug", "path": "/docs/next/tryToEscapeSlug", "sidebar": undefined, @@ -3479,23 +3542,23 @@ Object { "mainDocId": "hello", "name": "current", "path": "/docs/next", - "sidebars": Object { - "docs": Object { - "link": Object { + "sidebars": { + "docs": { + "link": { "label": "foo/bar", "path": "/docs/next/foo/barSlug", }, }, }, }, - Object { - "docs": Array [ - Object { + { + "docs": [ + { "id": "foo/bar", "path": "/docs/foo/bar", "sidebar": "VersionedSideBarNameDoesNotMatter/docs", }, - Object { + { "id": "hello", "path": "/docs/", "sidebar": "VersionedSideBarNameDoesNotMatter/docs", @@ -3506,28 +3569,28 @@ Object { "mainDocId": "hello", "name": "1.0.1", "path": "/docs", - "sidebars": Object { - "VersionedSideBarNameDoesNotMatter/docs": Object { - "link": Object { + "sidebars": { + "VersionedSideBarNameDoesNotMatter/docs": { + "link": { "label": "foo/bar", "path": "/docs/foo/bar", }, }, }, }, - Object { - "docs": Array [ - Object { + { + "docs": [ + { "id": "foo/bar", "path": "/docs/1.0.0/foo/barSlug", "sidebar": "version-1.0.0/docs", }, - Object { + { "id": "foo/baz", "path": "/docs/1.0.0/foo/baz", "sidebar": "version-1.0.0/docs", }, - Object { + { "id": "hello", "path": "/docs/1.0.0/", "sidebar": "version-1.0.0/docs", @@ -3538,53 +3601,53 @@ Object { "mainDocId": "hello", "name": "1.0.0", "path": "/docs/1.0.0", - "sidebars": Object { - "version-1.0.0/docs": Object { - "link": Object { + "sidebars": { + "version-1.0.0/docs": { + "link": { "label": "version-1.0.0/foo/bar", "path": "/docs/1.0.0/foo/barSlug", }, }, }, }, - Object { - "docs": Array [ - Object { + { + "docs": [ + { "id": "rootAbsoluteSlug", "path": "/docs/withSlugs/rootAbsoluteSlug", "sidebar": "version-1.0.1/docs", }, - Object { + { "id": "rootRelativeSlug", "path": "/docs/withSlugs/rootRelativeSlug", "sidebar": undefined, }, - Object { + { "id": "rootResolvedSlug", "path": "/docs/withSlugs/hey/rootResolvedSlug", "sidebar": undefined, }, - Object { + { "id": "rootTryToEscapeSlug", "path": "/docs/withSlugs/rootTryToEscapeSlug", "sidebar": undefined, }, - Object { + { "id": "slugs/absoluteSlug", "path": "/docs/withSlugs/absoluteSlug", "sidebar": undefined, }, - Object { + { "id": "slugs/relativeSlug", "path": "/docs/withSlugs/slugs/relativeSlug", "sidebar": undefined, }, - Object { + { "id": "slugs/resolvedSlug", "path": "/docs/withSlugs/slugs/hey/resolvedSlug", "sidebar": undefined, }, - Object { + { "id": "slugs/tryToEscapeSlug", "path": "/docs/withSlugs/tryToEscapeSlug", "sidebar": undefined, @@ -3595,9 +3658,9 @@ Object { "mainDocId": "rootAbsoluteSlug", "name": "withSlugs", "path": "/docs/withSlugs", - "sidebars": Object { - "version-1.0.1/docs": Object { - "link": Object { + "sidebars": { + "version-1.0.1/docs": { + "link": { "label": "version-withSlugs/rootAbsoluteSlug", "path": "/docs/withSlugs/rootAbsoluteSlug", }, @@ -3611,70 +3674,70 @@ Object { `; exports[`versioned website content: route config 1`] = ` -Array [ - Object { +[ + { "component": "@theme/DocTagsListPage", "exact": true, - "modules": Object { + "modules": { "tags": "~docs/tags-list-current-prop-15a.json", }, "path": "/docs/next/tags", }, - Object { + { "component": "@theme/DocTagDocListPage", "exact": true, - "modules": Object { + "modules": { "tag": "~docs/tag-docs-next-tags-bar-tag-1-a8f.json", }, "path": "/docs/next/tags/bar-tag-1", }, - Object { + { "component": "@theme/DocTagDocListPage", "exact": true, - "modules": Object { + "modules": { "tag": "~docs/tag-docs-next-tags-bar-tag-2-216.json", }, "path": "/docs/next/tags/bar-tag-2", }, - Object { + { "component": "@theme/DocTagDocListPage", "exact": true, - "modules": Object { + "modules": { "tag": "~docs/tag-docs-next-tags-bar-tag-3-permalink-94a.json", }, "path": "/docs/next/tags/barTag-3-permalink", }, - Object { + { "component": "@theme/DocPage", "exact": false, - "modules": Object { + "modules": { "versionMetadata": "~docs/version-1-0-0-metadata-prop-608.json", }, "path": "/docs/1.0.0", "priority": undefined, - "routes": Array [ - Object { + "routes": [ + { "component": "@theme/DocItem", "exact": true, - "modules": Object { + "modules": { "content": "@site/i18n/en/docusaurus-plugin-content-docs/version-1.0.0/hello.md", }, "path": "/docs/1.0.0/", "sidebar": "version-1.0.0/docs", }, - Object { + { "component": "@theme/DocItem", "exact": true, - "modules": Object { + "modules": { "content": "@site/versioned_docs/version-1.0.0/foo/bar.md", }, "path": "/docs/1.0.0/foo/barSlug", "sidebar": "version-1.0.0/docs", }, - Object { + { "component": "@theme/DocItem", "exact": true, - "modules": Object { + "modules": { "content": "@site/versioned_docs/version-1.0.0/foo/baz.md", }, "path": "/docs/1.0.0/foo/baz", @@ -3682,165 +3745,165 @@ Array [ }, ], }, - Object { + { "component": "@theme/DocPage", "exact": false, - "modules": Object { + "modules": { "versionMetadata": "~docs/version-current-metadata-prop-751.json", }, "path": "/docs/next", "priority": undefined, - "routes": Array [ - Object { + "routes": [ + { "component": "@theme/DocItem", "exact": true, - "modules": Object { + "modules": { "content": "@site/docs/hello.md", }, "path": "/docs/next/", "sidebar": "docs", }, - Object { + { "component": "@theme/DocItem", "exact": true, - "modules": Object { + "modules": { "content": "@site/docs/slugs/absoluteSlug.md", }, "path": "/docs/next/absoluteSlug", }, - Object { + { "component": "@theme/DocItem", "exact": true, - "modules": Object { + "modules": { "content": "@site/docs/foo/bar.md", }, "path": "/docs/next/foo/barSlug", "sidebar": "docs", }, - Object { + { "component": "@theme/DocItem", "exact": true, - "modules": Object { + "modules": { "content": "@site/docs/slugs/resolvedSlug.md", }, "path": "/docs/next/slugs/hey/resolvedSlug", }, - Object { + { "component": "@theme/DocItem", "exact": true, - "modules": Object { + "modules": { "content": "@site/docs/slugs/relativeSlug.md", }, "path": "/docs/next/slugs/relativeSlug", }, - Object { + { "component": "@theme/DocItem", "exact": true, - "modules": Object { + "modules": { "content": "@site/docs/slugs/tryToEscapeSlug.md", }, "path": "/docs/next/tryToEscapeSlug", }, ], }, - Object { + { "component": "@theme/DocPage", "exact": false, - "modules": Object { + "modules": { "versionMetadata": "~docs/version-with-slugs-metadata-prop-2bf.json", }, "path": "/docs/withSlugs", "priority": undefined, - "routes": Array [ - Object { + "routes": [ + { "component": "@theme/DocItem", "exact": true, - "modules": Object { + "modules": { "content": "@site/versioned_docs/version-withSlugs/slugs/absoluteSlug.md", }, "path": "/docs/withSlugs/absoluteSlug", }, - Object { + { "component": "@theme/DocItem", "exact": true, - "modules": Object { + "modules": { "content": "@site/versioned_docs/version-withSlugs/rootResolvedSlug.md", }, "path": "/docs/withSlugs/hey/rootResolvedSlug", }, - Object { + { "component": "@theme/DocItem", "exact": true, - "modules": Object { + "modules": { "content": "@site/versioned_docs/version-withSlugs/rootAbsoluteSlug.md", }, "path": "/docs/withSlugs/rootAbsoluteSlug", "sidebar": "version-1.0.1/docs", }, - Object { + { "component": "@theme/DocItem", "exact": true, - "modules": Object { + "modules": { "content": "@site/versioned_docs/version-withSlugs/rootRelativeSlug.md", }, "path": "/docs/withSlugs/rootRelativeSlug", }, - Object { + { "component": "@theme/DocItem", "exact": true, - "modules": Object { + "modules": { "content": "@site/versioned_docs/version-withSlugs/rootTryToEscapeSlug.md", }, "path": "/docs/withSlugs/rootTryToEscapeSlug", }, - Object { + { "component": "@theme/DocItem", "exact": true, - "modules": Object { + "modules": { "content": "@site/versioned_docs/version-withSlugs/slugs/resolvedSlug.md", }, "path": "/docs/withSlugs/slugs/hey/resolvedSlug", }, - Object { + { "component": "@theme/DocItem", "exact": true, - "modules": Object { + "modules": { "content": "@site/versioned_docs/version-withSlugs/slugs/relativeSlug.md", }, "path": "/docs/withSlugs/slugs/relativeSlug", }, - Object { + { "component": "@theme/DocItem", "exact": true, - "modules": Object { + "modules": { "content": "@site/versioned_docs/version-withSlugs/slugs/tryToEscapeSlug.md", }, "path": "/docs/withSlugs/tryToEscapeSlug", }, ], }, - Object { + { "component": "@theme/DocPage", "exact": false, - "modules": Object { + "modules": { "versionMetadata": "~docs/version-1-0-1-metadata-prop-e87.json", }, "path": "/docs", "priority": -1, - "routes": Array [ - Object { + "routes": [ + { "component": "@theme/DocItem", "exact": true, - "modules": Object { + "modules": { "content": "@site/versioned_docs/version-1.0.1/hello.md", }, "path": "/docs/", "sidebar": "VersionedSideBarNameDoesNotMatter/docs", }, - Object { + { "component": "@theme/DocItem", "exact": true, - "modules": Object { + "modules": { "content": "@site/versioned_docs/version-1.0.1/foo/bar.md", }, "path": "/docs/foo/bar", @@ -3852,13 +3915,13 @@ Array [ `; exports[`versioned website content: withSlugs version sidebars 1`] = ` -Object { - "version-1.0.1/docs": Array [ - Object { +{ + "version-1.0.1/docs": [ + { "collapsed": true, "collapsible": true, - "items": Array [ - Object { + "items": [ + { "id": "version-withSlugs/rootAbsoluteSlug", "type": "doc", }, @@ -3870,3 +3933,24 @@ Object { ], } `; + +exports[`versioned website getPathToWatch 1`] = ` +[ + "sidebars.json", + "i18n/en/docusaurus-plugin-content-docs/current/**/*.{md,mdx}", + "docs/**/*.{md,mdx}", + "docs/**/_category_.{json,yml,yaml}", + "versioned_sidebars/version-1.0.1-sidebars.json", + "i18n/en/docusaurus-plugin-content-docs/version-1.0.1/**/*.{md,mdx}", + "versioned_docs/version-1.0.1/**/*.{md,mdx}", + "versioned_docs/version-1.0.1/**/_category_.{json,yml,yaml}", + "versioned_sidebars/version-1.0.0-sidebars.json", + "i18n/en/docusaurus-plugin-content-docs/version-1.0.0/**/*.{md,mdx}", + "versioned_docs/version-1.0.0/**/*.{md,mdx}", + "versioned_docs/version-1.0.0/**/_category_.{json,yml,yaml}", + "versioned_sidebars/version-withSlugs-sidebars.json", + "i18n/en/docusaurus-plugin-content-docs/version-withSlugs/**/*.{md,mdx}", + "versioned_docs/version-withSlugs/**/*.{md,mdx}", + "versioned_docs/version-withSlugs/**/_category_.{json,yml,yaml}", +] +`; diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/translations.test.ts.snap b/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/translations.test.ts.snap index 408b3ed66574..a67e0081748c 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/translations.test.ts.snap +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/__snapshots__/translations.test.ts.snap @@ -1,76 +1,76 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`getLoadedContentTranslationFiles should return translation files matching snapshot 1`] = ` -Array [ - Object { - "content": Object { - "sidebar.docs.category.Getting started": Object { +exports[`getLoadedContentTranslationFiles returns translation files 1`] = ` +[ + { + "content": { + "sidebar.docs.category.Getting started": { "description": "The label for category Getting started in sidebar docs", "message": "Getting started", }, - "sidebar.docs.category.Getting started.link.generated-index.description": Object { + "sidebar.docs.category.Getting started.link.generated-index.description": { "description": "The generated-index page description for category Getting started in sidebar docs", "message": "Getting started index description", }, - "sidebar.docs.category.Getting started.link.generated-index.title": Object { + "sidebar.docs.category.Getting started.link.generated-index.title": { "description": "The generated-index page title for category Getting started in sidebar docs", "message": "Getting started index title", }, - "sidebar.docs.link.Link label": Object { + "sidebar.docs.link.Link label": { "description": "The label for link Link label in sidebar docs, linking to https://facebook.com", "message": "Link label", }, - "version.label": Object { + "version.label": { "description": "The label for version current", "message": "current label", }, }, "path": "current", }, - Object { - "content": Object { - "sidebar.docs.category.Getting started": Object { + { + "content": { + "sidebar.docs.category.Getting started": { "description": "The label for category Getting started in sidebar docs", "message": "Getting started", }, - "sidebar.docs.category.Getting started.link.generated-index.description": Object { + "sidebar.docs.category.Getting started.link.generated-index.description": { "description": "The generated-index page description for category Getting started in sidebar docs", "message": "Getting started index description", }, - "sidebar.docs.category.Getting started.link.generated-index.title": Object { + "sidebar.docs.category.Getting started.link.generated-index.title": { "description": "The generated-index page title for category Getting started in sidebar docs", "message": "Getting started index title", }, - "sidebar.docs.link.Link label": Object { + "sidebar.docs.link.Link label": { "description": "The label for link Link label in sidebar docs, linking to https://facebook.com", "message": "Link label", }, - "version.label": Object { + "version.label": { "description": "The label for version 2.0.0", "message": "2.0.0 label", }, }, "path": "version-2.0.0", }, - Object { - "content": Object { - "sidebar.docs.category.Getting started": Object { + { + "content": { + "sidebar.docs.category.Getting started": { "description": "The label for category Getting started in sidebar docs", "message": "Getting started", }, - "sidebar.docs.category.Getting started.link.generated-index.description": Object { + "sidebar.docs.category.Getting started.link.generated-index.description": { "description": "The generated-index page description for category Getting started in sidebar docs", "message": "Getting started index description", }, - "sidebar.docs.category.Getting started.link.generated-index.title": Object { + "sidebar.docs.category.Getting started.link.generated-index.title": { "description": "The generated-index page title for category Getting started in sidebar docs", "message": "Getting started index title", }, - "sidebar.docs.link.Link label": Object { + "sidebar.docs.link.Link label": { "description": "The label for link Link label in sidebar docs, linking to https://facebook.com", "message": "Link label", }, - "version.label": Object { + "version.label": { "description": "The label for version 1.0.0", "message": "1.0.0 label", }, @@ -80,14 +80,14 @@ Array [ ] `; -exports[`translateLoadedContent should return translated loaded content matching snapshot 1`] = ` -Object { - "loadedVersions": Array [ - Object { +exports[`translateLoadedContent returns translated loaded content 1`] = ` +{ + "loadedVersions": [ + { "contentPath": "any", "contentPathLocalized": "any", - "docs": Array [ - Object { + "docs": [ + { "description": "doc1 description", "editUrl": "any", "id": "doc1", @@ -103,7 +103,7 @@ Object { "unversionedId": "any", "version": "any", }, - Object { + { "description": "doc2 description", "editUrl": "any", "id": "doc2", @@ -119,7 +119,7 @@ Object { "unversionedId": "any", "version": "any", }, - Object { + { "description": "doc3 description", "editUrl": "any", "id": "doc3", @@ -135,7 +135,7 @@ Object { "unversionedId": "any", "version": "any", }, - Object { + { "description": "doc4 description", "editUrl": "any", "id": "doc4", @@ -151,7 +151,7 @@ Object { "unversionedId": "any", "version": "any", }, - Object { + { "description": "doc5 description", "editUrl": "any", "id": "doc5", @@ -169,34 +169,36 @@ Object { }, ], "isLast": true, + "label": "current label (translated)", "mainDocId": "", + "path": "/docs/", "routePriority": undefined, "sidebarFilePath": "any", - "sidebars": Object { - "docs": Array [ - Object { + "sidebars": { + "docs": [ + { "collapsed": false, - "items": Array [ - Object { + "items": [ + { "id": "doc1", "type": "doc", }, - Object { + { "id": "doc2", "type": "doc", }, - Object { + { "href": "https://facebook.com", "label": "Link label (translated)", "type": "link", }, - Object { + { "id": "doc1", "type": "ref", }, ], "label": "Getting started (translated)", - "link": Object { + "link": { "description": "Getting started index description (translated)", "permalink": "/docs/category/getting-started-index-slug", "slug": "/category/getting-started-index-slug", @@ -205,31 +207,29 @@ Object { }, "type": "category", }, - Object { + { "id": "doc3", "type": "doc", }, ], - "otherSidebar": Array [ - Object { + "otherSidebar": [ + { "id": "doc4", "type": "doc", }, - Object { + { "id": "doc5", "type": "doc", }, ], }, - "versionLabel": "current label (translated)", "versionName": "current", - "versionPath": "/docs/", }, - Object { + { "contentPath": "any", "contentPathLocalized": "any", - "docs": Array [ - Object { + "docs": [ + { "description": "doc1 description", "editUrl": "any", "id": "doc1", @@ -245,7 +245,7 @@ Object { "unversionedId": "any", "version": "any", }, - Object { + { "description": "doc2 description", "editUrl": "any", "id": "doc2", @@ -261,7 +261,7 @@ Object { "unversionedId": "any", "version": "any", }, - Object { + { "description": "doc3 description", "editUrl": "any", "id": "doc3", @@ -277,7 +277,7 @@ Object { "unversionedId": "any", "version": "any", }, - Object { + { "description": "doc4 description", "editUrl": "any", "id": "doc4", @@ -293,7 +293,7 @@ Object { "unversionedId": "any", "version": "any", }, - Object { + { "description": "doc5 description", "editUrl": "any", "id": "doc5", @@ -311,34 +311,36 @@ Object { }, ], "isLast": true, + "label": "2.0.0 label (translated)", "mainDocId": "", + "path": "/docs/", "routePriority": undefined, "sidebarFilePath": "any", - "sidebars": Object { - "docs": Array [ - Object { + "sidebars": { + "docs": [ + { "collapsed": false, - "items": Array [ - Object { + "items": [ + { "id": "doc1", "type": "doc", }, - Object { + { "id": "doc2", "type": "doc", }, - Object { + { "href": "https://facebook.com", "label": "Link label (translated)", "type": "link", }, - Object { + { "id": "doc1", "type": "ref", }, ], "label": "Getting started (translated)", - "link": Object { + "link": { "description": "Getting started index description (translated)", "permalink": "/docs/category/getting-started-index-slug", "slug": "/category/getting-started-index-slug", @@ -347,31 +349,29 @@ Object { }, "type": "category", }, - Object { + { "id": "doc3", "type": "doc", }, ], - "otherSidebar": Array [ - Object { + "otherSidebar": [ + { "id": "doc4", "type": "doc", }, - Object { + { "id": "doc5", "type": "doc", }, ], }, - "versionLabel": "2.0.0 label (translated)", "versionName": "2.0.0", - "versionPath": "/docs/", }, - Object { + { "contentPath": "any", "contentPathLocalized": "any", - "docs": Array [ - Object { + "docs": [ + { "description": "doc1 description", "editUrl": "any", "id": "doc1", @@ -387,7 +387,7 @@ Object { "unversionedId": "any", "version": "any", }, - Object { + { "description": "doc2 description", "editUrl": "any", "id": "doc2", @@ -403,7 +403,7 @@ Object { "unversionedId": "any", "version": "any", }, - Object { + { "description": "doc3 description", "editUrl": "any", "id": "doc3", @@ -419,7 +419,7 @@ Object { "unversionedId": "any", "version": "any", }, - Object { + { "description": "doc4 description", "editUrl": "any", "id": "doc4", @@ -435,7 +435,7 @@ Object { "unversionedId": "any", "version": "any", }, - Object { + { "description": "doc5 description", "editUrl": "any", "id": "doc5", @@ -453,34 +453,36 @@ Object { }, ], "isLast": true, + "label": "1.0.0 label (translated)", "mainDocId": "", + "path": "/docs/", "routePriority": undefined, "sidebarFilePath": "any", - "sidebars": Object { - "docs": Array [ - Object { + "sidebars": { + "docs": [ + { "collapsed": false, - "items": Array [ - Object { + "items": [ + { "id": "doc1", "type": "doc", }, - Object { + { "id": "doc2", "type": "doc", }, - Object { + { "href": "https://facebook.com", "label": "Link label (translated)", "type": "link", }, - Object { + { "id": "doc1", "type": "ref", }, ], "label": "Getting started (translated)", - "link": Object { + "link": { "description": "Getting started index description (translated)", "permalink": "/docs/category/getting-started-index-slug", "slug": "/category/getting-started-index-slug", @@ -489,25 +491,23 @@ Object { }, "type": "category", }, - Object { + { "id": "doc3", "type": "doc", }, ], - "otherSidebar": Array [ - Object { + "otherSidebar": [ + { "id": "doc4", "type": "doc", }, - Object { + { "id": "doc5", "type": "doc", }, ], }, - "versionLabel": "1.0.0 label (translated)", "versionName": "1.0.0", - "versionPath": "/docs/", }, ], } diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/cli.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/cli.test.ts index d7287128a271..22aeed790cb9 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/cli.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/cli.test.ts @@ -5,6 +5,7 @@ * LICENSE file in the root directory of this source tree. */ +import {jest} from '@jest/globals'; import path from 'path'; import {cliDocsVersionCommand} from '../cli'; import type { @@ -32,162 +33,161 @@ describe('docsVersion', () => { sidebarCollapsible: true, }; - test('no version tag provided', () => { - expect(() => + it('no version tag provided', async () => { + await expect(() => cliDocsVersionCommand( null, simpleSiteDir, DEFAULT_PLUGIN_ID, DEFAULT_OPTIONS, ), - ).toThrowErrorMatchingInlineSnapshot( + ).rejects.toThrowErrorMatchingInlineSnapshot( `"[docs]: no version tag specified! Pass the version you wish to create as an argument, for example: 1.0.0."`, ); - expect(() => + await expect(() => cliDocsVersionCommand( undefined, simpleSiteDir, DEFAULT_PLUGIN_ID, DEFAULT_OPTIONS, ), - ).toThrowErrorMatchingInlineSnapshot( + ).rejects.toThrowErrorMatchingInlineSnapshot( `"[docs]: no version tag specified! Pass the version you wish to create as an argument, for example: 1.0.0."`, ); - expect(() => + await expect(() => cliDocsVersionCommand( '', simpleSiteDir, DEFAULT_PLUGIN_ID, DEFAULT_OPTIONS, ), - ).toThrowErrorMatchingInlineSnapshot( + ).rejects.toThrowErrorMatchingInlineSnapshot( `"[docs]: no version tag specified! Pass the version you wish to create as an argument, for example: 1.0.0."`, ); }); - test('version tag should not have slash', () => { - expect(() => + it('version tag should not have slash', async () => { + await expect(() => cliDocsVersionCommand( 'foo/bar', simpleSiteDir, DEFAULT_PLUGIN_ID, DEFAULT_OPTIONS, ), - ).toThrowErrorMatchingInlineSnapshot( - `"[docs]: invalid version tag specified! Do not include slash (/) or backslash (\\\\). Try something like: 1.0.0."`, + ).rejects.toThrowError( + '[docs]: invalid version tag specified! Do not include slash (/) or backslash (\\). Try something like: 1.0.0.', ); - expect(() => + await expect(() => cliDocsVersionCommand( 'foo\\bar', simpleSiteDir, DEFAULT_PLUGIN_ID, DEFAULT_OPTIONS, ), - ).toThrowErrorMatchingInlineSnapshot( - `"[docs]: invalid version tag specified! Do not include slash (/) or backslash (\\\\). Try something like: 1.0.0."`, + ).rejects.toThrowError( + '[docs]: invalid version tag specified! Do not include slash (/) or backslash (\\). Try something like: 1.0.0.', ); }); - test('version tag should not be too long', () => { - expect(() => + it('version tag should not be too long', async () => { + await expect(() => cliDocsVersionCommand( 'a'.repeat(255), simpleSiteDir, DEFAULT_PLUGIN_ID, DEFAULT_OPTIONS, ), - ).toThrowErrorMatchingInlineSnapshot( + ).rejects.toThrowErrorMatchingInlineSnapshot( `"[docs]: invalid version tag specified! Length cannot exceed 32 characters. Try something like: 1.0.0."`, ); }); - test('version tag should not be a dot or two dots', () => { - expect(() => + it('version tag should not be a dot or two dots', async () => { + await expect(() => cliDocsVersionCommand( '..', simpleSiteDir, DEFAULT_PLUGIN_ID, DEFAULT_OPTIONS, ), - ).toThrowErrorMatchingInlineSnapshot( + ).rejects.toThrowErrorMatchingInlineSnapshot( `"[docs]: invalid version tag specified! Do not name your version \\".\\" or \\"..\\". Try something like: 1.0.0."`, ); - expect(() => + await expect(() => cliDocsVersionCommand( '.', simpleSiteDir, DEFAULT_PLUGIN_ID, DEFAULT_OPTIONS, ), - ).toThrowErrorMatchingInlineSnapshot( + ).rejects.toThrowErrorMatchingInlineSnapshot( `"[docs]: invalid version tag specified! Do not name your version \\".\\" or \\"..\\". Try something like: 1.0.0."`, ); }); - test('version tag should be a valid pathname', () => { - expect(() => + it('version tag should be a valid pathname', async () => { + await expect(() => cliDocsVersionCommand( '', simpleSiteDir, DEFAULT_PLUGIN_ID, DEFAULT_OPTIONS, ), - ).toThrowErrorMatchingInlineSnapshot( + ).rejects.toThrowErrorMatchingInlineSnapshot( `"[docs]: invalid version tag specified! Please ensure its a valid pathname too. Try something like: 1.0.0."`, ); - expect(() => + await expect(() => cliDocsVersionCommand( 'foo\x00bar', simpleSiteDir, DEFAULT_PLUGIN_ID, DEFAULT_OPTIONS, ), - ).toThrowErrorMatchingInlineSnapshot( + ).rejects.toThrowErrorMatchingInlineSnapshot( `"[docs]: invalid version tag specified! Please ensure its a valid pathname too. Try something like: 1.0.0."`, ); - expect(() => + await expect(() => cliDocsVersionCommand( 'foo:bar', simpleSiteDir, DEFAULT_PLUGIN_ID, DEFAULT_OPTIONS, ), - ).toThrowErrorMatchingInlineSnapshot( + ).rejects.toThrowErrorMatchingInlineSnapshot( `"[docs]: invalid version tag specified! Please ensure its a valid pathname too. Try something like: 1.0.0."`, ); }); - test('version tag already exist', () => { - expect(() => + it('version tag already exist', async () => { + await expect(() => cliDocsVersionCommand( '1.0.0', versionedSiteDir, DEFAULT_PLUGIN_ID, DEFAULT_OPTIONS, ), - ).toThrowErrorMatchingInlineSnapshot( + ).rejects.toThrowErrorMatchingInlineSnapshot( `"[docs]: this version already exists! Use a version tag that does not already exist."`, ); }); - test('no docs file to version', () => { + it('no docs file to version', async () => { const emptySiteDir = path.join(fixtureDir, 'empty-site'); - expect(() => + await expect(() => cliDocsVersionCommand( '1.0.0', emptySiteDir, DEFAULT_PLUGIN_ID, DEFAULT_OPTIONS, ), - ).toThrowErrorMatchingInlineSnapshot( - `"[docs]: there is no docs to version!"`, + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"[docs]: no docs found in /packages/docusaurus-plugin-content-docs/src/__tests__/__fixtures__/empty-site/docs."`, ); }); - test('first time versioning', () => { - const copyMock = jest.spyOn(fs, 'copySync').mockImplementation(); - const ensureMock = jest.spyOn(fs, 'ensureDirSync').mockImplementation(); - const writeMock = jest.spyOn(fs, 'writeFileSync'); + it('first time versioning', async () => { + const copyMock = jest.spyOn(fs, 'copy').mockImplementation(() => {}); + const writeMock = jest.spyOn(fs, 'outputFile'); let versionedSidebar; let versionedSidebarPath; writeMock.mockImplementationOnce((filepath, content) => { @@ -200,12 +200,17 @@ describe('docsVersion', () => { versionsPath = filepath; versions = JSON.parse(content as string); }); - const consoleMock = jest.spyOn(console, 'log').mockImplementation(); + const consoleMock = jest.spyOn(console, 'log').mockImplementation(() => {}); const options = { ...DEFAULT_OPTIONS, sidebarPath: path.join(simpleSiteDir, 'sidebars.json'), }; - cliDocsVersionCommand('1.0.0', simpleSiteDir, DEFAULT_PLUGIN_ID, options); + await cliDocsVersionCommand( + '1.0.0', + simpleSiteDir, + DEFAULT_PLUGIN_ID, + options, + ); expect(copyMock).toHaveBeenCalledWith( path.join(simpleSiteDir, options.path), path.join( @@ -226,20 +231,18 @@ describe('docsVersion', () => { expect(versions).toEqual(['1.0.0']); expect(consoleMock).toHaveBeenCalledWith( expect.stringMatching( - /.*\[SUCCESS\].* .*\[docs\].*: version .*1\.0\.0.* created!.*/, + /.*\[SUCCESS\].*\[docs\].*: version .*1\.0\.0.* created!.*/, ), ); copyMock.mockRestore(); writeMock.mockRestore(); consoleMock.mockRestore(); - ensureMock.mockRestore(); }); - test('not the first time versioning', () => { - const copyMock = jest.spyOn(fs, 'copySync').mockImplementation(); - const ensureMock = jest.spyOn(fs, 'ensureDirSync').mockImplementation(); - const writeMock = jest.spyOn(fs, 'writeFileSync'); + it('not the first time versioning', async () => { + const copyMock = jest.spyOn(fs, 'copy').mockImplementation(() => {}); + const writeMock = jest.spyOn(fs, 'outputFile'); let versionedSidebar; let versionedSidebarPath; writeMock.mockImplementationOnce((filepath, content) => { @@ -252,12 +255,12 @@ describe('docsVersion', () => { versionsPath = filepath; versions = JSON.parse(content as string); }); - const consoleMock = jest.spyOn(console, 'log').mockImplementation(); + const consoleMock = jest.spyOn(console, 'log').mockImplementation(() => {}); const options = { ...DEFAULT_OPTIONS, sidebarPath: path.join(versionedSiteDir, 'sidebars.json'), }; - cliDocsVersionCommand( + await cliDocsVersionCommand( '2.0.0', versionedSiteDir, DEFAULT_PLUGIN_ID, @@ -283,22 +286,20 @@ describe('docsVersion', () => { expect(versions).toEqual(['2.0.0', '1.0.1', '1.0.0', 'withSlugs']); expect(consoleMock).toHaveBeenCalledWith( expect.stringMatching( - /.*\[SUCCESS\].* .*\[docs\].*: version .*2\.0\.0.* created!.*/, + /.*\[SUCCESS\].*\[docs\].*: version .*2\.0\.0.* created!.*/, ), ); copyMock.mockRestore(); writeMock.mockRestore(); consoleMock.mockRestore(); - ensureMock.mockRestore(); }); - test('second docs instance versioning', () => { + it('second docs instance versioning', async () => { const pluginId = 'community'; - const copyMock = jest.spyOn(fs, 'copySync').mockImplementation(); - const ensureMock = jest.spyOn(fs, 'ensureDirSync').mockImplementation(); - const writeMock = jest.spyOn(fs, 'writeFileSync'); + const copyMock = jest.spyOn(fs, 'copy').mockImplementation(() => {}); + const writeMock = jest.spyOn(fs, 'outputFile'); let versionedSidebar; let versionedSidebarPath; writeMock.mockImplementationOnce((filepath, content) => { @@ -311,13 +312,13 @@ describe('docsVersion', () => { versionsPath = filepath; versions = JSON.parse(content as string); }); - const consoleMock = jest.spyOn(console, 'log').mockImplementation(); + const consoleMock = jest.spyOn(console, 'log').mockImplementation(() => {}); const options = { ...DEFAULT_OPTIONS, path: 'community', sidebarPath: path.join(versionedSiteDir, 'community_sidebars.json'), }; - cliDocsVersionCommand('2.0.0', versionedSiteDir, pluginId, options); + await cliDocsVersionCommand('2.0.0', versionedSiteDir, pluginId, options); expect(copyMock).toHaveBeenCalledWith( path.join(versionedSiteDir, options.path), path.join( @@ -338,13 +339,12 @@ describe('docsVersion', () => { expect(versions).toEqual(['2.0.0', '1.0.0']); expect(consoleMock).toHaveBeenCalledWith( expect.stringMatching( - /.*\[SUCCESS\].* .*\[community\].*: version .*2.0.0.* created!.*/, + /.*\[SUCCESS\].*\[community\].*: version .*2.0.0.* created!.*/, ), ); copyMock.mockRestore(); writeMock.mockRestore(); consoleMock.mockRestore(); - ensureMock.mockRestore(); }); }); diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/docs.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/docs.test.ts index f00525b88ea7..0623a345cc2f 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/docs.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/docs.test.ts @@ -5,6 +5,7 @@ * LICENSE file in the root directory of this source tree. */ +import {jest} from '@jest/globals'; import path from 'path'; import {loadContext} from '@docusaurus/core/src/server/index'; import { @@ -15,17 +16,16 @@ import { isCategoryIndex, } from '../docs'; import {loadSidebars} from '../sidebars'; +import type {Sidebars} from '../sidebars/types'; import {readVersionsMetadata} from '../versions'; -import type { - DocFile, - DocMetadataBase, - VersionMetadata, - DocNavLink, -} from '../types'; +import type {DocFile} from '../types'; import type { MetadataOptions, PluginOptions, EditUrlFunction, + DocMetadataBase, + VersionMetadata, + PropNavigationLink, } from '@docusaurus/plugin-content-docs'; import type {LoadContext} from '@docusaurus/types'; import {DEFAULT_OPTIONS} from '../options'; @@ -41,7 +41,7 @@ const createFakeDocFile = ({ markdown = 'some markdown content', }: { source: string; - frontMatter?: Record; + frontMatter?: {[key: string]: string}; markdown?: string; }): DocFile => { const content = `--- @@ -120,18 +120,21 @@ function createTestUtils({ expect(metadata.permalink).toEqual(expectedPermalink); } - async function generateNavigation( - docFiles: DocFile[], - ): Promise<[DocNavLink | undefined, DocNavLink | undefined][]> { - const rawDocs = await Promise.all( - docFiles.map((docFile) => - processDocMetadata({ - docFile, - versionMetadata, - context, - options, - }), - ), + async function generateNavigation(docFiles: DocFile[]): Promise<{ + pagination: { + prev?: PropNavigationLink; + next?: PropNavigationLink; + id: string; + }[]; + sidebars: Sidebars; + }> { + const rawDocs = docFiles.map((docFile) => + processDocMetadata({ + docFile, + versionMetadata, + context, + options, + }), ); const sidebars = await loadSidebars(versionMetadata.sidebarFilePath, { sidebarItemsGenerator: ({defaultSidebarItemsGenerator, ...args}) => @@ -147,11 +150,14 @@ function createTestUtils({ }); const sidebarsUtils = createSidebarsUtils(sidebars); - return addDocNavigation( - rawDocs, - sidebarsUtils, - versionMetadata.sidebarFilePath as string, - ).map((doc) => [doc.previous, doc.next]); + return { + pagination: addDocNavigation( + rawDocs, + sidebarsUtils, + versionMetadata.sidebarFilePath as string, + ).map((doc) => ({prev: doc.previous, next: doc.next, id: doc.id})), + sidebars, + }; } return {processDocFile, testMeta, testSlug, generateNavigation}; @@ -162,7 +168,7 @@ describe('simple site', () => { loadSiteOptions: {options: Partial} = {options: {}}, ) { const siteDir = path.join(fixtureDir, 'simple-site'); - const context = await loadContext(siteDir); + const context = await loadContext({siteDir}); const options = { id: DEFAULT_PLUGIN_ID, ...DEFAULT_OPTIONS, @@ -172,7 +178,7 @@ describe('simple site', () => { context, options, }); - expect(versionsMetadata.length).toEqual(1); + expect(versionsMetadata).toHaveLength(1); const [currentVersion] = versionsMetadata; const defaultTestUtils = createTestUtils({ @@ -191,7 +197,7 @@ describe('simple site', () => { }; } - test('readVersionDocs', async () => { + it('readVersionDocs', async () => { const {options, currentVersion} = await loadSite(); const docs = await readVersionDocs(currentVersion, options); expect(docs.map((doc) => doc.source).sort()).toEqual( @@ -215,7 +221,7 @@ describe('simple site', () => { ); }); - test('normal docs', async () => { + it('normal docs', async () => { const {defaultTestUtils} = await loadSite(); await defaultTestUtils.testMeta(path.join('foo', 'bar.md'), { version: 'current', @@ -264,7 +270,7 @@ describe('simple site', () => { }); }); - test('docs with editUrl', async () => { + it('docs with editUrl', async () => { const {siteDir, context, options, currentVersion} = await loadSite({ options: { editUrl: 'https://github.com/facebook/docusaurus/edit/main/website', @@ -313,7 +319,7 @@ describe('simple site', () => { }); }); - test('docs with custom editUrl & unrelated frontMatter', async () => { + it('docs with custom editUrl & unrelated frontMatter', async () => { const {defaultTestUtils} = await loadSite(); await defaultTestUtils.testMeta('lorem.md', { @@ -334,7 +340,7 @@ describe('simple site', () => { }); }); - test('docs with function editUrl', async () => { + it('docs with function editUrl', async () => { const hardcodedEditUrl = 'hardcoded-edit-url'; const editUrlFunction: EditUrlFunction = jest.fn(() => hardcodedEditUrl); @@ -395,7 +401,7 @@ describe('simple site', () => { }); }); - test('docs with last update time and author', async () => { + it('docs with last update time and author', async () => { const {siteDir, context, options, currentVersion} = await loadSite({ options: { showLastUpdateAuthor: true, @@ -431,7 +437,7 @@ describe('simple site', () => { }); }); - test('docs with slugs', async () => { + it('docs with slugs', async () => { const {defaultTestUtils} = await loadSite(); await defaultTestUtils.testSlug( @@ -469,7 +475,7 @@ describe('simple site', () => { ); }); - test('docs with invalid id', async () => { + it('docs with invalid id', async () => { const {defaultTestUtils} = await loadSite(); await expect(async () => defaultTestUtils.processDocFile( @@ -485,7 +491,7 @@ describe('simple site', () => { ); }); - test('custom pagination', async () => { + it('custom pagination', async () => { const {defaultTestUtils, options, versionsMetadata} = await loadSite(); const docs = await readVersionDocs(versionsMetadata[0], options); await expect( @@ -493,19 +499,19 @@ describe('simple site', () => { ).resolves.toMatchSnapshot(); }); - test('bad pagination', async () => { + it('bad pagination', async () => { const {defaultTestUtils, options, versionsMetadata} = await loadSite(); const docs = await readVersionDocs(versionsMetadata[0], options); docs.push( createFakeDocFile({ - source: 'hehe', + source: 'bad', frontMatter: {pagination_prev: 'nonexistent'}, }), ); await expect( defaultTestUtils.generateNavigation(docs), ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Error when loading hehe in .: the pagination_prev front matter points to a non-existent ID nonexistent."`, + `"Error when loading bad in .: the pagination_prev front matter points to a non-existent ID nonexistent."`, ); }); }); @@ -517,7 +523,8 @@ describe('versioned site', () => { }, ) { const siteDir = path.join(fixtureDir, 'versioned-site'); - const context = await loadContext(siteDir, { + const context = await loadContext({ + siteDir, locale: loadSiteOptions.locale, }); const options = { @@ -529,7 +536,7 @@ describe('versioned site', () => { context, options, }); - expect(versionsMetadata.length).toEqual(4); + expect(versionsMetadata).toHaveLength(4); const [currentVersion, version101, version100, versionWithSlugs] = versionsMetadata; @@ -573,7 +580,7 @@ describe('versioned site', () => { }; } - test('next docs', async () => { + it('next docs', async () => { const {currentVersionTestUtils} = await loadSite(); await currentVersionTestUtils.testMeta(path.join('foo', 'bar.md'), { @@ -627,7 +634,7 @@ describe('versioned site', () => { }); }); - test('versioned docs', async () => { + it('versioned docs', async () => { const {version101TestUtils, version100TestUtils} = await loadSite(); await version100TestUtils.testMeta(path.join('foo', 'bar.md'), { @@ -686,7 +693,7 @@ describe('versioned site', () => { }); }); - test('next doc slugs', async () => { + it('next doc slugs', async () => { const {currentVersionTestUtils} = await loadSite(); await currentVersionTestUtils.testSlug( @@ -707,7 +714,7 @@ describe('versioned site', () => { ); }); - test('versioned doc slugs', async () => { + it('versioned doc slugs', async () => { const {versionWithSlugsTestUtils} = await loadSite(); await versionWithSlugsTestUtils.testSlug( @@ -745,7 +752,7 @@ describe('versioned site', () => { ); }); - test('doc with editUrl function', async () => { + it('doc with editUrl function', async () => { const hardcodedEditUrl = 'hardcoded-edit-url'; const editUrlFunction: EditUrlFunction = jest.fn(() => hardcodedEditUrl); @@ -791,7 +798,7 @@ describe('versioned site', () => { }); }); - test('translated doc with editUrl', async () => { + it('translated doc with editUrl', async () => { const {siteDir, context, options, version100} = await loadSite({ options: { editUrl: 'https://github.com/facebook/docusaurus/edit/main/website', @@ -826,7 +833,7 @@ describe('versioned site', () => { }); }); - test('translated en doc with editUrl and editCurrentVersion=true', async () => { + it('translated en doc with editUrl and editCurrentVersion=true', async () => { const {siteDir, context, options, version100} = await loadSite({ options: { editUrl: 'https://github.com/facebook/docusaurus/edit/main/website', @@ -861,7 +868,7 @@ describe('versioned site', () => { }); }); - test('translated fr doc with editUrl and editLocalizedFiles=true', async () => { + it('translated fr doc with editUrl and editLocalizedFiles=true', async () => { const {siteDir, context, options, version100} = await loadSite({ options: { editUrl: 'https://github.com/facebook/docusaurus/edit/main/website', @@ -897,7 +904,7 @@ describe('versioned site', () => { }); }); - test('translated fr doc with editUrl and editLocalizedFiles=true + editCurrentVersion=true', async () => { + it('translated fr doc with editUrl and editLocalizedFiles=true + editCurrentVersion=true', async () => { const {siteDir, context, options, version100} = await loadSite({ options: { editUrl: 'https://github.com/facebook/docusaurus/edit/main/website', @@ -936,127 +943,127 @@ describe('versioned site', () => { }); describe('isConventionalDocIndex', () => { - test('supports readme', () => { + it('supports readme', () => { expect( isCategoryIndex({ fileName: 'readme', directories: ['doesNotMatter'], extension: '.md', }), - ).toEqual(true); + ).toBe(true); expect( isCategoryIndex({ fileName: 'readme', directories: ['doesNotMatter'], extension: '.mdx', }), - ).toEqual(true); + ).toBe(true); expect( isCategoryIndex({ fileName: 'README', directories: ['doesNotMatter'], extension: '.md', }), - ).toEqual(true); + ).toBe(true); expect( isCategoryIndex({ fileName: 'ReAdMe', directories: ['doesNotMatter'], extension: '', }), - ).toEqual(true); + ).toBe(true); }); - test('supports index', () => { + it('supports index', () => { expect( isCategoryIndex({ fileName: 'index', directories: ['doesNotMatter'], extension: '.md', }), - ).toEqual(true); + ).toBe(true); expect( isCategoryIndex({ fileName: 'index', directories: ['doesNotMatter'], extension: '.mdx', }), - ).toEqual(true); + ).toBe(true); expect( isCategoryIndex({ fileName: 'INDEX', directories: ['doesNotMatter'], extension: '.md', }), - ).toEqual(true); + ).toBe(true); expect( isCategoryIndex({ fileName: 'InDeX', directories: ['doesNotMatter'], extension: '', }), - ).toEqual(true); + ).toBe(true); }); - test('supports /.md', () => { + it('supports /.md', () => { expect( isCategoryIndex({ fileName: 'someCategory', directories: ['someCategory', 'doesNotMatter'], extension: '', }), - ).toEqual(true); + ).toBe(true); expect( isCategoryIndex({ fileName: 'someCategory', directories: ['someCategory'], extension: '.md', }), - ).toEqual(true); + ).toBe(true); expect( isCategoryIndex({ fileName: 'someCategory', directories: ['someCategory'], extension: '.mdx', }), - ).toEqual(true); + ).toBe(true); expect( isCategoryIndex({ fileName: 'SOME_CATEGORY', directories: ['some_category'], extension: '.md', }), - ).toEqual(true); + ).toBe(true); expect( isCategoryIndex({ fileName: 'some_category', directories: ['some_category'], extension: '', }), - ).toEqual(true); + ).toBe(true); }); - test('reject other cases', () => { + it('reject other cases', () => { expect( isCategoryIndex({ fileName: 'some_Category', directories: ['someCategory'], extension: '', }), - ).toEqual(false); + ).toBe(false); expect( isCategoryIndex({ fileName: 'read_me', directories: ['doesNotMatter'], extension: '', }), - ).toEqual(false); + ).toBe(false); expect( isCategoryIndex({ fileName: 'the index', directories: ['doesNotMatter'], extension: '', }), - ).toEqual(false); + ).toBe(false); }); }); diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/docFrontMatter.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/frontMatter.test.ts similarity index 90% rename from packages/docusaurus-plugin-content-docs/src/__tests__/docFrontMatter.test.ts rename to packages/docusaurus-plugin-content-docs/src/__tests__/frontMatter.test.ts index 861a0b711fa0..7e295afb0cb0 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/docFrontMatter.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/frontMatter.test.ts @@ -5,28 +5,30 @@ * LICENSE file in the root directory of this source tree. */ -import {validateDocFrontMatter} from '../docFrontMatter'; -import type {DocFrontMatter} from '../types'; +import {validateDocFrontMatter} from '../frontMatter'; +import type {DocFrontMatter} from '@docusaurus/plugin-content-docs'; import escapeStringRegexp from 'escape-string-regexp'; function testField(params: { prefix: string; validFrontMatters: DocFrontMatter[]; convertibleFrontMatter?: [ - ConvertableFrontMatter: Record, + ConvertibleFrontMatter: {[key: string]: unknown}, ConvertedFrontMatter: DocFrontMatter, ][]; invalidFrontMatters?: [ - InvalidFrontMatter: Record, + InvalidFrontMatter: {[key: string]: unknown}, ErrorMessage: string, ][]; }) { + // eslint-disable-next-line jest/require-top-level-describe test(`[${params.prefix}] accept valid values`, () => { params.validFrontMatters.forEach((frontMatter) => { expect(validateDocFrontMatter(frontMatter)).toEqual(frontMatter); }); }); + // eslint-disable-next-line jest/require-top-level-describe test(`[${params.prefix}] convert valid values`, () => { params.convertibleFrontMatter?.forEach( ([convertibleFrontMatter, convertedFrontMatter]) => { @@ -37,6 +39,7 @@ function testField(params: { ); }); + // eslint-disable-next-line jest/require-top-level-describe test(`[${params.prefix}] throw error for values`, () => { params.invalidFrontMatters?.forEach(([frontMatter, message]) => { try { @@ -51,21 +54,21 @@ function testField(params: { )}`, ), ); - } catch (e) { + } catch (err) { // eslint-disable-next-line jest/no-conditional-expect - expect(e.message).toMatch(new RegExp(escapeStringRegexp(message))); + expect(err.message).toMatch(new RegExp(escapeStringRegexp(message))); } }); }); } -describe('validateDocFrontMatter', () => { - test('accept empty object', () => { +describe('doc front matter schema', () => { + it('accepts empty object', () => { const frontMatter: DocFrontMatter = {}; expect(validateDocFrontMatter(frontMatter)).toEqual(frontMatter); }); - test('accept unknown field', () => { + it('accepts unknown field', () => { const frontMatter = {abc: '1'}; expect(validateDocFrontMatter(frontMatter)).toEqual(frontMatter); }); @@ -210,6 +213,19 @@ describe('validateDocFrontMatter sidebar_position', () => { }); }); +describe('validateDocFrontMatter sidebar_custom_props', () => { + testField({ + prefix: 'sidebar_custom_props', + validFrontMatters: [ + {sidebar_custom_props: {}}, + {sidebar_custom_props: {prop: 'custom', number: 1, boolean: true}}, + ], + invalidFrontMatters: [ + [{sidebar_custom_props: ''}, 'must be of type object'], + ], + }); +}); + describe('validateDocFrontMatter custom_edit_url', () => { testField({ prefix: 'custom_edit_url', @@ -264,7 +280,7 @@ describe('validateDocFrontMatter tags', () => { }); }); -describe('validateDocFrontMatter toc_min_heading_level', () => { +describe('toc_min_heading_level', () => { testField({ prefix: 'toc_min_heading_level', validFrontMatters: [ @@ -300,7 +316,7 @@ describe('validateDocFrontMatter toc_min_heading_level', () => { }); }); -describe('validateDocFrontMatter toc_max_heading_level', () => { +describe('toc_max_heading_level', () => { testField({ prefix: 'toc_max_heading_level', validFrontMatters: [ @@ -336,7 +352,7 @@ describe('validateDocFrontMatter toc_max_heading_level', () => { }); }); -describe('validateDocFrontMatter toc min/max consistency', () => { +describe('toc min/max consistency', () => { testField({ prefix: 'toc min/max', validFrontMatters: [ diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/globalData.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/globalData.test.ts new file mode 100644 index 000000000000..2c9a02966317 --- /dev/null +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/globalData.test.ts @@ -0,0 +1,100 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {toGlobalDataVersion} from '../globalData'; + +describe('toGlobalDataVersion', () => { + it('generates the right docs, sidebars, and metadata', () => { + expect( + toGlobalDataVersion({ + versionName: 'current', + label: 'Label', + isLast: true, + path: '/current', + mainDocId: 'main', + docs: [ + { + unversionedId: 'main', + permalink: '/current/main', + sidebar: 'tutorial', + }, + { + unversionedId: 'doc', + permalink: '/current/doc', + sidebar: 'tutorial', + }, + ], + sidebars: { + another: [ + { + type: 'category', + label: 'Generated', + link: { + type: 'generated-index', + permalink: '/current/generated', + }, + items: [ + { + type: 'doc', + id: 'doc', + }, + ], + }, + ], + tutorial: [ + { + type: 'doc', + id: 'main', + }, + { + type: 'category', + label: 'Generated', + link: { + type: 'generated-index', + permalink: '/current/generated', + }, + items: [ + { + type: 'doc', + id: 'doc', + }, + ], + }, + ], + links: [ + { + type: 'link', + href: 'foo', + label: 'Foo', + }, + { + type: 'link', + href: 'bar', + label: 'Bar', + }, + ], + }, + categoryGeneratedIndices: [ + { + title: 'Generated', + slug: '/current/generated', + permalink: '/current/generated', + sidebar: 'tutorial', + }, + ], + banner: 'unreleased', + badge: true, + className: 'current-cls', + tagsPath: '/current/tags', + contentPath: '', + contentPathLocalized: '', + sidebarFilePath: '', + routePriority: 0.5, + }), + ).toMatchSnapshot(); + }); +}); diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/index.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/index.test.ts index e73118780689..926bcfb8f96f 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/index.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/index.test.ts @@ -5,10 +5,11 @@ * LICENSE file in the root directory of this source tree. */ +import {jest} from '@jest/globals'; import path from 'path'; import {isMatch} from 'picomatch'; import commander from 'commander'; -import {kebabCase, orderBy} from 'lodash'; +import _ from 'lodash'; import fs from 'fs-extra'; import pluginContentDocs from '../index'; @@ -16,10 +17,10 @@ import {loadContext} from '@docusaurus/core/src/server/index'; import {applyConfigureWebpack} from '@docusaurus/core/src/webpack/utils'; import type {RouteConfig} from '@docusaurus/types'; import {posixPath, DEFAULT_PLUGIN_ID} from '@docusaurus/utils'; -import {sortConfig} from '@docusaurus/core/src/server/plugins'; +import {sortConfig} from '@docusaurus/core/src/server/plugins/routeConfig'; import * as cliDocs from '../cli'; -import {OptionsSchema} from '../options'; +import {validateOptions} from '../options'; import {normalizePluginOptions} from '@docusaurus/utils-validation'; import type {LoadedVersion} from '../types'; import type { @@ -29,7 +30,7 @@ import type { } from '../sidebars/types'; import {toSidebarsProp} from '../props'; -import {validate} from 'webpack'; +import webpack from 'webpack'; import {DefaultSidebarItemsGenerator} from '../sidebars/generator'; import {DisabledSidebars} from '../sidebars'; @@ -51,7 +52,7 @@ Available ids are:\n- ${version.docs.map((d) => d.unversionedId).join('\n- ')}`, const createFakeActions = (contentDir: string) => { const routeConfigs: RouteConfig[] = []; - const dataContainer: Record = {}; + const dataContainer: {[key: string]: unknown} = {}; const globalDataContainer: {pluginName?: {pluginId: unknown}} = {}; const actions = { @@ -89,13 +90,14 @@ Entries created: checkVersionMetadataPropCreated: (version: LoadedVersion) => { const versionMetadataProp = getCreatedDataByPrefix( - `version-${kebabCase(version.versionName)}-metadata-prop`, + `version-${_.kebabCase(version.versionName)}-metadata-prop`, ); expect(versionMetadataProp.docsSidebars).toEqual(toSidebarsProp(version)); }, expectSnapshot: () => { - // Sort the route config like in src/server/plugins/index.ts for consistent snapshot ordering + // Sort the route config like in src/server/plugins/index.ts for + // consistent snapshot ordering sortConfig(routeConfigs); expect(routeConfigs).not.toEqual([]); expect(routeConfigs).toMatchSnapshot('route config'); @@ -111,28 +113,34 @@ Entries created: }; describe('sidebar', () => { - test('site with wrong sidebar content', async () => { + it('site with wrong sidebar content', async () => { const siteDir = path.join(__dirname, '__fixtures__', 'simple-site'); - const context = await loadContext(siteDir); + const context = await loadContext({siteDir}); const sidebarPath = path.join(siteDir, 'wrong-sidebars.json'); const plugin = await pluginContentDocs( context, - normalizePluginOptions(OptionsSchema, { - sidebarPath, + validateOptions({ + validate: normalizePluginOptions, + options: { + sidebarPath, + }, }), ); await expect(plugin.loadContent!()).rejects.toThrowErrorMatchingSnapshot(); }); - test('site with wrong sidebar file path', async () => { + it('site with wrong sidebar file path', async () => { const siteDir = path.join(__dirname, '__fixtures__', 'site-with-doc-label'); - const context = await loadContext(siteDir); + const context = await loadContext({siteDir}); await expect(async () => { const plugin = await pluginContentDocs( context, - normalizePluginOptions(OptionsSchema, { - sidebarPath: 'wrong-path-sidebar.json', + validateOptions({ + validate: normalizePluginOptions, + options: { + sidebarPath: 'wrong-path-sidebar.json', + }, }), ); await plugin.loadContent!(); @@ -145,42 +153,34 @@ describe('sidebar', () => { `); }); - test('site with undefined sidebar', async () => { + it('site with undefined sidebar', async () => { const siteDir = path.join(__dirname, '__fixtures__', 'site-with-doc-label'); - const context = await loadContext(siteDir); + const context = await loadContext({siteDir}); const plugin = await pluginContentDocs( context, - normalizePluginOptions(OptionsSchema, { - sidebarPath: undefined, + validateOptions({ + validate: normalizePluginOptions, + options: { + sidebarPath: undefined, + }, }), ); const result = await plugin.loadContent!(); expect(result.loadedVersions).toHaveLength(1); - expect(result.loadedVersions[0].sidebars).toMatchInlineSnapshot(` - Object { - "defaultSidebar": Array [ - Object { - "id": "hello-1", - "type": "doc", - }, - Object { - "id": "hello-2", - "label": "Hello 2 From Doc", - "type": "doc", - }, - ], - } - `); + expect(result.loadedVersions[0].sidebars).toMatchSnapshot(); }); - test('site with disabled sidebar', async () => { + it('site with disabled sidebar', async () => { const siteDir = path.join(__dirname, '__fixtures__', 'site-with-doc-label'); - const context = await loadContext(siteDir); + const context = await loadContext({siteDir}); const plugin = await pluginContentDocs( context, - normalizePluginOptions(OptionsSchema, { - sidebarPath: false, + validateOptions({ + validate: normalizePluginOptions, + options: { + sidebarPath: false, + }, }), ); const result = await plugin.loadContent!(); @@ -193,12 +193,12 @@ describe('sidebar', () => { describe('empty/no docs website', () => { const siteDir = path.join(__dirname, '__fixtures__', 'empty-site'); - test('no files in docs folder', async () => { - const context = await loadContext(siteDir); + it('no files in docs folder', async () => { + const context = await loadContext({siteDir}); await fs.ensureDir(path.join(siteDir, 'docs')); const plugin = await pluginContentDocs( context, - normalizePluginOptions(OptionsSchema, {}), + validateOptions({validate: normalizePluginOptions, options: {}}), ); await expect( plugin.loadContent!(), @@ -207,21 +207,20 @@ describe('empty/no docs website', () => { ); }); - test('docs folder does not exist', async () => { - const context = await loadContext(siteDir); + it('docs folder does not exist', async () => { + const context = await loadContext({siteDir}); await expect( pluginContentDocs( context, - normalizePluginOptions(OptionsSchema, { - path: `path/doesnt/exist`, + validateOptions({ + validate: normalizePluginOptions, + options: { + path: 'path/does/not/exist', + }, }), ), - ).rejects.toThrowError( - `The docs folder does not exist for version "current". A docs folder is expected to be found at ${ - process.platform === 'win32' - ? 'path\\doesnt\\exist' - : 'path/doesnt/exist' - }.`, + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"The docs folder does not exist for version \\"current\\". A docs folder is expected to be found at path/does/not/exist."`, ); }); }); @@ -229,13 +228,16 @@ describe('empty/no docs website', () => { describe('simple website', () => { async function loadSite() { const siteDir = path.join(__dirname, '__fixtures__', 'simple-site'); - const context = await loadContext(siteDir); + const context = await loadContext({siteDir}); const sidebarPath = path.join(siteDir, 'sidebars.json'); const plugin = await pluginContentDocs( context, - normalizePluginOptions(OptionsSchema, { - path: 'docs', - sidebarPath, + validateOptions({ + validate: normalizePluginOptions, + options: { + path: 'docs', + sidebarPath, + }, }), ); const pluginContentDir = path.join(context.generatedFilesDir, plugin.name); @@ -243,13 +245,14 @@ describe('simple website', () => { return {siteDir, context, sidebarPath, plugin, pluginContentDir}; } - test('extendCli - docsVersion', async () => { + it('extendCli - docsVersion', async () => { const {siteDir, sidebarPath, plugin} = await loadSite(); const mock = jest .spyOn(cliDocs, 'cliDocsVersionCommand') - .mockImplementation(); + .mockImplementation(async () => {}); const cli = new commander.Command(); - // @ts-expect-error: TODO annoying type incompatibility + // @ts-expect-error: in actual usage, we pass the static commander instead + // of the new command plugin.extendCli!(cli); cli.parse(['node', 'test', 'docs:version', '1.0.0']); expect(mock).toHaveBeenCalledTimes(1); @@ -262,36 +265,28 @@ describe('simple website', () => { mock.mockRestore(); }); - test('getPathToWatch', async () => { + it('getPathToWatch', async () => { const {siteDir, plugin} = await loadSite(); const pathToWatch = plugin.getPathsToWatch!(); const matchPattern = pathToWatch.map((filepath) => posixPath(path.relative(siteDir, filepath)), ); - expect(matchPattern).not.toEqual([]); - expect(matchPattern).toMatchInlineSnapshot(` - Array [ - "sidebars.json", - "i18n/en/docusaurus-plugin-content-docs/current/**/*.{md,mdx}", - "docs/**/*.{md,mdx}", - "docs/**/_category_.{json,yml,yaml}", - ] - `); - expect(isMatch('docs/hello.md', matchPattern)).toEqual(true); - expect(isMatch('docs/hello.mdx', matchPattern)).toEqual(true); - expect(isMatch('docs/foo/bar.md', matchPattern)).toEqual(true); - expect(isMatch('docs/hello.js', matchPattern)).toEqual(false); - expect(isMatch('docs/super.mdl', matchPattern)).toEqual(false); - expect(isMatch('docs/mdx', matchPattern)).toEqual(false); - expect(isMatch('docs/headingAsTitle.md', matchPattern)).toEqual(true); - expect(isMatch('sidebars.json', matchPattern)).toEqual(true); - expect(isMatch('versioned_docs/hello.md', matchPattern)).toEqual(false); - expect(isMatch('hello.md', matchPattern)).toEqual(false); - expect(isMatch('super/docs/hello.md', matchPattern)).toEqual(false); + expect(matchPattern).toMatchSnapshot(); + expect(isMatch('docs/hello.md', matchPattern)).toBe(true); + expect(isMatch('docs/hello.mdx', matchPattern)).toBe(true); + expect(isMatch('docs/foo/bar.md', matchPattern)).toBe(true); + expect(isMatch('docs/hello.js', matchPattern)).toBe(false); + expect(isMatch('docs/super.mdl', matchPattern)).toBe(false); + expect(isMatch('docs/mdx', matchPattern)).toBe(false); + expect(isMatch('docs/headingAsTitle.md', matchPattern)).toBe(true); + expect(isMatch('sidebars.json', matchPattern)).toBe(true); + expect(isMatch('versioned_docs/hello.md', matchPattern)).toBe(false); + expect(isMatch('hello.md', matchPattern)).toBe(false); + expect(isMatch('super/docs/hello.md', matchPattern)).toBe(false); }); - test('configureWebpack', async () => { + it('configureWebpack', async () => { const {plugin} = await loadSite(); const content = await plugin.loadContent?.(); @@ -309,14 +304,14 @@ describe('simple website', () => { undefined, content, ); - const errors = validate(config); + const errors = webpack.validate(config); expect(errors).toBeUndefined(); }); - test('content', async () => { + it('content', async () => { const {plugin, pluginContentDir} = await loadSite(); const content = await plugin.loadContent!(); - expect(content.loadedVersions.length).toEqual(1); + expect(content.loadedVersions).toHaveLength(1); const [currentVersion] = content.loadedVersions; expect(findDocById(currentVersion, 'foo/baz')).toMatchSnapshot(); @@ -346,14 +341,17 @@ describe('simple website', () => { describe('versioned website', () => { async function loadSite() { const siteDir = path.join(__dirname, '__fixtures__', 'versioned-site'); - const context = await loadContext(siteDir); + const context = await loadContext({siteDir}); const sidebarPath = path.join(siteDir, 'sidebars.json'); const routeBasePath = 'docs'; const plugin = await pluginContentDocs( context, - normalizePluginOptions(OptionsSchema, { - routeBasePath, - sidebarPath, + validateOptions({ + validate: normalizePluginOptions, + options: { + routeBasePath, + sidebarPath, + }, }), ); const pluginContentDir = path.join(context.generatedFilesDir, plugin.name); @@ -367,13 +365,14 @@ describe('versioned website', () => { }; } - test('extendCli - docsVersion', async () => { + it('extendCli - docsVersion', async () => { const {siteDir, routeBasePath, sidebarPath, plugin} = await loadSite(); const mock = jest .spyOn(cliDocs, 'cliDocsVersionCommand') - .mockImplementation(); + .mockImplementation(async () => {}); const cli = new commander.Command(); - // @ts-expect-error: TODO annoying type incompatibility + // @ts-expect-error: in actual usage, we pass the static commander instead + // of the new command plugin.extendCli!(cli); cli.parse(['node', 'test', 'docs:version', '2.0.0']); expect(mock).toHaveBeenCalledTimes(1); @@ -386,69 +385,50 @@ describe('versioned website', () => { mock.mockRestore(); }); - test('getPathToWatch', async () => { + it('getPathToWatch', async () => { const {siteDir, plugin} = await loadSite(); const pathToWatch = plugin.getPathsToWatch!(); const matchPattern = pathToWatch.map((filepath) => posixPath(path.relative(siteDir, filepath)), ); expect(matchPattern).not.toEqual([]); - expect(matchPattern).toMatchInlineSnapshot(` - Array [ - "sidebars.json", - "i18n/en/docusaurus-plugin-content-docs/current/**/*.{md,mdx}", - "docs/**/*.{md,mdx}", - "docs/**/_category_.{json,yml,yaml}", - "versioned_sidebars/version-1.0.1-sidebars.json", - "i18n/en/docusaurus-plugin-content-docs/version-1.0.1/**/*.{md,mdx}", - "versioned_docs/version-1.0.1/**/*.{md,mdx}", - "versioned_docs/version-1.0.1/**/_category_.{json,yml,yaml}", - "versioned_sidebars/version-1.0.0-sidebars.json", - "i18n/en/docusaurus-plugin-content-docs/version-1.0.0/**/*.{md,mdx}", - "versioned_docs/version-1.0.0/**/*.{md,mdx}", - "versioned_docs/version-1.0.0/**/_category_.{json,yml,yaml}", - "versioned_sidebars/version-withSlugs-sidebars.json", - "i18n/en/docusaurus-plugin-content-docs/version-withSlugs/**/*.{md,mdx}", - "versioned_docs/version-withSlugs/**/*.{md,mdx}", - "versioned_docs/version-withSlugs/**/_category_.{json,yml,yaml}", - ] - `); - expect(isMatch('docs/hello.md', matchPattern)).toEqual(true); - expect(isMatch('docs/hello.mdx', matchPattern)).toEqual(true); - expect(isMatch('docs/foo/bar.md', matchPattern)).toEqual(true); - expect(isMatch('sidebars.json', matchPattern)).toEqual(true); - expect( - isMatch('versioned_docs/version-1.0.0/hello.md', matchPattern), - ).toEqual(true); + expect(matchPattern).toMatchSnapshot(); + expect(isMatch('docs/hello.md', matchPattern)).toBe(true); + expect(isMatch('docs/hello.mdx', matchPattern)).toBe(true); + expect(isMatch('docs/foo/bar.md', matchPattern)).toBe(true); + expect(isMatch('sidebars.json', matchPattern)).toBe(true); + expect(isMatch('versioned_docs/version-1.0.0/hello.md', matchPattern)).toBe( + true, + ); expect( isMatch('versioned_docs/version-1.0.0/foo/bar.md', matchPattern), - ).toEqual(true); + ).toBe(true); expect( isMatch('versioned_sidebars/version-1.0.0-sidebars.json', matchPattern), - ).toEqual(true); + ).toBe(true); // Non existing version expect( isMatch('versioned_docs/version-2.0.0/foo/bar.md', matchPattern), - ).toEqual(false); - expect( - isMatch('versioned_docs/version-2.0.0/hello.md', matchPattern), - ).toEqual(false); + ).toBe(false); + expect(isMatch('versioned_docs/version-2.0.0/hello.md', matchPattern)).toBe( + false, + ); expect( isMatch('versioned_sidebars/version-2.0.0-sidebars.json', matchPattern), - ).toEqual(false); + ).toBe(false); - expect(isMatch('docs/hello.js', matchPattern)).toEqual(false); - expect(isMatch('docs/super.mdl', matchPattern)).toEqual(false); - expect(isMatch('docs/mdx', matchPattern)).toEqual(false); - expect(isMatch('hello.md', matchPattern)).toEqual(false); - expect(isMatch('super/docs/hello.md', matchPattern)).toEqual(false); + expect(isMatch('docs/hello.js', matchPattern)).toBe(false); + expect(isMatch('docs/super.mdl', matchPattern)).toBe(false); + expect(isMatch('docs/mdx', matchPattern)).toBe(false); + expect(isMatch('hello.md', matchPattern)).toBe(false); + expect(isMatch('super/docs/hello.md', matchPattern)).toBe(false); }); - test('content', async () => { + it('content', async () => { const {plugin, pluginContentDir} = await loadSite(); const content = await plugin.loadContent!(); - expect(content.loadedVersions.length).toEqual(4); + expect(content.loadedVersions).toHaveLength(4); const [currentVersion, version101, version100, versionWithSlugs] = content.loadedVersions; @@ -490,17 +470,20 @@ describe('versioned website', () => { describe('versioned website (community)', () => { async function loadSite() { const siteDir = path.join(__dirname, '__fixtures__', 'versioned-site'); - const context = await loadContext(siteDir); + const context = await loadContext({siteDir}); const sidebarPath = path.join(siteDir, 'community_sidebars.json'); const routeBasePath = 'community'; const pluginId = 'community'; const plugin = await pluginContentDocs( context, - normalizePluginOptions(OptionsSchema, { - id: 'community', - path: 'community', - routeBasePath, - sidebarPath, + validateOptions({ + validate: normalizePluginOptions, + options: { + id: 'community', + path: 'community', + routeBasePath, + sidebarPath, + }, }), ); const pluginContentDir = path.join(context.generatedFilesDir, plugin.name); @@ -515,14 +498,15 @@ describe('versioned website (community)', () => { }; } - test('extendCli - docsVersion', async () => { + it('extendCli - docsVersion', async () => { const {siteDir, routeBasePath, sidebarPath, pluginId, plugin} = await loadSite(); const mock = jest .spyOn(cliDocs, 'cliDocsVersionCommand') - .mockImplementation(); + .mockImplementation(async () => {}); const cli = new commander.Command(); - // @ts-expect-error: TODO annoying type incompatibility + // @ts-expect-error: in actual usage, we pass the static commander instead + // of the new command plugin.extendCli!(cli); cli.parse(['node', 'test', `docs:version:${pluginId}`, '2.0.0']); expect(mock).toHaveBeenCalledTimes(1); @@ -535,51 +519,40 @@ describe('versioned website (community)', () => { mock.mockRestore(); }); - test('getPathToWatch', async () => { + it('getPathToWatch', async () => { const {siteDir, plugin} = await loadSite(); const pathToWatch = plugin.getPathsToWatch!(); const matchPattern = pathToWatch.map((filepath) => posixPath(path.relative(siteDir, filepath)), ); expect(matchPattern).not.toEqual([]); - expect(matchPattern).toMatchInlineSnapshot(` - Array [ - "community_sidebars.json", - "i18n/en/docusaurus-plugin-content-docs-community/current/**/*.{md,mdx}", - "community/**/*.{md,mdx}", - "community/**/_category_.{json,yml,yaml}", - "community_versioned_sidebars/version-1.0.0-sidebars.json", - "i18n/en/docusaurus-plugin-content-docs-community/version-1.0.0/**/*.{md,mdx}", - "community_versioned_docs/version-1.0.0/**/*.{md,mdx}", - "community_versioned_docs/version-1.0.0/**/_category_.{json,yml,yaml}", - ] - `); - expect(isMatch('community/team.md', matchPattern)).toEqual(true); + expect(matchPattern).toMatchSnapshot(); + expect(isMatch('community/team.md', matchPattern)).toBe(true); expect( isMatch('community_versioned_docs/version-1.0.0/team.md', matchPattern), - ).toEqual(true); + ).toBe(true); // Non existing version expect( isMatch('community_versioned_docs/version-2.0.0/team.md', matchPattern), - ).toEqual(false); + ).toBe(false); expect( isMatch( 'community_versioned_sidebars/version-2.0.0-sidebars.json', matchPattern, ), - ).toEqual(false); + ).toBe(false); - expect(isMatch('community/team.js', matchPattern)).toEqual(false); + expect(isMatch('community/team.js', matchPattern)).toBe(false); expect( isMatch('community_versioned_docs/version-1.0.0/team.js', matchPattern), - ).toEqual(false); + ).toBe(false); }); - test('content', async () => { + it('content', async () => { const {plugin, pluginContentDir} = await loadSite(); const content = await plugin.loadContent!(); - expect(content.loadedVersions.length).toEqual(2); + expect(content.loadedVersions).toHaveLength(2); const [currentVersion, version100] = content.loadedVersions; expect(getDocById(currentVersion, 'team')).toMatchSnapshot(); @@ -605,13 +578,16 @@ describe('versioned website (community)', () => { describe('site with doc label', () => { async function loadSite() { const siteDir = path.join(__dirname, '__fixtures__', 'site-with-doc-label'); - const context = await loadContext(siteDir); + const context = await loadContext({siteDir}); const sidebarPath = path.join(siteDir, 'sidebars.json'); const plugin = await pluginContentDocs( context, - normalizePluginOptions(OptionsSchema, { - path: 'docs', - sidebarPath, + validateOptions({ + validate: normalizePluginOptions, + options: { + path: 'docs', + sidebarPath, + }, }), ); @@ -620,7 +596,7 @@ describe('site with doc label', () => { return {content}; } - test('label in sidebar.json is used', async () => { + it('label in sidebar.json is used', async () => { const {content} = await loadSite(); const loadedVersion = content.loadedVersions[0]; const sidebarProps = toSidebarsProp(loadedVersion); @@ -628,7 +604,7 @@ describe('site with doc label', () => { expect(sidebarProps.docs[0].label).toBe('Hello One'); }); - test('sidebar_label in doc has higher precedence over label in sidebar.json', async () => { + it('sidebar_label in doc has higher precedence over label in sidebar.json', async () => { const {content} = await loadSite(); const loadedVersion = content.loadedVersions[0]; const sidebarProps = toSidebarsProp(loadedVersion); @@ -644,11 +620,14 @@ describe('site with full autogenerated sidebar', () => { '__fixtures__', 'site-with-autogenerated-sidebar', ); - const context = await loadContext(siteDir); + const context = await loadContext({siteDir}); const plugin = await pluginContentDocs( context, - normalizePluginOptions(OptionsSchema, { - path: 'docs', + validateOptions({ + validate: normalizePluginOptions, + options: { + path: 'docs', + }, }), ); @@ -657,14 +636,14 @@ describe('site with full autogenerated sidebar', () => { return {content, siteDir}; } - test('sidebar is fully autogenerated', async () => { + it('sidebar is fully autogenerated', async () => { const {content} = await loadSite(); const version = content.loadedVersions[0]; expect(version.sidebars).toMatchSnapshot(); }); - test('docs in fully generated sidebar have correct metadata', async () => { + it('docs in fully generated sidebar have correct metadata', async () => { const {content} = await loadSite(); const version = content.loadedVersions[0]; @@ -696,17 +675,20 @@ describe('site with partial autogenerated sidebars', () => { '__fixtures__', 'site-with-autogenerated-sidebar', ); - const context = await loadContext(siteDir, {}); + const context = await loadContext({siteDir}); const plugin = await pluginContentDocs( context, - normalizePluginOptions(OptionsSchema, { - path: 'docs', - sidebarPath: path.join( - __dirname, - '__fixtures__', - 'site-with-autogenerated-sidebar', - 'partialAutogeneratedSidebars.js', - ), + validateOptions({ + validate: normalizePluginOptions, + options: { + path: 'docs', + sidebarPath: path.join( + __dirname, + '__fixtures__', + 'site-with-autogenerated-sidebar', + 'partialAutogeneratedSidebars.js', + ), + }, }), ); @@ -715,18 +697,19 @@ describe('site with partial autogenerated sidebars', () => { return {content, siteDir}; } - test('sidebar is partially autogenerated', async () => { + it('sidebar is partially autogenerated', async () => { const {content} = await loadSite(); const version = content.loadedVersions[0]; expect(version.sidebars).toMatchSnapshot(); }); - test('docs in partially generated sidebar have correct metadata', async () => { + it('docs in partially generated sidebar have correct metadata', async () => { const {content} = await loadSite(); const version = content.loadedVersions[0]; - // Only looking at the docs of the autogen sidebar, others metadata should not be affected + // Only looking at the docs of the autogen sidebar, others metadata should + // not be affected expect(getDocById(version, 'API/api-end')).toMatchSnapshot(); expect(getDocById(version, 'API/api-overview')).toMatchSnapshot(); @@ -748,17 +731,20 @@ describe('site with partial autogenerated sidebars 2 (fix #4638)', () => { '__fixtures__', 'site-with-autogenerated-sidebar', ); - const context = await loadContext(siteDir, {}); + const context = await loadContext({siteDir}); const plugin = await pluginContentDocs( context, - normalizePluginOptions(OptionsSchema, { - path: 'docs', - sidebarPath: path.join( - __dirname, - '__fixtures__', - 'site-with-autogenerated-sidebar', - 'partialAutogeneratedSidebars2.js', - ), + validateOptions({ + validate: normalizePluginOptions, + options: { + path: 'docs', + sidebarPath: path.join( + __dirname, + '__fixtures__', + 'site-with-autogenerated-sidebar', + 'partialAutogeneratedSidebars2.js', + ), + }, }), ); @@ -767,7 +753,7 @@ describe('site with partial autogenerated sidebars 2 (fix #4638)', () => { return {content, siteDir}; } - test('sidebar is partially autogenerated', async () => { + it('sidebar is partially autogenerated', async () => { const {content} = await loadSite(); const version = content.loadedVersions[0]; @@ -782,34 +768,36 @@ describe('site with custom sidebar items generator', () => { '__fixtures__', 'site-with-autogenerated-sidebar', ); - const context = await loadContext(siteDir); + const context = await loadContext({siteDir}); const plugin = await pluginContentDocs( context, - normalizePluginOptions(OptionsSchema, { - path: 'docs', - sidebarItemsGenerator, + validateOptions({ + validate: normalizePluginOptions, + options: { + path: 'docs', + sidebarItemsGenerator, + }, }), ); const content = (await plugin.loadContent?.())!; return {content, siteDir}; } - test('sidebarItemsGenerator is called with appropriate data', async () => { - const customSidebarItemsGeneratorMock = jest.fn( - async (_arg: SidebarItemsGeneratorOptionArgs) => [], - ); + it('sidebarItemsGenerator is called with appropriate data', async () => { + const customSidebarItemsGeneratorMock = jest.fn(async () => []); const {siteDir} = await loadSite(customSidebarItemsGeneratorMock); const generatorArg: SidebarItemsGeneratorOptionArgs = customSidebarItemsGeneratorMock.mock.calls[0][0]; - // Make test pass even if docs are in different order and paths are absolutes + // Make test pass even if docs are in different order and paths are + // absolutes function makeDeterministic( arg: SidebarItemsGeneratorOptionArgs, ): SidebarItemsGeneratorOptionArgs { return { ...arg, - docs: orderBy(arg.docs, 'id'), + docs: _.orderBy(arg.docs, 'id'), version: { ...arg.version, contentPath: path.relative(siteDir, arg.version.contentPath), @@ -823,7 +811,7 @@ describe('site with custom sidebar items generator', () => { ); }); - test('sidebar is autogenerated according to a custom sidebarItemsGenerator', async () => { + it('sidebar is autogenerated according to a custom sidebarItemsGenerator', async () => { const customSidebarItemsGenerator: SidebarItemsGeneratorOption = async () => [ {type: 'doc', id: 'API/api-overview'}, @@ -836,7 +824,7 @@ describe('site with custom sidebar items generator', () => { expect(version.sidebars).toMatchSnapshot(); }); - test('sidebarItemsGenerator can wrap/enhance/sort/reverse the default sidebar generator', async () => { + it('sidebarItemsGenerator can wrap/enhance/sort/reverse the default sidebar generator', async () => { function reverseSidebarItems(items: SidebarItem[]): SidebarItem[] { const result: SidebarItem[] = items.map((item) => { if (item.type === 'category') { diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/lastUpdate.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/lastUpdate.test.ts index d90d3b2189f9..7f3e41bd195c 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/lastUpdate.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/lastUpdate.test.ts @@ -5,18 +5,20 @@ * LICENSE file in the root directory of this source tree. */ -import fs from 'fs'; +import {createTempRepo} from '@testing-utils/git'; +import {jest} from '@jest/globals'; +import fs from 'fs-extra'; import path from 'path'; import shell from 'shelljs'; import {getFileLastUpdate} from '../lastUpdate'; -describe('lastUpdate', () => { +describe('getFileLastUpdate', () => { const existingFilePath = path.join( __dirname, '__fixtures__/simple-site/docs/hello.md', ); - test('existing test file in repository with Git timestamp', async () => { + it('existing test file in repository with Git timestamp', async () => { const lastUpdateData = await getFileLastUpdate(existingFilePath); expect(lastUpdateData).not.toBeNull(); @@ -28,7 +30,7 @@ describe('lastUpdate', () => { expect(typeof timestamp).toBe('number'); }); - test('existing test file with spaces in path', async () => { + it('existing test file with spaces in path', async () => { const filePathWithSpace = path.join( __dirname, '__fixtures__/simple-site/docs/doc with space.md', @@ -44,8 +46,10 @@ describe('lastUpdate', () => { expect(typeof timestamp).toBe('number'); }); - test('non-existing file', async () => { - const consoleMock = jest.spyOn(console, 'error').mockImplementation(); + it('non-existing file', async () => { + const consoleMock = jest + .spyOn(console, 'warn') + .mockImplementation(() => {}); const nonExistingFileName = '.nonExisting'; const nonExistingFilePath = path.join( __dirname, @@ -55,23 +59,52 @@ describe('lastUpdate', () => { await expect(getFileLastUpdate(nonExistingFilePath)).resolves.toBeNull(); expect(consoleMock).toHaveBeenCalledTimes(1); expect(consoleMock).toHaveBeenLastCalledWith( - expect.stringMatching(/with exit code 128/), + expect.stringMatching(/because the file does not exist./), ); await expect(getFileLastUpdate(null)).resolves.toBeNull(); await expect(getFileLastUpdate(undefined)).resolves.toBeNull(); consoleMock.mockRestore(); }); - test('temporary created file that has no git timestamp', async () => { - const tempFilePath = path.join(__dirname, '__fixtures__', '.temp'); - fs.writeFileSync(tempFilePath, 'Lorem ipsum :)'); + it('temporary created file that is not tracked by git', async () => { + const consoleMock = jest + .spyOn(console, 'warn') + .mockImplementation(() => {}); + const {repoDir} = createTempRepo(); + const tempFilePath = path.join(repoDir, 'file.md'); + await fs.writeFile(tempFilePath, 'Lorem ipsum :)'); await expect(getFileLastUpdate(tempFilePath)).resolves.toBeNull(); - fs.unlinkSync(tempFilePath); + expect(consoleMock).toHaveBeenCalledTimes(1); + expect(consoleMock).toHaveBeenLastCalledWith( + expect.stringMatching(/not tracked by git./), + ); + await fs.unlink(tempFilePath); + }); + + it('multiple files not tracked by git', async () => { + const consoleMock = jest + .spyOn(console, 'warn') + .mockImplementation(() => {}); + const {repoDir} = createTempRepo(); + const tempFilePath1 = path.join(repoDir, 'file1.md'); + const tempFilePath2 = path.join(repoDir, 'file2.md'); + await fs.writeFile(tempFilePath1, 'Lorem ipsum :)'); + await fs.writeFile(tempFilePath2, 'Lorem ipsum :)'); + await expect(getFileLastUpdate(tempFilePath1)).resolves.toBeNull(); + await expect(getFileLastUpdate(tempFilePath2)).resolves.toBeNull(); + expect(consoleMock).toHaveBeenCalledTimes(1); + expect(consoleMock).toHaveBeenLastCalledWith( + expect.stringMatching(/not tracked by git./), + ); + await fs.unlink(tempFilePath1); + await fs.unlink(tempFilePath2); }); - test('Git does not exist', async () => { + it('git does not exist', async () => { const mock = jest.spyOn(shell, 'which').mockImplementationOnce(() => null); - const consoleMock = jest.spyOn(console, 'warn').mockImplementation(); + const consoleMock = jest + .spyOn(console, 'warn') + .mockImplementation(() => {}); const lastUpdateData = await getFileLastUpdate(existingFilePath); expect(lastUpdateData).toBeNull(); expect(consoleMock).toHaveBeenLastCalledWith( diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/numberPrefix.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/numberPrefix.test.ts index 6ca7081a345b..e1b7afd4c8a5 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/numberPrefix.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/numberPrefix.test.ts @@ -69,49 +69,41 @@ describe('stripNumberPrefix', () => { return stripNumberPrefix(str, DefaultNumberPrefixParser); } - test('should strip number prefix if present', () => { - expect(stripNumberPrefixDefault('1-My Doc')).toEqual('My Doc'); - expect(stripNumberPrefixDefault('01-My Doc')).toEqual('My Doc'); - expect(stripNumberPrefixDefault('001-My Doc')).toEqual('My Doc'); - expect(stripNumberPrefixDefault('001 - My Doc')).toEqual('My Doc'); - expect(stripNumberPrefixDefault('001 - My Doc')).toEqual('My Doc'); - expect(stripNumberPrefixDefault('999 - My Doc')).toEqual( - 'My Doc', - ); + it('strips number prefix if present', () => { + expect(stripNumberPrefixDefault('1-My Doc')).toBe('My Doc'); + expect(stripNumberPrefixDefault('01-My Doc')).toBe('My Doc'); + expect(stripNumberPrefixDefault('001-My Doc')).toBe('My Doc'); + expect(stripNumberPrefixDefault('001 - My Doc')).toBe('My Doc'); + expect(stripNumberPrefixDefault('001 - My Doc')).toBe('My Doc'); + expect(stripNumberPrefixDefault('999 - My Doc')).toBe('My Doc'); // - expect(stripNumberPrefixDefault('1---My Doc')).toEqual('My Doc'); - expect(stripNumberPrefixDefault('01---My Doc')).toEqual('My Doc'); - expect(stripNumberPrefixDefault('001---My Doc')).toEqual('My Doc'); - expect(stripNumberPrefixDefault('001 --- My Doc')).toEqual('My Doc'); - expect(stripNumberPrefixDefault('001 --- My Doc')).toEqual( - 'My Doc', - ); - expect(stripNumberPrefixDefault('999 --- My Doc')).toEqual( + expect(stripNumberPrefixDefault('1---My Doc')).toBe('My Doc'); + expect(stripNumberPrefixDefault('01---My Doc')).toBe('My Doc'); + expect(stripNumberPrefixDefault('001---My Doc')).toBe('My Doc'); + expect(stripNumberPrefixDefault('001 --- My Doc')).toBe('My Doc'); + expect(stripNumberPrefixDefault('001 --- My Doc')).toBe('My Doc'); + expect(stripNumberPrefixDefault('999 --- My Doc')).toBe( 'My Doc', ); // - expect(stripNumberPrefixDefault('1___My Doc')).toEqual('My Doc'); - expect(stripNumberPrefixDefault('01___My Doc')).toEqual('My Doc'); - expect(stripNumberPrefixDefault('001___My Doc')).toEqual('My Doc'); - expect(stripNumberPrefixDefault('001 ___ My Doc')).toEqual('My Doc'); - expect(stripNumberPrefixDefault('001 ___ My Doc')).toEqual( - 'My Doc', - ); - expect(stripNumberPrefixDefault('999 ___ My Doc')).toEqual( + expect(stripNumberPrefixDefault('1___My Doc')).toBe('My Doc'); + expect(stripNumberPrefixDefault('01___My Doc')).toBe('My Doc'); + expect(stripNumberPrefixDefault('001___My Doc')).toBe('My Doc'); + expect(stripNumberPrefixDefault('001 ___ My Doc')).toBe('My Doc'); + expect(stripNumberPrefixDefault('001 ___ My Doc')).toBe('My Doc'); + expect(stripNumberPrefixDefault('999 ___ My Doc')).toBe( 'My Doc', ); // - expect(stripNumberPrefixDefault('1.My Doc')).toEqual('My Doc'); - expect(stripNumberPrefixDefault('01.My Doc')).toEqual('My Doc'); - expect(stripNumberPrefixDefault('001.My Doc')).toEqual('My Doc'); - expect(stripNumberPrefixDefault('001 . My Doc')).toEqual('My Doc'); - expect(stripNumberPrefixDefault('001 . My Doc')).toEqual('My Doc'); - expect(stripNumberPrefixDefault('999 . My Doc')).toEqual( - 'My Doc', - ); + expect(stripNumberPrefixDefault('1.My Doc')).toBe('My Doc'); + expect(stripNumberPrefixDefault('01.My Doc')).toBe('My Doc'); + expect(stripNumberPrefixDefault('001.My Doc')).toBe('My Doc'); + expect(stripNumberPrefixDefault('001 . My Doc')).toBe('My Doc'); + expect(stripNumberPrefixDefault('001 . My Doc')).toBe('My Doc'); + expect(stripNumberPrefixDefault('999 . My Doc')).toBe('My Doc'); }); - test('should not strip number prefix if pattern does not match', () => { + it('does not strip number prefix if pattern does not match', () => { IgnoredNumberPrefixPatterns.forEach((badPattern) => { expect(stripNumberPrefixDefault(badPattern)).toEqual(badPattern); }); @@ -119,16 +111,16 @@ describe('stripNumberPrefix', () => { }); describe('stripPathNumberPrefix', () => { - test('should strip number prefixes in paths', () => { + it('strips number prefixes in paths', () => { expect( stripPathNumberPrefixes( '0-MyRootFolder0/1 - MySubFolder1/2. MyDeepFolder2/3 _MyDoc3', DefaultNumberPrefixParser, ), - ).toEqual('MyRootFolder0/MySubFolder1/MyDeepFolder2/MyDoc3'); + ).toBe('MyRootFolder0/MySubFolder1/MyDeepFolder2/MyDoc3'); }); - test('should strip number prefixes in paths with custom parser', () => { + it('strips number prefixes in paths with custom parser', () => { function stripPathNumberPrefixCustom(str: string) { return { filename: str.substring(1, str.length), @@ -138,21 +130,21 @@ describe('stripPathNumberPrefix', () => { expect( stripPathNumberPrefixes('aaaa/bbbb/cccc', stripPathNumberPrefixCustom), - ).toEqual('aaa/bbb/ccc'); + ).toBe('aaa/bbb/ccc'); }); - test('should strip number prefixes in paths with disabled parser', () => { + it('does not strip number prefixes in paths with disabled parser', () => { expect( stripPathNumberPrefixes( '0-MyRootFolder0/1 - MySubFolder1/2. MyDeepFolder2/3 _MyDoc3', DisabledNumberPrefixParser, ), - ).toEqual('0-MyRootFolder0/1 - MySubFolder1/2. MyDeepFolder2/3 _MyDoc3'); + ).toBe('0-MyRootFolder0/1 - MySubFolder1/2. MyDeepFolder2/3 _MyDoc3'); }); }); describe('DefaultNumberPrefixParser', () => { - test('should extract number prefix if present', () => { + it('extracts number prefix if present', () => { expect(DefaultNumberPrefixParser('0-My Doc')).toEqual({ filename: 'My Doc', numberPrefix: 0, @@ -188,7 +180,7 @@ describe('DefaultNumberPrefixParser', () => { }); }); - test('should not extract number prefix if pattern does not match', () => { + it('does not extract number prefix if pattern does not match', () => { IgnoredNumberPrefixPatterns.forEach((badPattern) => { expect(DefaultNumberPrefixParser(badPattern)).toEqual({ filename: badPattern, diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/options.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/options.test.ts index a4d94d4c36a5..8f21906faa5e 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/options.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/options.test.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import {OptionsSchema, DEFAULT_OPTIONS, validateOptions} from '../options'; +import {validateOptions, DEFAULT_OPTIONS} from '../options'; import {normalizePluginOptions} from '@docusaurus/utils-validation'; import {DefaultSidebarItemsGenerator} from '../sidebars/generator'; import { @@ -13,30 +13,29 @@ import { DisabledNumberPrefixParser, } from '../numberPrefix'; import {GlobExcludeDefault} from '@docusaurus/utils'; -import type {PluginOptions} from '@docusaurus/plugin-content-docs'; +import type {Options} from '@docusaurus/plugin-content-docs'; // the type of remark/rehype plugins is function const markdownPluginsFunctionStub = () => {}; const markdownPluginsObjectStub = {}; -function testValidateOptions(options: Partial) { - return validateOptions({ - options: { - ...DEFAULT_OPTIONS, - ...options, - }, - validate: normalizePluginOptions, - }); +function testValidate(options: Options) { + return validateOptions({validate: normalizePluginOptions, options}); } +const defaultOptions = { + ...DEFAULT_OPTIONS, + id: 'default', + // The admonitions plugin is automatically added. Not really worth testing + remarkPlugins: expect.any(Array), +}; + describe('normalizeDocsPluginOptions', () => { - test('should return default options for undefined user options', async () => { - const {value, error} = await OptionsSchema.validate({}); - expect(value).toEqual(DEFAULT_OPTIONS); - expect(error).toBe(undefined); + it('returns default options for undefined user options', async () => { + expect(testValidate({})).toEqual(defaultOptions); }); - test('should accept correctly defined user options', async () => { + it('accepts correctly defined user options', async () => { const userOptions = { path: 'my-docs', // Path to data on filesystem, relative to site dir. routeBasePath: 'my-docs', // URL Route. @@ -56,6 +55,7 @@ describe('normalizeDocsPluginOptions', () => { rehypePlugins: [markdownPluginsFunctionStub], beforeDefaultRehypePlugins: [], beforeDefaultRemarkPlugins: [], + breadcrumbs: true, showLastUpdateTime: true, showLastUpdateAuthor: true, admonitions: {}, @@ -76,14 +76,15 @@ describe('normalizeDocsPluginOptions', () => { sidebarCollapsible: false, sidebarCollapsed: false, }; - const {value, error} = await OptionsSchema.validate(userOptions); - expect(value).toEqual(userOptions); - expect(error).toBe(undefined); + expect(testValidate(userOptions)).toEqual({ + ...defaultOptions, + ...userOptions, + remarkPlugins: [...userOptions.remarkPlugins, expect.any(Array)], + }); }); - test('should accept correctly defined remark and rehype plugin options', async () => { + it('accepts correctly defined remark and rehype plugin options', async () => { const userOptions = { - ...DEFAULT_OPTIONS, beforeDefaultRemarkPlugins: [], beforeDefaultRehypePlugins: [markdownPluginsFunctionStub], remarkPlugins: [[markdownPluginsFunctionStub, {option1: '42'}]], @@ -92,85 +93,73 @@ describe('normalizeDocsPluginOptions', () => { [markdownPluginsFunctionStub, {option1: '42'}], ], }; - const {value, error} = await OptionsSchema.validate(userOptions); - expect(value).toEqual(userOptions); - expect(error).toBe(undefined); + expect(testValidate(userOptions)).toEqual({ + ...defaultOptions, + ...userOptions, + remarkPlugins: [...userOptions.remarkPlugins, expect.any(Array)], + }); }); - test('should accept admonitions false', async () => { + it('accepts admonitions false', async () => { const admonitionsFalse = { - ...DEFAULT_OPTIONS, admonitions: false, }; - const {value, error} = OptionsSchema.validate(admonitionsFalse); - expect(value).toEqual(admonitionsFalse); - expect(error).toBe(undefined); + expect(testValidate(admonitionsFalse)).toEqual({ + ...defaultOptions, + ...admonitionsFalse, + }); }); - test('should accept numberPrefixParser function', () => { + it('rejects admonitions true', async () => { + const admonitionsTrue = { + admonitions: true, + }; + expect(() => + testValidate(admonitionsTrue), + ).toThrowErrorMatchingInlineSnapshot( + `"\\"admonitions\\" contains an invalid value"`, + ); + }); + + it('accepts numberPrefixParser function', () => { function customNumberPrefixParser() {} expect( - normalizePluginOptions(OptionsSchema, { - ...DEFAULT_OPTIONS, - numberPrefixParser: customNumberPrefixParser, - }), + testValidate({numberPrefixParser: customNumberPrefixParser}), ).toEqual({ - ...DEFAULT_OPTIONS, - id: 'default', + ...defaultOptions, numberPrefixParser: customNumberPrefixParser, }); }); - test('should accept numberPrefixParser false', () => { - expect( - normalizePluginOptions(OptionsSchema, { - ...DEFAULT_OPTIONS, - numberPrefixParser: false, - }), - ).toEqual({ - ...DEFAULT_OPTIONS, - id: 'default', + it('accepts numberPrefixParser false', () => { + expect(testValidate({numberPrefixParser: false})).toEqual({ + ...defaultOptions, numberPrefixParser: DisabledNumberPrefixParser, }); }); - test('should accept numberPrefixParser true', () => { - expect( - normalizePluginOptions(OptionsSchema, { - ...DEFAULT_OPTIONS, - numberPrefixParser: true, - }), - ).toEqual({ - ...DEFAULT_OPTIONS, - id: 'default', + it('accepts numberPrefixParser true', () => { + expect(testValidate({numberPrefixParser: true})).toEqual({ + ...defaultOptions, numberPrefixParser: DefaultNumberPrefixParser, }); }); - test('should reject admonitions true', async () => { - const admonitionsTrue = { - ...DEFAULT_OPTIONS, - admonitions: true, - }; - const {error} = OptionsSchema.validate(admonitionsTrue); - expect(error).toMatchInlineSnapshot( - `[ValidationError: "admonitions" contains an invalid value]`, - ); - }); - - test('should reject invalid remark plugin options', () => { - expect(() => { - normalizePluginOptions(OptionsSchema, { + it('rejects invalid remark plugin options', () => { + expect(() => + testValidate({ remarkPlugins: [[{option1: '42'}, markdownPluginsFunctionStub]], - }); - }).toThrowErrorMatchingInlineSnapshot( - `"\\"remarkPlugins[0]\\" does not match any of the allowed types"`, - ); + }), + ).toThrowErrorMatchingInlineSnapshot(` + "\\"remarkPlugins[0]\\" does not look like a valid MDX plugin config. A plugin config entry should be one of: + - A tuple, like \`[require(\\"rehype-katex\\"), { strict: false }]\`, or + - A simple module, like \`require(\\"remark-math\\")\`" + `); }); - test('should reject invalid rehype plugin options', () => { - expect(() => { - normalizePluginOptions(OptionsSchema, { + it('rejects invalid rehype plugin options', () => { + expect(() => + testValidate({ rehypePlugins: [ [ markdownPluginsFunctionStub, @@ -178,61 +167,53 @@ describe('normalizeDocsPluginOptions', () => { markdownPluginsFunctionStub, ], ], - }); - }).toThrowErrorMatchingInlineSnapshot( - `"\\"rehypePlugins[0]\\" does not match any of the allowed types"`, - ); + }), + ).toThrowErrorMatchingInlineSnapshot(` + "\\"rehypePlugins[0]\\" does not look like a valid MDX plugin config. A plugin config entry should be one of: + - A tuple, like \`[require(\\"rehype-katex\\"), { strict: false }]\`, or + - A simple module, like \`require(\\"remark-math\\")\`" + `); }); - test('should reject bad path inputs', () => { - expect(() => { - normalizePluginOptions(OptionsSchema, { - path: 2, - }); - }).toThrowErrorMatchingInlineSnapshot(`"\\"path\\" must be a string"`); + it('rejects bad path inputs', () => { + expect(() => testValidate({path: 2})).toThrowErrorMatchingInlineSnapshot( + `"\\"path\\" must be a string"`, + ); }); - test('should reject bad include inputs', () => { - expect(() => { - normalizePluginOptions(OptionsSchema, { - include: '**/*.{md,mdx}', - }); - }).toThrowErrorMatchingInlineSnapshot(`"\\"include\\" must be an array"`); + it('rejects bad include inputs', () => { + expect(() => + testValidate({include: '**/*.{md,mdx}'}), + ).toThrowErrorMatchingInlineSnapshot(`"\\"include\\" must be an array"`); }); - test('should reject bad showLastUpdateTime inputs', () => { - expect(() => { - normalizePluginOptions(OptionsSchema, { - showLastUpdateTime: 'true', - }); - }).toThrowErrorMatchingInlineSnapshot( + it('rejects bad showLastUpdateTime inputs', () => { + expect(() => + testValidate({showLastUpdateTime: 'true'}), + ).toThrowErrorMatchingInlineSnapshot( `"\\"showLastUpdateTime\\" must be a boolean"`, ); }); - test('should reject bad remarkPlugins input', () => { - expect(() => { - normalizePluginOptions(OptionsSchema, { - remarkPlugins: 'remark-math', - }); - }).toThrowErrorMatchingInlineSnapshot( + it('rejects bad remarkPlugins input', () => { + expect(() => + testValidate({remarkPlugins: 'remark-math'}), + ).toThrowErrorMatchingInlineSnapshot( `"\\"remarkPlugins\\" must be an array"`, ); }); - test('should reject bad lastVersion', () => { - expect(() => { - normalizePluginOptions(OptionsSchema, { - lastVersion: false, - }); - }).toThrowErrorMatchingInlineSnapshot( + it('rejects bad lastVersion', () => { + expect(() => + testValidate({lastVersion: false}), + ).toThrowErrorMatchingInlineSnapshot( `"\\"lastVersion\\" must be a string"`, ); }); - test('should reject bad versions', () => { - expect(() => { - normalizePluginOptions(OptionsSchema, { + it('rejects bad versions', () => { + expect(() => + testValidate({ versions: { current: { hey: 3, @@ -242,35 +223,32 @@ describe('normalizeDocsPluginOptions', () => { label: 'world', }, }, - }); - }).toThrowErrorMatchingInlineSnapshot( + }), + ).toThrowErrorMatchingInlineSnapshot( `"\\"versions.current.hey\\" is not allowed"`, ); }); - test('should handle sidebarCollapsed option inconsistencies', () => { + it('handles sidebarCollapsed option inconsistencies', () => { expect( - testValidateOptions({ - ...DEFAULT_OPTIONS, + testValidate({ sidebarCollapsible: true, sidebarCollapsed: undefined, }).sidebarCollapsed, - ).toEqual(true); + ).toBe(true); expect( - testValidateOptions({ - ...DEFAULT_OPTIONS, + testValidate({ sidebarCollapsible: false, sidebarCollapsed: undefined, }).sidebarCollapsed, - ).toEqual(false); + ).toBe(false); expect( - testValidateOptions({ - ...DEFAULT_OPTIONS, + testValidate({ sidebarCollapsible: false, sidebarCollapsed: true, }).sidebarCollapsed, - ).toEqual(false); + ).toBe(false); }); }); diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/props.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/props.test.ts index 8a32f9d2463d..86bd7b88fc27 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/props.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/props.test.ts @@ -14,9 +14,9 @@ describe('toTagDocListProp', () => { const allTagsPath = '/all/tags'; - test('should work', () => { + it('works', () => { const tag: Tag = { - name: 'tag1', + label: 'tag1', permalink: '/tag1', docIds: ['id1', 'id3'], }; @@ -54,7 +54,7 @@ describe('toTagDocListProp', () => { expect(result).toEqual({ allTagsPath, - name: tag.name, + name: tag.label, permalink: tag.permalink, docs: [doc3, doc1], // docs sorted by title, ignore "id5" absence }); diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/slug.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/slug.test.ts index 04f2a93f3cd8..25ee077dad10 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/slug.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/slug.test.ts @@ -8,69 +8,69 @@ import getSlug from '../slug'; describe('getSlug', () => { - test('should default to dirname/id', () => { + it('defaults to dirname/id', () => { expect( getSlug({ baseID: 'doc', source: '@site/docs/dir/doc.md', sourceDirName: '/dir', }), - ).toEqual('/dir/doc'); + ).toBe('/dir/doc'); expect( getSlug({ baseID: 'doc', source: '@site/docs/dir/subdir/doc.md', sourceDirName: '/dir/subdir', }), - ).toEqual('/dir/subdir/doc'); + ).toBe('/dir/subdir/doc'); }); - test('should handle conventional doc indexes', () => { + it('handles conventional doc indexes', () => { expect( getSlug({ baseID: 'doc', source: '@site/docs/dir/subdir/index.md', sourceDirName: '/dir/subdir', }), - ).toEqual('/dir/subdir/'); + ).toBe('/dir/subdir/'); expect( getSlug({ baseID: 'doc', source: '@site/docs/dir/subdir/inDEx.mdx', sourceDirName: '/dir/subdir', }), - ).toEqual('/dir/subdir/'); + ).toBe('/dir/subdir/'); expect( getSlug({ baseID: 'doc', source: '@site/docs/dir/subdir/readme.md', sourceDirName: '/dir/subdir', }), - ).toEqual('/dir/subdir/'); + ).toBe('/dir/subdir/'); expect( getSlug({ baseID: 'doc', source: '@site/docs/dir/subdir/reADMe.mdx', sourceDirName: '/dir/subdir', }), - ).toEqual('/dir/subdir/'); + ).toBe('/dir/subdir/'); expect( getSlug({ baseID: 'doc', source: '@site/docs/dir/subdir/subdir.md', sourceDirName: '/dir/subdir', }), - ).toEqual('/dir/subdir/'); + ).toBe('/dir/subdir/'); expect( getSlug({ baseID: 'doc', source: '@site/docs/dir/subdir/suBDir.mdx', sourceDirName: '/dir/subdir', }), - ).toEqual('/dir/subdir/'); + ).toBe('/dir/subdir/'); }); - test('should ignore conventional doc index when explicit slug front matter is provided', () => { + it('ignores conventional doc index when explicit slug front matter is provided', () => { expect( getSlug({ baseID: 'doc', @@ -78,10 +78,10 @@ describe('getSlug', () => { sourceDirName: '/dir/subdir', frontMatterSlug: '/my/frontMatterSlug', }), - ).toEqual('/my/frontMatterSlug'); + ).toBe('/my/frontMatterSlug'); }); - test('can strip dir number prefixes', () => { + it('can strip dir number prefixes', () => { expect( getSlug({ baseID: 'doc', @@ -89,7 +89,7 @@ describe('getSlug', () => { sourceDirName: '/001-dir1/002-dir2', stripDirNumberPrefixes: true, }), - ).toEqual('/dir1/dir2/doc'); + ).toBe('/dir1/dir2/doc'); expect( getSlug({ baseID: 'doc', @@ -97,30 +97,51 @@ describe('getSlug', () => { sourceDirName: '/001-dir1/002-dir2', stripDirNumberPrefixes: false, }), - ).toEqual('/001-dir1/002-dir2/doc'); + ).toBe('/001-dir1/002-dir2/doc'); }); // See https://github.com/facebook/docusaurus/issues/3223 - test('should handle special chars in doc path', () => { + it('handles special chars in doc path', () => { expect( getSlug({ baseID: 'my dôc', source: '@site/docs/dir with spâce/hey $hello/doc.md', sourceDirName: '/dir with spâce/hey $hello', }), - ).toEqual('/dir with spâce/hey $hello/my dôc'); + ).toBe('/dir with spâce/hey $hello/my dôc'); }); - test('should handle current dir', () => { + it('throws for invalid routes', () => { + expect(() => + getSlug({ + baseID: 'my dôc', + source: '@site/docs/dir with spâce/hey $hello/doc.md', + sourceDirName: '/dir with spâce/hey $hello', + frontMatterSlug: '//', + }), + ).toThrowErrorMatchingInlineSnapshot(` + "We couldn't compute a valid slug for document with ID \\"my dôc\\" in \\"/dir with spâce/hey $hello\\" directory. + The slug we computed looks invalid: //. + Maybe your slug front matter is incorrect or there are special characters in the file path? + By using front matter to set a custom slug, you should be able to fix this error: + + --- + slug: /my/customDocPath + --- + " + `); + }); + + it('handles current dir', () => { expect( getSlug({baseID: 'doc', source: '@site/docs/doc.md', sourceDirName: '.'}), - ).toEqual('/doc'); + ).toBe('/doc'); expect( getSlug({baseID: 'doc', source: '@site/docs/doc.md', sourceDirName: '/'}), - ).toEqual('/doc'); + ).toBe('/doc'); }); - test('should resolve absolute slug front matter', () => { + it('resolves absolute slug front matter', () => { expect( getSlug({ baseID: 'any', @@ -128,7 +149,7 @@ describe('getSlug', () => { sourceDirName: '.', frontMatterSlug: '/abc/def', }), - ).toEqual('/abc/def'); + ).toBe('/abc/def'); expect( getSlug({ baseID: 'any', @@ -136,7 +157,7 @@ describe('getSlug', () => { sourceDirName: './any', frontMatterSlug: '/abc/def', }), - ).toEqual('/abc/def'); + ).toBe('/abc/def'); expect( getSlug({ baseID: 'any', @@ -144,10 +165,10 @@ describe('getSlug', () => { sourceDirName: './any/any', frontMatterSlug: '/abc/def', }), - ).toEqual('/abc/def'); + ).toBe('/abc/def'); }); - test('should resolve relative slug front matter', () => { + it('resolves relative slug front matter', () => { expect( getSlug({ baseID: 'any', @@ -155,7 +176,7 @@ describe('getSlug', () => { sourceDirName: '.', frontMatterSlug: 'abc/def', }), - ).toEqual('/abc/def'); + ).toBe('/abc/def'); expect( getSlug({ baseID: 'any', @@ -163,15 +184,15 @@ describe('getSlug', () => { sourceDirName: '/dir', frontMatterSlug: 'abc/def', }), - ).toEqual('/dir/abc/def'); + ).toBe('/dir/abc/def'); expect( getSlug({ baseID: 'any', - source: '@site/docs/unslashedDir/doc.md', - sourceDirName: 'unslashedDir', + source: '@site/docs/nonSlashedDir/doc.md', + sourceDirName: 'nonSlashedDir', frontMatterSlug: 'abc/def', }), - ).toEqual('/unslashedDir/abc/def'); + ).toBe('/nonSlashedDir/abc/def'); expect( getSlug({ baseID: 'any', @@ -179,7 +200,7 @@ describe('getSlug', () => { sourceDirName: 'dir/subdir', frontMatterSlug: 'abc/def', }), - ).toEqual('/dir/subdir/abc/def'); + ).toBe('/dir/subdir/abc/def'); expect( getSlug({ baseID: 'any', @@ -187,7 +208,7 @@ describe('getSlug', () => { sourceDirName: '/dir', frontMatterSlug: './abc/def', }), - ).toEqual('/dir/abc/def'); + ).toBe('/dir/abc/def'); expect( getSlug({ baseID: 'any', @@ -195,7 +216,7 @@ describe('getSlug', () => { sourceDirName: '/dir', frontMatterSlug: './abc/../def', }), - ).toEqual('/dir/def'); + ).toBe('/dir/def'); expect( getSlug({ baseID: 'any', @@ -203,14 +224,14 @@ describe('getSlug', () => { sourceDirName: '/dir/subdir', frontMatterSlug: '../abc/def', }), - ).toEqual('/dir/abc/def'); + ).toBe('/dir/abc/def'); expect( getSlug({ baseID: 'any', - source: '@site/docs/dir/subdirdoc.md', + source: '@site/docs/dir/subdirDoc.md', sourceDirName: '/dir/subdir', frontMatterSlug: '../../../../../abc/../def', }), - ).toEqual('/def'); + ).toBe('/def'); }); }); diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/translations.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/translations.test.ts index 0c70c715c55f..85af2ef203e0 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/translations.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/translations.test.ts @@ -5,12 +5,13 @@ * LICENSE file in the root directory of this source tree. */ -import type {LoadedContent, DocMetadata, LoadedVersion} from '../types'; +import type {LoadedContent, LoadedVersion} from '../types'; import {CURRENT_VERSION_NAME} from '../constants'; import { getLoadedContentTranslationFiles, translateLoadedContent, } from '../translations'; +import type {DocMetadata} from '@docusaurus/plugin-content-docs'; import {updateTranslationFileMessages} from '@docusaurus/utils'; function createSampleDoc(doc: Pick): DocMetadata { @@ -36,8 +37,8 @@ function createSampleVersion( version: Pick, ): LoadedVersion { return { - versionLabel: `${version.versionName} label`, - versionPath: '/docs/', + label: `${version.versionName} label`, + path: '/docs/', mainDocId: '', routePriority: undefined, sidebarFilePath: 'any', @@ -45,21 +46,11 @@ function createSampleVersion( contentPath: 'any', contentPathLocalized: 'any', docs: [ - createSampleDoc({ - id: 'doc1', - }), - createSampleDoc({ - id: 'doc2', - }), - createSampleDoc({ - id: 'doc3', - }), - createSampleDoc({ - id: 'doc4', - }), - createSampleDoc({ - id: 'doc5', - }), + createSampleDoc({id: 'doc1'}), + createSampleDoc({id: 'doc2'}), + createSampleDoc({id: 'doc3'}), + createSampleDoc({id: 'doc4'}), + createSampleDoc({id: 'doc5'}), ], sidebars: { docs: [ @@ -142,20 +133,20 @@ function getSampleTranslationFilesTranslated() { } describe('getLoadedContentTranslationFiles', () => { - test('should return translation files matching snapshot', async () => { + it('returns translation files', async () => { expect(getSampleTranslationFiles()).toMatchSnapshot(); }); }); describe('translateLoadedContent', () => { - test('should not translate anything if translation files are untranslated', () => { + it('does not translate anything if translation files are untranslated', () => { const translationFiles = getSampleTranslationFiles(); expect( translateLoadedContent(SampleLoadedContent, translationFiles), ).toEqual(SampleLoadedContent); }); - test('should return translated loaded content matching snapshot', () => { + it('returns translated loaded content', () => { const translationFiles = getSampleTranslationFilesTranslated(); expect( translateLoadedContent(SampleLoadedContent, translationFiles), diff --git a/packages/docusaurus-plugin-content-docs/src/__tests__/versions.test.ts b/packages/docusaurus-plugin-content-docs/src/__tests__/versions.test.ts index 3ac367db6860..5209f7dfea05 100644 --- a/packages/docusaurus-plugin-content-docs/src/__tests__/versions.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/__tests__/versions.test.ts @@ -5,6 +5,7 @@ * LICENSE file in the root directory of this source tree. */ +import {jest} from '@jest/globals'; import path from 'path'; import { getVersionsFilePath, @@ -14,9 +15,11 @@ import { } from '../versions'; import {DEFAULT_OPTIONS} from '../options'; import {DEFAULT_PLUGIN_ID} from '@docusaurus/utils'; -import type {VersionMetadata} from '../types'; import type {I18n} from '@docusaurus/types'; -import type {PluginOptions} from '@docusaurus/plugin-content-docs'; +import type { + PluginOptions, + VersionMetadata, +} from '@docusaurus/plugin-content-docs'; const DefaultI18N: I18n = { currentLocale: 'en', @@ -25,8 +28,8 @@ const DefaultI18N: I18n = { localeConfigs: {}, }; -describe('version paths', () => { - test('getVersionsFilePath', () => { +describe('getVersionsFilePath', () => { + it('works', () => { expect(getVersionsFilePath('someSiteDir', DEFAULT_PLUGIN_ID)).toBe( `someSiteDir${path.sep}versions.json`, ); @@ -34,8 +37,10 @@ describe('version paths', () => { `otherSite${path.sep}dir${path.sep}pluginId_versions.json`, ); }); +}); - test('getVersionedDocsDirPath', () => { +describe('getVersionedDocsDirPath', () => { + it('works', () => { expect(getVersionedDocsDirPath('someSiteDir', DEFAULT_PLUGIN_ID)).toBe( `someSiteDir${path.sep}versioned_docs`, ); @@ -43,8 +48,10 @@ describe('version paths', () => { `otherSite${path.sep}dir${path.sep}pluginId_versioned_docs`, ); }); +}); - test('getVersionedSidebarsDirPath', () => { +describe('getVersionedSidebarsDirPath', () => { + it('works', () => { expect(getVersionedSidebarsDirPath('someSiteDir', DEFAULT_PLUGIN_ID)).toBe( `someSiteDir${path.sep}versioned_sidebars`, ); @@ -54,692 +61,722 @@ describe('version paths', () => { }); }); -describe('simple site', () => { - async function loadSite() { - const simpleSiteDir = path.resolve( - path.join(__dirname, '__fixtures__', 'simple-site'), - ); - const defaultOptions: PluginOptions = { - id: DEFAULT_PLUGIN_ID, - ...DEFAULT_OPTIONS, - }; - const defaultContext = { - siteDir: simpleSiteDir, - baseUrl: '/', - i18n: DefaultI18N, - }; - - const vCurrent: VersionMetadata = { - contentPath: path.join(simpleSiteDir, 'docs'), - contentPathLocalized: path.join( - simpleSiteDir, - 'i18n/en/docusaurus-plugin-content-docs/current', - ), - isLast: true, - routePriority: -1, - sidebarFilePath: undefined, - tagsPath: '/docs/tags', - versionLabel: 'Next', - versionName: 'current', - versionPath: '/docs', - versionBanner: null, - versionBadge: false, - versionClassName: 'docs-version-current', - }; - return {simpleSiteDir, defaultOptions, defaultContext, vCurrent}; - } - - test('readVersionsMetadata simple site', async () => { - const {defaultOptions, defaultContext, vCurrent} = await loadSite(); - - const versionsMetadata = await readVersionsMetadata({ - options: defaultOptions, - context: defaultContext, - }); - - expect(versionsMetadata).toEqual([vCurrent]); - }); - - test('readVersionsMetadata simple site with base url', async () => { - const {defaultOptions, defaultContext, vCurrent} = await loadSite(); +describe('readVersionsMetadata', () => { + describe('simple site', () => { + async function loadSite() { + const simpleSiteDir = path.resolve( + path.join(__dirname, '__fixtures__', 'simple-site'), + ); + const defaultOptions: PluginOptions = { + id: DEFAULT_PLUGIN_ID, + ...DEFAULT_OPTIONS, + }; + const defaultContext = { + siteDir: simpleSiteDir, + baseUrl: '/', + i18n: DefaultI18N, + }; + + const vCurrent: VersionMetadata = { + contentPath: path.join(simpleSiteDir, 'docs'), + contentPathLocalized: path.join( + simpleSiteDir, + 'i18n/en/docusaurus-plugin-content-docs/current', + ), + isLast: true, + routePriority: -1, + sidebarFilePath: undefined, + tagsPath: '/docs/tags', + label: 'Next', + versionName: 'current', + path: '/docs', + banner: null, + badge: false, + className: 'docs-version-current', + }; + return {simpleSiteDir, defaultOptions, defaultContext, vCurrent}; + } + + it('works', async () => { + const {defaultOptions, defaultContext, vCurrent} = await loadSite(); + + const versionsMetadata = await readVersionsMetadata({ + options: defaultOptions, + context: defaultContext, + }); - const versionsMetadata = await readVersionsMetadata({ - options: defaultOptions, - context: { - ...defaultContext, - baseUrl: '/myBaseUrl', - }, + expect(versionsMetadata).toEqual([vCurrent]); }); - expect(versionsMetadata).toEqual([ - { - ...vCurrent, - versionPath: '/myBaseUrl/docs', - tagsPath: '/myBaseUrl/docs/tags', - }, - ]); - }); + it('works with base url', async () => { + const {defaultOptions, defaultContext, vCurrent} = await loadSite(); - test('readVersionsMetadata simple site with current version config', async () => { - const {defaultOptions, defaultContext, vCurrent} = await loadSite(); + const versionsMetadata = await readVersionsMetadata({ + options: defaultOptions, + context: { + ...defaultContext, + baseUrl: '/myBaseUrl', + }, + }); - const versionsMetadata = await readVersionsMetadata({ - options: { - ...defaultOptions, - versions: { - current: { - label: 'current-label', - path: 'current-path', - }, + expect(versionsMetadata).toEqual([ + { + ...vCurrent, + path: '/myBaseUrl/docs', + tagsPath: '/myBaseUrl/docs/tags', }, - }, - context: { - ...defaultContext, - baseUrl: '/myBaseUrl', - }, + ]); }); - expect(versionsMetadata).toEqual([ - { - ...vCurrent, - versionPath: '/myBaseUrl/docs/current-path', - versionLabel: 'current-label', - routePriority: undefined, - sidebarFilePath: undefined, - tagsPath: '/myBaseUrl/docs/current-path/tags', - versionEditUrl: undefined, - versionEditUrlLocalized: undefined, - }, - ]); - }); - - test('readVersionsMetadata simple site with unknown lastVersion should throw', async () => { - const {defaultOptions, defaultContext} = await loadSite(); + it('works with current version config', async () => { + const {defaultOptions, defaultContext, vCurrent} = await loadSite(); - await expect( - readVersionsMetadata({ - options: {...defaultOptions, lastVersion: 'unknownVersionName'}, - context: defaultContext, - }), - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Docs option lastVersion: unknownVersionName is invalid. Available version names are: current"`, - ); - }); - - test('readVersionsMetadata simple site with unknown version configurations should throw', async () => { - const {defaultOptions, defaultContext} = await loadSite(); - - await expect( - readVersionsMetadata({ + const versionsMetadata = await readVersionsMetadata({ options: { ...defaultOptions, versions: { - current: {label: 'current'}, - unknownVersionName1: {label: 'unknownVersionName1'}, - unknownVersionName2: {label: 'unknownVersionName2'}, + current: { + label: 'current-label', + path: 'current-path', + }, }, }, - context: defaultContext, - }), - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Invalid docs option \\"versions\\": unknown versions (unknownVersionName1,unknownVersionName2) found. Available version names are: current"`, - ); - }); - - test('readVersionsMetadata simple site with disableVersioning while single version should throw', async () => { - const {defaultOptions, defaultContext} = await loadSite(); - - await expect( - readVersionsMetadata({ - options: {...defaultOptions, disableVersioning: true}, - context: defaultContext, - }), - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Docs: using \\"disableVersioning: true\\" option on a non-versioned site does not make sense."`, - ); - }); - - test('readVersionsMetadata simple site without including current version should throw', async () => { - const {defaultOptions, defaultContext} = await loadSite(); - - await expect( - readVersionsMetadata({ - options: {...defaultOptions, includeCurrentVersion: false}, - context: defaultContext, - }), - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"It is not possible to use docs without any version. Please check the configuration of these options: \\"includeCurrentVersion: false\\", \\"disableVersioning: false\\"."`, - ); - }); -}); - -describe('versioned site, pluginId=default', () => { - async function loadSite() { - const versionedSiteDir = path.resolve( - path.join(__dirname, '__fixtures__', 'versioned-site'), - ); - const defaultOptions: PluginOptions = { - id: DEFAULT_PLUGIN_ID, - ...DEFAULT_OPTIONS, - sidebarPath: 'sidebars.json', - }; - const defaultContext = { - siteDir: versionedSiteDir, - baseUrl: '/', - i18n: DefaultI18N, - }; - - const vCurrent: VersionMetadata = { - contentPath: path.join(versionedSiteDir, 'docs'), - contentPathLocalized: path.join( - versionedSiteDir, - 'i18n/en/docusaurus-plugin-content-docs/current', - ), - isLast: false, - routePriority: undefined, - sidebarFilePath: path.join(versionedSiteDir, 'sidebars.json'), - tagsPath: '/docs/next/tags', - versionLabel: 'Next', - versionName: 'current', - versionPath: '/docs/next', - versionBanner: 'unreleased', - versionBadge: true, - versionClassName: 'docs-version-current', - }; - - const v101: VersionMetadata = { - contentPath: path.join(versionedSiteDir, 'versioned_docs/version-1.0.1'), - contentPathLocalized: path.join( - versionedSiteDir, - 'i18n/en/docusaurus-plugin-content-docs/version-1.0.1', - ), - isLast: true, - routePriority: -1, - sidebarFilePath: path.join( - versionedSiteDir, - 'versioned_sidebars/version-1.0.1-sidebars.json', - ), - tagsPath: '/docs/tags', - versionLabel: '1.0.1', - versionName: '1.0.1', - versionPath: '/docs', - versionBanner: null, - versionBadge: true, - versionClassName: 'docs-version-1.0.1', - }; - - const v100: VersionMetadata = { - contentPath: path.join(versionedSiteDir, 'versioned_docs/version-1.0.0'), - contentPathLocalized: path.join( - versionedSiteDir, - 'i18n/en/docusaurus-plugin-content-docs/version-1.0.0', - ), - isLast: false, - routePriority: undefined, - sidebarFilePath: path.join( - versionedSiteDir, - 'versioned_sidebars/version-1.0.0-sidebars.json', - ), - tagsPath: '/docs/1.0.0/tags', - versionLabel: '1.0.0', - versionName: '1.0.0', - versionPath: '/docs/1.0.0', - versionBanner: 'unmaintained', - versionBadge: true, - versionClassName: 'docs-version-1.0.0', - }; - - const vwithSlugs: VersionMetadata = { - contentPath: path.join( - versionedSiteDir, - 'versioned_docs/version-withSlugs', - ), - contentPathLocalized: path.join( - versionedSiteDir, - 'i18n/en/docusaurus-plugin-content-docs/version-withSlugs', - ), - isLast: false, - routePriority: undefined, - sidebarFilePath: path.join( - versionedSiteDir, - 'versioned_sidebars/version-withSlugs-sidebars.json', - ), - tagsPath: '/docs/withSlugs/tags', - versionLabel: 'withSlugs', - versionName: 'withSlugs', - versionPath: '/docs/withSlugs', - versionBanner: 'unmaintained', - versionBadge: true, - versionClassName: 'docs-version-withSlugs', - }; - - return { - versionedSiteDir, - defaultOptions, - defaultContext, - vCurrent, - v101, - v100, - vwithSlugs, - }; - } - - test('readVersionsMetadata versioned site', async () => { - const {defaultOptions, defaultContext, vCurrent, v101, v100, vwithSlugs} = - await loadSite(); - - const versionsMetadata = await readVersionsMetadata({ - options: defaultOptions, - context: defaultContext, + context: { + ...defaultContext, + baseUrl: '/myBaseUrl', + }, + }); + + expect(versionsMetadata).toEqual([ + { + ...vCurrent, + path: '/myBaseUrl/docs/current-path', + label: 'current-label', + routePriority: undefined, + sidebarFilePath: undefined, + tagsPath: '/myBaseUrl/docs/current-path/tags', + editUrl: undefined, + editUrlLocalized: undefined, + }, + ]); }); - expect(versionsMetadata).toEqual([vCurrent, v101, v100, vwithSlugs]); - }); - - test('readVersionsMetadata versioned site with includeCurrentVersion=false', async () => { - const {defaultOptions, defaultContext, v101, v100, vwithSlugs} = - await loadSite(); + it('throws with unknown lastVersion', async () => { + const {defaultOptions, defaultContext} = await loadSite(); + + await expect( + readVersionsMetadata({ + options: {...defaultOptions, lastVersion: 'unknownVersionName'}, + context: defaultContext, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Docs option lastVersion: unknownVersionName is invalid. Available version names are: current"`, + ); + }); - const versionsMetadata = await readVersionsMetadata({ - options: {...defaultOptions, includeCurrentVersion: false}, - context: defaultContext, + it('throws with unknown version configurations', async () => { + const {defaultOptions, defaultContext} = await loadSite(); + + await expect( + readVersionsMetadata({ + options: { + ...defaultOptions, + versions: { + current: {label: 'current'}, + unknownVersionName1: {label: 'unknownVersionName1'}, + unknownVersionName2: {label: 'unknownVersionName2'}, + }, + }, + context: defaultContext, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Invalid docs option \\"versions\\": unknown versions (unknownVersionName1,unknownVersionName2) found. Available version names are: current"`, + ); }); - expect(versionsMetadata).toEqual([ - // vCurrent removed - v101, - v100, - vwithSlugs, - ]); - }); + it('throws with disableVersioning while single version', async () => { + const {defaultOptions, defaultContext} = await loadSite(); + + await expect( + readVersionsMetadata({ + options: {...defaultOptions, disableVersioning: true}, + context: defaultContext, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Docs: using \\"disableVersioning: true\\" option on a non-versioned site does not make sense."`, + ); + }); - test('readVersionsMetadata versioned site with version options', async () => { - const {defaultOptions, defaultContext, vCurrent, v101, v100, vwithSlugs} = - await loadSite(); - - const versionsMetadata = await readVersionsMetadata({ - options: { - ...defaultOptions, - lastVersion: '1.0.0', - versions: { - current: { - path: 'current-path', - banner: 'unmaintained', - badge: false, - className: 'custom-current-className', - }, - '1.0.0': { - label: '1.0.0-label', - banner: 'unreleased', - }, - }, - }, - context: defaultContext, + it('throws without including current version', async () => { + const {defaultOptions, defaultContext} = await loadSite(); + + await expect( + readVersionsMetadata({ + options: {...defaultOptions, includeCurrentVersion: false}, + context: defaultContext, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"It is not possible to use docs without any version. Please check the configuration of these options: \\"includeCurrentVersion: false\\", \\"disableVersioning: false\\"."`, + ); }); + }); - expect(versionsMetadata).toEqual([ - { - ...vCurrent, - tagsPath: '/docs/current-path/tags', - versionPath: '/docs/current-path', - versionBanner: 'unmaintained', - versionBadge: false, - versionClassName: 'custom-current-className', - }, - { - ...v101, + describe('versioned site, pluginId=default', () => { + async function loadSite() { + const versionedSiteDir = path.resolve( + path.join(__dirname, '__fixtures__', 'versioned-site'), + ); + const defaultOptions: PluginOptions = { + id: DEFAULT_PLUGIN_ID, + ...DEFAULT_OPTIONS, + sidebarPath: 'sidebars.json', + }; + const defaultContext = { + siteDir: versionedSiteDir, + baseUrl: '/', + i18n: DefaultI18N, + }; + + const vCurrent: VersionMetadata = { + contentPath: path.join(versionedSiteDir, 'docs'), + contentPathLocalized: path.join( + versionedSiteDir, + 'i18n/en/docusaurus-plugin-content-docs/current', + ), isLast: false, routePriority: undefined, - tagsPath: '/docs/1.0.1/tags', - versionPath: '/docs/1.0.1', - versionBanner: 'unreleased', - }, - { - ...v100, + sidebarFilePath: path.join(versionedSiteDir, 'sidebars.json'), + tagsPath: '/docs/next/tags', + label: 'Next', + versionName: 'current', + path: '/docs/next', + banner: 'unreleased', + badge: true, + className: 'docs-version-current', + }; + + const v101: VersionMetadata = { + contentPath: path.join( + versionedSiteDir, + 'versioned_docs/version-1.0.1', + ), + contentPathLocalized: path.join( + versionedSiteDir, + 'i18n/en/docusaurus-plugin-content-docs/version-1.0.1', + ), isLast: true, routePriority: -1, + sidebarFilePath: path.join( + versionedSiteDir, + 'versioned_sidebars/version-1.0.1-sidebars.json', + ), tagsPath: '/docs/tags', - versionLabel: '1.0.0-label', - versionPath: '/docs', - versionBanner: 'unreleased', - }, - vwithSlugs, - ]); - }); - - test('readVersionsMetadata versioned site with editUrl', async () => { - const {defaultOptions, defaultContext, vCurrent, v101, v100, vwithSlugs} = - await loadSite(); - - const versionsMetadata = await readVersionsMetadata({ - options: { - ...defaultOptions, - editUrl: 'https://github.com/facebook/docusaurus/edit/main/website/', - }, - context: defaultContext, - }); - - expect(versionsMetadata).toEqual([ - { - ...vCurrent, - versionEditUrl: - 'https://github.com/facebook/docusaurus/edit/main/website/docs', - versionEditUrlLocalized: - 'https://github.com/facebook/docusaurus/edit/main/website/i18n/en/docusaurus-plugin-content-docs/current', - }, - { - ...v101, - versionEditUrl: - 'https://github.com/facebook/docusaurus/edit/main/website/versioned_docs/version-1.0.1', - versionEditUrlLocalized: - 'https://github.com/facebook/docusaurus/edit/main/website/i18n/en/docusaurus-plugin-content-docs/version-1.0.1', - }, - { - ...v100, - versionEditUrl: - 'https://github.com/facebook/docusaurus/edit/main/website/versioned_docs/version-1.0.0', - versionEditUrlLocalized: - 'https://github.com/facebook/docusaurus/edit/main/website/i18n/en/docusaurus-plugin-content-docs/version-1.0.0', - }, - { - ...vwithSlugs, - versionEditUrl: - 'https://github.com/facebook/docusaurus/edit/main/website/versioned_docs/version-withSlugs', - versionEditUrlLocalized: - 'https://github.com/facebook/docusaurus/edit/main/website/i18n/en/docusaurus-plugin-content-docs/version-withSlugs', - }, - ]); - }); - - test('readVersionsMetadata versioned site with editUrl and editCurrentVersion=true', async () => { - const {defaultOptions, defaultContext, vCurrent, v101, v100, vwithSlugs} = - await loadSite(); - - const versionsMetadata = await readVersionsMetadata({ - options: { - ...defaultOptions, - editUrl: 'https://github.com/facebook/docusaurus/edit/main/website/', - editCurrentVersion: true, - }, - context: defaultContext, - }); - - expect(versionsMetadata).toEqual([ - { - ...vCurrent, - versionEditUrl: - 'https://github.com/facebook/docusaurus/edit/main/website/docs', - versionEditUrlLocalized: - 'https://github.com/facebook/docusaurus/edit/main/website/i18n/en/docusaurus-plugin-content-docs/current', - }, - { - ...v101, - versionEditUrl: - 'https://github.com/facebook/docusaurus/edit/main/website/docs', - versionEditUrlLocalized: - 'https://github.com/facebook/docusaurus/edit/main/website/i18n/en/docusaurus-plugin-content-docs/current', - }, - { - ...v100, - versionEditUrl: - 'https://github.com/facebook/docusaurus/edit/main/website/docs', - versionEditUrlLocalized: - 'https://github.com/facebook/docusaurus/edit/main/website/i18n/en/docusaurus-plugin-content-docs/current', - }, - { - ...vwithSlugs, - versionEditUrl: - 'https://github.com/facebook/docusaurus/edit/main/website/docs', - versionEditUrlLocalized: - 'https://github.com/facebook/docusaurus/edit/main/website/i18n/en/docusaurus-plugin-content-docs/current', - }, - ]); - }); - - test('readVersionsMetadata versioned site with onlyIncludeVersions option', async () => { - const {defaultOptions, defaultContext, v101, vwithSlugs} = await loadSite(); + label: '1.0.1', + versionName: '1.0.1', + path: '/docs', + banner: null, + badge: true, + className: 'docs-version-1.0.1', + }; + + const v100: VersionMetadata = { + contentPath: path.join( + versionedSiteDir, + 'versioned_docs/version-1.0.0', + ), + contentPathLocalized: path.join( + versionedSiteDir, + 'i18n/en/docusaurus-plugin-content-docs/version-1.0.0', + ), + isLast: false, + routePriority: undefined, + sidebarFilePath: path.join( + versionedSiteDir, + 'versioned_sidebars/version-1.0.0-sidebars.json', + ), + tagsPath: '/docs/1.0.0/tags', + label: '1.0.0', + versionName: '1.0.0', + path: '/docs/1.0.0', + banner: 'unmaintained', + badge: true, + className: 'docs-version-1.0.0', + }; + + const vWithSlugs: VersionMetadata = { + contentPath: path.join( + versionedSiteDir, + 'versioned_docs/version-withSlugs', + ), + contentPathLocalized: path.join( + versionedSiteDir, + 'i18n/en/docusaurus-plugin-content-docs/version-withSlugs', + ), + isLast: false, + routePriority: undefined, + sidebarFilePath: path.join( + versionedSiteDir, + 'versioned_sidebars/version-withSlugs-sidebars.json', + ), + tagsPath: '/docs/withSlugs/tags', + label: 'withSlugs', + versionName: 'withSlugs', + path: '/docs/withSlugs', + banner: 'unmaintained', + badge: true, + className: 'docs-version-withSlugs', + }; + + return { + versionedSiteDir, + defaultOptions, + defaultContext, + vCurrent, + v101, + v100, + vWithSlugs, + }; + } + + it('works', async () => { + const {defaultOptions, defaultContext, vCurrent, v101, v100, vWithSlugs} = + await loadSite(); + + const versionsMetadata = await readVersionsMetadata({ + options: defaultOptions, + context: defaultContext, + }); - const versionsMetadata = await readVersionsMetadata({ - options: { - ...defaultOptions, - // Order reversed on purpose: should not have any impact - onlyIncludeVersions: [vwithSlugs.versionName, v101.versionName], - }, - context: defaultContext, + expect(versionsMetadata).toEqual([vCurrent, v101, v100, vWithSlugs]); }); - expect(versionsMetadata).toEqual([v101, vwithSlugs]); - }); - - test('readVersionsMetadata versioned site with disableVersioning', async () => { - const {defaultOptions, defaultContext, vCurrent} = await loadSite(); + it('works with includeCurrentVersion=false', async () => { + const {defaultOptions, defaultContext, v101, v100, vWithSlugs} = + await loadSite(); - const versionsMetadata = await readVersionsMetadata({ - options: {...defaultOptions, disableVersioning: true}, - context: defaultContext, + const versionsMetadata = await readVersionsMetadata({ + options: {...defaultOptions, includeCurrentVersion: false}, + context: defaultContext, + }); + + expect(versionsMetadata).toEqual([ + // vCurrent removed + v101, + v100, + vWithSlugs, + ]); }); - expect(versionsMetadata).toEqual([ - { - ...vCurrent, - isLast: true, - routePriority: -1, - tagsPath: '/docs/tags', - versionPath: '/docs', - versionBanner: null, - versionBadge: false, - }, - ]); - }); - - test('readVersionsMetadata versioned site with all versions disabled', async () => { - const {defaultOptions, defaultContext} = await loadSite(); + it('works with version options', async () => { + const {defaultOptions, defaultContext, vCurrent, v101, v100, vWithSlugs} = + await loadSite(); - await expect( - readVersionsMetadata({ + const versionsMetadata = await readVersionsMetadata({ options: { ...defaultOptions, - includeCurrentVersion: false, - disableVersioning: true, + lastVersion: '1.0.0', + versions: { + current: { + path: 'current-path', + banner: 'unmaintained', + badge: false, + className: 'custom-current-className', + }, + '1.0.0': { + label: '1.0.0-label', + banner: 'unreleased', + }, + }, }, context: defaultContext, - }), - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"It is not possible to use docs without any version. Please check the configuration of these options: \\"includeCurrentVersion: false\\", \\"disableVersioning: true\\"."`, - ); - }); + }); + + expect(versionsMetadata).toEqual([ + { + ...vCurrent, + tagsPath: '/docs/current-path/tags', + path: '/docs/current-path', + banner: 'unmaintained', + badge: false, + className: 'custom-current-className', + }, + { + ...v101, + isLast: false, + routePriority: undefined, + tagsPath: '/docs/1.0.1/tags', + path: '/docs/1.0.1', + banner: 'unreleased', + }, + { + ...v100, + isLast: true, + routePriority: -1, + tagsPath: '/docs/tags', + label: '1.0.0-label', + path: '/docs', + banner: 'unreleased', + }, + vWithSlugs, + ]); + }); - test('readVersionsMetadata versioned site with empty onlyIncludeVersions', async () => { - const {defaultOptions, defaultContext} = await loadSite(); + it('works with editUrl', async () => { + const {defaultOptions, defaultContext, vCurrent, v101, v100, vWithSlugs} = + await loadSite(); - await expect( - readVersionsMetadata({ + const versionsMetadata = await readVersionsMetadata({ options: { ...defaultOptions, - onlyIncludeVersions: [], + editUrl: 'https://github.com/facebook/docusaurus/edit/main/website/', }, context: defaultContext, - }), - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Invalid docs option \\"onlyIncludeVersions\\": an empty array is not allowed, at least one version is needed."`, - ); - }); + }); + + expect(versionsMetadata).toEqual([ + { + ...vCurrent, + editUrl: + 'https://github.com/facebook/docusaurus/edit/main/website/docs', + editUrlLocalized: + 'https://github.com/facebook/docusaurus/edit/main/website/i18n/en/docusaurus-plugin-content-docs/current', + }, + { + ...v101, + editUrl: + 'https://github.com/facebook/docusaurus/edit/main/website/versioned_docs/version-1.0.1', + editUrlLocalized: + 'https://github.com/facebook/docusaurus/edit/main/website/i18n/en/docusaurus-plugin-content-docs/version-1.0.1', + }, + { + ...v100, + editUrl: + 'https://github.com/facebook/docusaurus/edit/main/website/versioned_docs/version-1.0.0', + editUrlLocalized: + 'https://github.com/facebook/docusaurus/edit/main/website/i18n/en/docusaurus-plugin-content-docs/version-1.0.0', + }, + { + ...vWithSlugs, + editUrl: + 'https://github.com/facebook/docusaurus/edit/main/website/versioned_docs/version-withSlugs', + editUrlLocalized: + 'https://github.com/facebook/docusaurus/edit/main/website/i18n/en/docusaurus-plugin-content-docs/version-withSlugs', + }, + ]); + }); - test('readVersionsMetadata versioned site with unknown versions in onlyIncludeVersions', async () => { - const {defaultOptions, defaultContext} = await loadSite(); + it('works with editUrl and editCurrentVersion=true', async () => { + const {defaultOptions, defaultContext, vCurrent, v101, v100, vWithSlugs} = + await loadSite(); - await expect( - readVersionsMetadata({ + const versionsMetadata = await readVersionsMetadata({ options: { ...defaultOptions, - onlyIncludeVersions: ['unknownVersion1', 'unknownVersion2'], + editUrl: 'https://github.com/facebook/docusaurus/edit/main/website/', + editCurrentVersion: true, }, context: defaultContext, - }), - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Invalid docs option \\"onlyIncludeVersions\\": unknown versions (unknownVersion1,unknownVersion2) found. Available version names are: current, 1.0.1, 1.0.0, withSlugs"`, - ); - }); + }); + + expect(versionsMetadata).toEqual([ + { + ...vCurrent, + editUrl: + 'https://github.com/facebook/docusaurus/edit/main/website/docs', + editUrlLocalized: + 'https://github.com/facebook/docusaurus/edit/main/website/i18n/en/docusaurus-plugin-content-docs/current', + }, + { + ...v101, + editUrl: + 'https://github.com/facebook/docusaurus/edit/main/website/docs', + editUrlLocalized: + 'https://github.com/facebook/docusaurus/edit/main/website/i18n/en/docusaurus-plugin-content-docs/current', + }, + { + ...v100, + editUrl: + 'https://github.com/facebook/docusaurus/edit/main/website/docs', + editUrlLocalized: + 'https://github.com/facebook/docusaurus/edit/main/website/i18n/en/docusaurus-plugin-content-docs/current', + }, + { + ...vWithSlugs, + editUrl: + 'https://github.com/facebook/docusaurus/edit/main/website/docs', + editUrlLocalized: + 'https://github.com/facebook/docusaurus/edit/main/website/i18n/en/docusaurus-plugin-content-docs/current', + }, + ]); + }); - test('readVersionsMetadata versioned site with lastVersion not in onlyIncludeVersions', async () => { - const {defaultOptions, defaultContext} = await loadSite(); + it('works with onlyIncludeVersions option', async () => { + const {defaultOptions, defaultContext, v101, vWithSlugs} = + await loadSite(); - await expect( - readVersionsMetadata({ + const versionsMetadata = await readVersionsMetadata({ options: { ...defaultOptions, - lastVersion: '1.0.1', - onlyIncludeVersions: ['current', '1.0.0'], + // Order reversed on purpose: should not have any impact + onlyIncludeVersions: [vWithSlugs.versionName, v101.versionName], }, context: defaultContext, - }), - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Invalid docs option \\"lastVersion\\": if you use both the \\"onlyIncludeVersions\\" and \\"lastVersion\\" options, then \\"lastVersion\\" must be present in the provided \\"onlyIncludeVersions\\" array."`, - ); - }); + }); - test('readVersionsMetadata versioned site with invalid versions.json file', async () => { - const {defaultOptions, defaultContext} = await loadSite(); + expect(versionsMetadata).toEqual([v101, vWithSlugs]); + }); - const mock = jest.spyOn(JSON, 'parse').mockImplementationOnce(() => ({ - invalid: 'json', - })); + it('works with disableVersioning', async () => { + const {defaultOptions, defaultContext, vCurrent} = await loadSite(); - await expect( - readVersionsMetadata({ - options: defaultOptions, + const versionsMetadata = await readVersionsMetadata({ + options: {...defaultOptions, disableVersioning: true}, context: defaultContext, - }), - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"The versions file should contain an array of versions! Found content: {\\"invalid\\":\\"json\\"}"`, - ); - mock.mockRestore(); - }); -}); + }); + + expect(versionsMetadata).toEqual([ + { + ...vCurrent, + isLast: true, + routePriority: -1, + tagsPath: '/docs/tags', + path: '/docs', + banner: null, + badge: false, + }, + ]); + }); -describe('versioned site, pluginId=community', () => { - async function loadSite() { - const versionedSiteDir = path.resolve( - path.join(__dirname, '__fixtures__', 'versioned-site'), - ); - const defaultOptions: PluginOptions = { - ...DEFAULT_OPTIONS, - id: 'community', - path: 'community', - routeBasePath: 'communityBasePath', - sidebarPath: 'sidebars.json', - }; - const defaultContext = { - siteDir: versionedSiteDir, - baseUrl: '/', - i18n: DefaultI18N, - }; - - const vCurrent: VersionMetadata = { - contentPath: path.join(versionedSiteDir, 'community'), - contentPathLocalized: path.join( - versionedSiteDir, - 'i18n/en/docusaurus-plugin-content-docs-community/current', - ), - isLast: false, - routePriority: undefined, - sidebarFilePath: path.join(versionedSiteDir, 'sidebars.json'), - tagsPath: '/communityBasePath/next/tags', - versionLabel: 'Next', - versionName: 'current', - versionPath: '/communityBasePath/next', - versionBanner: 'unreleased', - versionBadge: true, - versionClassName: 'docs-version-current', - }; - - const v100: VersionMetadata = { - contentPath: path.join( - versionedSiteDir, - 'community_versioned_docs/version-1.0.0', - ), - contentPathLocalized: path.join( - versionedSiteDir, - 'i18n/en/docusaurus-plugin-content-docs-community/version-1.0.0', - ), - isLast: true, - routePriority: -1, - sidebarFilePath: path.join( - versionedSiteDir, - 'community_versioned_sidebars/version-1.0.0-sidebars.json', - ), - tagsPath: '/communityBasePath/tags', - versionLabel: '1.0.0', - versionName: '1.0.0', - versionPath: '/communityBasePath', - versionBanner: null, - versionBadge: true, - versionClassName: 'docs-version-1.0.0', - }; - - return {versionedSiteDir, defaultOptions, defaultContext, vCurrent, v100}; - } - - test('readVersionsMetadata versioned site (community)', async () => { - const {defaultOptions, defaultContext, vCurrent, v100} = await loadSite(); - - const versionsMetadata = await readVersionsMetadata({ - options: defaultOptions, - context: defaultContext, + it('throws with all versions disabled', async () => { + const {defaultOptions, defaultContext} = await loadSite(); + + await expect( + readVersionsMetadata({ + options: { + ...defaultOptions, + includeCurrentVersion: false, + disableVersioning: true, + }, + context: defaultContext, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"It is not possible to use docs without any version. Please check the configuration of these options: \\"includeCurrentVersion: false\\", \\"disableVersioning: true\\"."`, + ); }); - expect(versionsMetadata).toEqual([vCurrent, v100]); - }); + it('throws with empty onlyIncludeVersions', async () => { + const {defaultOptions, defaultContext} = await loadSite(); - test('readVersionsMetadata versioned site (community) with includeCurrentVersion=false', async () => { - const {defaultOptions, defaultContext, v100} = await loadSite(); + await expect( + readVersionsMetadata({ + options: { + ...defaultOptions, + onlyIncludeVersions: [], + }, + context: defaultContext, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Invalid docs option \\"onlyIncludeVersions\\": an empty array is not allowed, at least one version is needed."`, + ); + }); + + it('throws with unknown versions in onlyIncludeVersions', async () => { + const {defaultOptions, defaultContext} = await loadSite(); - const versionsMetadata = await readVersionsMetadata({ - options: {...defaultOptions, includeCurrentVersion: false}, - context: defaultContext, + await expect( + readVersionsMetadata({ + options: { + ...defaultOptions, + onlyIncludeVersions: ['unknownVersion1', 'unknownVersion2'], + }, + context: defaultContext, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Invalid docs option \\"onlyIncludeVersions\\": unknown versions (unknownVersion1,unknownVersion2) found. Available version names are: current, 1.0.1, 1.0.0, withSlugs"`, + ); }); - expect(versionsMetadata).toEqual([ - // vCurrent removed - {...v100, versionBadge: false}, - ]); - }); + it('throws with lastVersion not in onlyIncludeVersions', async () => { + const {defaultOptions, defaultContext} = await loadSite(); - test('readVersionsMetadata versioned site (community) with disableVersioning', async () => { - const {defaultOptions, defaultContext, vCurrent} = await loadSite(); + await expect( + readVersionsMetadata({ + options: { + ...defaultOptions, + lastVersion: '1.0.1', + onlyIncludeVersions: ['current', '1.0.0'], + }, + context: defaultContext, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Invalid docs option \\"lastVersion\\": if you use both the \\"onlyIncludeVersions\\" and \\"lastVersion\\" options, then \\"lastVersion\\" must be present in the provided \\"onlyIncludeVersions\\" array."`, + ); + }); - const versionsMetadata = await readVersionsMetadata({ - options: {...defaultOptions, disableVersioning: true}, - context: defaultContext, + it('throws with invalid versions.json file', async () => { + const {defaultOptions, defaultContext} = await loadSite(); + + const jsonMock = jest.spyOn(JSON, 'parse'); + jsonMock.mockImplementationOnce(() => ({ + invalid: 'json', + })); + + await expect( + readVersionsMetadata({ + options: defaultOptions, + context: defaultContext, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"The versions file should contain an array of version names! Found content: {\\"invalid\\":\\"json\\"}"`, + ); + jsonMock.mockImplementationOnce(() => [1.1]); + + await expect( + readVersionsMetadata({ + options: defaultOptions, + context: defaultContext, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Versions should be strings. Found type \\"number\\" for version \\"1.1\\"."`, + ); + jsonMock.mockImplementationOnce(() => [' ']); + + await expect( + readVersionsMetadata({ + options: defaultOptions, + context: defaultContext, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Invalid version \\" \\"."`, + ); + jsonMock.mockRestore(); }); + }); - expect(versionsMetadata).toEqual([ - { - ...vCurrent, + describe('versioned site, pluginId=community', () => { + async function loadSite() { + const versionedSiteDir = path.resolve( + path.join(__dirname, '__fixtures__', 'versioned-site'), + ); + const defaultOptions: PluginOptions = { + ...DEFAULT_OPTIONS, + id: 'community', + path: 'community', + routeBasePath: 'communityBasePath', + sidebarPath: 'sidebars.json', + }; + const defaultContext = { + siteDir: versionedSiteDir, + baseUrl: '/', + i18n: DefaultI18N, + }; + + const vCurrent: VersionMetadata = { + contentPath: path.join(versionedSiteDir, 'community'), + contentPathLocalized: path.join( + versionedSiteDir, + 'i18n/en/docusaurus-plugin-content-docs-community/current', + ), + isLast: false, + routePriority: undefined, + sidebarFilePath: path.join(versionedSiteDir, 'sidebars.json'), + tagsPath: '/communityBasePath/next/tags', + label: 'Next', + versionName: 'current', + path: '/communityBasePath/next', + banner: 'unreleased', + badge: true, + className: 'docs-version-current', + }; + + const v100: VersionMetadata = { + contentPath: path.join( + versionedSiteDir, + 'community_versioned_docs/version-1.0.0', + ), + contentPathLocalized: path.join( + versionedSiteDir, + 'i18n/en/docusaurus-plugin-content-docs-community/version-1.0.0', + ), isLast: true, routePriority: -1, + sidebarFilePath: path.join( + versionedSiteDir, + 'community_versioned_sidebars/version-1.0.0-sidebars.json', + ), tagsPath: '/communityBasePath/tags', - versionPath: '/communityBasePath', - versionBanner: null, - versionBadge: false, - }, - ]); - }); + label: '1.0.0', + versionName: '1.0.0', + path: '/communityBasePath', + banner: null, + badge: true, + className: 'docs-version-1.0.0', + }; - test('readVersionsMetadata versioned site (community) with all versions disabled', async () => { - const {defaultOptions, defaultContext} = await loadSite(); + return {versionedSiteDir, defaultOptions, defaultContext, vCurrent, v100}; + } - await expect( - readVersionsMetadata({ - options: { - ...defaultOptions, - includeCurrentVersion: false, - disableVersioning: true, - }, + it('works', async () => { + const {defaultOptions, defaultContext, vCurrent, v100} = await loadSite(); + + const versionsMetadata = await readVersionsMetadata({ + options: defaultOptions, context: defaultContext, - }), - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"It is not possible to use docs without any version. Please check the configuration of these options: \\"includeCurrentVersion: false\\", \\"disableVersioning: true\\"."`, - ); + }); + + expect(versionsMetadata).toEqual([vCurrent, v100]); + }); + + it('works with includeCurrentVersion=false', async () => { + const {defaultOptions, defaultContext, v100} = await loadSite(); + + const versionsMetadata = await readVersionsMetadata({ + options: {...defaultOptions, includeCurrentVersion: false}, + context: defaultContext, + }); + + expect(versionsMetadata).toEqual([ + // vCurrent removed + {...v100, badge: false}, + ]); + }); + + it('works with disableVersioning', async () => { + const {defaultOptions, defaultContext, vCurrent} = await loadSite(); + + const versionsMetadata = await readVersionsMetadata({ + options: {...defaultOptions, disableVersioning: true}, + context: defaultContext, + }); + + expect(versionsMetadata).toEqual([ + { + ...vCurrent, + isLast: true, + routePriority: -1, + tagsPath: '/communityBasePath/tags', + path: '/communityBasePath', + banner: null, + badge: false, + }, + ]); + }); + + it('throws with all versions disabled', async () => { + const {defaultOptions, defaultContext} = await loadSite(); + + await expect( + readVersionsMetadata({ + options: { + ...defaultOptions, + includeCurrentVersion: false, + disableVersioning: true, + }, + context: defaultContext, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"It is not possible to use docs without any version. Please check the configuration of these options: \\"includeCurrentVersion: false\\", \\"disableVersioning: true\\"."`, + ); + }); }); }); diff --git a/packages/docusaurus-plugin-content-docs/src/categoryGeneratedIndex.ts b/packages/docusaurus-plugin-content-docs/src/categoryGeneratedIndex.ts index ac2cb55e1ed9..4c87e711a515 100644 --- a/packages/docusaurus-plugin-content-docs/src/categoryGeneratedIndex.ts +++ b/packages/docusaurus-plugin-content-docs/src/categoryGeneratedIndex.ts @@ -5,7 +5,10 @@ * LICENSE file in the root directory of this source tree. */ -import type {CategoryGeneratedIndexMetadata, DocMetadataBase} from './types'; +import type { + CategoryGeneratedIndexMetadata, + DocMetadataBase, +} from '@docusaurus/plugin-content-docs'; import type {SidebarItemCategoryWithGeneratedIndex} from './sidebars/types'; import {type SidebarsUtils, toNavigationLink} from './sidebars/utils'; import {createDocsByIdIndex} from './docs'; @@ -17,14 +20,10 @@ function getCategoryGeneratedIndexMetadata({ }: { category: SidebarItemCategoryWithGeneratedIndex; sidebarsUtils: SidebarsUtils; - docsById: Record; + docsById: {[docId: string]: DocMetadataBase}; }): CategoryGeneratedIndexMetadata { const {sidebarName, previous, next} = sidebarsUtils.getCategoryGeneratedIndexNavigation(category.link.permalink); - if (!sidebarName) { - throw new Error('unexpected'); - } - return { title: category.link.title ?? category.label, description: category.link.description, @@ -32,9 +31,11 @@ function getCategoryGeneratedIndexMetadata({ keywords: category.link.keywords, slug: category.link.slug, permalink: category.link.permalink, - sidebar: sidebarName, - previous: toNavigationLink(previous, docsById), - next: toNavigationLink(next, docsById), + sidebar: sidebarName!, + navigation: { + previous: toNavigationLink(previous, docsById), + next: toNavigationLink(next, docsById), + }, }; } diff --git a/packages/docusaurus-plugin-content-docs/src/cli.ts b/packages/docusaurus-plugin-content-docs/src/cli.ts index 76045beef05c..710a5c3d7c67 100644 --- a/packages/docusaurus-plugin-content-docs/src/cli.ts +++ b/packages/docusaurus-plugin-content-docs/src/cli.ts @@ -16,11 +16,11 @@ import type { PathOptions, SidebarOptions, } from '@docusaurus/plugin-content-docs'; -import {loadSidebarsFile, resolveSidebarPathOption} from './sidebars'; +import {loadSidebarsFileUnsafe, resolveSidebarPathOption} from './sidebars'; import {DEFAULT_PLUGIN_ID} from '@docusaurus/utils'; import logger from '@docusaurus/logger'; -function createVersionedSidebarFile({ +async function createVersionedSidebarFile({ siteDir, pluginId, sidebarPath, @@ -32,10 +32,13 @@ function createVersionedSidebarFile({ version: string; }) { // Load current sidebar and create a new versioned sidebars file (if needed). - // Note: we don't need the sidebars file to be normalized: it's ok to let plugin option changes to impact older, versioned sidebars - const sidebars = loadSidebarsFile(sidebarPath); + // Note: we don't need the sidebars file to be normalized: it's ok to let + // plugin option changes to impact older, versioned sidebars + // We don't validate here, assuming the user has already built the version + const sidebars = await loadSidebarsFileUnsafe(sidebarPath); - // Do not create a useless versioned sidebars file if sidebars file is empty or sidebars are disabled/false) + // Do not create a useless versioned sidebars file if sidebars file is empty + // or sidebars are disabled/false) const shouldCreateVersionedSidebarFile = Object.keys(sidebars).length > 0; if (shouldCreateVersionedSidebarFile) { @@ -44,8 +47,7 @@ function createVersionedSidebarFile({ versionedSidebarsDir, `version-${version}-sidebars.json`, ); - fs.ensureDirSync(path.dirname(newSidebarFile)); - fs.writeFileSync( + await fs.outputFile( newSidebarFile, `${JSON.stringify(sidebars, null, 2)}\n`, 'utf8', @@ -54,12 +56,12 @@ function createVersionedSidebarFile({ } // Tests depend on non-default export for mocking. -export function cliDocsVersionCommand( +export async function cliDocsVersionCommand( version: string | null | undefined, siteDir: string, pluginId: string, options: PathOptions & SidebarOptions, -): void { +): Promise { // It wouldn't be very user-friendly to show a [default] log prefix, // so we use [docs] instead of [default] const pluginIdLogPrefix = @@ -86,7 +88,7 @@ export function cliDocsVersionCommand( // Since we are going to create `version-${version}` folder, we need to make // sure it's a valid pathname. // eslint-disable-next-line no-control-regex - if (/[<>:"|?*\x00-\x1F]/g.test(version)) { + if (/[<>:"|?*\x00-\x1F]/.test(version)) { throw new Error( `${pluginIdLogPrefix}: invalid version tag specified! Please ensure its a valid pathname too. Try something like: 1.0.0.`, ); @@ -101,8 +103,8 @@ export function cliDocsVersionCommand( // Load existing versions. let versions = []; const versionsJSONFile = getVersionsFilePath(siteDir, pluginId); - if (fs.existsSync(versionsJSONFile)) { - versions = JSON.parse(fs.readFileSync(versionsJSONFile, 'utf8')); + if (await fs.pathExists(versionsJSONFile)) { + versions = JSON.parse(await fs.readFile(versionsJSONFile, 'utf8')); } // Check if version already exists. @@ -115,17 +117,20 @@ export function cliDocsVersionCommand( const {path: docsPath, sidebarPath} = options; // Copy docs files. - const docsDir = path.join(siteDir, docsPath); + const docsDir = path.resolve(siteDir, docsPath); - if (fs.existsSync(docsDir) && fs.readdirSync(docsDir).length > 0) { + if ( + (await fs.pathExists(docsDir)) && + (await fs.readdir(docsDir)).length > 0 + ) { const versionedDir = getVersionedDocsDirPath(siteDir, pluginId); const newVersionDir = path.join(versionedDir, `version-${version}`); - fs.copySync(docsDir, newVersionDir); + await fs.copy(docsDir, newVersionDir); } else { - throw new Error(`${pluginIdLogPrefix}: there is no docs to version!`); + throw new Error(`${pluginIdLogPrefix}: no docs found in ${docsDir}.`); } - createVersionedSidebarFile({ + await createVersionedSidebarFile({ siteDir, pluginId, version, @@ -134,8 +139,10 @@ export function cliDocsVersionCommand( // Update versions.json file. versions.unshift(version); - fs.ensureDirSync(path.dirname(versionsJSONFile)); - fs.writeFileSync(versionsJSONFile, `${JSON.stringify(versions, null, 2)}\n`); + await fs.outputFile( + versionsJSONFile, + `${JSON.stringify(versions, null, 2)}\n`, + ); logger.success`name=${pluginIdLogPrefix}: version name=${version} created!`; } diff --git a/packages/docusaurus-plugin-content-docs/src/client/__tests__/docsClientUtils.test.ts b/packages/docusaurus-plugin-content-docs/src/client/__tests__/docsClientUtils.test.ts index 0a414d474950..9663b014627c 100644 --- a/packages/docusaurus-plugin-content-docs/src/client/__tests__/docsClientUtils.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/client/__tests__/docsClientUtils.test.ts @@ -17,11 +17,11 @@ import type { GlobalVersion, ActivePlugin, } from '@docusaurus/plugin-content-docs/client'; -import {shuffle} from 'lodash'; +import _ from 'lodash'; describe('docsClientUtils', () => { - test('getActivePlugin', () => { - const data: Record = { + it('getActivePlugin', () => { + const data: {[key: string]: GlobalPluginData} = { pluginIosId: { path: '/ios', versions: [], @@ -32,8 +32,8 @@ describe('docsClientUtils', () => { }, }; - expect(getActivePlugin(data, '/')).toEqual(undefined); - expect(getActivePlugin(data, '/xyz')).toEqual(undefined); + expect(getActivePlugin(data, '/')).toBeUndefined(); + expect(getActivePlugin(data, '/xyz')).toBeUndefined(); expect(() => getActivePlugin(data, '/', {failfast: true}), @@ -73,7 +73,7 @@ describe('docsClientUtils', () => { versions: [], }, }; - expect(getActivePlugin(onePluginAtRoot, '/android/foo').pluginId).toEqual( + expect(getActivePlugin(onePluginAtRoot, '/android/foo').pluginId).toBe( 'pluginAndroidId', ); const onePluginAtRootReversed = { @@ -88,10 +88,10 @@ describe('docsClientUtils', () => { }; expect( getActivePlugin(onePluginAtRootReversed, '/android/foo').pluginId, - ).toEqual('pluginAndroidId'); + ).toBe('pluginAndroidId'); }); - test('getLatestVersion', () => { + it('getLatestVersion', () => { const versions: GlobalVersion[] = [ { name: 'version1', @@ -127,7 +127,7 @@ describe('docsClientUtils', () => { ).toEqual(versions[1]); }); - test('getActiveVersion', () => { + it('getActiveVersion', () => { const data: GlobalPluginData = { path: 'docs', versions: [ @@ -158,24 +158,24 @@ describe('docsClientUtils', () => { ], }; - expect(getActiveVersion(data, '/someUnknownPath')).toEqual(undefined); + expect(getActiveVersion(data, '/someUnknownPath')).toBeUndefined(); - expect(getActiveVersion(data, '/docs/next')?.name).toEqual('next'); - expect(getActiveVersion(data, '/docs/next/')?.name).toEqual('next'); - expect(getActiveVersion(data, '/docs/next/someDoc')?.name).toEqual('next'); + expect(getActiveVersion(data, '/docs/next')?.name).toBe('next'); + expect(getActiveVersion(data, '/docs/next/')?.name).toBe('next'); + expect(getActiveVersion(data, '/docs/next/someDoc')?.name).toBe('next'); - expect(getActiveVersion(data, '/docs')?.name).toEqual('version2'); - expect(getActiveVersion(data, '/docs/')?.name).toEqual('version2'); - expect(getActiveVersion(data, '/docs/someDoc')?.name).toEqual('version2'); + expect(getActiveVersion(data, '/docs')?.name).toBe('version2'); + expect(getActiveVersion(data, '/docs/')?.name).toBe('version2'); + expect(getActiveVersion(data, '/docs/someDoc')?.name).toBe('version2'); - expect(getActiveVersion(data, '/docs/version1')?.name).toEqual('version1'); - expect(getActiveVersion(data, '/docs/version1')?.name).toEqual('version1'); - expect(getActiveVersion(data, '/docs/version1/someDoc')?.name).toEqual( + expect(getActiveVersion(data, '/docs/version1')?.name).toBe('version1'); + expect(getActiveVersion(data, '/docs/version1')?.name).toBe('version1'); + expect(getActiveVersion(data, '/docs/version1/someDoc')?.name).toBe( 'version1', ); }); - test('getActiveDocContext', () => { + it('getActiveDocContext', () => { const versionNext: GlobalVersion = { name: 'next', label: 'next', @@ -227,7 +227,7 @@ describe('docsClientUtils', () => { }; // shuffle, because order shouldn't matter - const versions: GlobalVersion[] = shuffle([ + const versions: GlobalVersion[] = _.shuffle([ versionNext, version2, version1, @@ -304,7 +304,7 @@ describe('docsClientUtils', () => { }); }); - test('getDocVersionSuggestions', () => { + it('getDocVersionSuggestions', () => { const versionNext: GlobalVersion = { name: 'next', label: 'next', @@ -356,7 +356,7 @@ describe('docsClientUtils', () => { }; // shuffle, because order shouldn't matter - const versions: GlobalVersion[] = shuffle([ + const versions: GlobalVersion[] = _.shuffle([ versionNext, version2, version1, diff --git a/packages/docusaurus-plugin-content-docs/src/client/docsClientUtils.ts b/packages/docusaurus-plugin-content-docs/src/client/docsClientUtils.ts index 3af5af23ae12..1a07cd180735 100644 --- a/packages/docusaurus-plugin-content-docs/src/client/docsClientUtils.ts +++ b/packages/docusaurus-plugin-content-docs/src/client/docsClientUtils.ts @@ -23,12 +23,12 @@ import type { // ie the docs of that plugin are currently browsed // it is useful to support multiple docs plugin instances export function getActivePlugin( - allPluginDatas: Record, + allPluginData: {[pluginId: string]: GlobalPluginData}, pathname: string, options: GetActivePluginOptions = {}, ): ActivePlugin | undefined { - const activeEntry = Object.entries(allPluginDatas) - // A quick route sorting: '/android/foo' should match '/android' instead of '/' + const activeEntry = Object.entries(allPluginData) + // Route sorting: '/android/foo' should match '/android' instead of '/' .sort((a, b) => b[1].path.localeCompare(a[1].path)) .find( ([, pluginData]) => @@ -46,7 +46,7 @@ export function getActivePlugin( if (!activePlugin && options.failfast) { throw new Error( `Can't find active docs plugin for "${pathname}" pathname, while it was expected to be found. Maybe you tried to use a docs feature that can only be used on a docs-related page? Existing docs plugin paths are: ${Object.values( - allPluginDatas, + allPluginData, ) .map((plugin) => plugin.path) .join(', ')}`, @@ -67,7 +67,7 @@ export const getActiveVersion = ( ): GlobalVersion | undefined => { const lastVersion = getLatestVersion(data); // Last version is a route like /docs/*, - // we need to try to match it last or it would match /docs/version-1.0/* as well + // we need to match it last or it would match /docs/version-1.0/* as well const orderedVersionsMetadata = [ ...data.versions.filter((version) => version !== lastVersion), lastVersion, diff --git a/packages/docusaurus-plugin-content-docs/src/client/globalDataHooks.ts b/packages/docusaurus-plugin-content-docs/src/client/globalDataHooks.ts deleted file mode 100644 index 96a14ce909d6..000000000000 --- a/packages/docusaurus-plugin-content-docs/src/client/globalDataHooks.ts +++ /dev/null @@ -1,107 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import {useLocation} from '@docusaurus/router'; -import useGlobalData, { - // useAllPluginInstancesData, - usePluginData, -} from '@docusaurus/useGlobalData'; - -import { - getActivePlugin, - getLatestVersion, - getActiveVersion, - getActiveDocContext, - getDocVersionSuggestions, -} from './docsClientUtils'; -import type { - GlobalPluginData, - GlobalVersion, - ActivePlugin, - ActiveDocContext, - DocVersionSuggestions, - GetActivePluginOptions, -} from '@docusaurus/plugin-content-docs/client'; - -// Important to use a constant object to avoid React useEffect executions etc..., -// see https://github.com/facebook/docusaurus/issues/5089 -const StableEmptyObject = {}; - -// Not using useAllPluginInstancesData() because in blog-only mode, docs hooks are still used by the theme -// We need a fail-safe fallback when the docs plugin is not in use -export const useAllDocsData = (): Record => - // useAllPluginInstancesData('docusaurus-plugin-content-docs'); - useGlobalData()['docusaurus-plugin-content-docs'] ?? StableEmptyObject; - -export const useDocsData = (pluginId: string | undefined): GlobalPluginData => - usePluginData('docusaurus-plugin-content-docs', pluginId) as GlobalPluginData; - -// TODO this feature should be provided by docusaurus core -export const useActivePlugin = ( - options: GetActivePluginOptions = {}, -): ActivePlugin | undefined => { - const data = useAllDocsData(); - const {pathname} = useLocation(); - return getActivePlugin(data, pathname, options); -}; - -export const useActivePluginAndVersion = ( - options: GetActivePluginOptions = {}, -): - | undefined - | {activePlugin: ActivePlugin; activeVersion: GlobalVersion | undefined} => { - const activePlugin = useActivePlugin(options); - const {pathname} = useLocation(); - if (activePlugin) { - const activeVersion = getActiveVersion(activePlugin.pluginData, pathname); - return { - activePlugin, - activeVersion, - }; - } - return undefined; -}; - -// versions are returned ordered (most recent first) -export const useVersions = (pluginId: string | undefined): GlobalVersion[] => { - const data = useDocsData(pluginId); - return data.versions; -}; - -export const useLatestVersion = ( - pluginId: string | undefined, -): GlobalVersion => { - const data = useDocsData(pluginId); - return getLatestVersion(data); -}; - -// Note: return undefined on doc-unrelated pages, -// because there's no version currently considered as active -export const useActiveVersion = ( - pluginId: string | undefined, -): GlobalVersion | undefined => { - const data = useDocsData(pluginId); - const {pathname} = useLocation(); - return getActiveVersion(data, pathname); -}; - -export const useActiveDocContext = ( - pluginId: string | undefined, -): ActiveDocContext => { - const data = useDocsData(pluginId); - const {pathname} = useLocation(); - return getActiveDocContext(data, pathname); -}; - -// Useful to say "hey, you are not on the latest docs version, please switch" -export const useDocVersionSuggestions = ( - pluginId: string | undefined, -): DocVersionSuggestions => { - const data = useDocsData(pluginId); - const {pathname} = useLocation(); - return getDocVersionSuggestions(data, pathname); -}; diff --git a/packages/docusaurus-plugin-content-docs/src/client/index.ts b/packages/docusaurus-plugin-content-docs/src/client/index.ts index d2d1cf887ed1..803456fbbe7d 100644 --- a/packages/docusaurus-plugin-content-docs/src/client/index.ts +++ b/packages/docusaurus-plugin-content-docs/src/client/index.ts @@ -5,4 +5,100 @@ * LICENSE file in the root directory of this source tree. */ -export * from './globalDataHooks'; +import {useLocation} from '@docusaurus/router'; +import useGlobalData, {usePluginData} from '@docusaurus/useGlobalData'; + +import { + getActivePlugin, + getLatestVersion, + getActiveVersion, + getActiveDocContext, + getDocVersionSuggestions, +} from './docsClientUtils'; +import type { + GlobalPluginData, + GlobalVersion, + ActivePlugin, + ActiveDocContext, + DocVersionSuggestions, + GetActivePluginOptions, +} from '@docusaurus/plugin-content-docs/client'; + +// Important to use a constant object to avoid React useEffect executions etc. +// see https://github.com/facebook/docusaurus/issues/5089 +const StableEmptyObject = {}; + +// Not using useAllPluginInstancesData() because in blog-only mode, docs hooks +// are still used by the theme. We need a fail-safe fallback when the docs +// plugin is not in use +export const useAllDocsData = (): {[pluginId: string]: GlobalPluginData} => + useGlobalData()['docusaurus-plugin-content-docs'] ?? StableEmptyObject; + +export const useDocsData = (pluginId: string | undefined): GlobalPluginData => + usePluginData('docusaurus-plugin-content-docs', pluginId) as GlobalPluginData; + +// TODO this feature should be provided by docusaurus core +export const useActivePlugin = ( + options: GetActivePluginOptions = {}, +): ActivePlugin | undefined => { + const data = useAllDocsData(); + const {pathname} = useLocation(); + return getActivePlugin(data, pathname, options); +}; + +export const useActivePluginAndVersion = ( + options: GetActivePluginOptions = {}, +): + | undefined + | {activePlugin: ActivePlugin; activeVersion: GlobalVersion | undefined} => { + const activePlugin = useActivePlugin(options); + const {pathname} = useLocation(); + if (activePlugin) { + const activeVersion = getActiveVersion(activePlugin.pluginData, pathname); + return { + activePlugin, + activeVersion, + }; + } + return undefined; +}; + +// versions are returned ordered (most recent first) +export const useVersions = (pluginId: string | undefined): GlobalVersion[] => { + const data = useDocsData(pluginId); + return data.versions; +}; + +export const useLatestVersion = ( + pluginId: string | undefined, +): GlobalVersion => { + const data = useDocsData(pluginId); + return getLatestVersion(data); +}; + +// Note: return undefined on doc-unrelated pages, +// because there's no version currently considered as active +export const useActiveVersion = ( + pluginId: string | undefined, +): GlobalVersion | undefined => { + const data = useDocsData(pluginId); + const {pathname} = useLocation(); + return getActiveVersion(data, pathname); +}; + +export const useActiveDocContext = ( + pluginId: string | undefined, +): ActiveDocContext => { + const data = useDocsData(pluginId); + const {pathname} = useLocation(); + return getActiveDocContext(data, pathname); +}; + +// Useful to say "hey, you are not on the latest docs version, please switch" +export const useDocVersionSuggestions = ( + pluginId: string | undefined, +): DocVersionSuggestions => { + const data = useDocsData(pluginId); + const {pathname} = useLocation(); + return getDocVersionSuggestions(data, pathname); +}; diff --git a/packages/docusaurus-plugin-content-docs/src/deps.d.ts b/packages/docusaurus-plugin-content-docs/src/deps.d.ts index c9976d8a584b..6b8b33906b54 100644 --- a/packages/docusaurus-plugin-content-docs/src/deps.d.ts +++ b/packages/docusaurus-plugin-content-docs/src/deps.d.ts @@ -6,7 +6,7 @@ */ declare module 'remark-admonitions' { - type Options = Record; + type Options = {[key: string]: unknown}; const plugin: (options?: Options) => void; export = plugin; diff --git a/packages/docusaurus-plugin-content-docs/src/docs.ts b/packages/docusaurus-plugin-content-docs/src/docs.ts index 9002479b9ae0..ee247ec0a97a 100644 --- a/packages/docusaurus-plugin-content-docs/src/docs.ts +++ b/packages/docusaurus-plugin-content-docs/src/docs.ts @@ -8,11 +8,11 @@ import path from 'path'; import fs from 'fs-extra'; import logger from '@docusaurus/logger'; -import {keyBy} from 'lodash'; import { aliasedSitePath, getEditUrl, getFolderContainingFile, + getContentPathList, normalizeUrl, parseMarkdownString, posixPath, @@ -22,27 +22,22 @@ import { import type {LoadContext} from '@docusaurus/types'; import {getFileLastUpdate} from './lastUpdate'; -import type { - DocFile, - DocMetadataBase, - DocMetadata, - DocNavLink, - LastUpdateData, - VersionMetadata, - LoadedVersion, -} from './types'; +import type {DocFile, LoadedVersion} from './types'; import getSlug from './slug'; import {CURRENT_VERSION_NAME} from './constants'; -import {getDocsDirPaths} from './versions'; import {stripPathNumberPrefixes} from './numberPrefix'; -import {validateDocFrontMatter} from './docFrontMatter'; +import {validateDocFrontMatter} from './frontMatter'; import type {SidebarsUtils} from './sidebars/utils'; import {toDocNavigationLink, toNavigationLink} from './sidebars/utils'; import type { MetadataOptions, PluginOptions, CategoryIndexMatcher, - CategoryIndexMatcherParam, + DocMetadataBase, + DocMetadata, + PropNavigationLink, + LastUpdateData, + VersionMetadata, } from '@docusaurus/plugin-content-docs'; type LastUpdateOptions = Pick< @@ -86,7 +81,7 @@ export async function readDocFile( options: LastUpdateOptions, ): Promise { const contentPath = await getFolderContainingFile( - getDocsDirPaths(versionMetadata), + getContentPathList(versionMetadata), source, ); @@ -139,7 +134,8 @@ function doProcessDocMetadata({ const { custom_edit_url: customEditURL, - // Strip number prefixes by default (01-MyFolder/01-MyDoc.md => MyFolder/MyDoc) by default, + // Strip number prefixes by default + // (01-MyFolder/01-MyDoc.md => MyFolder/MyDoc) // but allow to disable this behavior with front matter parse_number_prefixes: parseNumberPrefixes = true, } = frontMatter; @@ -164,7 +160,8 @@ function doProcessDocMetadata({ throw new Error(`Document id "${baseID}" cannot include slash.`); } - // For autogenerated sidebars, sidebar position can come from filename number prefix or front matter + // For autogenerated sidebars, sidebar position can come from filename number + // prefix or front matter const sidebarPosition: number | undefined = frontMatter.sidebar_position ?? numberPrefix; @@ -205,13 +202,14 @@ function doProcessDocMetadata({ numberPrefixParser: options.numberPrefixParser, }); - // Note: the title is used by default for page title, sidebar label, pagination buttons... - // frontMatter.title should be used in priority over contentTitle (because it can contain markdown/JSX syntax) + // Note: the title is used by default for page title, sidebar label, + // pagination buttons... frontMatter.title should be used in priority over + // contentTitle (because it can contain markdown/JSX syntax) const title: string = frontMatter.title ?? contentTitle ?? baseID; const description: string = frontMatter.description ?? excerpt ?? ''; - const permalink = normalizeUrl([versionMetadata.versionPath, docSlug]); + const permalink = normalizeUrl([versionMetadata.path, docSlug]); function getDocEditUrl() { const relativeFilePath = path.relative(contentPath, filePath); @@ -230,12 +228,11 @@ function doProcessDocMetadata({ const isLocalized = contentPath === versionMetadata.contentPathLocalized; const baseVersionEditUrl = isLocalized && options.editLocalizedFiles - ? versionMetadata.versionEditUrlLocalized - : versionMetadata.versionEditUrl; + ? versionMetadata.editUrlLocalized + : versionMetadata.editUrl; return getEditUrl(relativeFilePath, baseVersionEditUrl); - } else { - return undefined; } + return undefined; } // Assign all of object properties during instantiation (if possible) for @@ -274,9 +271,9 @@ export function processDocMetadata(args: { }): DocMetadataBase { try { return doProcessDocMetadata(args); - } catch (e) { + } catch (err) { logger.error`Can't process doc metadata for doc at path path=${args.docFile.filePath} in version name=${args.versionMetadata.versionName}`; - throw e; + throw err; } } @@ -303,7 +300,7 @@ export function addDocNavigation( const toNavigationLinkByDocId = ( docId: string | null | undefined, type: 'prev' | 'next', - ): DocNavLink | undefined => { + ): PropNavigationLink | undefined => { if (!docId) { return undefined; } @@ -361,9 +358,8 @@ export function getMainDocId({ doc.id === firstDocIdOfFirstSidebar || doc.unversionedId === firstDocIdOfFirstSidebar, )!; - } else { - return docs[0]; } + return docs[0]!; } return getMainDoc().unversionedId; @@ -391,13 +387,17 @@ export const isCategoryIndex: CategoryIndexMatcher = ({ return eligibleDocIndexNames.includes(fileName.toLowerCase()); }; +/** + * `guides/sidebar/autogenerated.md` -> + * `'autogenerated', '.md', ['sidebar', 'guides']` + */ export function toCategoryIndexMatcherParam({ source, sourceDirName, }: Pick< DocMetadataBase, 'source' | 'sourceDirName' ->): CategoryIndexMatcherParam { +>): Parameters[0] { // source + sourceDirName are always posix-style return { fileName: path.posix.parse(source).name, @@ -406,42 +406,25 @@ export function toCategoryIndexMatcherParam({ }; } -/** - * guides/sidebar/autogenerated.md -> 'autogenerated', '.md', ['sidebar', 'guides'] - */ -export function splitPath(str: string): { - /** - * The list of directories, from lowest level to highest. - * If there's no dir name, directories is ['.'] - */ - directories: string[]; - /** The file name, without extension */ - fileName: string; - /** The extension, with a leading dot */ - extension: string; -} { - return { - fileName: path.parse(str).name, - extension: path.parse(str).ext, - directories: path.dirname(str).split(path.sep).reverse(), - }; -} - // Return both doc ids -// TODO legacy retro-compatibility due to old versioned sidebars using versioned doc ids -// ("id" should be removed & "versionedId" should be renamed to "id") +// TODO legacy retro-compatibility due to old versioned sidebars using +// versioned doc ids ("id" should be removed & "versionedId" should be renamed +// to "id") export function getDocIds(doc: DocMetadataBase): [string, string] { return [doc.unversionedId, doc.id]; } // docs are indexed by both versioned and unversioned ids at the same time -// TODO legacy retro-compatibility due to old versioned sidebars using versioned doc ids -// ("id" should be removed & "versionedId" should be renamed to "id") +// TODO legacy retro-compatibility due to old versioned sidebars using +// versioned doc ids ("id" should be removed & "versionedId" should be renamed +// to "id") export function createDocsByIdIndex< Doc extends {id: string; unversionedId: string}, ->(docs: Doc[]): Record { - return { - ...keyBy(docs, (doc) => doc.unversionedId), - ...keyBy(docs, (doc) => doc.id), - }; +>(docs: Doc[]): {[docId: string]: Doc} { + return Object.fromEntries( + docs.flatMap((doc) => [ + [doc.unversionedId, doc], + [doc.id, doc], + ]), + ); } diff --git a/packages/docusaurus-plugin-content-docs/src/docFrontMatter.ts b/packages/docusaurus-plugin-content-docs/src/frontMatter.ts similarity index 81% rename from packages/docusaurus-plugin-content-docs/src/docFrontMatter.ts rename to packages/docusaurus-plugin-content-docs/src/frontMatter.ts index 432c9ea7aa9f..52e5af68b383 100644 --- a/packages/docusaurus-plugin-content-docs/src/docFrontMatter.ts +++ b/packages/docusaurus-plugin-content-docs/src/frontMatter.ts @@ -12,7 +12,7 @@ import { FrontMatterTOCHeadingLevels, validateFrontMatter, } from '@docusaurus/utils-validation'; -import type {DocFrontMatter} from './types'; +import type {DocFrontMatter} from '@docusaurus/plugin-content-docs'; // NOTE: we don't add any default value on purpose here // We don't want default values to magically appear in doc metadata and props @@ -25,11 +25,12 @@ const DocFrontMatterSchema = Joi.object({ hide_table_of_contents: Joi.boolean(), keywords: Joi.array().items(Joi.string().required()), image: URISchema, - description: Joi.string().allow(''), // see https://github.com/facebook/docusaurus/issues/4591#issuecomment-822372398 + description: Joi.string().allow(''), // see https://github.com/facebook/docusaurus/issues/4591#issuecomment-822372398 slug: Joi.string(), sidebar_label: Joi.string(), sidebar_position: Joi.number(), sidebar_class_name: Joi.string(), + sidebar_custom_props: Joi.object().unknown(), displayed_sidebar: Joi.string().allow(null), tags: FrontMatterTagsSchema, pagination_label: Joi.string(), @@ -40,8 +41,8 @@ const DocFrontMatterSchema = Joi.object({ ...FrontMatterTOCHeadingLevels, }).unknown(); -export function validateDocFrontMatter( - frontMatter: Record, -): DocFrontMatter { +export function validateDocFrontMatter(frontMatter: { + [key: string]: unknown; +}): DocFrontMatter { return validateFrontMatter(frontMatter, DocFrontMatterSchema); } diff --git a/packages/docusaurus-plugin-content-docs/src/globalData.ts b/packages/docusaurus-plugin-content-docs/src/globalData.ts index c97feb340c0d..ebeb61c8228e 100644 --- a/packages/docusaurus-plugin-content-docs/src/globalData.ts +++ b/packages/docusaurus-plugin-content-docs/src/globalData.ts @@ -5,22 +5,21 @@ * LICENSE file in the root directory of this source tree. */ -import {mapValues} from 'lodash'; -import {normalizeUrl} from '@docusaurus/utils'; +import _ from 'lodash'; import type {Sidebars} from './sidebars/types'; import {createSidebarsUtils} from './sidebars/utils'; +import type {LoadedVersion} from './types'; import type { CategoryGeneratedIndexMetadata, DocMetadata, - LoadedVersion, -} from './types'; +} from '@docusaurus/plugin-content-docs'; import type { GlobalVersion, GlobalSidebar, GlobalDoc, } from '@docusaurus/plugin-content-docs/client'; -export function toGlobalDataDoc(doc: DocMetadata): GlobalDoc { +function toGlobalDataDoc(doc: DocMetadata): GlobalDoc { return { id: doc.unversionedId, path: doc.permalink, @@ -28,7 +27,7 @@ export function toGlobalDataDoc(doc: DocMetadata): GlobalDoc { }; } -export function toGlobalDataGeneratedIndex( +function toGlobalDataGeneratedIndex( doc: CategoryGeneratedIndexMetadata, ): GlobalDoc { return { @@ -38,12 +37,12 @@ export function toGlobalDataGeneratedIndex( }; } -export function toGlobalSidebars( +function toGlobalSidebars( sidebars: Sidebars, version: LoadedVersion, -): Record { +): {[sidebarId: string]: GlobalSidebar} { const {getFirstLink} = createSidebarsUtils(sidebars); - return mapValues(sidebars, (sidebar, sidebarId) => { + return _.mapValues(sidebars, (sidebar, sidebarId) => { const firstLink = getFirstLink(sidebarId); if (!firstLink) { return {}; @@ -52,7 +51,7 @@ export function toGlobalSidebars( link: { path: firstLink.type === 'generated-index' - ? normalizeUrl([version.versionPath, firstLink.slug]) + ? firstLink.permalink : version.docs.find( (doc) => doc.id === firstLink.id || doc.unversionedId === firstLink.id, @@ -66,9 +65,9 @@ export function toGlobalSidebars( export function toGlobalDataVersion(version: LoadedVersion): GlobalVersion { return { name: version.versionName, - label: version.versionLabel, + label: version.label, isLast: version.isLast, - path: version.versionPath, + path: version.path, mainDocId: version.mainDocId, docs: version.docs .map(toGlobalDataDoc) diff --git a/packages/docusaurus-plugin-content-docs/src/index.ts b/packages/docusaurus-plugin-content-docs/src/index.ts index f15f0ca72985..ed35fa390c55 100644 --- a/packages/docusaurus-plugin-content-docs/src/index.ts +++ b/packages/docusaurus-plugin-content-docs/src/index.ts @@ -11,6 +11,7 @@ import { normalizeUrl, docuHash, aliasedSitePath, + getContentPathList, reportMessage, posixPath, addTrailingPathSeparator, @@ -27,13 +28,10 @@ import { addDocNavigation, getMainDocId, } from './docs'; -import {getDocsDirPaths, readVersionsMetadata} from './versions'; - +import {readVersionsMetadata} from './versions'; import type { LoadedContent, SourceToPermalink, - DocMetadataBase, - VersionMetadata, LoadedVersion, DocFile, DocsMarkdownOption, @@ -42,7 +40,6 @@ import type { import type {RuleSetRule} from 'webpack'; import {cliDocsVersionCommand} from './cli'; import {VERSIONS_JSON_FILE} from './constants'; -import {keyBy, mapValues} from 'lodash'; import {toGlobalDataVersion} from './globalData'; import {toTagDocListProp} from './props'; import { @@ -55,8 +52,10 @@ import {createVersionRoutes} from './routes'; import type { PropTagsListPage, PluginOptions, + DocMetadataBase, + VersionMetadata, + DocFrontMatter, } from '@docusaurus/plugin-content-docs'; -import type {GlobalPluginData} from '@docusaurus/plugin-content-docs/client'; import {createSidebarsUtils} from './sidebars/utils'; import {getCategoryGeneratedIndexMetadataList} from './categoryGeneratedIndex'; @@ -115,7 +114,7 @@ export default async function pluginContentDocs( function getVersionPathsToWatch(version: VersionMetadata): string[] { const result = [ ...options.include.flatMap((pattern) => - getDocsDirPaths(version).map( + getContentPathList(version).map( (docsDirPath) => `${docsDirPath}/${pattern}`, ), ), @@ -196,9 +195,9 @@ export default async function pluginContentDocs( async function loadVersion(versionMetadata: VersionMetadata) { try { return await doLoadVersion(versionMetadata); - } catch (e) { + } catch (err) { logger.error`Loading of version failed for version name=${versionMetadata.versionName}`; - throw e; + throw err; } } @@ -217,6 +216,7 @@ export default async function pluginContentDocs( docLayoutComponent, docItemComponent, docCategoryGeneratedIndexComponent, + breadcrumbs, } = options; const {addRoute, createData, setGlobalData} = actions; @@ -228,7 +228,7 @@ export default async function pluginContentDocs( const tagsProp: PropTagsListPage['tags'] = Object.values( versionTags, ).map((tagValue) => ({ - name: tagValue.name, + name: tagValue.label, permalink: tagValue.permalink, count: tagValue.docIds.length, })); @@ -292,9 +292,10 @@ export default async function pluginContentDocs( // TODO tags should be a sub route of the version route await Promise.all(loadedVersions.map(createVersionTagsRoutes)); - setGlobalData({ + setGlobalData({ path: normalizeUrl([baseUrl, options.routeBasePath]), versions: loadedVersions.map(toGlobalDataVersion), + breadcrumbs, }); }, @@ -309,9 +310,8 @@ export default async function pluginContentDocs( function getSourceToPermalink(): SourceToPermalink { const allDocs = content.loadedVersions.flatMap((v) => v.docs); - return mapValues( - keyBy(allDocs, (d) => d.source), - (d) => d.permalink, + return Object.fromEntries( + allDocs.map(({source, permalink}) => [source, permalink]), ); } @@ -331,9 +331,9 @@ export default async function pluginContentDocs( }; function createMDXLoaderRule(): RuleSetRule { - const contentDirs = versionsMetadata.flatMap(getDocsDirPaths); + const contentDirs = versionsMetadata.flatMap(getContentPathList); return { - test: /(\.mdx?)$/, + test: /\.mdx?$/i, include: contentDirs // Trailing slash is important, see https://github.com/facebook/docusaurus/pull/3970 .map(addTrailingPathSeparator), @@ -360,6 +360,15 @@ export default async function pluginContentDocs( const aliasedPath = aliasedSitePath(mdxPath, siteDir); return path.join(dataDir, `${docuHash(aliasedPath)}.json`); }, + // Assets allow to convert some relative images paths to + // require(...) calls + createAssets: ({ + frontMatter, + }: { + frontMatter: DocFrontMatter; + }) => ({ + image: frontMatter.image, + }), }, }, { diff --git a/packages/docusaurus-plugin-content-docs/src/lastUpdate.ts b/packages/docusaurus-plugin-content-docs/src/lastUpdate.ts index b78c4146c39e..a0eb4d775997 100644 --- a/packages/docusaurus-plugin-content-docs/src/lastUpdate.ts +++ b/packages/docusaurus-plugin-content-docs/src/lastUpdate.ts @@ -5,56 +5,47 @@ * LICENSE file in the root directory of this source tree. */ -import shell from 'shelljs'; import logger from '@docusaurus/logger'; - -type FileLastUpdateData = {timestamp?: number; author?: string}; - -const GIT_COMMIT_TIMESTAMP_AUTHOR_REGEX = /^(\d+),(.+)$/; +import { + getFileCommitDate, + FileNotTrackedError, + GitNotFoundError, +} from '@docusaurus/utils'; let showedGitRequirementError = false; +let showedFileNotTrackedError = false; export async function getFileLastUpdate( filePath?: string, -): Promise { +): Promise<{timestamp: number; author: string} | null> { if (!filePath) { return null; } - function getTimestampAndAuthor(str: string): FileLastUpdateData | null { - if (!str) { - return null; - } - - const temp = str.match(GIT_COMMIT_TIMESTAMP_AUTHOR_REGEX); - return !temp || temp.length < 3 - ? null - : {timestamp: +temp[1], author: temp[2]}; - } // Wrap in try/catch in case the shell commands fail // (e.g. project doesn't use Git, etc). try { - if (!shell.which('git')) { + const result = getFileCommitDate(filePath, { + age: 'newest', + includeAuthor: true, + }); + return {timestamp: result.timestamp, author: result.author}; + } catch (err) { + if (err instanceof GitNotFoundError) { if (!showedGitRequirementError) { - showedGitRequirementError = true; logger.warn('Sorry, the docs plugin last update options require Git.'); + showedGitRequirementError = true; } - - return null; - } - - const result = shell.exec(`git log -1 --format=%ct,%an "${filePath}"`, { - silent: true, - }); - if (result.code !== 0) { - throw new Error( - `Retrieval of git history failed at "${filePath}" with exit code ${result.code}: ${result.stderr}`, - ); + } else if (err instanceof FileNotTrackedError) { + if (!showedFileNotTrackedError) { + logger.warn( + 'Cannot infer the update date for some files, as they are not tracked by git.', + ); + showedFileNotTrackedError = true; + } + } else { + logger.warn(err); } - return getTimestampAndAuthor(result.stdout.trim()); - } catch (e) { - logger.error(e); + return null; } - - return null; } diff --git a/packages/docusaurus-plugin-content-docs/src/markdown/__tests__/__fixtures__/outside/doc1.md b/packages/docusaurus-plugin-content-docs/src/markdown/__tests__/__fixtures__/outside/doc1.md new file mode 100644 index 000000000000..4fd86e1c55ca --- /dev/null +++ b/packages/docusaurus-plugin-content-docs/src/markdown/__tests__/__fixtures__/outside/doc1.md @@ -0,0 +1 @@ +[link](../docs/doc1.md) diff --git a/packages/docusaurus-plugin-content-docs/src/markdown/__tests__/__snapshots__/linkify.test.ts.snap b/packages/docusaurus-plugin-content-docs/src/markdown/__tests__/__snapshots__/linkify.test.ts.snap index 95982ca782e5..39d7880d48a1 100644 --- a/packages/docusaurus-plugin-content-docs/src/markdown/__tests__/__snapshots__/linkify.test.ts.snap +++ b/packages/docusaurus-plugin-content-docs/src/markdown/__tests__/__snapshots__/linkify.test.ts.snap @@ -1,6 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`transform nothing 1`] = ` +exports[`linkify transforms absolute links in versioned docs 1`] = ` +"### Existing Docs + +- [doc1](/docs/1.0.0/subdir/doc1) + +### With hash + +- [doc2](/docs/1.0.0/doc2#existing-docs) +" +`; + +exports[`linkify transforms nothing with no links 1`] = ` "# Don't transform any link here ![image1](assets/image1.png) @@ -17,41 +28,7 @@ exports[`transform nothing 1`] = ` " `; -exports[`transform relative links 1`] = ` -"### Relative linking - -- [doc1](/docs/doc2) -" -`; - -exports[`transform to correct links 1`] = ` -"### Existing Docs - -- [doc1](/docs/doc1) -- [doc2](/docs/doc2) -- [doc3](/docs/subdir/doc3) - -## Repeating Docs - -- [doc1](/docs/doc1) -- [doc2](/docs/doc2) - -- [doc-localized](/fr/doc-localized) -" -`; - -exports[`transforms absolute links in versioned docs 1`] = ` -"### Existing Docs - -- [doc1](/docs/1.0.0/subdir/doc1) - -### With hash - -- [doc2](/docs/1.0.0/doc2#existing-docs) -" -`; - -exports[`transforms reference links 1`] = ` +exports[`linkify transforms reference links 1`] = ` "### Existing Docs - [doc1][doc1] @@ -74,9 +51,32 @@ exports[`transforms reference links 1`] = ` " `; -exports[`transforms relative links in versioned docs 1`] = ` +exports[`linkify transforms relative links 1`] = ` +"### Relative linking + +- [doc1](/docs/doc2) +" +`; + +exports[`linkify transforms relative links in versioned docs 1`] = ` "### Relative linking - [doc1](/docs/1.0.0/doc2) " `; + +exports[`linkify transforms to correct links 1`] = ` +"### Existing Docs + +- [doc1](/docs/doc1) +- [doc2](/docs/doc2) +- [doc3](/docs/subdir/doc3) + +## Repeating Docs + +- [doc1](/docs/doc1) +- [doc2](/docs/doc2) + +- [doc-localized](/fr/doc-localized) +" +`; diff --git a/packages/docusaurus-plugin-content-docs/src/markdown/__tests__/linkify.test.ts b/packages/docusaurus-plugin-content-docs/src/markdown/__tests__/linkify.test.ts index 56dc2cc9e9a8..f106ea6edb9d 100644 --- a/packages/docusaurus-plugin-content-docs/src/markdown/__tests__/linkify.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/markdown/__tests__/linkify.test.ts @@ -5,15 +5,16 @@ * LICENSE file in the root directory of this source tree. */ +import {jest} from '@jest/globals'; import fs from 'fs-extra'; import path from 'path'; import {linkify} from '../linkify'; import type { DocsMarkdownOption, SourceToPermalink, - VersionMetadata, - BrokenMarkdownLink, + DocBrokenMarkdownLink, } from '../../types'; +import type {VersionMetadata} from '@docusaurus/plugin-content-docs'; import {VERSIONED_DOCS_DIR, CURRENT_VERSION_NAME} from '../../constants'; function createFakeVersion({ @@ -89,102 +90,117 @@ function createMarkdownOptions( }; } -const transform = (filepath: string, options?: Partial) => { +const transform = async ( + filepath: string, + options?: Partial, +) => { const markdownOptions = createMarkdownOptions(options); - const content = fs.readFileSync(filepath, 'utf-8'); + const content = await fs.readFile(filepath, 'utf-8'); const transformedContent = linkify(content, filepath, markdownOptions); return [content, transformedContent]; }; -test('transform nothing', () => { - const doc1 = path.join(versionCurrent.contentPath, 'doc1.md'); - const [content, transformedContent] = transform(doc1); - expect(transformedContent).toMatchSnapshot(); - expect(content).toEqual(transformedContent); -}); +describe('linkify', () => { + it('transforms nothing with no links', async () => { + const doc1 = path.join(versionCurrent.contentPath, 'doc1.md'); + const [content, transformedContent] = await transform(doc1); + expect(transformedContent).toMatchSnapshot(); + expect(content).toEqual(transformedContent); + }); -test('transform to correct links', () => { - const doc2 = path.join(versionCurrent.contentPath, 'doc2.md'); - const [content, transformedContent] = transform(doc2); - expect(transformedContent).toMatchSnapshot(); - expect(transformedContent).toContain('](/docs/doc1'); - expect(transformedContent).toContain('](/docs/doc2'); - expect(transformedContent).toContain('](/docs/subdir/doc3'); - expect(transformedContent).toContain('](/fr/doc-localized'); - expect(transformedContent).not.toContain('](doc1.md)'); - expect(transformedContent).not.toContain('](./doc2.md)'); - expect(transformedContent).not.toContain('](subdir/doc3.md)'); - expect(transformedContent).not.toContain('](/doc-localized'); - expect(content).not.toEqual(transformedContent); -}); + it('transforms to correct links', async () => { + const doc2 = path.join(versionCurrent.contentPath, 'doc2.md'); + const [content, transformedContent] = await transform(doc2); + expect(transformedContent).toMatchSnapshot(); + expect(transformedContent).toContain('](/docs/doc1'); + expect(transformedContent).toContain('](/docs/doc2'); + expect(transformedContent).toContain('](/docs/subdir/doc3'); + expect(transformedContent).toContain('](/fr/doc-localized'); + expect(transformedContent).not.toContain('](doc1.md)'); + expect(transformedContent).not.toContain('](./doc2.md)'); + expect(transformedContent).not.toContain('](subdir/doc3.md)'); + expect(transformedContent).not.toContain('](/doc-localized'); + expect(content).not.toEqual(transformedContent); + }); -test('transform relative links', () => { - const doc3 = path.join(versionCurrent.contentPath, 'subdir', 'doc3.md'); + it('transforms relative links', async () => { + const doc3 = path.join(versionCurrent.contentPath, 'subdir', 'doc3.md'); - const [content, transformedContent] = transform(doc3); - expect(transformedContent).toMatchSnapshot(); - expect(transformedContent).toContain('](/docs/doc2'); - expect(transformedContent).not.toContain('](../doc2.md)'); - expect(content).not.toEqual(transformedContent); -}); + const [content, transformedContent] = await transform(doc3); + expect(transformedContent).toMatchSnapshot(); + expect(transformedContent).toContain('](/docs/doc2'); + expect(transformedContent).not.toContain('](../doc2.md)'); + expect(content).not.toEqual(transformedContent); + }); -test('transforms reference links', () => { - const doc4 = path.join(versionCurrent.contentPath, 'doc4.md'); - const [content, transformedContent] = transform(doc4); - expect(transformedContent).toMatchSnapshot(); - expect(transformedContent).toContain('[doc1]: /docs/doc1'); - expect(transformedContent).toContain('[doc2]: /docs/doc2'); - expect(transformedContent).not.toContain('[doc1]: doc1.md'); - expect(transformedContent).not.toContain('[doc2]: ./doc2.md'); - expect(content).not.toEqual(transformedContent); -}); + it('transforms reference links', async () => { + const doc4 = path.join(versionCurrent.contentPath, 'doc4.md'); + const [content, transformedContent] = await transform(doc4); + expect(transformedContent).toMatchSnapshot(); + expect(transformedContent).toContain('[doc1]: /docs/doc1'); + expect(transformedContent).toContain('[doc2]: /docs/doc2'); + expect(transformedContent).not.toContain('[doc1]: doc1.md'); + expect(transformedContent).not.toContain('[doc2]: ./doc2.md'); + expect(content).not.toEqual(transformedContent); + }); -test('report broken markdown links', () => { - const doc5 = path.join(versionCurrent.contentPath, 'doc5.md'); - const onBrokenMarkdownLink = jest.fn(); - const [content, transformedContent] = transform(doc5, { - onBrokenMarkdownLink, + it('reports broken markdown links', async () => { + const doc5 = path.join(versionCurrent.contentPath, 'doc5.md'); + const onBrokenMarkdownLink = jest.fn(); + const [content, transformedContent] = await transform(doc5, { + onBrokenMarkdownLink, + }); + expect(transformedContent).toEqual(content); + expect(onBrokenMarkdownLink).toHaveBeenCalledTimes(4); + expect(onBrokenMarkdownLink).toHaveBeenNthCalledWith(1, { + filePath: doc5, + link: 'docNotExist1.md', + contentPaths: versionCurrent, + } as DocBrokenMarkdownLink); + expect(onBrokenMarkdownLink).toHaveBeenNthCalledWith(2, { + filePath: doc5, + link: './docNotExist2.mdx', + contentPaths: versionCurrent, + } as DocBrokenMarkdownLink); + expect(onBrokenMarkdownLink).toHaveBeenNthCalledWith(3, { + filePath: doc5, + link: '../docNotExist3.mdx', + contentPaths: versionCurrent, + } as DocBrokenMarkdownLink); + expect(onBrokenMarkdownLink).toHaveBeenNthCalledWith(4, { + filePath: doc5, + link: './subdir/docNotExist4.md', + contentPaths: versionCurrent, + } as DocBrokenMarkdownLink); }); - expect(transformedContent).toEqual(content); - expect(onBrokenMarkdownLink).toHaveBeenCalledTimes(4); - expect(onBrokenMarkdownLink).toHaveBeenNthCalledWith(1, { - filePath: doc5, - link: 'docNotExist1.md', - contentPaths: versionCurrent, - } as BrokenMarkdownLink); - expect(onBrokenMarkdownLink).toHaveBeenNthCalledWith(2, { - filePath: doc5, - link: './docNotExist2.mdx', - contentPaths: versionCurrent, - } as BrokenMarkdownLink); - expect(onBrokenMarkdownLink).toHaveBeenNthCalledWith(3, { - filePath: doc5, - link: '../docNotExist3.mdx', - contentPaths: versionCurrent, - } as BrokenMarkdownLink); - expect(onBrokenMarkdownLink).toHaveBeenNthCalledWith(4, { - filePath: doc5, - link: './subdir/docNotExist4.md', - contentPaths: versionCurrent, - } as BrokenMarkdownLink); -}); -test('transforms absolute links in versioned docs', () => { - const doc2 = path.join(version100.contentPath, 'doc2.md'); - const [content, transformedContent] = transform(doc2); - expect(transformedContent).toMatchSnapshot(); - expect(transformedContent).toContain('](/docs/1.0.0/subdir/doc1'); - expect(transformedContent).toContain('](/docs/1.0.0/doc2#existing-docs'); - expect(transformedContent).not.toContain('](subdir/doc1.md)'); - expect(transformedContent).not.toContain('](doc2.md#existing-docs)'); - expect(content).not.toEqual(transformedContent); -}); + it('transforms absolute links in versioned docs', async () => { + const doc2 = path.join(version100.contentPath, 'doc2.md'); + const [content, transformedContent] = await transform(doc2); + expect(transformedContent).toMatchSnapshot(); + expect(transformedContent).toContain('](/docs/1.0.0/subdir/doc1'); + expect(transformedContent).toContain('](/docs/1.0.0/doc2#existing-docs'); + expect(transformedContent).not.toContain('](subdir/doc1.md)'); + expect(transformedContent).not.toContain('](doc2.md#existing-docs)'); + expect(content).not.toEqual(transformedContent); + }); -test('transforms relative links in versioned docs', () => { - const doc1 = path.join(version100.contentPath, 'subdir', 'doc1.md'); - const [content, transformedContent] = transform(doc1); - expect(transformedContent).toMatchSnapshot(); - expect(transformedContent).toContain('](/docs/1.0.0/doc2'); - expect(transformedContent).not.toContain('](../doc2.md)'); - expect(content).not.toEqual(transformedContent); + it('transforms relative links in versioned docs', async () => { + const doc1 = path.join(version100.contentPath, 'subdir', 'doc1.md'); + const [content, transformedContent] = await transform(doc1); + expect(transformedContent).toMatchSnapshot(); + expect(transformedContent).toContain('](/docs/1.0.0/doc2'); + expect(transformedContent).not.toContain('](../doc2.md)'); + expect(content).not.toEqual(transformedContent); + }); + + // See comment in linkify.ts + it('throws for file outside version', async () => { + const doc1 = path.join(__dirname, '__fixtures__/outside/doc1.md'); + await expect(() => + transform(doc1), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Unexpected error: Markdown file at \\"/packages/docusaurus-plugin-content-docs/src/markdown/__tests__/__fixtures__/outside/doc1.md\\" does not belong to any docs version!"`, + ); + }); }); diff --git a/packages/docusaurus-plugin-content-docs/src/markdown/index.ts b/packages/docusaurus-plugin-content-docs/src/markdown/index.ts index d141bfe3a3c3..5de4aae87f4d 100644 --- a/packages/docusaurus-plugin-content-docs/src/markdown/index.ts +++ b/packages/docusaurus-plugin-content-docs/src/markdown/index.ts @@ -16,7 +16,5 @@ export default function markdownLoader( const fileString = source; const callback = this.async(); const options = this.getOptions(); - return ( - callback && callback(null, linkify(fileString, this.resourcePath, options)) - ); + return callback?.(null, linkify(fileString, this.resourcePath, options)); } diff --git a/packages/docusaurus-plugin-content-docs/src/markdown/linkify.ts b/packages/docusaurus-plugin-content-docs/src/markdown/linkify.ts index a029e92610d8..704bcbd4974f 100644 --- a/packages/docusaurus-plugin-content-docs/src/markdown/linkify.ts +++ b/packages/docusaurus-plugin-content-docs/src/markdown/linkify.ts @@ -6,15 +6,18 @@ */ import type {DocsMarkdownOption} from '../types'; -import {getDocsDirPaths} from '../versions'; -import {replaceMarkdownLinks} from '@docusaurus/utils'; +import {replaceMarkdownLinks, getContentPathList} from '@docusaurus/utils'; function getVersion(filePath: string, options: DocsMarkdownOption) { const versionFound = options.versionsMetadata.find((version) => - getDocsDirPaths(version).some((docsDirPath) => + getContentPathList(version).some((docsDirPath) => filePath.startsWith(docsDirPath), ), ); + // At this point, this should never happen, because the MDX loaders' paths are + // literally using the version content paths; but if we allow sourcing content + // from outside the docs directory (through the `include` option, for example; + // is there a compelling use-case?), this would actually be testable if (!versionFound) { throw new Error( `Unexpected error: Markdown file at "${filePath}" does not belong to any docs version!`, diff --git a/packages/docusaurus-plugin-content-docs/src/numberPrefix.ts b/packages/docusaurus-plugin-content-docs/src/numberPrefix.ts index 85933aa94c04..a838d7667d7b 100644 --- a/packages/docusaurus-plugin-content-docs/src/numberPrefix.ts +++ b/packages/docusaurus-plugin-content-docs/src/numberPrefix.ts @@ -8,42 +8,33 @@ import type {NumberPrefixParser} from '@docusaurus/plugin-content-docs'; // Best-effort to avoid parsing some patterns as number prefix -const IgnoredPrefixPatterns = (function () { - // ignore common date-like patterns: https://github.com/facebook/docusaurus/issues/4640 - const DateLikePrefixRegex = - /^((\d{2}|\d{4})[-_.]\d{2}([-_.](\d{2}|\d{4}))?)(.*)$/; - - // ignore common versioning patterns: https://github.com/facebook/docusaurus/issues/4653 - // note: we could try to parse float numbers in filenames but that is probably not worth it - // as a version such as "8.0" can be interpreted as both a version and a float - // User can configure his own NumberPrefixParser if he wants 8.0 to be interpreted as a float - const VersionLikePrefixRegex = /^(\d+[-_.]\d+)(.*)$/; - - return new RegExp( - `${DateLikePrefixRegex.source}|${VersionLikePrefixRegex.source}`, - ); -})(); - -const NumberPrefixRegex = - /^(?\d+)(?\s*[-_.]+\s*)(?.*)$/; +// ignore common date-like patterns: https://github.com/facebook/docusaurus/issues/4640 +// ignore common versioning patterns: https://github.com/facebook/docusaurus/issues/4653 +// Both of them would look like 7.0-foo or 2021-11-foo +// note: we could try to parse float numbers in filenames, but that is probably +// not worth it, as a version such as "8.0" can be interpreted as either a +// version or a float. User can configure her own NumberPrefixParser if she +// wants 8.0 to be interpreted as a float +const ignoredPrefixPattern = /^\d+[-_.]\d+/; + +const numberPrefixPattern = + /^(?\d+)\s*[-_.]+\s*(?[^-_.\s].*)$/; // 0-myDoc => {filename: myDoc, numberPrefix: 0} // 003 - myDoc => {filename: myDoc, numberPrefix: 3} export const DefaultNumberPrefixParser: NumberPrefixParser = ( filename: string, ) => { - if (IgnoredPrefixPatterns.exec(filename)) { + if (ignoredPrefixPattern.test(filename)) { + return {filename, numberPrefix: undefined}; + } + const match = numberPrefixPattern.exec(filename); + if (!match) { return {filename, numberPrefix: undefined}; } - const match = NumberPrefixRegex.exec(filename); - const cleanFileName = match?.groups?.suffix ?? filename; - const numberPrefixString = match?.groups?.numberPrefix; - const numberPrefix = numberPrefixString - ? parseInt(numberPrefixString, 10) - : undefined; return { - filename: cleanFileName, - numberPrefix, + filename: match.groups!.suffix!, + numberPrefix: parseInt(match.groups!.numberPrefix!, 10), }; }; diff --git a/packages/docusaurus-plugin-content-docs/src/options.ts b/packages/docusaurus-plugin-content-docs/src/options.ts index f7b2f7ed47e9..258236acaaea 100644 --- a/packages/docusaurus-plugin-content-docs/src/options.ts +++ b/packages/docusaurus-plugin-content-docs/src/options.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import type {PluginOptions} from '@docusaurus/plugin-content-docs'; +import type {PluginOptions, Options} from '@docusaurus/plugin-content-docs'; import { Joi, RemarkPluginsSchema, @@ -15,10 +15,7 @@ import { } from '@docusaurus/utils-validation'; import {GlobExcludeDefault} from '@docusaurus/utils'; -import type { - OptionValidationContext, - ValidationResult, -} from '@docusaurus/types'; +import type {OptionValidationContext} from '@docusaurus/types'; import logger from '@docusaurus/logger'; import admonitions from 'remark-admonitions'; import {DefaultSidebarItemsGenerator} from './sidebars/generator'; @@ -55,6 +52,7 @@ export const DEFAULT_OPTIONS: Omit = { editLocalizedFiles: false, sidebarCollapsible: true, sidebarCollapsed: true, + breadcrumbs: true, }; const VersionOptionsSchema = Joi.object({ @@ -69,7 +67,7 @@ const VersionsOptionsSchema = Joi.object() .pattern(Joi.string().required(), VersionOptionsSchema) .default(DEFAULT_OPTIONS.versions); -export const OptionsSchema = Joi.object({ +const OptionsSchema = Joi.object({ path: Joi.string().default(DEFAULT_OPTIONS.path), editUrl: Joi.alternatives().try(URISchema, Joi.function()), editCurrentVersion: Joi.boolean().default(DEFAULT_OPTIONS.editCurrentVersion), @@ -79,6 +77,7 @@ export const OptionsSchema = Joi.object({ // .allow('') "" .default(DEFAULT_OPTIONS.routeBasePath), tagsBasePath: Joi.string().default(DEFAULT_OPTIONS.tagsBasePath), + // @ts-expect-error: deprecated homePageId: Joi.any().forbidden().messages({ 'any.unknown': 'The docs plugin option homePageId is not supported anymore. To make a doc the "home", please add "slug: /" in its front matter. See: https://docusaurus.io/docs/next/docs-introduction#home-page-docs', @@ -139,17 +138,19 @@ export const OptionsSchema = Joi.object({ disableVersioning: Joi.bool().default(DEFAULT_OPTIONS.disableVersioning), lastVersion: Joi.string().optional(), versions: VersionsOptionsSchema, + breadcrumbs: Joi.bool().default(DEFAULT_OPTIONS.breadcrumbs), }); export function validateOptions({ validate, options: userOptions, -}: OptionValidationContext): ValidationResult { +}: OptionValidationContext): PluginOptions { let options = userOptions; if (options.sidebarCollapsible === false) { - // When sidebarCollapsible=false and sidebarCollapsed=undefined, we don't want to have the inconsistency warning - // We let options.sidebarCollapsible become the default value for options.sidebarCollapsed + // When sidebarCollapsible=false and sidebarCollapsed=undefined, we don't + // want to have the inconsistency warning. We let options.sidebarCollapsible + // become the default value for options.sidebarCollapsed if (typeof options.sidebarCollapsed === 'undefined') { options = { ...options, @@ -165,7 +166,7 @@ export function validateOptions({ } } - const normalizedOptions = validate(OptionsSchema, options); + const normalizedOptions = validate(OptionsSchema, options) as PluginOptions; if (normalizedOptions.admonitions) { normalizedOptions.remarkPlugins = normalizedOptions.remarkPlugins.concat([ diff --git a/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts b/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts index 59f54a69c723..92d375fc700a 100644 --- a/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts +++ b/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts @@ -6,125 +6,480 @@ */ declare module '@docusaurus/plugin-content-docs' { - import type {RemarkAndRehypePluginOptions} from '@docusaurus/mdx-loader'; + import type {MDXOptions} from '@docusaurus/mdx-loader'; + import type {ContentPaths, Tag, FrontMatterTag} from '@docusaurus/utils'; + import type {Required} from 'utility-types'; + export interface Assets { + image?: string; + } + + /** + * Custom callback for parsing number prefixes from file/folder names. + */ export type NumberPrefixParser = (filename: string) => { + /** file name without number prefix, without any other modification. */ filename: string; + /** The number prefix. Can be float, integer, negative, or anything. */ numberPrefix?: number; }; - export type CategoryIndexMatcherParam = { + export type CategoryIndexMatcher = (param: { + /** The file name, without extension */ fileName: string; + /** + * The list of directories, from lowest level to highest. + * If there's no dir name, directories is ['.'] + */ directories: string[]; + /** The extension, with a leading dot */ extension: string; - }; - export type CategoryIndexMatcher = ( - param: CategoryIndexMatcherParam, - ) => boolean; + }) => boolean; export type EditUrlFunction = (editUrlParams: { + /** Name of the version. */ version: string; + /** + * Path of the version's root content path, relative to the site directory. + * Usually the same as `options.path` but can be localized or versioned. + */ versionDocsDirPath: string; + /** Path of the doc file, relative to `versionDocsDirPath`. */ docPath: string; + /** @see {@link DocMetadata.permalink} */ permalink: string; + /** Locale name. */ locale: string; }) => string | undefined; export type MetadataOptions = { + /** + * URL route for the docs section of your site. **DO NOT** include a + * trailing slash. Use `/` for shipping docs without base path. + */ routeBasePath: string; + /** + * Base URL to edit your site. The final URL is computed by `editUrl + + * relativeDocPath`. Using a function allows more nuanced control for each + * file. Omitting this variable entirely will disable edit links. + */ editUrl?: string | EditUrlFunction; + /** + * The edit URL will always target the current version doc instead of older + * versions. Ignored when `editUrl` is a function. + */ editCurrentVersion: boolean; + /** + * The edit URL will target the localized file, instead of the original + * unlocalized file. Ignored when `editUrl` is a function. + */ editLocalizedFiles: boolean; + /** Whether to display the last date the doc was updated. */ showLastUpdateTime?: boolean; + /** Whether to display the author who last updated the doc. */ showLastUpdateAuthor?: boolean; + /** + * Custom parsing logic to extract number prefixes from file names. Use + * `false` to disable this behavior and leave the docs untouched, and `true` + * to use the default parser. + * + * @param filename One segment of the path, without any slashes. + * @see https://docusaurus.io/docs/sidebar#using-number-prefixes + */ numberPrefixParser: NumberPrefixParser; + /** Enable or disable the breadcrumbs on doc pages. */ + breadcrumbs: boolean; }; export type PathOptions = { + /** + * Path to the docs content directory on the file system, relative to site + * directory. + */ path: string; + /** + * Path to sidebar configuration. Use `false` to disable sidebars, or + * `undefined` to create a fully autogenerated sidebar. + */ sidebarPath?: string | false | undefined; }; - // TODO support custom version banner? {type: "error", content: "html content"} + // TODO support custom version banner? + // {type: "error", content: "html content"} export type VersionBanner = 'unreleased' | 'unmaintained'; - export type VersionOptions = { - path?: string; - label?: string; - banner?: 'none' | VersionBanner; - badge?: boolean; - className?: string; - }; export type VersionsOptions = { + /** + * The version navigated to in priority and displayed by default for docs + * navbar items. + * + * @see https://docusaurus.io/docs/versioning#terminology + */ lastVersion?: string; - versions: Record; + /** Only include a subset of all available versions. */ onlyIncludeVersions?: string[]; + /** + * Explicitly disable versioning even when multiple versions exist. This + * will make the site only include the current version. Will error if + * `includeCurrentVersion: false` and `disableVersioning: true`. + */ + disableVersioning: boolean; + /** Include the current version of your docs. */ + includeCurrentVersion: boolean; + /** Independent customization of each version's properties. */ + versions: { + [versionName: string]: { + /** + * The base path of the version, will be appended to `baseUrl` + + * `routeBasePath`. + */ + path?: string; + /** The label of the version to be used in badges, dropdowns, etc. */ + label?: string; + /** The banner to show at the top of a doc of that version. */ + banner?: 'none' | VersionBanner; + /** Show a badge with the version label at the top of each doc. */ + badge?: boolean; + /** Add a custom class name to the element of each doc. */ + className?: string; + }; + }; }; export type SidebarOptions = { + /** + * Whether sidebar categories are collapsible by default. + * + * @see https://docusaurus.io/docs/sidebar#collapsible-categories + */ sidebarCollapsible: boolean; + /** + * Whether sidebar categories are collapsed by default. + * + * @see https://docusaurus.io/docs/sidebar#expanded-categories-by-default + */ sidebarCollapsed: boolean; }; export type PluginOptions = MetadataOptions & PathOptions & VersionsOptions & - RemarkAndRehypePluginOptions & + MDXOptions & SidebarOptions & { + /** Plugin ID. */ id: string; + /** + * Array of glob patterns matching Markdown files to be built, relative to + * the content path. + */ include: string[]; + /** + * Array of glob patterns matching Markdown files to be excluded. Serves + * as refinement based on the `include` option. + */ exclude: string[]; + /** + * Root layout component of each doc page. Provides the version data + * context, and is not unmounted when switching docs. + */ docLayoutComponent: string; + /** Main doc container, with TOC, pagination, etc. */ docItemComponent: string; + /** Root component of the "docs containing tag X" page. */ docTagDocListComponent: string; + /** Root component of the tags list page */ docTagsListComponent: string; + /** Root component of the generated category index page. */ docCategoryGeneratedIndexComponent: string; - admonitions: Record; - disableVersioning: boolean; - includeCurrentVersion: boolean; + admonitions: {[key: string]: unknown}; sidebarItemsGenerator: import('./sidebars/types').SidebarItemsGeneratorOption; + /** + * URL route for the tags section of your doc version. Will be appended to + * `routeBasePath`. **DO NOT** include a trailing slash. + */ tagsBasePath: string; }; export type Options = Partial; export type SidebarsConfig = import('./sidebars/types').SidebarsConfig; + export type VersionMetadata = ContentPaths & { + /** A name like `1.0.0`. Acquired from `versions.json`. */ + versionName: string; + /** Like `Version 1.0.0`. Can be configured through `versions.label`. */ + label: string; + /** + * Version's base path in the form of `///1.0.0`. + * Can be configured through `versions.path`. + */ + path: string; + /** Tags base path in the form of `/tags`. */ + tagsPath: string; + /** + * The base URL to which the doc file path will be appended. Will be + * `undefined` if `editUrl` is `undefined` or a function. + */ + editUrl?: string | undefined; + /** + * The base URL to which the localized doc file path will be appended. Will + * be `undefined` if `editUrl` is `undefined` or a function. + */ + editUrlLocalized?: string | undefined; + /** + * "unmaintained" is the version before latest; "unreleased" is the version + * after latest. `null` is the latest version without a banner. Can be + * configured with `versions.banner`: `banner: "none"` will be transformed + * to `null` here. + */ + banner: VersionBanner | null; + /** Show a badge with the version label at the top of each doc. */ + badge: boolean; + /** Add a custom class name to the element of each doc. */ + className: string; + /** + * Whether this version is the "last" version. Can be configured with + * `lastVersion` option. + */ + isLast: boolean; + /** + * Like `versioned_sidebars/1.0.0.json`. Versioned sidebars file may be + * nonexistent since we don't create empty files. + */ + sidebarFilePath: string | false | undefined; + /** + * Will be -1 for the latest docs, and `undefined` for everything else. + * Because `/docs/foo` should always be after `/docs//foo`. + */ + routePriority: number | undefined; + }; + + export type DocFrontMatter = { + /** + * The last part of the doc ID (will be refactored in the future to be the + * full ID instead) + * @see {@link DocMetadata.id} + */ + id?: string; + /** + * Will override the default title collected from h1 heading. + * @see {@link DocMetadata.title} + */ + title?: string; + /** + * Front matter tags, unnormalized. + * @see {@link DocMetadata.tags} + */ + tags?: FrontMatterTag[]; + /** + * If there isn't a Markdown h1 heading (which, if there is, we don't + * remove), this front matter will cause the front matter title to not be + * displayed in the doc page. + */ + hide_title?: boolean; + /** Hide the TOC on the right. */ + hide_table_of_contents?: boolean; + /** Used in the head meta. */ + keywords?: string[]; + /** Used in the head meta. Should use `assets.image` in priority. */ + image?: string; + /** + * Will override the default excerpt. + * @see {@link DocMetadata.description} + */ + description?: string; + /** + * Custom slug appended after /// + * @see {@link DocMetadata.slug} + */ + slug?: string; + /** Customizes the sidebar label for this doc. Will default to its title. */ + sidebar_label?: string; + /** + * Controls the position of a doc inside the generated sidebar slice when + * using autogenerated sidebar items. + * + * @see https://docusaurus.io/docs/sidebar#autogenerated-sidebar-metadata + */ + sidebar_position?: number; + /** + * Gives the corresponding sidebar label a special class name when using + * autogenerated sidebars. + */ + sidebar_class_name?: string; + /** + * Will be propagated to the final sidebars data structure. Useful if you + * have swizzled sidebar-related code or simply querying doc data through + * sidebars. + */ + sidebar_custom_props?: {[key: string]: unknown}; + /** + * Changes the sidebar association of the current doc. Use `null` to make + * the current doc not associated to any sidebar. + */ + displayed_sidebar?: string | null; + /** + * Customizes the pagination label for this doc. Will default to the sidebar + * label. + */ + pagination_label?: string; + /** Overrides the default URL computed for this doc. */ + custom_edit_url?: string | null; + /** + * Whether number prefix parsing is disabled on this doc. + * @see https://docusaurus.io/docs/sidebar#using-number-prefixes + */ + parse_number_prefixes?: boolean; + /** + * Minimum TOC heading level. Must be between 2 and 6 and lower or equal to + * the max value. + */ + toc_min_heading_level?: number; + /** Maximum TOC heading level. Must be between 2 and 6. */ + toc_max_heading_level?: number; + /** + * The ID of the documentation you want the "Next" pagination to link to. + * Use `null` to disable showing "Next" for this page. + * @see {@link DocMetadata.next} + */ + pagination_next?: string | null; + /** + * The ID of the documentation you want the "Previous" pagination to link + * to. Use `null` to disable showing "Previous" for this page. + * @see {@link DocMetadata.prev} + */ + pagination_prev?: string | null; + }; + + export type LastUpdateData = { + /** A timestamp in **seconds**, directly acquired from `git log`. */ + lastUpdatedAt?: number; + /** `lastUpdatedAt` formatted as a date according to the current locale. */ + formattedLastUpdatedAt?: string; + /** The author's name directly acquired from `git log`. */ + lastUpdatedBy?: string; + }; + + export type DocMetadataBase = LastUpdateData & { + // TODO + /** + * Legacy versioned ID. Will be refactored in the future to be unversioned. + */ + id: string; + // TODO + /** + * Unversioned ID. Should be preferred everywhere over `id` until the latter + * is refactored. + */ + unversionedId: string; + /** The name of the version this doc belongs to. */ + version: string; + /** + * Used to generate the page h1 heading, tab title, and pagination title. + */ + title: string; + /** + * Description used in the meta. Could be an empty string (empty content) + */ + description: string; + /** Path to the Markdown source, with `@site` alias. */ + source: string; + /** + * Posix path relative to the content path. Can be `"."`. + * e.g. "folder/subfolder/subsubfolder" + */ + sourceDirName: string; + /** `permalink` without base URL or version path. */ + slug: string; + /** Full URL to this doc, with base URL and version path. */ + permalink: string; + /** + * Position in an autogenerated sidebar slice, acquired through front matter + * or number prefix. + */ + sidebarPosition?: number; + /** + * Acquired from the options; can be customized with front matter. + * `custom_edit_url` will always lead to it being null, but you should treat + * `undefined` and `null` as equivalent. + */ + editUrl?: string | null; + /** Tags, normalized. */ + tags: Tag[]; + /** Front matter, as-is. */ + frontMatter: DocFrontMatter & {[key: string]: unknown}; + }; + + export type DocMetadata = DocMetadataBase & + PropNavigation & { + /** Name of the sidebar this doc is associated with. */ + sidebar?: string; + }; + + export type CategoryGeneratedIndexMetadata = Required< + Omit< + import('./sidebars/types').SidebarItemCategoryLinkGeneratedIndex, + 'type' + >, + 'title' + > & { + navigation: PropNavigation; + /** + * Name of the sidebar this doc is associated with. Unlike + * `DocMetadata.sidebar`, this will always be defined, because a generated + * index can only be generated from a category. + */ + sidebar: string; + }; + export type PropNavigationLink = { readonly title: string; readonly permalink: string; }; export type PropNavigation = { + /** + * Used in pagination. Content is just a subset of another doc's metadata. + */ readonly previous?: PropNavigationLink; + /** + * Used in pagination. Content is just a subset of another doc's metadata. + */ readonly next?: PropNavigationLink; }; - export type PropVersionDoc = import('./sidebars/types').PropVersionDoc; - export type PropVersionDocs = import('./sidebars/types').PropVersionDocs; + export type PropVersionDoc = Pick< + DocMetadata, + 'id' | 'title' | 'description' | 'sidebar' + >; - export type PropVersionMetadata = { + export type PropVersionDocs = { + [docId: string]: PropVersionDoc; + }; + + export type PropVersionMetadata = Pick< + VersionMetadata, + 'label' | 'banner' | 'badge' | 'className' | 'isLast' + > & { + /** ID of the docs plugin this version belongs to. */ pluginId: string; + /** Name of this version. */ version: string; - label: string; - banner: VersionBanner | null; - badge: boolean; - className: string; - isLast: boolean; + /** Sidebars contained in this version. */ docsSidebars: PropSidebars; + /** Docs contained in this version. */ docs: PropVersionDocs; }; - export type PropCategoryGeneratedIndex = { - title: string; - description?: string; - image?: string; - keywords?: string | readonly string[]; - slug: string; - permalink: string; - navigation: PropNavigation; - }; + export type PropCategoryGeneratedIndex = Omit< + CategoryGeneratedIndexMetadata, + 'sidebar' + >; export type PropSidebarItemLink = import('./sidebars/types').PropSidebarItemLink; + export type PropSidebarItemHtml = + import('./sidebars/types').PropSidebarItemHtml; export type PropSidebarItemCategory = import('./sidebars/types').PropSidebarItemCategory; export type PropSidebarItem = import('./sidebars/types').PropSidebarItem; + export type PropSidebarBreadcrumbsItem = + import('./sidebars/types').PropSidebarBreadcrumbsItem; export type PropSidebar = import('./sidebars/types').PropSidebar; export type PropSidebars = import('./sidebars/types').PropSidebars; @@ -153,8 +508,10 @@ declare module '@docusaurus/plugin-content-docs' { declare module '@theme/DocItem' { import type {TOCItem} from '@docusaurus/types'; import type { - PropNavigationLink, PropVersionMetadata, + Assets, + DocMetadata, + DocFrontMatter, } from '@docusaurus/plugin-content-docs'; export type DocumentRoute = { @@ -164,68 +521,20 @@ declare module '@theme/DocItem' { readonly sidebar?: string; }; - export type FrontMatter = { - readonly id: string; - readonly title: string; - readonly image?: string; - readonly keywords?: readonly string[]; - readonly hide_title?: boolean; - readonly hide_table_of_contents?: boolean; - readonly toc_min_heading_level?: number; - readonly toc_max_heading_level?: number; - }; - - export type Metadata = { - readonly description?: string; - readonly title?: string; - readonly permalink?: string; - readonly editUrl?: string; - readonly lastUpdatedAt?: number; - readonly formattedLastUpdatedAt?: string; - readonly lastUpdatedBy?: string; - readonly version?: string; - readonly previous?: PropNavigationLink; - readonly next?: PropNavigationLink; - readonly tags: readonly { - readonly label: string; - readonly permalink: string; - }[]; - }; - export interface Props { readonly route: DocumentRoute; readonly versionMetadata: PropVersionMetadata; readonly content: { - readonly frontMatter: FrontMatter; - readonly metadata: Metadata; + readonly frontMatter: DocFrontMatter; + readonly metadata: DocMetadata; readonly toc: readonly TOCItem[]; readonly contentTitle: string | undefined; + readonly assets: Assets; (): JSX.Element; }; } - const DocItem: (props: Props) => JSX.Element; - export default DocItem; -} - -declare module '@theme/DocCard' { - import type {PropSidebarItem} from '@docusaurus/plugin-content-docs'; - - export interface Props { - readonly item: PropSidebarItem; - } - - export default function DocCard(props: Props): JSX.Element; -} - -declare module '@theme/DocCardList' { - import type {PropSidebarItem} from '@docusaurus/plugin-content-docs'; - - export interface Props { - readonly items: PropSidebarItem[]; - } - - export default function DocCardList(props: Props): JSX.Element; + export default function DocItem(props: Props): JSX.Element; } declare module '@theme/DocCategoryGeneratedIndexPage' { @@ -240,12 +549,6 @@ declare module '@theme/DocCategoryGeneratedIndexPage' { ): JSX.Element; } -declare module '@theme/DocItemFooter' { - import type {Props} from '@theme/DocItem'; - - export default function DocItemFooter(props: Props): JSX.Element; -} - declare module '@theme/DocTagsListPage' { import type {PropTagsListPage} from '@docusaurus/plugin-content-docs'; @@ -262,20 +565,8 @@ declare module '@theme/DocTagDocListPage' { export default function DocTagDocListPage(props: Props): JSX.Element; } -declare module '@theme/DocVersionBanner' { - export interface Props { - readonly className?: string; - } - - export default function DocVersionBanner(props: Props): JSX.Element; -} - -declare module '@theme/DocVersionBadge' { - export interface Props { - readonly className?: string; - } - - export default function DocVersionBadge(props: Props): JSX.Element; +declare module '@theme/DocBreadcrumbs' { + export default function DocBreadcrumbs(): JSX.Element; } declare module '@theme/DocPage' { @@ -292,23 +583,41 @@ declare module '@theme/DocPage' { }; } - const DocPage: (props: Props) => JSX.Element; - export default DocPage; + export default function DocPage(props: Props): JSX.Element; +} + +declare module '@theme/DocPage/Layout' { + import type {ReactNode} from 'react'; + + export interface Props { + children: ReactNode; + } + + export default function DocPageLayout(props: Props): JSX.Element; +} + +declare module '@theme/DocPage/Layout/Aside' { + import type {Dispatch, SetStateAction} from 'react'; + import type {PropSidebar} from '@docusaurus/plugin-content-docs'; + + export interface Props { + sidebar: PropSidebar; + hiddenSidebarContainer: boolean; + setHiddenSidebarContainer: Dispatch>; + } + + export default function DocPageLayoutAside(props: Props): JSX.Element; } -declare module '@theme/Seo' { +declare module '@theme/DocPage/Layout/Main' { import type {ReactNode} from 'react'; export interface Props { - readonly title?: string; - readonly description?: string; - readonly keywords?: readonly string[] | string; - readonly image?: string; - readonly children?: ReactNode; + hiddenSidebarContainer: boolean; + children: ReactNode; } - const Seo: (props: Props) => JSX.Element; - export default Seo; + export default function DocPageLayoutMain(props: Props): JSX.Element; } // TODO until TS supports exports field... hope it's in 4.6 @@ -320,7 +629,7 @@ declare module '@docusaurus/plugin-content-docs/client' { export type ActiveDocContext = { activeVersion?: GlobalVersion; activeDoc?: GlobalDoc; - alternateDocVersions: Record; + alternateDocVersions: {[versionName: string]: GlobalDoc}; }; export type GlobalDoc = { id: string; @@ -335,21 +644,20 @@ declare module '@docusaurus/plugin-content-docs/client' { path: string; mainDocId: string; // home doc (if docs homepage configured), or first doc docs: GlobalDoc[]; - sidebars?: Record; - }; - - export type GlobalSidebarLink = { - label: string; - path: string; + sidebars?: {[sidebarId: string]: GlobalSidebar}; }; export type GlobalSidebar = { - link?: GlobalSidebarLink; + link?: { + label: string; + path: string; + }; // ... we may add other things here later }; export type GlobalPluginData = { path: string; versions: GlobalVersion[]; + breadcrumbs: boolean; }; export type DocVersionSuggestions = { // suggest the latest version @@ -359,7 +667,7 @@ declare module '@docusaurus/plugin-content-docs/client' { }; export type GetActivePluginOptions = {failfast?: boolean}; // use fail-fast option if you know for sure one plugin instance is active - export const useAllDocsData: () => Record; + export const useAllDocsData: () => {[pluginId: string]: GlobalPluginData}; export const useDocsData: (pluginId?: string) => GlobalPluginData; export const useActivePlugin: ( options?: GetActivePluginOptions, diff --git a/packages/docusaurus-plugin-content-docs/src/props.ts b/packages/docusaurus-plugin-content-docs/src/props.ts index 88c154051952..d25888db63a8 100644 --- a/packages/docusaurus-plugin-content-docs/src/props.ts +++ b/packages/docusaurus-plugin-content-docs/src/props.ts @@ -5,13 +5,12 @@ * LICENSE file in the root directory of this source tree. */ -import type {LoadedVersion, VersionTag, DocMetadata} from './types'; +import type {LoadedVersion, VersionTag} from './types'; import type { SidebarItemDoc, SidebarItem, SidebarItemCategory, SidebarItemCategoryLink, - PropVersionDocs, } from './sidebars/types'; import type { PropSidebars, @@ -21,8 +20,10 @@ import type { PropTagDocList, PropTagDocListDoc, PropSidebarItemLink, + PropVersionDocs, + DocMetadata, } from '@docusaurus/plugin-content-docs'; -import {compact, keyBy, mapValues} from 'lodash'; +import _ from 'lodash'; import {createDocsByIdIndex} from './docs'; export function toSidebarsProp(loadedVersion: LoadedVersion): PropSidebars { @@ -52,7 +53,8 @@ Available document ids are: label: sidebarLabel || item.label || title, href: permalink, className: item.className, - customProps: item.customProps, + customProps: + item.customProps ?? docMetadata.frontMatter.sidebar_custom_props, docId: docMetadata.unversionedId, }; }; @@ -92,18 +94,22 @@ Available document ids are: // Transform the sidebar so that all sidebar item will be in the // form of 'link' or 'category' only. // This is what will be passed as props to the UI component. - return mapValues(loadedVersion.sidebars, (items) => items.map(normalizeItem)); + return _.mapValues(loadedVersion.sidebars, (items) => + items.map(normalizeItem), + ); } function toVersionDocsProp(loadedVersion: LoadedVersion): PropVersionDocs { - return mapValues( - keyBy(loadedVersion.docs, (doc) => doc.unversionedId), - (doc) => ({ - id: doc.unversionedId, - title: doc.title, - description: doc.description, - sidebar: doc.sidebar, - }), + return Object.fromEntries( + loadedVersion.docs.map((doc) => [ + doc.unversionedId, + { + id: doc.unversionedId, + title: doc.title, + description: doc.description, + sidebar: doc.sidebar, + }, + ]), ); } @@ -114,10 +120,10 @@ export function toVersionMetadataProp( return { pluginId, version: loadedVersion.versionName, - label: loadedVersion.versionLabel, - banner: loadedVersion.versionBanner, - badge: loadedVersion.versionBadge, - className: loadedVersion.versionClassName, + label: loadedVersion.label, + banner: loadedVersion.banner, + badge: loadedVersion.badge, + className: loadedVersion.className, isLast: loadedVersion.isLast, docsSidebars: toSidebarsProp(loadedVersion), docs: toVersionDocsProp(loadedVersion), @@ -134,7 +140,7 @@ export function toTagDocListProp({ docs: Pick[]; }): PropTagDocList { function toDocListProp(): PropTagDocListDoc[] { - const list = compact( + const list = _.compact( tag.docIds.map((id) => docs.find((doc) => doc.id === id)), ); // Sort docs by title @@ -148,7 +154,7 @@ export function toTagDocListProp({ } return { - name: tag.name, + name: tag.label, permalink: tag.permalink, docs: toDocListProp(), allTagsPath, diff --git a/packages/docusaurus-plugin-content-docs/src/routes.ts b/packages/docusaurus-plugin-content-docs/src/routes.ts index a853901df48b..3ef6e60ba146 100644 --- a/packages/docusaurus-plugin-content-docs/src/routes.ts +++ b/packages/docusaurus-plugin-content-docs/src/routes.ts @@ -7,12 +7,11 @@ import type {PluginContentLoadedActions, RouteConfig} from '@docusaurus/types'; import {docuHash, createSlugger} from '@docusaurus/utils'; +import type {LoadedVersion} from './types'; import type { CategoryGeneratedIndexMetadata, DocMetadata, - LoadedVersion, -} from './types'; -import type {PropCategoryGeneratedIndex} from '@docusaurus/plugin-content-docs'; +} from '@docusaurus/plugin-content-docs'; import {toVersionMetadataProp} from './props'; import logger from '@docusaurus/logger'; @@ -32,48 +31,26 @@ export async function createCategoryGeneratedIndexRoutes({ async function createCategoryGeneratedIndexRoute( categoryGeneratedIndex: CategoryGeneratedIndexMetadata, ): Promise { - const { - sidebar, - title, - description, - slug, - permalink, - previous, - next, - image, - keywords, - } = categoryGeneratedIndex; + const {sidebar, ...prop} = categoryGeneratedIndex; const propFileName = slugs.slug( - `${version.versionPath}-${categoryGeneratedIndex.sidebar}-category-${categoryGeneratedIndex.title}`, + `${version.path}-${categoryGeneratedIndex.sidebar}-category-${categoryGeneratedIndex.title}`, ); - const prop: PropCategoryGeneratedIndex = { - title, - description, - slug, - permalink, - image, - keywords, - navigation: { - previous, - next, - }, - }; - const propData = await actions.createData( `${docuHash(`category/${propFileName}`)}.json`, JSON.stringify(prop, null, 2), ); return { - path: permalink, + path: categoryGeneratedIndex.permalink, component: docCategoryGeneratedIndexComponent, exact: true, modules: { categoryGeneratedIndex: aliasedSource(propData), }, - // Same as doc, this sidebar route attribute permits to associate this subpage to the given sidebar + // Same as doc, this sidebar route attribute permits to associate this + // subpage to the given sidebar ...(sidebar && {sidebar}), }; } @@ -109,7 +86,8 @@ export async function createDocRoutes({ content: metadataItem.source, }, // Because the parent (DocPage) comp need to access it easily - // This permits to render the sidebar once without unmount/remount when navigating (and preserve sidebar state) + // This permits to render the sidebar once without unmount/remount when + // navigating (and preserve sidebar state) ...(metadataItem.sidebar && { sidebar: metadataItem.sidebar, }), @@ -160,7 +138,7 @@ export async function createVersionRoutes({ } actions.addRoute({ - path: version.versionPath, + path: version.path, // allow matching /docs/* as well exact: false, // main docs component (DocPage) @@ -176,8 +154,8 @@ export async function createVersionRoutes({ try { return await doCreateVersionRoutes(loadedVersion); - } catch (e) { + } catch (err) { logger.error`Can't create version routes for version name=${loadedVersion.versionName}`; - throw e; + throw err; } } diff --git a/packages/docusaurus-plugin-content-docs/src/server-export.ts b/packages/docusaurus-plugin-content-docs/src/server-export.ts new file mode 100644 index 000000000000..a1942a1e8a32 --- /dev/null +++ b/packages/docusaurus-plugin-content-docs/src/server-export.ts @@ -0,0 +1,24 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +// APIs available to Node.js +export { + CURRENT_VERSION_NAME, + VERSIONED_DOCS_DIR, + VERSIONED_SIDEBARS_DIR, + VERSIONS_JSON_FILE, +} from './constants'; + +export { + filterVersions, + getDefaultVersionBanner, + getVersionBadge, + getVersionBanner, + getVersionsFilePath, + readVersionsFile, + readVersionNames, +} from './versions'; diff --git a/packages/docusaurus-plugin-content-docs/src/sidebars/README.md b/packages/docusaurus-plugin-content-docs/src/sidebars/README.md new file mode 100644 index 000000000000..6b10e0602bd0 --- /dev/null +++ b/packages/docusaurus-plugin-content-docs/src/sidebars/README.md @@ -0,0 +1,9 @@ +# Sidebars + +This part is very complicated and hard to navigate. Sidebars are loaded through the following steps: + +1. **Loading**. The sidebars file is read. Returns `SidebarsConfig`. +2. **Normalization**. The shorthands are expanded. This step is very lenient about the sidebars' shapes. Returns `NormalizedSidebars`. +3. **Validation**. The normalized sidebars are validated. This step happens after normalization, because the normalized sidebars are easier to validate, and allows us to repeatedly validate & generate in the future. +4. **Generation**. This step is done through the "processor" (naming is hard). The `autogenerated` items are unwrapped. In the future, steps 3 and 4 may be repeatedly done until all autogenerated items are unwrapped. Returns `ProcessedSidebars`. +5. **Post-processing**. Defaults are applied (collapsed states), category links are resolved, empty categories are flattened. Returns `Sidebars`. diff --git a/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/__fixtures__/sidebars/invalid-docs/foo/_category_.json b/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/__fixtures__/sidebars/invalid-docs/foo/_category_.json new file mode 100644 index 000000000000..c8c4105eb57c --- /dev/null +++ b/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/__fixtures__/sidebars/invalid-docs/foo/_category_.json @@ -0,0 +1,3 @@ +{ + "foo": "bar" +} diff --git a/packages/docusaurus/src/server/themes/__tests__/__fixtures__/theme-2/NavbarItem/index.js b/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/__fixtures__/sidebars/invalid-docs/foo/_category_.yml similarity index 100% rename from packages/docusaurus/src/server/themes/__tests__/__fixtures__/theme-2/NavbarItem/index.js rename to packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/__fixtures__/sidebars/invalid-docs/foo/_category_.yml diff --git a/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/__fixtures__/sidebars/sidebars-category-wrong-label.json b/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/__fixtures__/sidebars/sidebars-category-wrong-label.json deleted file mode 100644 index d86e00db5da1..000000000000 --- a/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/__fixtures__/sidebars/sidebars-category-wrong-label.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "docs": { - "Test": [ - { - "type": "category", - "label": true, - "items": ["doc1"] - } - ] - } -} diff --git a/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/__fixtures__/sidebars/sidebars-doc-id-not-string.json b/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/__fixtures__/sidebars/sidebars-doc-id-not-string.json deleted file mode 100644 index e778910ad18f..000000000000 --- a/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/__fixtures__/sidebars/sidebars-doc-id-not-string.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "docs": { - "Test": [ - { - "type": "doc", - "id": ["doc1"] - } - ] - } -} diff --git a/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/__fixtures__/sidebars/sidebars-link-wrong-href.json b/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/__fixtures__/sidebars/sidebars-link-wrong-href.json deleted file mode 100644 index 6a13b92b83ad..000000000000 --- a/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/__fixtures__/sidebars/sidebars-link-wrong-href.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "docs": { - "Test": [ - { - "type": "link", - "label": "GitHub", - "href": ["example.com"] - } - ] - } -} diff --git a/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/__fixtures__/sidebars/sidebars-link-wrong-label.json b/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/__fixtures__/sidebars/sidebars-link-wrong-label.json deleted file mode 100644 index 39dcab07497f..000000000000 --- a/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/__fixtures__/sidebars/sidebars-link-wrong-label.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "docs": { - "Test": [ - { - "type": "link", - "label": false, - "href": "https://github.com" - } - ] - } -} diff --git a/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/__fixtures__/sidebars/sidebars-unknown-type.json b/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/__fixtures__/sidebars/sidebars-unknown-type.json deleted file mode 100644 index 618c52d81753..000000000000 --- a/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/__fixtures__/sidebars/sidebars-unknown-type.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "docs": { - "Test": [ - "foo/bar", - "foo/baz", - { - "type": "superman" - } - ], - "Guides": [ - "hello" - ] - } -} diff --git a/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/__fixtures__/sidebars/sidebars-wrong-field.json b/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/__fixtures__/sidebars/sidebars-wrong-field.json deleted file mode 100644 index 43dff88d7c96..000000000000 --- a/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/__fixtures__/sidebars/sidebars-wrong-field.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "docs": { - "Test": [ - "foo/bar", - "foo/baz", - { - "type": "category", - "label": "category", - "href": "https://github.com" - }, - { - "type": "ref", - "id": "hello" - } - ], - "Guides": [ - "hello" - ] - } -} diff --git a/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/__snapshots__/generator.test.ts.snap b/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/__snapshots__/generator.test.ts.snap new file mode 100644 index 000000000000..e042c366581d --- /dev/null +++ b/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/__snapshots__/generator.test.ts.snap @@ -0,0 +1,255 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`DefaultSidebarItemsGenerator generates complex nested sidebar 1`] = ` +[ + { + "id": "intro", + "type": "doc", + }, + { + "collapsed": undefined, + "collapsible": undefined, + "items": [ + { + "id": "tutorial1", + "type": "doc", + }, + { + "id": "tutorial2", + "type": "doc", + }, + ], + "label": "Tutorials", + "link": { + "id": "tutorials-index", + "type": "doc", + }, + "type": "category", + }, + { + "collapsed": false, + "collapsible": undefined, + "customProps": { + "description": "foo", + }, + "items": [ + { + "className": "foo", + "id": "guide1", + "type": "doc", + }, + { + "collapsed": undefined, + "collapsible": undefined, + "items": [ + { + "id": "nested-guide", + "type": "doc", + }, + ], + "label": "SubGuides (metadata file label)", + "link": { + "description": "subGuides-description", + "slug": "subGuides-generated-index-slug", + "title": "subGuides-title", + "type": "generated-index", + }, + "type": "category", + }, + { + "id": "guide2", + "type": "doc", + }, + ], + "label": "Guides", + "link": { + "id": "guides-index", + "type": "doc", + }, + "type": "category", + }, + { + "id": "end", + "type": "doc", + }, +] +`; + +exports[`DefaultSidebarItemsGenerator generates simple flat sidebar 1`] = ` +[ + { + "id": "doc3", + "type": "doc", + }, + { + "id": "doc4", + "type": "doc", + }, + { + "id": "doc1", + "label": "doc1 sidebar label", + "type": "doc", + }, + { + "id": "doc2", + "type": "doc", + }, + { + "id": "doc5", + "type": "doc", + }, +] +`; + +exports[`DefaultSidebarItemsGenerator generates subfolder sidebar 1`] = ` +[ + { + "collapsed": undefined, + "collapsible": undefined, + "items": [ + { + "id": "doc8", + "type": "doc", + }, + { + "id": "doc7", + "type": "doc", + }, + ], + "label": "Subsubsubfolder category label", + "link": { + "id": "doc1", + "type": "doc", + }, + "type": "category", + }, + { + "className": "bar", + "collapsed": undefined, + "collapsible": undefined, + "items": [ + { + "id": "doc6", + "type": "doc", + }, + ], + "label": "subsubsubfolder2 (_category_.yml label)", + "type": "category", + }, + { + "id": "doc1", + "type": "doc", + }, + { + "id": "doc4", + "type": "doc", + }, + { + "collapsed": undefined, + "collapsible": undefined, + "items": [ + { + "id": "doc5", + "type": "doc", + }, + ], + "label": "subsubsubfolder", + "type": "category", + }, +] +`; + +exports[`DefaultSidebarItemsGenerator respects custom isCategoryIndex 1`] = ` +[ + { + "id": "intro", + "type": "doc", + }, + { + "collapsed": undefined, + "collapsible": undefined, + "items": [ + { + "id": "tutorial1", + "type": "doc", + }, + { + "id": "tutorial2", + "type": "doc", + }, + ], + "label": "Tutorials", + "link": { + "id": "tutorials-index", + "type": "doc", + }, + "type": "category", + }, + { + "collapsed": undefined, + "collapsible": undefined, + "items": [ + { + "className": "foo", + "id": "guide1", + "type": "doc", + }, + { + "id": "guide2", + "type": "doc", + }, + { + "id": "not-guides-index", + "type": "doc", + }, + ], + "label": "Guides", + "type": "category", + }, +] +`; + +exports[`DefaultSidebarItemsGenerator uses explicit link over the index/readme.{md,mdx} naming convention 1`] = ` +[ + { + "collapsed": undefined, + "collapsible": undefined, + "items": [ + { + "id": "parent/doc2", + "type": "doc", + }, + { + "id": "parent/doc1", + "type": "doc", + }, + ], + "label": "Category label", + "link": { + "id": "parent/doc3", + "type": "doc", + }, + "type": "category", + }, + { + "collapsed": undefined, + "collapsible": undefined, + "items": [ + { + "id": "parent/doc4", + "type": "doc", + }, + { + "id": "parent/doc6", + "type": "doc", + }, + { + "id": "parent/doc5", + "type": "doc", + }, + ], + "label": "Category 2 label", + "type": "category", + }, +] +`; diff --git a/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/__snapshots__/index.test.ts.snap index be3e4e880033..42b1cd180df8 100644 --- a/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/__snapshots__/index.test.ts.snap +++ b/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/__snapshots__/index.test.ts.snap @@ -1,13 +1,13 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`loadNormalizedSidebars sidebars link 1`] = ` -Object { - "docs": Array [ - Object { +exports[`loadSidebars sidebars link 1`] = ` +{ + "docs": [ + { "collapsed": true, "collapsible": true, - "items": Array [ - Object { + "items": [ + { "href": "https://github.com", "label": "category", "type": "link", @@ -21,18 +21,18 @@ Object { } `; -exports[`loadNormalizedSidebars sidebars with category.collapsed property 1`] = ` -Object { - "docs": Array [ - Object { +exports[`loadSidebars sidebars with category.collapsed property 1`] = ` +{ + "docs": [ + { "collapsed": true, "collapsible": true, - "items": Array [ - Object { + "items": [ + { "collapsed": false, "collapsible": true, - "items": Array [ - Object { + "items": [ + { "id": "doc1", "type": "doc", }, @@ -46,15 +46,15 @@ Object { "link": undefined, "type": "category", }, - Object { + { "collapsed": true, "collapsible": true, - "items": Array [ - Object { + "items": [ + { "collapsed": false, "collapsible": true, - "items": Array [ - Object { + "items": [ + { "id": "doc2", "type": "doc", }, @@ -72,14 +72,14 @@ Object { } `; -exports[`loadNormalizedSidebars sidebars with category.collapsed property at first level 1`] = ` -Object { - "docs": Array [ - Object { +exports[`loadSidebars sidebars with category.collapsed property at first level 1`] = ` +{ + "docs": [ + { "collapsed": false, "collapsible": true, - "items": Array [ - Object { + "items": [ + { "id": "doc1", "type": "doc", }, @@ -88,11 +88,11 @@ Object { "link": undefined, "type": "category", }, - Object { + { "collapsed": false, "collapsible": true, - "items": Array [ - Object { + "items": [ + { "id": "doc2", "type": "doc", }, @@ -105,42 +105,42 @@ Object { } `; -exports[`loadNormalizedSidebars sidebars with deep level of category 1`] = ` -Object { - "docs": Array [ - Object { +exports[`loadSidebars sidebars with deep level of category 1`] = ` +{ + "docs": [ + { "collapsed": true, "collapsible": true, - "items": Array [ - Object { + "items": [ + { "id": "a", "type": "doc", }, - Object { + { "collapsed": true, "collapsible": true, - "items": Array [ - Object { + "items": [ + { "collapsed": true, "collapsible": true, - "items": Array [ - Object { + "items": [ + { "id": "c", "type": "doc", }, - Object { + { "collapsed": true, "collapsible": true, - "items": Array [ - Object { + "items": [ + { "id": "d", "type": "doc", }, - Object { + { "collapsed": true, "collapsible": true, - "items": Array [ - Object { + "items": [ + { "id": "e", "type": "doc", }, @@ -159,7 +159,7 @@ Object { "link": undefined, "type": "category", }, - Object { + { "id": "f", "type": "doc", }, @@ -177,14 +177,14 @@ Object { } `; -exports[`loadNormalizedSidebars sidebars with first level not a category 1`] = ` -Object { - "docs": Array [ - Object { +exports[`loadSidebars sidebars with first level not a category 1`] = ` +{ + "docs": [ + { "collapsed": true, "collapsible": true, - "items": Array [ - Object { + "items": [ + { "id": "greeting", "type": "doc", }, @@ -193,7 +193,7 @@ Object { "link": undefined, "type": "category", }, - Object { + { "id": "api", "type": "doc", }, @@ -201,27 +201,27 @@ Object { } `; -exports[`loadNormalizedSidebars sidebars with known sidebar item type 1`] = ` -Object { - "docs": Array [ - Object { +exports[`loadSidebars sidebars with known sidebar item type 1`] = ` +{ + "docs": [ + { "collapsed": true, "collapsible": true, - "items": Array [ - Object { + "items": [ + { "id": "foo/bar", "type": "doc", }, - Object { + { "id": "foo/baz", "type": "doc", }, - Object { + { "href": "https://github.com", "label": "Github", "type": "link", }, - Object { + { "id": "hello", "type": "ref", }, @@ -230,11 +230,11 @@ Object { "link": undefined, "type": "category", }, - Object { + { "collapsed": true, "collapsible": true, - "items": Array [ - Object { + "items": [ + { "id": "hello", "type": "doc", }, @@ -246,3 +246,23 @@ Object { ], } `; + +exports[`loadSidebars undefined path 1`] = ` +{ + "defaultSidebar": [ + { + "collapsed": true, + "collapsible": true, + "items": [ + { + "id": "bar", + "type": "doc", + }, + ], + "label": "foo", + "link": undefined, + "type": "category", + }, + ], +} +`; diff --git a/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/__snapshots__/normalization.test.ts.snap b/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/__snapshots__/normalization.test.ts.snap new file mode 100644 index 000000000000..f847163c3050 --- /dev/null +++ b/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/__snapshots__/normalization.test.ts.snap @@ -0,0 +1,96 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`normalization normalizes shorthands 1`] = ` +{ + "sidebar": [ + { + "items": [ + { + "id": "doc1", + "type": "doc", + }, + { + "id": "doc2", + "type": "doc", + }, + ], + "label": "Category", + "type": "category", + }, + { + "items": [ + { + "items": [ + { + "id": "doc3", + "type": "doc", + }, + { + "id": "doc4", + "type": "doc", + }, + ], + "label": "Subcategory 1", + "type": "category", + }, + { + "items": [ + { + "id": "doc5", + "type": "doc", + }, + { + "id": "doc6", + "type": "doc", + }, + ], + "label": "Subcategory 2", + "type": "category", + }, + ], + "label": "Category 2", + "type": "category", + }, + ], +} +`; + +exports[`normalization normalizes shorthands 2`] = ` +{ + "sidebar": [ + { + "href": "https://google.com", + "label": "Google", + "type": "link", + }, + { + "items": [ + { + "id": "doc1", + "type": "doc", + }, + { + "id": "doc2", + "type": "doc", + }, + ], + "label": "Category 1", + "type": "category", + }, + { + "items": [ + { + "id": "doc3", + "type": "doc", + }, + { + "id": "doc4", + "type": "doc", + }, + ], + "label": "Category 2", + "type": "category", + }, + ], +} +`; diff --git a/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/__snapshots__/postProcessor.test.ts.snap b/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/__snapshots__/postProcessor.test.ts.snap new file mode 100644 index 000000000000..cb42628261f3 --- /dev/null +++ b/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/__snapshots__/postProcessor.test.ts.snap @@ -0,0 +1,78 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`postProcess corrects collapsed state inconsistencies 1`] = ` +{ + "sidebar": [ + { + "collapsed": false, + "collapsible": false, + "items": [ + { + "id": "foo", + "type": "doc", + }, + ], + "label": "Category", + "link": undefined, + "type": "category", + }, + ], +} +`; + +exports[`postProcess corrects collapsed state inconsistencies 2`] = ` +{ + "sidebar": [ + { + "collapsed": false, + "collapsible": false, + "items": [ + { + "id": "foo", + "type": "doc", + }, + ], + "label": "Category", + "link": undefined, + "type": "category", + }, + ], +} +`; + +exports[`postProcess corrects collapsed state inconsistencies 3`] = ` +{ + "sidebar": [ + { + "collapsed": false, + "collapsible": false, + "items": [ + { + "id": "foo", + "type": "doc", + }, + ], + "label": "Category", + "link": undefined, + "type": "category", + }, + ], +} +`; + +exports[`postProcess transforms category without subitems 1`] = ` +{ + "sidebar": [ + { + "href": "version/generated/permalink", + "label": "Category", + "type": "link", + }, + { + "id": "doc ID", + "label": "Category 2", + "type": "doc", + }, + ], +} +`; diff --git a/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/generator.test.ts b/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/generator.test.ts index 67e70732946b..66281f746b68 100644 --- a/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/generator.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/generator.test.ts @@ -5,12 +5,9 @@ * LICENSE file in the root directory of this source tree. */ -import { - DefaultSidebarItemsGenerator, - type CategoryMetadataFile, -} from '../generator'; -import type {Sidebar, SidebarItemsGenerator} from '../types'; -import fs from 'fs-extra'; +import {jest} from '@jest/globals'; +import {DefaultSidebarItemsGenerator} from '../generator'; +import type {SidebarItemsGenerator} from '../types'; import {DefaultNumberPrefixParser} from '../../numberPrefix'; import {isCategoryIndex} from '../../docs'; @@ -34,27 +31,12 @@ describe('DefaultSidebarItemsGenerator', () => { sidebarCollapsed: true, sidebarCollapsible: true, }, + categoriesMetadata: {}, ...params, }); } - function mockCategoryMetadataFiles( - categoryMetadataFiles: Record>, - ) { - jest - .spyOn(fs, 'pathExists') - .mockImplementation( - (metadataFilePath) => - typeof categoryMetadataFiles[metadataFilePath] !== 'undefined', - ); - jest.spyOn(fs, 'readFile').mockImplementation( - // @ts-expect-error: annoying TS error due to overrides - async (metadataFilePath: string) => - JSON.stringify(categoryMetadataFiles[metadataFilePath]), - ); - } - - test('generates empty sidebar slice when no docs and emit a warning', async () => { + it('generates empty sidebar slice when no docs and emit a warning', async () => { const consoleWarn = jest.spyOn(console, 'warn'); const sidebarSlice = await testDefaultSidebarItemsGenerator({ docs: [], @@ -62,12 +44,12 @@ describe('DefaultSidebarItemsGenerator', () => { expect(sidebarSlice).toEqual([]); expect(consoleWarn).toHaveBeenCalledWith( expect.stringMatching( - /.*\[WARNING\].* No docs found in .*\..*: can't auto-generate a sidebar\..*/, + /.*\[WARNING\].* No docs found in [^.]*\..*: can't auto-generate a sidebar\..*/, ), ); }); - test('generates simple flat sidebar', async () => { + it('generates simple flat sidebar', async () => { const sidebarSlice = await DefaultSidebarItemsGenerator({ numberPrefixParser: DefaultNumberPrefixParser, item: { @@ -123,29 +105,10 @@ describe('DefaultSidebarItemsGenerator', () => { }, }); - expect(sidebarSlice).toEqual([ - {type: 'doc', id: 'doc3'}, - {type: 'doc', id: 'doc4'}, - {type: 'doc', id: 'doc1', label: 'doc1 sidebar label'}, - {type: 'doc', id: 'doc2'}, - {type: 'doc', id: 'doc5'}, - ] as Sidebar); + expect(sidebarSlice).toMatchSnapshot(); }); - test('generates complex nested sidebar', async () => { - mockCategoryMetadataFiles({ - '02-Guides/_category_.json': {collapsed: false} as CategoryMetadataFile, - '02-Guides/01-SubGuides/_category_.yml': { - label: 'SubGuides (metadata file label)', - link: { - type: 'generated-index', - slug: 'subguides-generated-index-slug', - title: 'subguides-title', - description: 'subguides-description', - }, - }, - }); - + it('generates complex nested sidebar', async () => { const sidebarSlice = await DefaultSidebarItemsGenerator({ numberPrefixParser: DefaultNumberPrefixParser, isCategoryIndex, @@ -157,12 +120,29 @@ describe('DefaultSidebarItemsGenerator', () => { versionName: 'current', contentPath: '', }, + categoriesMetadata: { + '02-Guides': { + collapsed: false, + customProps: { + description: 'foo', + }, + }, + '02-Guides/01-SubGuides': { + label: 'SubGuides (metadata file label)', + link: { + type: 'generated-index', + slug: 'subGuides-generated-index-slug', + title: 'subGuides-title', + description: 'subGuides-description', + }, + }, + }, docs: [ { id: 'intro', source: '@site/docs/intro.md', sourceDirName: '.', - sidebarPosition: 1, + sidebarPosition: 0, frontMatter: {}, }, { @@ -203,7 +183,7 @@ describe('DefaultSidebarItemsGenerator', () => { id: 'guide1', source: '@site/docs/02-Guides/guide1.md', sourceDirName: '02-Guides', - sidebarPosition: 1, + sidebarPosition: 0, frontMatter: { sidebar_class_name: 'foo', }, @@ -229,74 +209,12 @@ describe('DefaultSidebarItemsGenerator', () => { }, }); - expect(sidebarSlice).toEqual([ - {type: 'doc', id: 'intro'}, - { - type: 'category', - label: 'Tutorials', - collapsed: true, - collapsible: true, - link: { - type: 'doc', - id: 'tutorials-index', - }, - items: [ - {type: 'doc', id: 'tutorial1'}, - {type: 'doc', id: 'tutorial2'}, - ], - }, - { - type: 'category', - label: 'Guides', - collapsed: false, - collapsible: true, - link: { - type: 'doc', - id: 'guides-index', - }, - items: [ - {type: 'doc', id: 'guide1', className: 'foo'}, - { - type: 'category', - label: 'SubGuides (metadata file label)', - collapsed: true, - collapsible: true, - items: [{type: 'doc', id: 'nested-guide'}], - link: { - type: 'generated-index', - slug: 'subguides-generated-index-slug', - title: 'subguides-title', - description: 'subguides-description', - }, - }, - {type: 'doc', id: 'guide2'}, - ], - }, - {type: 'doc', id: 'end'}, - ] as Sidebar); + expect(sidebarSlice).toMatchSnapshot(); }); - test('generates subfolder sidebar', async () => { + it('generates subfolder sidebar', async () => { // Ensure that category metadata file is correctly read // fix edge case found in https://github.com/facebook/docusaurus/issues/4638 - mockCategoryMetadataFiles({ - 'subfolder/subsubfolder/subsubsubfolder2/_category_.yml': { - position: 2, - label: 'subsubsubfolder2 (_category_.yml label)', - className: 'bar', - }, - 'subfolder/subsubfolder/subsubsubfolder3/_category_.json': { - position: 1, - label: 'subsubsubfolder3 (_category_.json label)', - collapsible: false, - collapsed: false, - link: { - type: 'doc', - id: 'doc1', // This is a "fully-qualified" ID that can't be found locally - }, - }, - }); - const sidebarSlice = await DefaultSidebarItemsGenerator({ numberPrefixParser: DefaultNumberPrefixParser, isCategoryIndex, @@ -308,11 +226,27 @@ describe('DefaultSidebarItemsGenerator', () => { versionName: 'current', contentPath: '', }, + categoriesMetadata: { + 'subfolder/subsubfolder/subsubsubfolder2': { + position: 2, + label: 'subsubsubfolder2 (_category_.yml label)', + className: 'bar', + }, + 'subfolder/subsubfolder/subsubsubfolder3': { + position: 1, + // This item's label is defined from the index doc instead + link: { + type: 'doc', + id: 'doc1', // This is a "fully-qualified" ID that can't be found locally + }, + }, + }, docs: [ { id: 'doc1', source: 'doc1.md', sourceDirName: 'subfolder/subsubfolder', + title: 'Subsubsubfolder category label', sidebarPosition: undefined, frontMatter: {}, }, @@ -372,52 +306,10 @@ describe('DefaultSidebarItemsGenerator', () => { }, }); - expect(sidebarSlice).toEqual([ - { - type: 'category', - label: 'subsubsubfolder3 (_category_.json label)', - collapsed: false, - collapsible: false, - link: { - id: 'doc1', - type: 'doc', - }, - items: [ - {type: 'doc', id: 'doc8'}, - {type: 'doc', id: 'doc7'}, - ], - }, - { - type: 'category', - label: 'subsubsubfolder2 (_category_.yml label)', - collapsed: true, - collapsible: true, - className: 'bar', - items: [{type: 'doc', id: 'doc6'}], - }, - {type: 'doc', id: 'doc1'}, - {type: 'doc', id: 'doc4'}, - { - type: 'category', - label: 'subsubsubfolder', - collapsed: true, - collapsible: true, - items: [{type: 'doc', id: 'doc5'}], - }, - ] as Sidebar); + expect(sidebarSlice).toMatchSnapshot(); }); - test('uses explicit link over the index/readme.{md,mdx} naming convention', async () => { - mockCategoryMetadataFiles({ - 'Category/_category_.yml': { - label: 'Category label', - link: { - type: 'doc', - id: 'doc3', // Using a "local doc id" ("doc1" instead of "parent/doc1") on purpose - }, - }, - }); - + it('uses explicit link over the index/readme.{md,mdx} naming convention', async () => { const sidebarSlice = await DefaultSidebarItemsGenerator({ numberPrefixParser: DefaultNumberPrefixParser, item: { @@ -428,6 +320,19 @@ describe('DefaultSidebarItemsGenerator', () => { versionName: 'current', contentPath: '', }, + categoriesMetadata: { + Category: { + label: 'Category label', + link: { + type: 'doc', + id: 'doc3', // Using a "local doc id" ("doc1" instead of "parent/doc1") on purpose + }, + }, + Category2: { + label: 'Category 2 label', + link: null, + }, + }, docs: [ { id: 'parent/doc1', @@ -437,7 +342,7 @@ describe('DefaultSidebarItemsGenerator', () => { }, { id: 'parent/doc2', - source: '@site/docs/Category/index.md', + source: '@site/docs/Category/doc2.md', sourceDirName: 'Category', frontMatter: {}, }, @@ -447,6 +352,24 @@ describe('DefaultSidebarItemsGenerator', () => { sourceDirName: 'Category', frontMatter: {}, }, + { + id: 'parent/doc4', + source: '@site/docs/Category2/doc1.md', + sourceDirName: 'Category2', + frontMatter: {}, + }, + { + id: 'parent/doc5', + source: '@site/docs/Category2/index.md', + sourceDirName: 'Category2', + frontMatter: {}, + }, + { + id: 'parent/doc6', + source: '@site/docs/Category2/doc3.md', + sourceDirName: 'Category2', + frontMatter: {}, + }, ], options: { sidebarCollapsed: true, @@ -454,31 +377,10 @@ describe('DefaultSidebarItemsGenerator', () => { }, }); - expect(sidebarSlice).toEqual([ - { - type: 'category', - label: 'Category label', - collapsed: true, - collapsible: true, - link: { - id: 'parent/doc3', - type: 'doc', - }, - items: [ - { - id: 'parent/doc1', - type: 'doc', - }, - { - id: 'parent/doc2', - type: 'doc', - }, - ], - }, - ] as Sidebar); + expect(sidebarSlice).toMatchSnapshot(); }); - test('respects custom isCategoryIndex', async () => { + it('respects custom isCategoryIndex', async () => { const sidebarSlice = await DefaultSidebarItemsGenerator({ numberPrefixParser: DefaultNumberPrefixParser, isCategoryIndex({fileName, directories}) { @@ -499,12 +401,13 @@ describe('DefaultSidebarItemsGenerator', () => { versionName: 'current', contentPath: '', }, + categoriesMetadata: {}, docs: [ { id: 'intro', source: '@site/docs/intro.md', sourceDirName: '.', - sidebarPosition: 1, + sidebarPosition: 0, frontMatter: {}, }, { @@ -557,36 +460,49 @@ describe('DefaultSidebarItemsGenerator', () => { }, }); - expect(sidebarSlice).toEqual([ - {type: 'doc', id: 'intro'}, - { - type: 'category', - label: 'Tutorials', - collapsed: true, - collapsible: true, - link: { - type: 'doc', - id: 'tutorials-index', + expect(sidebarSlice).toMatchSnapshot(); + }); + + it('throws for unknown index link', async () => { + const generateSidebar = () => + DefaultSidebarItemsGenerator({ + numberPrefixParser: DefaultNumberPrefixParser, + isCategoryIndex, + item: { + type: 'autogenerated', + dirName: '.', + }, + version: { + versionName: 'current', + contentPath: '', + }, + categoriesMetadata: { + category: { + link: { + type: 'doc', + id: 'foo', + }, + }, }, - items: [ - {type: 'doc', id: 'tutorial1'}, - {type: 'doc', id: 'tutorial2'}, - ], - }, - { - type: 'category', - label: 'Guides', - collapsed: true, - collapsible: true, - items: [ - {type: 'doc', id: 'guide1', className: 'foo'}, - {type: 'doc', id: 'guide2'}, + docs: [ { - type: 'doc', - id: 'not-guides-index', + id: 'intro', + unversionedId: 'intro', + source: '@site/docs/category/intro.md', + sourceDirName: 'category', + frontMatter: {}, }, ], - }, - ] as Sidebar); + options: { + sidebarCollapsed: true, + sidebarCollapsible: true, + }, + }); + + await expect(generateSidebar).rejects.toThrowErrorMatchingInlineSnapshot(` + "Can't find any doc with ID foo. + Available doc IDs: + - intro" + `); }); }); diff --git a/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/index.test.ts b/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/index.test.ts index 13a41c213ec6..ec19a5c4fb69 100644 --- a/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/index.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/index.test.ts @@ -5,200 +5,142 @@ * LICENSE file in the root directory of this source tree. */ +import {jest} from '@jest/globals'; import path from 'path'; -import { - loadNormalizedSidebars, - DefaultSidebars, - DisabledSidebars, -} from '../index'; -import type {NormalizeSidebarsParams, VersionMetadata} from '../../types'; +import {loadSidebars, DisabledSidebars} from '../index'; +import type {SidebarProcessorParams} from '../types'; +import {DefaultSidebarItemsGenerator} from '../generator'; -describe('loadNormalizedSidebars', () => { +describe('loadSidebars', () => { const fixtureDir = path.join(__dirname, '__fixtures__', 'sidebars'); - const options: NormalizeSidebarsParams = { - sidebarCollapsed: true, - sidebarCollapsible: true, + const params: SidebarProcessorParams = { + sidebarItemsGenerator: DefaultSidebarItemsGenerator, + numberPrefixParser: (filename) => ({filename}), + docs: [ + { + source: '@site/docs/foo/bar.md', + sourceDirName: 'foo', + id: 'bar', + frontMatter: {}, + }, + ], version: { - versionName: 'version', - versionPath: 'versionPath', - } as VersionMetadata, + contentPath: path.join(fixtureDir, 'docs'), + contentPathLocalized: path.join(fixtureDir, 'docs'), + }, + categoryLabelSlugger: null, + sidebarOptions: {sidebarCollapsed: true, sidebarCollapsible: true}, }; - test('sidebars with known sidebar item type', async () => { + it('sidebars with known sidebar item type', async () => { const sidebarPath = path.join(fixtureDir, 'sidebars.json'); - const result = loadNormalizedSidebars(sidebarPath, options); + const result = await loadSidebars(sidebarPath, params); expect(result).toMatchSnapshot(); }); - test('sidebars with deep level of category', async () => { + it('sidebars with deep level of category', async () => { const sidebarPath = path.join(fixtureDir, 'sidebars-category.js'); - const result = loadNormalizedSidebars(sidebarPath, options); + const result = await loadSidebars(sidebarPath, params); expect(result).toMatchSnapshot(); }); - test('sidebars shorthand and longform lead to exact same sidebar', async () => { + it('sidebars shorthand and longhand lead to exact same sidebar', async () => { const sidebarPath1 = path.join(fixtureDir, 'sidebars-category.js'); const sidebarPath2 = path.join( fixtureDir, 'sidebars-category-shorthand.js', ); - const sidebar1 = loadNormalizedSidebars(sidebarPath1, options); - const sidebar2 = loadNormalizedSidebars(sidebarPath2, options); + const sidebar1 = await loadSidebars(sidebarPath1, params); + const sidebar2 = await loadSidebars(sidebarPath2, params); expect(sidebar1).toEqual(sidebar2); }); - test('sidebars with category but category.items is not an array', async () => { + it('sidebars with category but category.items is not an array', async () => { const sidebarPath = path.join( fixtureDir, 'sidebars-category-wrong-items.json', ); - expect(() => loadNormalizedSidebars(sidebarPath, options)) - .toThrowErrorMatchingInlineSnapshot(` - "{ - \\"type\\": \\"category\\", - \\"label\\": \\"Category Label\\", - \\"items\\" [1]: \\"doc1\\" - } -  - [1] \\"items\\" must be an array" - `); - }); - - test('sidebars with category but category label is not a string', async () => { - const sidebarPath = path.join( - fixtureDir, - 'sidebars-category-wrong-label.json', - ); - expect(() => loadNormalizedSidebars(sidebarPath, options)) - .toThrowErrorMatchingInlineSnapshot(` - "{ - \\"type\\": \\"category\\", - \\"items\\": [ - \\"doc1\\" - ], - \\"label\\" [1]: true - } -  - [1] \\"label\\" must be a string" - `); - }); - - test('sidebars item doc but id is not a string', async () => { - const sidebarPath = path.join( - fixtureDir, - 'sidebars-doc-id-not-string.json', + await expect(() => + loadSidebars(sidebarPath, params), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Invalid sidebar items collection \`\\"doc1\\"\` in \`items\` of the category Category Label: it must either be an array of sidebar items or a shorthand notation (which doesn't contain a \`type\` property). See https://docusaurus.io/docs/sidebar/items for all valid syntaxes."`, ); - expect(() => loadNormalizedSidebars(sidebarPath, options)) - .toThrowErrorMatchingInlineSnapshot(` - "{ - \\"type\\": \\"doc\\", - \\"id\\" [1]: [ - \\"doc1\\" - ] - } -  - [1] \\"id\\" must be a string" - `); }); - test('sidebars with first level not a category', async () => { + it('sidebars with first level not a category', async () => { const sidebarPath = path.join( fixtureDir, 'sidebars-first-level-not-category.js', ); - const result = loadNormalizedSidebars(sidebarPath, options); + const result = await loadSidebars(sidebarPath, params); expect(result).toMatchSnapshot(); }); - test('sidebars link', async () => { + it('sidebars link', async () => { const sidebarPath = path.join(fixtureDir, 'sidebars-link.json'); - const result = loadNormalizedSidebars(sidebarPath, options); + const result = await loadSidebars(sidebarPath, params); expect(result).toMatchSnapshot(); }); - test('sidebars link wrong label', async () => { - const sidebarPath = path.join(fixtureDir, 'sidebars-link-wrong-label.json'); - expect(() => loadNormalizedSidebars(sidebarPath, options)) - .toThrowErrorMatchingInlineSnapshot(` - "{ - \\"type\\": \\"link\\", - \\"href\\": \\"https://github.com\\", - \\"label\\" [1]: false - } -  - [1] \\"label\\" must be a string" - `); - }); - - test('sidebars link wrong href', async () => { - const sidebarPath = path.join(fixtureDir, 'sidebars-link-wrong-href.json'); - expect(() => loadNormalizedSidebars(sidebarPath, options)) - .toThrowErrorMatchingInlineSnapshot(` - "{ - \\"type\\": \\"link\\", - \\"label\\": \\"GitHub\\", - \\"href\\" [1]: [ - \\"example.com\\" - ] - } -  - [1] \\"href\\" contains an invalid value" - `); - }); - - test('sidebars with unknown sidebar item type', async () => { - const sidebarPath = path.join(fixtureDir, 'sidebars-unknown-type.json'); - expect(() => loadNormalizedSidebars(sidebarPath, options)) - .toThrowErrorMatchingInlineSnapshot(` - "{ - \\"type\\": \\"superman\\", - \\"undefined\\" [1]: -- missing -- - } -  - [1] Unknown sidebar item type \\"superman\\"." - `); - }); - - test('sidebars with known sidebar item type but wrong field', async () => { - const sidebarPath = path.join(fixtureDir, 'sidebars-wrong-field.json'); - expect(() => loadNormalizedSidebars(sidebarPath, options)) - .toThrowErrorMatchingInlineSnapshot(` - "{ - \\"type\\": \\"category\\", - \\"label\\": \\"category\\", - \\"href\\": \\"https://github.com\\", - \\"items\\" [1]: -- missing -- - } -  - [1] \\"items\\" is required" - `); - }); - - test('unexisting path', () => { - expect(loadNormalizedSidebars('badpath', options)).toEqual( + it('nonexistent path', async () => { + await expect(loadSidebars('bad/path', params)).resolves.toEqual( DisabledSidebars, ); }); - test('undefined path', () => { - expect(loadNormalizedSidebars(undefined, options)).toEqual(DefaultSidebars); + it('undefined path', async () => { + await expect(loadSidebars(undefined, params)).resolves.toMatchSnapshot(); }); - test('literal false path', () => { - expect(loadNormalizedSidebars(false, options)).toEqual(DisabledSidebars); + it('literal false path', async () => { + await expect(loadSidebars(false, params)).resolves.toEqual( + DisabledSidebars, + ); }); - test('sidebars with category.collapsed property', async () => { + it('sidebars with category.collapsed property', async () => { const sidebarPath = path.join(fixtureDir, 'sidebars-collapsed.json'); - const result = loadNormalizedSidebars(sidebarPath, options); + const result = await loadSidebars(sidebarPath, params); expect(result).toMatchSnapshot(); }); - test('sidebars with category.collapsed property at first level', async () => { + it('sidebars with category.collapsed property at first level', async () => { const sidebarPath = path.join( fixtureDir, 'sidebars-collapsed-first-level.json', ); - const result = loadNormalizedSidebars(sidebarPath, options); + const result = await loadSidebars(sidebarPath, params); expect(result).toMatchSnapshot(); }); + + it('duplicate category metadata files', async () => { + const sidebarPath = path.join( + fixtureDir, + 'sidebars-collapsed-first-level.json', + ); + const consoleWarnMock = jest + .spyOn(console, 'warn') + .mockImplementation(() => {}); + const consoleErrorMock = jest + .spyOn(console, 'error') + .mockImplementation(() => {}); + await expect(() => + loadSidebars(sidebarPath, { + ...params, + version: { + contentPath: path.join(fixtureDir, 'invalid-docs'), + contentPathLocalized: path.join(fixtureDir, 'invalid-docs'), + }, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot(`"\\"foo\\" is not allowed"`); + expect(consoleWarnMock).toBeCalledWith( + expect.stringMatching( + /.*\[WARNING\].* There are more than one category metadata files for .*foo.*: foo\/_category_.json, foo\/_category_.yml. The behavior is undetermined./, + ), + ); + expect(consoleErrorMock).toBeCalledWith( + expect.stringMatching( + /.*\[ERROR\].* The docs sidebar category metadata file .*foo\/_category_.json.* looks invalid!/, + ), + ); + }); }); diff --git a/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/normalization.test.ts b/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/normalization.test.ts new file mode 100644 index 000000000000..66a27886d0ba --- /dev/null +++ b/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/normalization.test.ts @@ -0,0 +1,90 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {normalizeSidebars} from '../normalization'; + +describe('normalization', () => { + it('normalizes shorthands', () => { + expect( + normalizeSidebars({ + sidebar: { + Category: ['doc1', 'doc2'], + 'Category 2': { + 'Subcategory 1': ['doc3', 'doc4'], + 'Subcategory 2': ['doc5', 'doc6'], + }, + }, + }), + ).toMatchSnapshot(); + + expect( + normalizeSidebars({ + sidebar: [ + { + type: 'link', + label: 'Google', + href: 'https://google.com', + }, + { + 'Category 1': ['doc1', 'doc2'], + 'Category 2': ['doc3', 'doc4'], + }, + ], + }), + ).toMatchSnapshot(); + }); + it('rejects some invalid cases', () => { + expect(() => + normalizeSidebars({ + sidebar: { + Category: {type: 'autogenerated', dirName: 'foo'}, + Category2: {type: 'autogenerated', dirName: 'bar'}, + }, + }), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid sidebar items collection \`{\\"type\\":\\"autogenerated\\",\\"dirName\\":\\"foo\\"}\` in \`items\` of the category Category: it must either be an array of sidebar items or a shorthand notation (which doesn't contain a \`type\` property). See https://docusaurus.io/docs/sidebar/items for all valid syntaxes."`, + ); + + expect(() => + normalizeSidebars({ + sidebar: [ + 'foo', + { + Category: { + type: 'category', + items: ['bar', 'baz'], + }, + }, + ], + }), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid sidebar items collection \`{\\"type\\":\\"category\\",\\"items\\":[\\"bar\\",\\"baz\\"]}\` in \`items\` of the category Category: it must either be an array of sidebar items or a shorthand notation (which doesn't contain a \`type\` property). See https://docusaurus.io/docs/sidebar/items for all valid syntaxes."`, + ); + + expect(() => + normalizeSidebars({ + sidebar: [ + 'foo', + { + Category: 'bar', + }, + ], + }), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid sidebar items collection \`\\"bar\\"\` in \`items\` of the category Category: it must either be an array of sidebar items or a shorthand notation (which doesn't contain a \`type\` property). See https://docusaurus.io/docs/sidebar/items for all valid syntaxes."`, + ); + + expect(() => + normalizeSidebars({ + sidebar: 'item', + }), + ).toThrowErrorMatchingInlineSnapshot( + // cSpell:ignore msidebar + `"Invalid sidebar items collection \`\\"item\\"\` in sidebar sidebar: it must either be an array of sidebar items or a shorthand notation (which doesn't contain a \`type\` property). See https://docusaurus.io/docs/sidebar/items for all valid syntaxes."`, + ); + }); +}); diff --git a/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/postProcessor.test.ts b/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/postProcessor.test.ts new file mode 100644 index 000000000000..a1a8e56d8fd1 --- /dev/null +++ b/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/postProcessor.test.ts @@ -0,0 +1,125 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {postProcessSidebars} from '../postProcessor'; + +describe('postProcess', () => { + it('transforms category without subitems', () => { + const processedSidebar = postProcessSidebars( + { + sidebar: [ + { + type: 'category', + label: 'Category', + link: { + type: 'generated-index', + slug: 'generated/permalink', + }, + items: [], + }, + { + type: 'category', + label: 'Category 2', + link: { + type: 'doc', + id: 'doc ID', + }, + items: [], + }, + ], + }, + { + sidebarOptions: {sidebarCollapsed: true, sidebarCollapsible: true}, + version: {path: 'version'}, + }, + ); + + expect(processedSidebar).toMatchSnapshot(); + + expect(() => { + postProcessSidebars( + { + sidebar: [ + { + type: 'category', + label: 'Bad category', + items: [], + }, + ], + }, + { + sidebarOptions: {sidebarCollapsed: true, sidebarCollapsible: true}, + version: {path: 'version'}, + }, + ); + }).toThrowErrorMatchingInlineSnapshot( + `"Sidebar category Bad category has neither any subitem nor a link. This makes this item not able to link to anything."`, + ); + }); + + it('corrects collapsed state inconsistencies', () => { + expect( + postProcessSidebars( + { + sidebar: [ + { + type: 'category', + label: 'Category', + collapsed: true, + collapsible: false, + items: [{type: 'doc', id: 'foo'}], + }, + ], + }, + + { + sidebarOptions: {sidebarCollapsed: true, sidebarCollapsible: true}, + version: {path: 'version'}, + }, + ), + ).toMatchSnapshot(); + + expect( + postProcessSidebars( + { + sidebar: [ + { + type: 'category', + label: 'Category', + collapsed: true, + items: [{type: 'doc', id: 'foo'}], + }, + ], + }, + + { + sidebarOptions: {sidebarCollapsed: false, sidebarCollapsible: false}, + version: {path: 'version'}, + }, + ), + ).toMatchSnapshot(); + + expect( + postProcessSidebars( + { + sidebar: [ + { + type: 'category', + label: 'Category', + items: [{type: 'doc', id: 'foo'}], + }, + ], + }, + + { + sidebarOptions: {sidebarCollapsed: true, sidebarCollapsible: false}, + version: {path: 'version'}, + }, + ), + ).toMatchSnapshot(); + }); +}); diff --git a/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/processor.test.ts b/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/processor.test.ts index 3341d8c39933..500eefbfe1dd 100644 --- a/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/processor.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/processor.test.ts @@ -5,16 +5,20 @@ * LICENSE file in the root directory of this source tree. */ -import {processSidebars, type SidebarProcessorParams} from '../processor'; +import {jest} from '@jest/globals'; +import {processSidebars} from '../processor'; import type { SidebarItem, SidebarItemsGenerator, - Sidebars, + NormalizedSidebar, NormalizedSidebars, + SidebarProcessorParams, + CategoryMetadataFile, + ProcessedSidebars, } from '../types'; import {DefaultSidebarItemsGenerator} from '../generator'; import {createSlugger} from '@docusaurus/utils'; -import type {VersionMetadata} from '../../types'; +import type {VersionMetadata} from '@docusaurus/plugin-content-docs'; import {DefaultNumberPrefixParser} from '../../numberPrefix'; import {isCategoryIndex} from '../../docs'; @@ -25,7 +29,7 @@ describe('processSidebars', () => { return jest.fn(async () => sidebarSlice); } - const StaticGeneratedSidebarSlice: SidebarItem[] = [ + const StaticGeneratedSidebarSlice: NormalizedSidebar = [ {type: 'doc', id: 'doc-generated-id-1'}, {type: 'doc', id: 'doc-generated-id-2'}, ]; @@ -53,15 +57,16 @@ describe('processSidebars', () => { async function testProcessSidebars( unprocessedSidebars: NormalizedSidebars, + categoriesMetadata: {[filePath: string]: CategoryMetadataFile} = {}, paramsOverrides: Partial = {}, ) { - return processSidebars(unprocessedSidebars, { + return processSidebars(unprocessedSidebars, categoriesMetadata, { ...params, ...paramsOverrides, }); } - test('let sidebars without autogenerated items untouched', async () => { + it('leaves sidebars without autogenerated items untouched', async () => { const unprocessedSidebars: NormalizedSidebars = { someSidebar: [ {type: 'doc', id: 'doc1'}, @@ -91,7 +96,7 @@ describe('processSidebars', () => { expect(processedSidebar).toEqual(unprocessedSidebars); }); - test('replace autogenerated items by generated sidebars slices', async () => { + it('replaces autogenerated items by generated sidebars slices', async () => { const unprocessedSidebars: NormalizedSidebars = { someSidebar: [ {type: 'doc', id: 'doc1'}, @@ -101,10 +106,7 @@ describe('processSidebars', () => { link: { type: 'generated-index', slug: 'category-generated-index-slug', - permalink: 'category-generated-index-permalink', }, - collapsed: true, // A suspicious bad config that will be normalized - collapsible: false, items: [ {type: 'doc', id: 'doc2'}, {type: 'autogenerated', dirName: 'dir1'}, @@ -131,6 +133,7 @@ describe('processSidebars', () => { expect(StaticSidebarItemsGenerator).toHaveBeenCalledTimes(3); expect(StaticSidebarItemsGenerator).toHaveBeenCalledWith({ + categoriesMetadata: {}, defaultSidebarItemsGenerator: DefaultSidebarItemsGenerator, item: {type: 'autogenerated', dirName: 'dir1'}, docs: params.docs, @@ -139,10 +142,10 @@ describe('processSidebars', () => { }, numberPrefixParser: DefaultNumberPrefixParser, isCategoryIndex, - options: params.sidebarOptions, }); expect(StaticSidebarItemsGenerator).toHaveBeenCalledWith({ defaultSidebarItemsGenerator: DefaultSidebarItemsGenerator, + categoriesMetadata: {}, item: {type: 'autogenerated', dirName: 'dir2'}, docs: params.docs, version: { @@ -150,10 +153,10 @@ describe('processSidebars', () => { }, numberPrefixParser: DefaultNumberPrefixParser, isCategoryIndex, - options: params.sidebarOptions, }); expect(StaticSidebarItemsGenerator).toHaveBeenCalledWith({ defaultSidebarItemsGenerator: DefaultSidebarItemsGenerator, + categoriesMetadata: {}, item: {type: 'autogenerated', dirName: 'dir3'}, docs: params.docs, version: { @@ -161,7 +164,6 @@ describe('processSidebars', () => { }, numberPrefixParser: DefaultNumberPrefixParser, isCategoryIndex, - options: params.sidebarOptions, }); expect(processedSidebar).toEqual({ @@ -173,10 +175,7 @@ describe('processSidebars', () => { link: { type: 'generated-index', slug: 'category-generated-index-slug', - permalink: 'category-generated-index-permalink', }, - collapsed: false, - collapsible: false, items: [{type: 'doc', id: 'doc2'}, ...StaticGeneratedSidebarSlice], }, {type: 'link', href: 'https://facebook.com', label: 'FB'}, @@ -194,32 +193,40 @@ describe('processSidebars', () => { items: [{type: 'doc', id: 'doc4'}], }, ], - } as Sidebars); + } as ProcessedSidebars); }); - test('ensure generated items are normalized', async () => { - const sidebarSliceContainingCategoryGeneratedIndex: SidebarItem[] = [ + it('ensures generated items are normalized', async () => { + const sidebarSliceContainingCategoryGeneratedIndex: NormalizedSidebar = [ { type: 'category', label: 'Generated category', link: { type: 'generated-index', slug: 'generated-cat-index-slug', - // @ts-expect-error: TODO undefined should be allowed here, typing error needing refactor - permalink: undefined, }, + items: [ + { + type: 'doc', + id: 'foo', + }, + ], }, ]; - const unprocessedSidebars: NormalizedSidebars = { + const unprocessedSidebars = { someSidebar: [{type: 'autogenerated', dirName: 'dir2'}], }; - const processedSidebar = await testProcessSidebars(unprocessedSidebars, { - sidebarItemsGenerator: createStaticSidebarItemGenerator( - sidebarSliceContainingCategoryGeneratedIndex, - ), - }); + const processedSidebar = await testProcessSidebars( + unprocessedSidebars, + {}, + { + sidebarItemsGenerator: createStaticSidebarItemGenerator( + sidebarSliceContainingCategoryGeneratedIndex, + ), + }, + ); expect(processedSidebar).toEqual({ someSidebar: [ @@ -229,13 +236,15 @@ describe('processSidebars', () => { link: { type: 'generated-index', slug: 'generated-cat-index-slug', - permalink: '/docs/1.0.0/generated-cat-index-slug', }, - items: [], - collapsible: true, - collapsed: true, + items: [ + { + type: 'doc', + id: 'foo', + }, + ], }, ], - } as Sidebars); + } as ProcessedSidebars); }); }); diff --git a/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/utils.test.ts b/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/utils.test.ts index 83f28e6c4621..7c2d4428c642 100644 --- a/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/utils.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/utils.test.ts @@ -16,7 +16,10 @@ import { toNavigationLink, } from '../utils'; import type {Sidebar, Sidebars} from '../types'; -import type {DocMetadataBase, DocNavLink} from '../../types'; +import type { + DocMetadataBase, + PropNavigationLink, +} from '@docusaurus/plugin-content-docs'; describe('createSidebarsUtils', () => { const sidebar1: Sidebar = [ @@ -111,7 +114,7 @@ describe('createSidebarsUtils', () => { link: { type: 'generated-index', slug: '/s4-category-slug', - permalink: '/s4-category-permalink', + permalink: '/s4-category-slug', }, items: [ {type: 'doc', id: 'doc8'}, @@ -131,22 +134,22 @@ describe('createSidebarsUtils', () => { getFirstLink, } = createSidebarsUtils(sidebars); - test('getFirstDocIdOfFirstSidebar', async () => { - expect(getFirstDocIdOfFirstSidebar()).toEqual('doc1'); + it('getFirstDocIdOfFirstSidebar', async () => { + expect(getFirstDocIdOfFirstSidebar()).toBe('doc1'); }); - test('getSidebarNameByDocId', async () => { - expect(getSidebarNameByDocId('doc1')).toEqual('sidebar1'); - expect(getSidebarNameByDocId('doc2')).toEqual('sidebar1'); - expect(getSidebarNameByDocId('doc3')).toEqual('sidebar2'); - expect(getSidebarNameByDocId('doc4')).toEqual('sidebar2'); - expect(getSidebarNameByDocId('doc5')).toEqual('sidebar3'); - expect(getSidebarNameByDocId('doc6')).toEqual('sidebar3'); - expect(getSidebarNameByDocId('doc7')).toEqual('sidebar3'); - expect(getSidebarNameByDocId('unknown_id')).toEqual(undefined); + it('getSidebarNameByDocId', async () => { + expect(getSidebarNameByDocId('doc1')).toBe('sidebar1'); + expect(getSidebarNameByDocId('doc2')).toBe('sidebar1'); + expect(getSidebarNameByDocId('doc3')).toBe('sidebar2'); + expect(getSidebarNameByDocId('doc4')).toBe('sidebar2'); + expect(getSidebarNameByDocId('doc5')).toBe('sidebar3'); + expect(getSidebarNameByDocId('doc6')).toBe('sidebar3'); + expect(getSidebarNameByDocId('doc7')).toBe('sidebar3'); + expect(getSidebarNameByDocId('unknown_id')).toBeUndefined(); }); - test('getDocNavigation', async () => { + it('getDocNavigation', async () => { expect(getDocNavigation('doc1', 'doc1', undefined)).toEqual({ sidebarName: 'sidebar1', previous: undefined, @@ -226,7 +229,7 @@ describe('createSidebarsUtils', () => { }); }); - test('getCategoryGeneratedIndexNavigation', async () => { + it('getCategoryGeneratedIndexNavigation', async () => { expect( getCategoryGeneratedIndexNavigation('/s3-subcategory-index-permalink'), ).toMatchObject({ @@ -256,7 +259,7 @@ describe('createSidebarsUtils', () => { }); }); - test('getCategoryGeneratedIndexList', async () => { + it('getCategoryGeneratedIndexList', async () => { expect(getCategoryGeneratedIndexList()).toMatchObject([ { type: 'category', @@ -273,7 +276,7 @@ describe('createSidebarsUtils', () => { ]); }); - test('getFirstLink', () => { + it('getFirstLink', () => { expect(getFirstLink('sidebar1')).toEqual({ id: 'doc1', type: 'doc', @@ -291,14 +294,14 @@ describe('createSidebarsUtils', () => { }); expect(getFirstLink('sidebar4')).toEqual({ type: 'generated-index', - slug: '/s4-category-slug', + permalink: '/s4-category-slug', label: 'S4 Category', }); }); }); describe('collectSidebarDocItems', () => { - test('can collect docs', async () => { + it('can collect docs', async () => { const sidebar: Sidebar = [ { type: 'category', @@ -354,7 +357,7 @@ describe('collectSidebarDocItems', () => { }); describe('collectSidebarCategories', () => { - test('can collect categories', async () => { + it('can collect categories', async () => { const sidebar: Sidebar = [ { type: 'category', @@ -412,7 +415,7 @@ describe('collectSidebarCategories', () => { }); describe('collectSidebarLinks', () => { - test('can collect links', async () => { + it('can collect links', async () => { const sidebar: Sidebar = [ { type: 'category', @@ -450,7 +453,7 @@ describe('collectSidebarLinks', () => { }); describe('collectSidebarsDocIds', () => { - test('can collect sidebars doc items', async () => { + it('can collect sidebars doc items', async () => { const sidebar1: Sidebar = [ { type: 'category', @@ -496,7 +499,7 @@ describe('collectSidebarsDocIds', () => { }); describe('transformSidebarItems', () => { - test('can transform sidebar items', async () => { + it('can transform sidebar items', async () => { const sidebar: Sidebar = [ { type: 'category', @@ -606,7 +609,7 @@ describe('toDocNavigationLink', () => { return data as DocMetadataBase; } - test('with no front matter', () => { + it('with no front matter', () => { expect( toDocNavigationLink( testDoc({ @@ -618,10 +621,10 @@ describe('toDocNavigationLink', () => { ).toEqual({ title: 'Doc Title', permalink: '/docPermalink', - } as DocNavLink); + } as PropNavigationLink); }); - test('with pagination_label front matter', () => { + it('with pagination_label front matter', () => { expect( toDocNavigationLink( testDoc({ @@ -635,10 +638,10 @@ describe('toDocNavigationLink', () => { ).toEqual({ title: 'pagination_label', permalink: '/docPermalink', - } as DocNavLink); + } as PropNavigationLink); }); - test('with sidebar_label front matter', () => { + it('with sidebar_label front matter', () => { expect( toDocNavigationLink( testDoc({ @@ -652,10 +655,10 @@ describe('toDocNavigationLink', () => { ).toEqual({ title: 'sidebar_label', permalink: '/docPermalink', - } as DocNavLink); + } as PropNavigationLink); }); - test('with pagination_label + sidebar_label front matter', () => { + it('with pagination_label + sidebar_label front matter', () => { expect( toDocNavigationLink( testDoc({ @@ -670,7 +673,7 @@ describe('toDocNavigationLink', () => { ).toEqual({ title: 'pagination_label', permalink: '/docPermalink', - } as DocNavLink); + } as PropNavigationLink); }); }); @@ -680,7 +683,7 @@ describe('toNavigationLink', () => { return {...data, frontMatter: {}} as DocMetadataBase; } - const docsById: Record = { + const docsById: {[docId: string]: DocMetadataBase} = { doc1: testDoc({ title: 'Doc 1', permalink: '/doc1', @@ -691,7 +694,7 @@ describe('toNavigationLink', () => { }), }; - test('with doc items', () => { + it('with doc items', () => { expect(toNavigationLink({type: 'doc', id: 'doc1'}, docsById)).toEqual( toDocNavigationLink(docsById.doc1), ); @@ -705,7 +708,7 @@ describe('toNavigationLink', () => { ); }); - test('with category item and doc link', () => { + it('with category item and doc link', () => { expect( toNavigationLink( { @@ -742,7 +745,7 @@ describe('toNavigationLink', () => { ); }); - test('with category item and generated-index link', () => { + it('with category item and generated-index link', () => { expect( toNavigationLink( { diff --git a/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/validation.test.ts b/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/validation.test.ts index 49ac30fdfe3f..96887b889c5b 100644 --- a/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/validation.test.ts +++ b/packages/docusaurus-plugin-content-docs/src/sidebars/__tests__/validation.test.ts @@ -6,15 +6,10 @@ */ import {validateSidebars, validateCategoryMetadataFile} from '../validation'; -import type {CategoryMetadataFile} from '../generator'; -import type {SidebarsConfig} from '../types'; +import type {SidebarsConfig, CategoryMetadataFile} from '../types'; describe('validateSidebars', () => { - // TODO add more tests - - // TODO it seems many error cases are not validated properly - // and error messages are quite bad - test('throw for bad value', async () => { + it('throw for bad value', async () => { expect(() => validateSidebars({sidebar: [{type: 42}]})) .toThrowErrorMatchingInlineSnapshot(` "{ @@ -26,12 +21,12 @@ describe('validateSidebars', () => { `); }); - test('accept empty object', async () => { + it('accept empty object', async () => { const sidebars: SidebarsConfig = {}; validateSidebars(sidebars); }); - test('accept valid values', async () => { + it('accept valid values', async () => { const sidebars: SidebarsConfig = { sidebar1: [ {type: 'doc', id: 'doc1'}, @@ -45,12 +40,233 @@ describe('validateSidebars', () => { }; validateSidebars(sidebars); }); + + it('sidebar category wrong label', () => { + expect( + () => + validateSidebars({ + docs: [ + { + type: 'category', + label: true, + items: [{type: 'doc', id: 'doc1'}], + }, + ], + }), + // eslint-disable-next-line jest/no-large-snapshots + ).toThrowErrorMatchingInlineSnapshot(` + "{ + \\"type\\": \\"category\\", + \\"items\\": [ + { + \\"type\\": \\"doc\\", + \\"id\\": \\"doc1\\" + } + ], + \\"label\\" [1]: true + } +  + [1] \\"label\\" must be a string" + `); + }); + + it('sidebars link wrong label', () => { + expect(() => + validateSidebars({ + docs: [ + { + type: 'link', + label: false, + href: 'https://github.com', + }, + ], + }), + ).toThrowErrorMatchingInlineSnapshot(` + "{ + \\"type\\": \\"link\\", + \\"href\\": \\"https://github.com\\", + \\"label\\" [1]: false + } +  + [1] \\"label\\" must be a string" + `); + }); + + it('sidebars link wrong href', () => { + expect(() => + validateSidebars({ + docs: [ + { + type: 'link', + label: 'GitHub', + href: ['example.com'], + }, + ], + }), + ).toThrowErrorMatchingInlineSnapshot(` + "{ + \\"type\\": \\"link\\", + \\"label\\": \\"GitHub\\", + \\"href\\" [1]: [ + \\"example.com\\" + ] + } +  + [1] \\"href\\" contains an invalid value" + `); + }); + + it('sidebars with unknown sidebar item type', () => { + expect(() => + validateSidebars({ + docs: [ + { + type: 'superman', + }, + ], + }), + ).toThrowErrorMatchingInlineSnapshot(` + "{ + \\"type\\": \\"superman\\", + \\"undefined\\" [1]: -- missing -- + } +  + [1] Unknown sidebar item type \\"superman\\"." + `); + }); + + it('sidebars category missing items', () => { + expect(() => + validateSidebars({ + docs: [ + { + type: 'category', + label: 'category', + }, + + { + type: 'ref', + id: 'hello', + }, + ], + }), + ).toThrowErrorMatchingInlineSnapshot(` + "{ + \\"type\\": \\"category\\", + \\"label\\": \\"category\\", + \\"items\\" [1]: -- missing -- + } +  + [1] \\"items\\" is required" + `); + }); + + it('sidebars category wrong field', () => { + expect(() => + validateSidebars({ + docs: [ + { + type: 'category', + label: 'category', + items: [], + href: 'https://google.com', + }, + + { + type: 'ref', + id: 'hello', + }, + ], + }), + ).toThrowErrorMatchingInlineSnapshot(` + "{ + \\"type\\": \\"category\\", + \\"label\\": \\"category\\", + \\"items\\": [], + \\"href\\" [1]: \\"https://google.com\\" + } +  + [1] \\"href\\" is not allowed" + `); + }); + + it('sidebar category wrong items', () => { + expect(() => + validateSidebars({ + docs: { + Test: [ + { + type: 'category', + label: 'Category Label', + items: 'doc1', + }, + ], + }, + }), + ).toThrowErrorMatchingInlineSnapshot(`"sidebar.forEach is not a function"`); + }); + + it('sidebars item doc but id is not a string', async () => { + expect(() => + validateSidebars({ + docs: [ + { + type: 'doc', + id: ['doc1'], + }, + ], + }), + ).toThrowErrorMatchingInlineSnapshot(` + "{ + \\"type\\": \\"doc\\", + \\"id\\" [1]: [ + \\"doc1\\" + ] + } +  + [1] \\"id\\" must be a string" + `); + }); + + it('hTML type requires a value', () => { + const sidebars: SidebarsConfig = { + sidebar1: [ + { + // @ts-expect-error - test missing value + type: 'html', + }, + ], + }; + expect(() => validateSidebars(sidebars)) + .toThrowErrorMatchingInlineSnapshot(` + "{ + \\"type\\": \\"html\\", + \\"value\\" [1]: -- missing -- + } +  + [1] \\"value\\" is required" + `); + }); + + it('hTML type accepts valid values', () => { + const sidebars: SidebarsConfig = { + sidebar1: [ + { + type: 'html', + value: '

Hello, World!

', + defaultStyle: true, + className: 'foo', + }, + ], + }; + validateSidebars(sidebars); + }); }); describe('validateCategoryMetadataFile', () => { // TODO add more tests - test('throw for bad value', async () => { + it('throw for bad value', async () => { expect(() => validateCategoryMetadataFile(42), ).toThrowErrorMatchingInlineSnapshot( @@ -58,12 +274,12 @@ describe('validateCategoryMetadataFile', () => { ); }); - test('accept empty object', async () => { + it('accept empty object', async () => { const content: CategoryMetadataFile = {}; expect(validateCategoryMetadataFile(content)).toEqual(content); }); - test('accept valid values', async () => { + it('accept valid values', async () => { const content: CategoryMetadataFile = { className: 'className', label: 'Category Label', @@ -80,7 +296,7 @@ describe('validateCategoryMetadataFile', () => { expect(validateCategoryMetadataFile(content)).toEqual(content); }); - test('rejects permalink', async () => { + it('rejects permalink', async () => { const content: CategoryMetadataFile = { className: 'className', label: 'Category Label', diff --git a/packages/docusaurus-plugin-content-docs/src/sidebars/generator.ts b/packages/docusaurus-plugin-content-docs/src/sidebars/generator.ts index 0807112cbadf..4642897f3df4 100644 --- a/packages/docusaurus-plugin-content-docs/src/sidebars/generator.ts +++ b/packages/docusaurus-plugin-content-docs/src/sidebars/generator.ts @@ -6,99 +6,52 @@ */ import type { - SidebarItem, SidebarItemDoc, - SidebarItemCategory, SidebarItemsGenerator, SidebarItemsGeneratorDoc, - SidebarItemCategoryLink, + NormalizedSidebarItemCategory, + NormalizedSidebarItem, SidebarItemCategoryLinkConfig, } from './types'; -import {sortBy, last} from 'lodash'; -import { - addTrailingSlash, - posixPath, - findAsyncSequential, -} from '@docusaurus/utils'; +import _ from 'lodash'; +import {addTrailingSlash} from '@docusaurus/utils'; import logger from '@docusaurus/logger'; import path from 'path'; -import fs from 'fs-extra'; -import Yaml from 'js-yaml'; -import {validateCategoryMetadataFile} from './validation'; import {createDocsByIdIndex, toCategoryIndexMatcherParam} from '../docs'; const BreadcrumbSeparator = '/'; -// To avoid possible name clashes with a folder of the same name as the ID -const docIdPrefix = '$doc$/'; // Just an alias to the make code more explicit function getLocalDocId(docId: string): string { - return last(docId.split('/'))!; + return _.last(docId.split('/'))!; } export const CategoryMetadataFilenameBase = '_category_'; export const CategoryMetadataFilenamePattern = '_category_.{json,yml,yaml}'; -export type CategoryMetadataFile = { - label?: string; +type WithPosition = T & { position?: number; - collapsed?: boolean; - collapsible?: boolean; - className?: string; - link?: SidebarItemCategoryLinkConfig; - - // TODO should we allow "items" here? how would this work? would an "autogenerated" type be allowed? - // This mkdocs plugin do something like that: https://github.com/lukasgeiter/mkdocs-awesome-pages-plugin/ - // cf comment: https://github.com/facebook/docusaurus/issues/3464#issuecomment-784765199 + /** The source is the file/folder name */ + source?: string; }; -type WithPosition = T & {position?: number}; - /** * A representation of the fs structure. For each object entry: - * If it's a folder, the key is the directory name, and value is the directory content; - * If it's a doc file, the key is the doc id prefixed with '$doc$/', and value is null + * If it's a folder, the key is the directory name, and value is the directory + * content; If it's a doc file, the key is the doc's source file name, and value + * is the doc ID */ type Dir = { - [item: string]: Dir | null; + [item: string]: Dir | string; }; -// TODO I now believe we should read all the category metadata files ahead of time: we may need this metadata to customize docs metadata -// Example use-case being able to disable number prefix parsing at the folder level, or customize the default route path segment for an intermediate directory... -// TODO later if there is `CategoryFolder/with-category-name-doc.md`, we may want to read the metadata as yaml on it -// see https://github.com/facebook/docusaurus/issues/3464#issuecomment-818670449 -async function readCategoryMetadataFile( - categoryDirPath: string, -): Promise { - async function tryReadFile(filePath: string): Promise { - const contentString = await fs.readFile(filePath, {encoding: 'utf8'}); - const unsafeContent = Yaml.load(contentString); - try { - return validateCategoryMetadataFile(unsafeContent); - } catch (e) { - logger.error`The docs sidebar category metadata file path=${filePath} looks invalid!`; - throw e; - } - } - const filePath = await findAsyncSequential( - ['.json', '.yml', '.yaml'].map((ext) => - posixPath( - path.join(categoryDirPath, `${CategoryMetadataFilenameBase}${ext}`), - ), - ), - fs.pathExists, - ); - return filePath ? tryReadFile(filePath) : null; -} - // Comment for this feature: https://github.com/facebook/docusaurus/issues/3464#issuecomment-818670449 export const DefaultSidebarItemsGenerator: SidebarItemsGenerator = async ({ numberPrefixParser, isCategoryIndex, docs: allDocs, - options, item: {dirName: autogenDir}, - version, + categoriesMetadata, }) => { const docsById = createDocsByIdIndex(allDocs); const findDoc = (docId: string): SidebarItemsGeneratorDoc | undefined => @@ -107,9 +60,9 @@ export const DefaultSidebarItemsGenerator: SidebarItemsGenerator = async ({ const doc = findDoc(docId); if (!doc) { throw new Error( - `Can't find any doc with id=${docId}.\nAvailable doc ids:\n- ${Object.keys( - docsById, - ).join('\n- ')}`, + `Can't find any doc with ID ${docId}. +Available doc IDs: +- ${Object.keys(docsById).join('\n- ')}`, ); } return doc; @@ -142,7 +95,8 @@ export const DefaultSidebarItemsGenerator: SidebarItemsGenerator = async ({ * Step 2. Turn the linear file list into a tree structure. */ function treeify(docs: SidebarItemsGeneratorDoc[]): Dir { - // Get the category breadcrumb of a doc (relative to the dir of the autogenerated sidebar item) + // Get the category breadcrumb of a doc (relative to the dir of the + // autogenerated sidebar item) // autogenDir=a/b and docDir=a/b/c/d => returns [c, d] // autogenDir=a/b and docDir=a/b => returns [] // TODO: try to use path.relative() @@ -156,24 +110,32 @@ export const DefaultSidebarItemsGenerator: SidebarItemsGenerator = async ({ const treeRoot: Dir = {}; docs.forEach((doc) => { const breadcrumb = getRelativeBreadcrumb(doc); - let currentDir = treeRoot; // We walk down the file's path to generate the fs structure + // We walk down the file's path to generate the fs structure + let currentDir = treeRoot; breadcrumb.forEach((dir) => { if (typeof currentDir[dir] === 'undefined') { currentDir[dir] = {}; // Create new folder. } - currentDir = currentDir[dir]!; // Go into the subdirectory. + currentDir = currentDir[dir] as Dir; // Go into the subdirectory. }); - currentDir[`${docIdPrefix}${doc.id}`] = null; // We've walked through the file path. Register the file in this directory. + // We've walked through the path. Register the file in this directory. + currentDir[path.basename(doc.source)] = doc.id; }); return treeRoot; } /** - * Step 3. Recursively transform the tree-like file structure to sidebar items. + * Step 3. Recursively transform the tree-like structure to sidebar items. * (From a record to an array of items, akin to normalizing shorthand) */ - function generateSidebar(fsModel: Dir): Promise[]> { - function createDocItem(id: string): WithPosition { + function generateSidebar( + fsModel: Dir, + ): WithPosition[] { + function createDocItem( + id: string, + fullPath: string, + fileName: string, + ): WithPosition { const { sidebarPosition: position, frontMatter: {sidebar_label: label, sidebar_class_name: className}, @@ -182,126 +144,149 @@ export const DefaultSidebarItemsGenerator: SidebarItemsGenerator = async ({ type: 'doc', id, position, - // We don't want these fields to magically appear in the generated sidebar + source: fileName, + // We don't want these fields to magically appear in the generated + // sidebar ...(label !== undefined && {label}), ...(className !== undefined && {className}), }; } - async function createCategoryItem( + function createCategoryItem( dir: Dir, fullPath: string, folderName: string, - ): Promise> { - const categoryPath = path.join(version.contentPath, autogenDir, fullPath); - const categoryMetadata = await readCategoryMetadataFile(categoryPath); - const className = categoryMetadata?.className; - const {filename, numberPrefix} = numberPrefixParser(folderName); - const allItems = await Promise.all( - Object.entries(dir).map(([key, content]) => - dirToItem(content, key, `${fullPath}/${key}`), - ), + ): WithPosition { + const categoryMetadata = + categoriesMetadata[path.posix.join(autogenDir, fullPath)]; + const allItems = Object.entries(dir).map(([key, content]) => + dirToItem(content, key, `${fullPath}/${key}`), ); // Try to match a doc inside the category folder, // using the "local id" (myDoc) or "qualified id" (dirName/myDoc) function findDocByLocalId(localId: string): SidebarItemDoc | undefined { return allItems.find( - (item) => item.type === 'doc' && getLocalDocId(item.id) === localId, - ) as SidebarItemDoc | undefined; + (item): item is SidebarItemDoc => + item.type === 'doc' && getLocalDocId(item.id) === localId, + ); } function findConventionalCategoryDocLink(): SidebarItemDoc | undefined { - return allItems.find((item) => { + return allItems.find((item): item is SidebarItemDoc => { if (item.type !== 'doc') { return false; } const doc = getDoc(item.id); return isCategoryIndex(toCategoryIndexMatcherParam(doc)); - }) as SidebarItemDoc | undefined; + }); } - function getCategoryLinkedDocId(): string | undefined { - const link = categoryMetadata?.link; - if (link) { - if (link.type === 'doc') { - return findDocByLocalId(link.id)?.id || getDoc(link.id).id; - } else { - // We don't continue for other link types on purpose! - // IE if user decide to use type "generated-index", we should not pick a README.md file as the linked doc - return undefined; + // In addition to the ID, this function also retrieves metadata of the + // linked doc that could be used as fallback values for category metadata + function getCategoryLinkedDocMetadata(): + | { + id: string; + position?: number; + label?: string; + customProps?: {[key: string]: unknown}; + className?: string; } + | undefined { + const link = categoryMetadata?.link; + if (link !== undefined && link?.type !== 'doc') { + // If a link is explicitly specified, we won't apply conventions + return undefined; + } + const id = link + ? findDocByLocalId(link.id)?.id ?? getDoc(link.id).id + : findConventionalCategoryDocLink()?.id; + if (!id) { + return undefined; } - // Apply default convention to pick index.md, README.md or .md as the category doc - return findConventionalCategoryDocLink()?.id; + const doc = getDoc(id); + return { + id, + position: doc.sidebarPosition, + label: doc.frontMatter.sidebar_label ?? doc.title, + customProps: doc.frontMatter.sidebar_custom_props, + className: doc.frontMatter.sidebar_class_name, + }; } - - const categoryLinkedDocId = getCategoryLinkedDocId(); - - const link: SidebarItemCategoryLink | undefined = categoryLinkedDocId - ? { - type: 'doc', - id: categoryLinkedDocId, // We "remap" a potentially "local id" to a "qualified id" - } - : // TODO typing issue - (categoryMetadata?.link as SidebarItemCategoryLink | undefined); - + const categoryLinkedDoc = getCategoryLinkedDocMetadata(); + const link: SidebarItemCategoryLinkConfig | null | undefined = + categoryLinkedDoc + ? { + type: 'doc', + id: categoryLinkedDoc.id, // We "remap" a potentially "local id" to a "qualified id" + } + : categoryMetadata?.link; // If a doc is linked, remove it from the category subItems const items = allItems.filter( - (item) => !(item.type === 'doc' && item.id === categoryLinkedDocId), + (item) => !(item.type === 'doc' && item.id === categoryLinkedDoc?.id), ); + const className = + categoryMetadata?.className ?? categoryLinkedDoc?.className; + const customProps = + categoryMetadata?.customProps ?? categoryLinkedDoc?.customProps; + const {filename, numberPrefix} = numberPrefixParser(folderName); + return { type: 'category', - label: categoryMetadata?.label ?? filename, - collapsible: - categoryMetadata?.collapsible ?? options.sidebarCollapsible, - collapsed: categoryMetadata?.collapsed ?? options.sidebarCollapsed, - position: categoryMetadata?.position ?? numberPrefix, + label: categoryMetadata?.label ?? categoryLinkedDoc?.label ?? filename, + collapsible: categoryMetadata?.collapsible, + collapsed: categoryMetadata?.collapsed, + position: + categoryMetadata?.position ?? + categoryLinkedDoc?.position ?? + numberPrefix, + source: folderName, + ...(customProps !== undefined && {customProps}), ...(className !== undefined && {className}), items, ...(link && {link}), }; } - async function dirToItem( - dir: Dir | null, // The directory item to be transformed. - itemKey: string, // For docs, it's the doc ID; for categories, it's used to generate the next `relativePath`. + function dirToItem( + dir: Dir | string, // The directory item to be transformed. + itemKey: string, // File/folder name; for categories, it's used to generate the next `relativePath`. fullPath: string, // `dir`'s full path relative to the autogen dir. - ): Promise> { - return dir + ): WithPosition { + return typeof dir === 'object' ? createCategoryItem(dir, fullPath, itemKey) - : createDocItem(itemKey.substring(docIdPrefix.length)); + : createDocItem(dir, fullPath, itemKey); } - return Promise.all( - Object.entries(fsModel).map(([key, content]) => - dirToItem(content, key, key), - ), + return Object.entries(fsModel).map(([key, content]) => + dirToItem(content, key, key), ); } /** - * Step 4. Recursively sort the categories/docs + remove the "position" attribute from final output. - * Note: the "position" is only used to sort "inside" a sidebar slice. It is not - * used to sort across multiple consecutive sidebar slices (ie a whole Category - * composed of multiple autogenerated items) + * Step 4. Recursively sort the categories/docs + remove the "position" + * attribute from final output. Note: the "position" is only used to sort + * "inside" a sidebar slice. It is not used to sort across multiple + * consecutive sidebar slices (i.e. a whole category composed of multiple + * autogenerated items) */ - function sortItems(sidebarItems: WithPosition[]): SidebarItem[] { + function sortItems( + sidebarItems: WithPosition[], + ): NormalizedSidebarItem[] { const processedSidebarItems = sidebarItems.map((item) => { if (item.type === 'category') { return {...item, items: sortItems(item.items)}; } return item; }); - const sortedSidebarItems = sortBy( - processedSidebarItems, - (item) => item.position, - ); - return sortedSidebarItems.map(({position, ...item}) => item); + const sortedSidebarItems = _.sortBy(processedSidebarItems, [ + 'position', + 'source', + ]); + return sortedSidebarItems.map(({position, source, ...item}) => item); } // TODO: the whole code is designed for pipeline operator - // return getAutogenDocs() |> treeify |> await generateSidebar(^) |> sortItems; const docs = getAutogenDocs(); const fsModel = treeify(docs); - const sidebarWithPosition = await generateSidebar(fsModel); + const sidebarWithPosition = generateSidebar(fsModel); const sortedSidebar = sortItems(sidebarWithPosition); return sortedSidebar; }; diff --git a/packages/docusaurus-plugin-content-docs/src/sidebars/index.ts b/packages/docusaurus-plugin-content-docs/src/sidebars/index.ts index eae11f19495d..b28e94ca1744 100644 --- a/packages/docusaurus-plugin-content-docs/src/sidebars/index.ts +++ b/packages/docusaurus-plugin-content-docs/src/sidebars/index.ts @@ -7,14 +7,18 @@ import fs from 'fs-extra'; import importFresh from 'import-fresh'; -import type {SidebarsConfig, Sidebars, NormalizedSidebars} from './types'; -import type {NormalizeSidebarsParams} from '../types'; -import {validateSidebars} from './validation'; +import type {SidebarsConfig, Sidebars, SidebarProcessorParams} from './types'; +import {validateSidebars, validateCategoryMetadataFile} from './validation'; import {normalizeSidebars} from './normalization'; -import {processSidebars, type SidebarProcessorParams} from './processor'; +import {processSidebars} from './processor'; +import {postProcessSidebars} from './postProcessor'; import path from 'path'; -import {createSlugger} from '@docusaurus/utils'; +import {Globby} from '@docusaurus/utils'; +import logger from '@docusaurus/logger'; import type {PluginOptions} from '@docusaurus/plugin-content-docs'; +import Yaml from 'js-yaml'; +import _ from 'lodash'; +import combinePromises from 'combine-promises'; export const DefaultSidebars: SidebarsConfig = { defaultSidebar: [ @@ -38,9 +42,36 @@ export function resolveSidebarPathOption( : sidebarPathOption; } -function loadSidebarsFileUnsafe( +async function readCategoriesMetadata(contentPath: string) { + const categoryFiles = await Globby('**/_category_.{json,yml,yaml}', { + cwd: contentPath, + }); + const categoryToFile = _.groupBy(categoryFiles, path.dirname); + return combinePromises( + _.mapValues(categoryToFile, async (files, folder) => { + const filePath = files[0]!; + if (files.length > 1) { + logger.warn`There are more than one category metadata files for path=${folder}: ${files.join( + ', ', + )}. The behavior is undetermined.`; + } + const content = await fs.readFile( + path.join(contentPath, filePath), + 'utf-8', + ); + try { + return validateCategoryMetadataFile(Yaml.load(content)); + } catch (err) { + logger.error`The docs sidebar category metadata file path=${filePath} looks invalid!`; + throw err; + } + }), + ); +} + +export async function loadSidebarsFileUnsafe( sidebarFilePath: string | false | undefined, -): SidebarsConfig { +): Promise { // false => no sidebars if (sidebarFilePath === false) { return DisabledSidebars; @@ -54,7 +85,7 @@ function loadSidebarsFileUnsafe( // Non-existent sidebars file: no sidebars // Note: this edge case can happen on versioned docs, not current version // We avoid creating empty versioned sidebars file with the CLI - if (!fs.existsSync(sidebarFilePath)) { + if (!(await fs.pathExists(sidebarFilePath))) { return DisabledSidebars; } @@ -62,34 +93,28 @@ function loadSidebarsFileUnsafe( return importFresh(sidebarFilePath); } -export function loadSidebarsFile( - sidebarFilePath: string | false | undefined, -): SidebarsConfig { - const sidebarsConfig = loadSidebarsFileUnsafe(sidebarFilePath); - validateSidebars(sidebarsConfig); - return sidebarsConfig; -} - -export function loadNormalizedSidebars( - sidebarFilePath: string | false | undefined, - params: NormalizeSidebarsParams, -): NormalizedSidebars { - return normalizeSidebars(loadSidebarsFile(sidebarFilePath), params); -} - // Note: sidebarFilePath must be absolute, use resolveSidebarPathOption export async function loadSidebars( sidebarFilePath: string | false | undefined, options: SidebarProcessorParams, ): Promise { - const normalizeSidebarsParams: NormalizeSidebarsParams = { - ...options.sidebarOptions, - version: options.version, - categoryLabelSlugger: createSlugger(), - }; - const normalizedSidebars = loadNormalizedSidebars( - sidebarFilePath, - normalizeSidebarsParams, - ); - return processSidebars(normalizedSidebars, options); + try { + const sidebarsConfig = await loadSidebarsFileUnsafe(sidebarFilePath); + const normalizedSidebars = normalizeSidebars(sidebarsConfig); + validateSidebars(normalizedSidebars); + const categoriesMetadata = await readCategoriesMetadata( + options.version.contentPath, + ); + const processedSidebars = await processSidebars( + normalizedSidebars, + categoriesMetadata, + options, + ); + return postProcessSidebars(processedSidebars, options); + } catch (err) { + logger.error`Sidebars file at path=${ + sidebarFilePath as string + } failed to be loaded.`; + throw err; + } } diff --git a/packages/docusaurus-plugin-content-docs/src/sidebars/normalization.ts b/packages/docusaurus-plugin-content-docs/src/sidebars/normalization.ts index 48666e89873c..d36be98fcd6a 100644 --- a/packages/docusaurus-plugin-content-docs/src/sidebars/normalization.ts +++ b/packages/docusaurus-plugin-content-docs/src/sidebars/normalization.ts @@ -5,7 +5,6 @@ * LICENSE file in the root directory of this source tree. */ -import type {NormalizeSidebarsParams} from '../types'; import type { NormalizedSidebarItem, NormalizedSidebar, @@ -15,41 +14,17 @@ import type { SidebarItemConfig, SidebarConfig, SidebarsConfig, - SidebarItemCategoryLink, NormalizedSidebarItemCategory, } from './types'; import {isCategoriesShorthand} from './utils'; -import {mapValues} from 'lodash'; -import {normalizeUrl} from '@docusaurus/utils'; -import type {SidebarOptions} from '@docusaurus/plugin-content-docs'; - -function normalizeCategoryLink( - category: SidebarItemCategoryConfig, - params: NormalizeSidebarsParams, -): SidebarItemCategoryLink | undefined { - if (category.link?.type === 'generated-index') { - // default slug logic can be improved - const getDefaultSlug = () => - `/category/${params.categoryLabelSlugger.slug(category.label)}`; - const slug = category.link.slug ?? getDefaultSlug(); - const permalink = normalizeUrl([params.version.versionPath, slug]); - return { - ...category.link, - slug, - permalink, - }; - } - return category.link; -} +import _ from 'lodash'; +import logger from '@docusaurus/logger'; function normalizeCategoriesShorthand( sidebar: SidebarCategoriesShorthand, - options: SidebarOptions, ): SidebarItemCategoryConfig[] { return Object.entries(sidebar).map(([label, items]) => ({ type: 'category', - collapsed: options.sidebarCollapsed, - collapsible: options.sidebarCollapsible, label, items, })); @@ -61,31 +36,21 @@ function normalizeCategoriesShorthand( */ export function normalizeItem( item: SidebarItemConfig, - options: NormalizeSidebarsParams, ): NormalizedSidebarItem[] { if (typeof item === 'string') { - return [ - { - type: 'doc', - id: item, - }, - ]; + return [{type: 'doc', id: item}]; } if (isCategoriesShorthand(item)) { - return normalizeCategoriesShorthand(item, options).flatMap((subItem) => - normalizeItem(subItem, options), - ); + // This will never throw anyways + return normalizeSidebar(item, 'sidebar items slice'); } if (item.type === 'category') { - const link = normalizeCategoryLink(item, options); const normalizedCategory: NormalizedSidebarItemCategory = { ...item, - link, - items: (item.items ?? []).flatMap((subItem) => - normalizeItem(subItem, options), + items: normalizeSidebar( + item.items, + logger.interpolate`code=${'items'} of the category name=${item.label}`, ), - collapsible: item.collapsible ?? options.sidebarCollapsible, - collapsed: item.collapsed ?? options.sidebarCollapsed, }; return [normalizedCategory]; } @@ -94,20 +59,27 @@ export function normalizeItem( function normalizeSidebar( sidebar: SidebarConfig, - options: NormalizeSidebarsParams, + place: string, ): NormalizedSidebar { + if (!Array.isArray(sidebar) && !isCategoriesShorthand(sidebar)) { + throw new Error( + logger.interpolate`Invalid sidebar items collection code=${JSON.stringify( + sidebar, + )} in ${place}: it must either be an array of sidebar items or a shorthand notation (which doesn't contain a code=${'type'} property). See url=${'https://docusaurus.io/docs/sidebar/items'} for all valid syntaxes.`, + ); + } + const normalizedSidebar = Array.isArray(sidebar) ? sidebar - : normalizeCategoriesShorthand(sidebar, options); + : normalizeCategoriesShorthand(sidebar); - return normalizedSidebar.flatMap((subItem) => - normalizeItem(subItem, options), - ); + return normalizedSidebar.flatMap((subItem) => normalizeItem(subItem)); } export function normalizeSidebars( sidebars: SidebarsConfig, - params: NormalizeSidebarsParams, ): NormalizedSidebars { - return mapValues(sidebars, (items) => normalizeSidebar(items, params)); + return _.mapValues(sidebars, (sidebar, id) => + normalizeSidebar(sidebar, logger.interpolate`sidebar name=${id}`), + ); } diff --git a/packages/docusaurus-plugin-content-docs/src/sidebars/postProcessor.ts b/packages/docusaurus-plugin-content-docs/src/sidebars/postProcessor.ts new file mode 100644 index 000000000000..363c731696b9 --- /dev/null +++ b/packages/docusaurus-plugin-content-docs/src/sidebars/postProcessor.ts @@ -0,0 +1,89 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {normalizeUrl} from '@docusaurus/utils'; +import type { + SidebarItem, + Sidebars, + SidebarProcessorParams, + ProcessedSidebarItemCategory, + ProcessedSidebarItem, + ProcessedSidebars, + SidebarItemCategoryLink, +} from './types'; +import _ from 'lodash'; + +function normalizeCategoryLink( + category: ProcessedSidebarItemCategory, + params: SidebarProcessorParams, +): SidebarItemCategoryLink | undefined { + if (category.link?.type === 'generated-index') { + // default slug logic can be improved + const getDefaultSlug = () => + `/category/${params.categoryLabelSlugger.slug(category.label)}`; + const slug = category.link.slug ?? getDefaultSlug(); + const permalink = normalizeUrl([params.version.path, slug]); + return { + ...category.link, + slug, + permalink, + }; + } + return category.link; +} + +function postProcessSidebarItem( + item: ProcessedSidebarItem, + params: SidebarProcessorParams, +): SidebarItem { + if (item.type === 'category') { + const category = { + ...item, + collapsed: item.collapsed ?? params.sidebarOptions.sidebarCollapsed, + collapsible: item.collapsible ?? params.sidebarOptions.sidebarCollapsible, + link: normalizeCategoryLink(item, params), + items: item.items.map((subItem) => + postProcessSidebarItem(subItem, params), + ), + }; + // If the current category doesn't have subitems, we render a normal link + // instead. + if (category.items.length === 0) { + if (!category.link) { + throw new Error( + `Sidebar category ${item.label} has neither any subitem nor a link. This makes this item not able to link to anything.`, + ); + } + return category.link.type === 'doc' + ? { + type: 'doc', + label: category.label, + id: category.link.id, + } + : { + type: 'link', + label: category.label, + href: category.link.permalink, + }; + } + // A non-collapsible category can't be collapsed! + if (category.collapsible === false) { + category.collapsed = false; + } + return category; + } + return item; +} + +export function postProcessSidebars( + sidebars: ProcessedSidebars, + params: SidebarProcessorParams, +): Sidebars { + return _.mapValues(sidebars, (sidebar) => + sidebar.map((item) => postProcessSidebarItem(item, params)), + ); +} diff --git a/packages/docusaurus-plugin-content-docs/src/sidebars/processor.ts b/packages/docusaurus-plugin-content-docs/src/sidebars/processor.ts index 0ef94ba25f55..7e83dd08a921 100644 --- a/packages/docusaurus-plugin-content-docs/src/sidebars/processor.ts +++ b/packages/docusaurus-plugin-content-docs/src/sidebars/processor.ts @@ -5,48 +5,36 @@ * LICENSE file in the root directory of this source tree. */ -import type {DocMetadataBase, VersionMetadata} from '../types'; import type { - Sidebars, - Sidebar, - SidebarItem, + DocMetadataBase, + VersionMetadata, +} from '@docusaurus/plugin-content-docs'; +import type { NormalizedSidebarItem, NormalizedSidebar, NormalizedSidebars, - SidebarItemsGeneratorOption, SidebarItemsGeneratorDoc, SidebarItemsGeneratorVersion, - NormalizedSidebarItemCategory, - SidebarItemCategory, SidebarItemAutogenerated, + ProcessedSidebarItem, + ProcessedSidebar, + ProcessedSidebars, + SidebarProcessorParams, + CategoryMetadataFile, } from './types'; -import {transformSidebarItems} from './utils'; import {DefaultSidebarItemsGenerator} from './generator'; -import {mapValues, memoize, pick} from 'lodash'; +import {validateSidebars} from './validation'; +import _ from 'lodash'; import combinePromises from 'combine-promises'; -import {normalizeItem} from './normalization'; import {isCategoryIndex} from '../docs'; -import type {Slugger} from '@docusaurus/utils'; -import type { - NumberPrefixParser, - SidebarOptions, -} from '@docusaurus/plugin-content-docs'; - -export type SidebarProcessorParams = { - sidebarItemsGenerator: SidebarItemsGeneratorOption; - numberPrefixParser: NumberPrefixParser; - docs: DocMetadataBase[]; - version: VersionMetadata; - categoryLabelSlugger: Slugger; - sidebarOptions: SidebarOptions; -}; function toSidebarItemsGeneratorDoc( doc: DocMetadataBase, ): SidebarItemsGeneratorDoc { - return pick(doc, [ + return _.pick(doc, [ 'id', 'unversionedId', + 'title', 'frontMatter', 'source', 'sourceDirName', @@ -57,64 +45,52 @@ function toSidebarItemsGeneratorDoc( function toSidebarItemsGeneratorVersion( version: VersionMetadata, ): SidebarItemsGeneratorVersion { - return pick(version, ['versionName', 'contentPath']); + return _.pick(version, ['versionName', 'contentPath']); } -// Handle the generation of autogenerated sidebar items and other post-processing checks +// Handle the generation of autogenerated sidebar items and other +// post-processing checks async function processSidebar( unprocessedSidebar: NormalizedSidebar, + categoriesMetadata: {[filePath: string]: CategoryMetadataFile}, params: SidebarProcessorParams, -): Promise { - const { - sidebarItemsGenerator, - numberPrefixParser, - docs, - version, - sidebarOptions, - } = params; +): Promise { + const {sidebarItemsGenerator, numberPrefixParser, docs, version} = params; // Just a minor lazy transformation optimization - const getSidebarItemsGeneratorDocsAndVersion = memoize(() => ({ + const getSidebarItemsGeneratorDocsAndVersion = _.memoize(() => ({ docs: docs.map(toSidebarItemsGeneratorDoc), version: toSidebarItemsGeneratorVersion(version), })); - async function processCategoryItem( - item: NormalizedSidebarItemCategory, - ): Promise { - return { - ...item, - items: (await Promise.all(item.items.map(processItem))).flat(), - }; - } - async function processAutoGeneratedItem( item: SidebarItemAutogenerated, - ): Promise { - // TODO the returned type can't be trusted in practice (generator can be user-provided) + ): Promise { const generatedItems = await sidebarItemsGenerator({ item, numberPrefixParser, defaultSidebarItemsGenerator: DefaultSidebarItemsGenerator, isCategoryIndex, ...getSidebarItemsGeneratorDocsAndVersion(), - options: sidebarOptions, + categoriesMetadata, }); - // TODO validate generated items: user can generate bad items - - const generatedItemsNormalized = generatedItems.flatMap((generatedItem) => - normalizeItem(generatedItem, {...params, ...sidebarOptions}), - ); - - // Process again... weird but sidebar item generated might generate some auto-generated items? - return processItems(generatedItemsNormalized); + // Process again... weird but sidebar item generated might generate some + // auto-generated items? + // TODO repeatedly process & unwrap autogenerated items until there are no + // more autogenerated items, or when loop count (e.g. 10) is reached + return processItems(generatedItems); } async function processItem( item: NormalizedSidebarItem, - ): Promise { + ): Promise { if (item.type === 'category') { - return [await processCategoryItem(item)]; + return [ + { + ...item, + items: (await Promise.all(item.items.map(processItem))).flat(), + }, + ]; } if (item.type === 'autogenerated') { return processAutoGeneratedItem(item); @@ -124,32 +100,24 @@ async function processSidebar( async function processItems( items: NormalizedSidebarItem[], - ): Promise { + ): Promise { return (await Promise.all(items.map(processItem))).flat(); } const processedSidebar = await processItems(unprocessedSidebar); - - const fixSidebarItemInconsistencies = (item: SidebarItem): SidebarItem => { - // A non-collapsible category can't be collapsed! - if (item.type === 'category' && !item.collapsible && item.collapsed) { - return { - ...item, - collapsed: false, - }; - } - return item; - }; - return transformSidebarItems(processedSidebar, fixSidebarItemInconsistencies); + return processedSidebar; } export async function processSidebars( unprocessedSidebars: NormalizedSidebars, + categoriesMetadata: {[filePath: string]: CategoryMetadataFile}, params: SidebarProcessorParams, -): Promise { - return combinePromises( - mapValues(unprocessedSidebars, (unprocessedSidebar) => - processSidebar(unprocessedSidebar, params), +): Promise { + const processedSidebars = await combinePromises( + _.mapValues(unprocessedSidebars, (unprocessedSidebar) => + processSidebar(unprocessedSidebar, categoriesMetadata, params), ), ); + validateSidebars(processedSidebars); + return processedSidebars; } diff --git a/packages/docusaurus-plugin-content-docs/src/sidebars/types.ts b/packages/docusaurus-plugin-content-docs/src/sidebars/types.ts index 56a4be084a5f..9f4d68a3ae45 100644 --- a/packages/docusaurus-plugin-content-docs/src/sidebars/types.ts +++ b/packages/docusaurus-plugin-content-docs/src/sidebars/types.ts @@ -6,19 +6,21 @@ */ import type {Optional, Required} from 'utility-types'; -import type {DocMetadataBase, VersionMetadata} from '../types'; import type { NumberPrefixParser, SidebarOptions, CategoryIndexMatcher, + DocMetadataBase, + VersionMetadata, } from '@docusaurus/plugin-content-docs'; +import type {Slugger} from '@docusaurus/utils'; // Makes all properties visible when hovering over the type -type Expand> = {[P in keyof T]: T[P]}; +type Expand = {[P in keyof T]: T[P]}; export type SidebarItemBase = { className?: string; - customProps?: Record; + customProps?: {[key: string]: unknown}; }; export type SidebarItemDoc = SidebarItemBase & { @@ -27,6 +29,12 @@ export type SidebarItemDoc = SidebarItemBase & { id: string; }; +export type SidebarItemHtml = SidebarItemBase & { + type: 'html'; + value: string; + defaultStyle?: boolean; +}; + export type SidebarItemLink = SidebarItemBase & { type: 'link'; href: string; @@ -76,17 +84,18 @@ export type SidebarItemCategoryLink = // The user-given configuration in sidebars.js, before normalization export type SidebarItemCategoryConfig = Expand< Optional & { - items: SidebarItemConfig[]; + items: SidebarCategoriesShorthand | SidebarItemConfig[]; link?: SidebarItemCategoryLinkConfig; } >; export type SidebarCategoriesShorthand = { - [sidebarCategory: string]: SidebarItemConfig[]; + [sidebarCategory: string]: SidebarCategoriesShorthand | SidebarItemConfig[]; }; export type SidebarItemConfig = | SidebarItemDoc + | SidebarItemHtml | SidebarItemLink | SidebarItemAutogenerated | SidebarItemCategoryConfig @@ -100,14 +109,15 @@ export type SidebarsConfig = { // Normalized but still has 'autogenerated', which will be handled in processing export type NormalizedSidebarItemCategory = Expand< - SidebarItemCategoryBase & { + Optional & { items: NormalizedSidebarItem[]; - link?: SidebarItemCategoryLink; + link?: SidebarItemCategoryLinkConfig; } >; export type NormalizedSidebarItem = | SidebarItemDoc + | SidebarItemHtml | SidebarItemLink | NormalizedSidebarItemCategory | SidebarItemAutogenerated; @@ -117,6 +127,22 @@ export type NormalizedSidebars = { [sidebarId: string]: NormalizedSidebar; }; +export type ProcessedSidebarItemCategory = Expand< + Optional & { + items: ProcessedSidebarItem[]; + link?: SidebarItemCategoryLinkConfig; + } +>; +export type ProcessedSidebarItem = + | SidebarItemDoc + | SidebarItemHtml + | SidebarItemLink + | ProcessedSidebarItemCategory; +export type ProcessedSidebar = ProcessedSidebarItem[]; +export type ProcessedSidebars = { + [sidebarId: string]: ProcessedSidebar; +}; + export type SidebarItemCategory = Expand< SidebarItemCategoryBase & { items: SidebarItem[]; @@ -131,6 +157,7 @@ export type SidebarItemCategoryWithGeneratedIndex = export type SidebarItem = | SidebarItemDoc + | SidebarItemHtml | SidebarItemLink | SidebarItemCategory; @@ -158,20 +185,34 @@ export type PropSidebarItemLink = SidebarItemLink & { docId?: string; }; -export type PropSidebarItem = PropSidebarItemLink | PropSidebarItemCategory; +export type PropSidebarItemHtml = SidebarItemHtml; + +export type PropSidebarItem = + | PropSidebarItemLink + | PropSidebarItemCategory + | PropSidebarItemHtml; export type PropSidebar = PropSidebarItem[]; export type PropSidebars = { [sidebarId: string]: PropSidebar; }; -export type PropVersionDoc = { - id: string; - title: string; - description?: string; - sidebar?: string; -}; -export type PropVersionDocs = { - [docId: string]: PropVersionDoc; +export type PropSidebarBreadcrumbsItem = + | PropSidebarItemLink + | PropSidebarItemCategory; + +export type CategoryMetadataFile = { + label?: string; + position?: number; + collapsed?: boolean; + collapsible?: boolean; + className?: string; + link?: SidebarItemCategoryLinkConfig | null; + customProps?: {[key: string]: unknown}; + + // TODO should we allow "items" here? how would this work? would an + // "autogenerated" type be allowed? + // This mkdocs plugin do something like that: https://github.com/lukasgeiter/mkdocs-awesome-pages-plugin/ + // cf comment: https://github.com/facebook/docusaurus/issues/3464#issuecomment-784765199 }; // Reduce API surface for options.sidebarItemsGenerator @@ -181,6 +222,7 @@ export type SidebarItemsGeneratorDoc = Pick< DocMetadataBase, | 'id' | 'unversionedId' + | 'title' | 'frontMatter' | 'source' | 'sourceDirName' @@ -192,24 +234,45 @@ export type SidebarItemsGeneratorVersion = Pick< >; export type SidebarItemsGeneratorArgs = { + /** The sidebar item with type "autogenerated" to be transformed. */ item: SidebarItemAutogenerated; + /** Useful metadata for the version this sidebar belongs to. */ version: SidebarItemsGeneratorVersion; + /** All the docs of that version (unfiltered). */ docs: SidebarItemsGeneratorDoc[]; + /** Number prefix parser configured for this plugin. */ numberPrefixParser: NumberPrefixParser; + /** The default category index matcher which you can override. */ isCategoryIndex: CategoryIndexMatcher; - options: SidebarOptions; + /** + * key is the path relative to the doc content directory, value is the + * category metadata file's content. + */ + categoriesMetadata: {[filePath: string]: CategoryMetadataFile}; }; export type SidebarItemsGenerator = ( generatorArgs: SidebarItemsGeneratorArgs, -) => // TODO TS issue: the generator can generate un-normalized items! -Promise; -// Promise; +) => Promise; -// Also inject the default generator to conveniently wrap/enhance/sort the default sidebar gen logic +// Also inject the default generator to conveniently wrap/enhance/sort the +// default sidebar gen logic // see https://github.com/facebook/docusaurus/issues/4640#issuecomment-822292320 export type SidebarItemsGeneratorOptionArgs = { + /** + * Useful to re-use/enhance the default sidebar generation logic from + * Docusaurus. + */ defaultSidebarItemsGenerator: SidebarItemsGenerator; } & SidebarItemsGeneratorArgs; export type SidebarItemsGeneratorOption = ( generatorArgs: SidebarItemsGeneratorOptionArgs, -) => Promise; +) => Promise; + +export type SidebarProcessorParams = { + sidebarItemsGenerator: SidebarItemsGeneratorOption; + numberPrefixParser: NumberPrefixParser; + docs: DocMetadataBase[]; + version: VersionMetadata; + categoryLabelSlugger: Slugger; + sidebarOptions: SidebarOptions; +}; diff --git a/packages/docusaurus-plugin-content-docs/src/sidebars/utils.ts b/packages/docusaurus-plugin-content-docs/src/sidebars/utils.ts index 80bc2a3fd3fe..ee27177c1e4a 100644 --- a/packages/docusaurus-plugin-content-docs/src/sidebars/utils.ts +++ b/packages/docusaurus-plugin-content-docs/src/sidebars/utils.ts @@ -16,18 +16,20 @@ import type { SidebarCategoriesShorthand, SidebarItemConfig, SidebarItemCategoryWithGeneratedIndex, - SidebarItemCategoryWithLink, SidebarNavigationItem, } from './types'; -import {mapValues, difference, uniq} from 'lodash'; -import {getElementsAround, toMessageRelativeFilePath} from '@docusaurus/utils'; -import type {DocMetadataBase, DocNavLink} from '../types'; +import _ from 'lodash'; +import {toMessageRelativeFilePath} from '@docusaurus/utils'; +import type { + DocMetadataBase, + PropNavigationLink, +} from '@docusaurus/plugin-content-docs'; export function isCategoriesShorthand( item: SidebarItemConfig, ): item is SidebarCategoriesShorthand { - return typeof item !== 'string' && !item.type; + return typeof item === 'object' && !item.type; } export function transformSidebarItems( @@ -46,8 +48,11 @@ export function transformSidebarItems( return sidebar.map(transformRecursive); } -// Flatten sidebar items into a single flat array (containing categories/docs on the same level) -// /!\ order matters (useful for next/prev nav), top categories appear before their child elements +/** + * Flatten sidebar items into a single flat array (containing categories/docs on + * the same level). Order matters (useful for next/prev nav), top categories + * appear before their child elements + */ function flattenSidebarItems(items: SidebarItem[]): SidebarItem[] { function flattenRecursive(item: SidebarItem): SidebarItem[] { return item.type === 'category' @@ -105,16 +110,16 @@ export function collectSidebarNavigation( }); } -export function collectSidebarsDocIds( - sidebars: Sidebars, -): Record { - return mapValues(sidebars, collectSidebarDocIds); +export function collectSidebarsDocIds(sidebars: Sidebars): { + [sidebarId: string]: string[]; +} { + return _.mapValues(sidebars, collectSidebarDocIds); } -export function collectSidebarsNavigations( - sidebars: Sidebars, -): Record { - return mapValues(sidebars, collectSidebarNavigation); +export function collectSidebarsNavigations(sidebars: Sidebars): { + [sidebarId: string]: SidebarNavigationItem[]; +} { + return _.mapValues(sidebars, collectSidebarNavigation); } export type SidebarNavigation = { @@ -137,6 +142,11 @@ export type SidebarsUtils = { getCategoryGeneratedIndexNavigation: ( categoryGeneratedIndexPermalink: string, ) => SidebarNavigation; + /** + * This function may return undefined. This is usually a user mistake, because + * it means this sidebar will never be displayed; however, we can still use + * `displayed_sidebar` to make it displayed. Pretty weird but valid use-case + */ getFirstLink: (sidebarId: string) => | { type: 'doc'; @@ -145,7 +155,7 @@ export type SidebarsUtils = { } | { type: 'generated-index'; - slug: string; + permalink: string; label: string; } | undefined; @@ -196,34 +206,33 @@ export function createSidebarsUtils(sidebars: Sidebars): SidebarsUtils { sidebarName = getSidebarNameByDocId(docId); } - if (sidebarName) { - if (!sidebarNameToNavigationItems[sidebarName]) { - throw new Error( - `Doc with ID ${docId} wants to display sidebar ${sidebarName} but a sidebar with this name doesn't exist`, - ); + if (!sidebarName) { + return emptySidebarNavigation(); + } + const navigationItems = sidebarNameToNavigationItems[sidebarName]; + if (!navigationItems) { + throw new Error( + `Doc with ID ${docId} wants to display sidebar ${sidebarName} but a sidebar with this name doesn't exist`, + ); + } + const currentItemIndex = navigationItems.findIndex((item) => { + if (item.type === 'doc') { + return item.id === docId; } - const navigationItems = sidebarNameToNavigationItems[sidebarName]; - const currentItemIndex = navigationItems.findIndex((item) => { - if (item.type === 'doc') { - return item.id === docId; - } - if (item.type === 'category' && item.link.type === 'doc') { - return item.link.id === docId; - } - return false; - }); - if (currentItemIndex === -1) { - return {sidebarName, next: undefined, previous: undefined}; + if (item.type === 'category' && item.link.type === 'doc') { + return item.link.id === docId; } - - const {previous, next} = getElementsAround( - navigationItems, - currentItemIndex, - ); - return {sidebarName, previous, next}; - } else { - return emptySidebarNavigation(); + return false; + }); + if (currentItemIndex === -1) { + return {sidebarName, next: undefined, previous: undefined}; } + + return { + sidebarName, + previous: navigationItems[currentItemIndex - 1], + next: navigationItems[currentItemIndex + 1], + }; } function getCategoryGeneratedIndexList(): SidebarItemCategoryWithGeneratedIndex[] { @@ -237,8 +246,10 @@ export function createSidebarsUtils(sidebars: Sidebars): SidebarsUtils { }); } - // We identity the category generated index by its permalink (should be unique) - // More reliable than using object identity + /** + * We identity the category generated index by its permalink (should be + * unique). More reliable than using object identity + */ function getCategoryGeneratedIndexNavigation( categoryGeneratedIndexPermalink: string, ): SidebarNavigation { @@ -255,26 +266,21 @@ export function createSidebarsUtils(sidebars: Sidebars): SidebarsUtils { const sidebarName = Object.entries(sidebarNameToNavigationItems).find( ([, navigationItems]) => navigationItems.find(isCurrentCategoryGeneratedIndexItem), - )?.[0]; - - if (sidebarName) { - const navigationItems = sidebarNameToNavigationItems[sidebarName]; - const currentItemIndex = navigationItems.findIndex( - isCurrentCategoryGeneratedIndexItem, - ); - const {previous, next} = getElementsAround( - navigationItems, - currentItemIndex, - ); - return {sidebarName, previous, next}; - } else { - return emptySidebarNavigation(); - } + )![0]; + const navigationItems = sidebarNameToNavigationItems[sidebarName]!; + const currentItemIndex = navigationItems.findIndex( + isCurrentCategoryGeneratedIndexItem, + ); + return { + sidebarName, + previous: navigationItems[currentItemIndex - 1], + next: navigationItems[currentItemIndex + 1], + }; } function checkSidebarsDocIds(validDocIds: string[], sidebarFilePath: string) { const allSidebarDocIds = Object.values(sidebarNameToDocIds).flat(); - const invalidSidebarDocIds = difference(allSidebarDocIds, validDocIds); + const invalidSidebarDocIds = _.difference(allSidebarDocIds, validDocIds); if (invalidSidebarDocIds.length > 0) { throw new Error( `Invalid sidebar file at "${toMessageRelativeFilePath( @@ -284,7 +290,7 @@ These sidebar document ids do not exist: - ${invalidSidebarDocIds.sort().join('\n- ')} Available document ids are: -- ${uniq(validDocIds).sort().join('\n- ')}`, +- ${_.uniq(validDocIds).sort().join('\n- ')}`, ); } } @@ -297,11 +303,10 @@ Available document ids are: } | { type: 'generated-index'; - slug: string; + permalink: string; label: string; } | undefined { - // eslint-disable-next-line no-restricted-syntax for (const item of sidebar) { if (item.type === 'doc') { return { @@ -319,14 +324,13 @@ Available document ids are: } else if (item.link?.type === 'generated-index') { return { type: 'generated-index', - slug: item.link.slug, + permalink: item.link.permalink, label: item.label, }; - } else { - const firstSubItem = getFirstLink(item.items); - if (firstSubItem) { - return firstSubItem; - } + } + const firstSubItem = getFirstLink(item.items); + if (firstSubItem) { + return firstSubItem; } } } @@ -341,11 +345,11 @@ Available document ids are: getCategoryGeneratedIndexList, getCategoryGeneratedIndexNavigation, checkSidebarsDocIds, - getFirstLink: (id) => getFirstLink(sidebars[id]), + getFirstLink: (id) => getFirstLink(sidebars[id]!), }; } -export function toDocNavigationLink(doc: DocMetadataBase): DocNavLink { +export function toDocNavigationLink(doc: DocMetadataBase): PropNavigationLink { const { title, permalink, @@ -359,8 +363,8 @@ export function toDocNavigationLink(doc: DocMetadataBase): DocNavLink { export function toNavigationLink( navigationItem: SidebarNavigationItem | undefined, - docsById: Record, -): DocNavLink | undefined { + docsById: {[docId: string]: DocMetadataBase}, +): PropNavigationLink | undefined { function getDocById(docId: string) { const doc = docsById[docId]; if (!doc) { @@ -371,27 +375,17 @@ export function toNavigationLink( return doc; } - function handleCategory(category: SidebarItemCategoryWithLink): DocNavLink { - if (category.link.type === 'doc') { - return toDocNavigationLink(getDocById(category.link.id)); - } else if (category.link.type === 'generated-index') { - return { - title: category.label, - permalink: category.link.permalink, - }; - } else { - throw new Error('unexpected category link type'); - } - } if (!navigationItem) { return undefined; } - if (navigationItem.type === 'doc') { - return toDocNavigationLink(getDocById(navigationItem.id)); - } else if (navigationItem.type === 'category') { - return handleCategory(navigationItem); - } else { - throw new Error('unexpected navigation item'); + if (navigationItem.type === 'category') { + return navigationItem.link.type === 'doc' + ? toDocNavigationLink(getDocById(navigationItem.link.id)) + : { + title: navigationItem.label, + permalink: navigationItem.link.permalink, + }; } + return toDocNavigationLink(getDocById(navigationItem.id)); } diff --git a/packages/docusaurus-plugin-content-docs/src/sidebars/validation.ts b/packages/docusaurus-plugin-content-docs/src/sidebars/validation.ts index 4db90a97c718..22ddcfbf77ce 100644 --- a/packages/docusaurus-plugin-content-docs/src/sidebars/validation.ts +++ b/packages/docusaurus-plugin-content-docs/src/sidebars/validation.ts @@ -8,22 +8,24 @@ import {Joi, URISchema} from '@docusaurus/utils-validation'; import type { SidebarItemConfig, - SidebarCategoriesShorthand, SidebarItemBase, SidebarItemAutogenerated, SidebarItemDoc, + SidebarItemHtml, SidebarItemLink, SidebarItemCategoryConfig, SidebarItemCategoryLink, - SidebarsConfig, SidebarItemCategoryLinkDoc, SidebarItemCategoryLinkGeneratedIndex, + NormalizedSidebars, + NormalizedSidebarItem, + NormalizedSidebarItemCategory, + CategoryMetadataFile, } from './types'; -import {isCategoriesShorthand} from './utils'; -import type {CategoryMetadataFile} from './generator'; // NOTE: we don't add any default values during validation on purpose! -// Config types are exposed to users for typechecking and we use the same type in normalization +// Config types are exposed to users for typechecking and we use the same type +// in normalization const sidebarItemBaseSchema = Joi.object({ className: Joi.string(), @@ -35,7 +37,7 @@ const sidebarItemAutogeneratedSchema = type: 'autogenerated', dirName: Joi.string() .required() - .pattern(/^[^/](.*[^/])?$/) + .pattern(/^[^/](?:.*[^/])?$/) .message( '"dirName" must be a dir path relative to the docs folder root, and should not start or end with slash', ), @@ -47,6 +49,12 @@ const sidebarItemDocSchema = sidebarItemBaseSchema.append({ label: Joi.string(), }); +const sidebarItemHtmlSchema = sidebarItemBaseSchema.append({ + type: 'html', + value: Joi.string().required(), + defaultStyle: Joi.boolean(), +}); + const sidebarItemLinkSchema = sidebarItemBaseSchema.append({ type: 'link', href: URISchema.required(), @@ -56,6 +64,7 @@ const sidebarItemLinkSchema = sidebarItemBaseSchema.append({ }); const sidebarItemCategoryLinkSchema = Joi.object() + .allow(null) .when('.type', { switch: [ { @@ -70,7 +79,8 @@ const sidebarItemCategoryLinkSchema = Joi.object() then: Joi.object({ type: 'generated-index', slug: Joi.string().optional(), - // permalink: Joi.string().optional(), // No, this one is not in the user config, only in the normalized version + // This one is not in the user config, only in the normalized version + // permalink: Joi.string().optional(), title: Joi.string().optional(), description: Joi.string().optional(), image: Joi.string().optional(), @@ -78,14 +88,13 @@ const sidebarItemCategoryLinkSchema = Joi.object() }), }, { - is: Joi.string().required(), + is: Joi.required(), then: Joi.forbidden().messages({ 'any.unknown': 'Unknown sidebar category link type "{.type}".', }), }, ], - }) - .id('sidebarCategoryLinkSchema'); + }); const sidebarItemCategorySchema = sidebarItemBaseSchema.append({ @@ -93,10 +102,11 @@ const sidebarItemCategorySchema = label: Joi.string() .required() .messages({'any.unknown': '"label" must be a string'}), - // TODO: Joi doesn't allow mutual recursion. See https://github.com/sideway/joi/issues/2611 items: Joi.array() .required() - .messages({'any.unknown': '"items" must be an array'}), // .items(Joi.link('#sidebarItemSchema')), + .messages({'any.unknown': '"items" must be an array'}), + // TODO: Joi doesn't allow mutual recursion. See https://github.com/sideway/joi/issues/2611 + // .items(Joi.link('#sidebarItemSchema')), link: sidebarItemCategoryLinkSchema, collapsed: Joi.boolean().messages({ 'any.unknown': '"collapsed" must be a boolean', @@ -106,54 +116,44 @@ const sidebarItemCategorySchema = }), }); -const sidebarItemSchema: Joi.Schema = Joi.object() - .when('.type', { - switch: [ - {is: 'link', then: sidebarItemLinkSchema}, - { - is: Joi.string().valid('doc', 'ref').required(), - then: sidebarItemDocSchema, - }, - {is: 'autogenerated', then: sidebarItemAutogeneratedSchema}, - {is: 'category', then: sidebarItemCategorySchema}, - { - is: Joi.any().required(), - then: Joi.forbidden().messages({ - 'any.unknown': 'Unknown sidebar item type "{.type}".', - }), - }, - ], - }) - .id('sidebarItemSchema'); +const sidebarItemSchema = Joi.object().when('.type', { + switch: [ + {is: 'link', then: sidebarItemLinkSchema}, + { + is: Joi.string().valid('doc', 'ref').required(), + then: sidebarItemDocSchema, + }, + {is: 'html', then: sidebarItemHtmlSchema}, + {is: 'autogenerated', then: sidebarItemAutogeneratedSchema}, + {is: 'category', then: sidebarItemCategorySchema}, + { + is: Joi.any().required(), + then: Joi.forbidden().messages({ + 'any.unknown': 'Unknown sidebar item type "{.type}".', + }), + }, + ], +}); +// .id('sidebarItemSchema'); -function validateSidebarItem(item: unknown): asserts item is SidebarItemConfig { - if (typeof item === 'string') { - return; - } +function validateSidebarItem( + item: unknown, +): asserts item is NormalizedSidebarItem { // TODO: remove once with proper Joi support - // Because we can't use Joi to validate nested items (see above), we do it manually - if (isCategoriesShorthand(item as SidebarItemConfig)) { - Object.values(item as SidebarCategoriesShorthand).forEach((category) => - category.forEach(validateSidebarItem), - ); - } else { - Joi.assert(item, sidebarItemSchema); + // Because we can't use Joi to validate nested items (see above), we do it + // manually + Joi.assert(item, sidebarItemSchema); - if ((item as SidebarItemCategoryConfig).type === 'category') { - (item as SidebarItemCategoryConfig).items.forEach(validateSidebarItem); - } + if ((item as NormalizedSidebarItemCategory).type === 'category') { + (item as NormalizedSidebarItemCategory).items.forEach(validateSidebarItem); } } -export function validateSidebars( - sidebars: unknown, -): asserts sidebars is SidebarsConfig { - Object.values(sidebars as SidebarsConfig).forEach((sidebar) => { - if (Array.isArray(sidebar)) { - sidebar.forEach(validateSidebarItem); - } else { - validateSidebarItem(sidebar); - } +export function validateSidebars(sidebars: { + [sidebarId: string]: unknown; +}): asserts sidebars is NormalizedSidebars { + Object.values(sidebars as NormalizedSidebars).forEach((sidebar) => { + sidebar.forEach(validateSidebarItem); }); } @@ -164,6 +164,7 @@ const categoryMetadataFileSchema = Joi.object({ collapsible: Joi.boolean(), className: Joi.string(), link: sidebarItemCategoryLinkSchema, + customProps: Joi.object().unknown(), }); export function validateCategoryMetadataFile( diff --git a/packages/docusaurus-plugin-content-docs/src/slug.ts b/packages/docusaurus-plugin-content-docs/src/slug.ts index e5168563c245..4815e210b01d 100644 --- a/packages/docusaurus-plugin-content-docs/src/slug.ts +++ b/packages/docusaurus-plugin-content-docs/src/slug.ts @@ -15,9 +15,11 @@ import { DefaultNumberPrefixParser, stripPathNumberPrefixes, } from './numberPrefix'; -import type {DocMetadataBase} from './types'; import {isCategoryIndex, toCategoryIndexMatcherParam} from './docs'; -import type {NumberPrefixParser} from '@docusaurus/plugin-content-docs'; +import type { + NumberPrefixParser, + DocMetadataBase, +} from '@docusaurus/plugin-content-docs'; export default function getSlug({ baseID, @@ -48,28 +50,26 @@ export default function getSlug({ function computeSlug(): string { if (frontMatterSlug?.startsWith('/')) { return frontMatterSlug; - } else { - const dirNameSlug = getDirNameSlug(); - if ( - !frontMatterSlug && - isCategoryIndex(toCategoryIndexMatcherParam({source, sourceDirName})) - ) { - return dirNameSlug; - } - const baseSlug = frontMatterSlug || baseID; - return resolvePathname(baseSlug, getDirNameSlug()); } + const dirNameSlug = getDirNameSlug(); + if ( + !frontMatterSlug && + isCategoryIndex(toCategoryIndexMatcherParam({source, sourceDirName})) + ) { + return dirNameSlug; + } + const baseSlug = frontMatterSlug || baseID; + return resolvePathname(baseSlug, getDirNameSlug()); } function ensureValidSlug(slug: string): string { if (!isValidPathname(slug)) { throw new Error( - `We couldn't compute a valid slug for document with id "${baseID}" in "${sourceDirName}" directory. + `We couldn't compute a valid slug for document with ID "${baseID}" in "${sourceDirName}" directory. The slug we computed looks invalid: ${slug}. -Maybe your slug front matter is incorrect or you use weird chars in the file path? -By using the slug front matter, you should be able to fix this error, by using the slug of your choice: +Maybe your slug front matter is incorrect or there are special characters in the file path? +By using front matter to set a custom slug, you should be able to fix this error: -Example => --- slug: /my/customDocPath --- diff --git a/packages/docusaurus-plugin-content-docs/src/tags.ts b/packages/docusaurus-plugin-content-docs/src/tags.ts index 6d0c9a01f3ea..1de963d81f56 100644 --- a/packages/docusaurus-plugin-content-docs/src/tags.ts +++ b/packages/docusaurus-plugin-content-docs/src/tags.ts @@ -6,13 +6,14 @@ */ import {groupTaggedItems} from '@docusaurus/utils'; -import type {VersionTags, DocMetadata} from './types'; -import {mapValues} from 'lodash'; +import type {VersionTags} from './types'; +import type {DocMetadata} from '@docusaurus/plugin-content-docs'; +import _ from 'lodash'; export function getVersionTags(docs: DocMetadata[]): VersionTags { const groups = groupTaggedItems(docs, (doc) => doc.tags); - return mapValues(groups, (group) => ({ - name: group.tag.label, + return _.mapValues(groups, (group) => ({ + label: group.tag.label, docIds: group.items.map((item) => item.id), permalink: group.tag.permalink, })); diff --git a/packages/docusaurus-plugin-content-docs/src/translations.ts b/packages/docusaurus-plugin-content-docs/src/translations.ts index 772c350ab3a9..4289f3579adf 100644 --- a/packages/docusaurus-plugin-content-docs/src/translations.ts +++ b/packages/docusaurus-plugin-content-docs/src/translations.ts @@ -13,7 +13,7 @@ import type { Sidebars, } from './sidebars/types'; -import {chain, mapValues, keyBy} from 'lodash'; +import _ from 'lodash'; import { collectSidebarCategories, transformSidebarItems, @@ -22,7 +22,6 @@ import { import type { TranslationFileContent, TranslationFile, - TranslationFiles, TranslationMessage, } from '@docusaurus/types'; import {mergeTranslations} from '@docusaurus/utils'; @@ -31,11 +30,10 @@ import {CURRENT_VERSION_NAME} from './constants'; function getVersionFileName(versionName: string): string { if (versionName === CURRENT_VERSION_NAME) { return versionName; - } else { - // I don't like this "version-" prefix, - // but it's for consistency with site/versioned_docs - return `version-${versionName}`; } + // I don't like this "version-" prefix, + // but it's for consistency with site/versioned_docs + return `version-${versionName}`; } // TODO legacy, the sidebar name is like "version-2.0.0-alpha.66/docs" @@ -68,7 +66,8 @@ function getDocTranslations(doc: DocMetadata): TranslationFileContent { ? { [`${doc.unversionedId}.sidebar_label`]: { message: doc.sidebar_label, - description: `The sidebar label for doc with id=${doc.unversionedId}`, + description: + `The sidebar label for doc with id=${doc.unversionedId}`, }, } : undefined), @@ -118,26 +117,24 @@ function getSidebarTranslationFileContent( }, ]); - if (category.link) { - if (category.link.type === 'generated-index') { - if (category.link.title) { - entries.push([ - `sidebar.${sidebarName}.category.${category.label}.link.generated-index.title`, - { - message: category.link.title, - description: `The generated-index page title for category ${category.label} in sidebar ${sidebarName}`, - }, - ]); - } - if (category.link.description) { - entries.push([ - `sidebar.${sidebarName}.category.${category.label}.link.generated-index.description`, - { - message: category.link.description, - description: `The generated-index page description for category ${category.label} in sidebar ${sidebarName}`, - }, - ]); - } + if (category.link?.type === 'generated-index') { + if (category.link.title) { + entries.push([ + `sidebar.${sidebarName}.category.${category.label}.link.generated-index.title`, + { + message: category.link.title, + description: `The generated-index page title for category ${category.label} in sidebar ${sidebarName}`, + }, + ]); + } + if (category.link.description) { + entries.push([ + `sidebar.${sidebarName}.category.${category.label}.link.generated-index.description`, + { + message: category.link.description, + description: `The generated-index page description for category ${category.label} in sidebar ${sidebarName}`, + }, + ]); } } @@ -146,13 +143,15 @@ function getSidebarTranslationFileContent( ); const links = collectSidebarLinks(sidebar); - const linksContent: TranslationFileContent = chain(links) - .keyBy((link) => `sidebar.${sidebarName}.link.${link.label}`) - .mapValues((link) => ({ - message: link.label, - description: `The label for link ${link.label} in sidebar ${sidebarName}, linking to ${link.href}`, - })) - .value(); + const linksContent: TranslationFileContent = Object.fromEntries( + links.map((link) => [ + `sidebar.${sidebarName}.link.${link.label}`, + { + message: link.label, + description: `The label for link ${link.label} in sidebar ${sidebarName}, linking to ${link.href}`, + }, + ]), + ); return mergeTranslations([categoryContent, linksContent]); } @@ -230,7 +229,7 @@ function translateSidebars( version: LoadedVersion, sidebarsTranslations: TranslationFileContent, ): Sidebars { - return mapValues(version.sidebars, (sidebar, sidebarName) => + return _.mapValues(version.sidebars, (sidebar, sidebarName) => translateSidebar({ sidebar, sidebarName: getNormalizedSidebarName({ @@ -242,10 +241,10 @@ function translateSidebars( ); } -function getVersionTranslationFiles(version: LoadedVersion): TranslationFiles { +function getVersionTranslationFiles(version: LoadedVersion): TranslationFile[] { const versionTranslations: TranslationFileContent = { 'version.label': { - message: version.versionLabel, + message: version.label, description: `The label for version ${version.versionName}`, }, }; @@ -253,7 +252,8 @@ function getVersionTranslationFiles(version: LoadedVersion): TranslationFiles { const sidebarsTranslations: TranslationFileContent = getSidebarsTranslations(version); - // const docsTranslations: TranslationFileContent = getDocsTranslations(version); + // const docsTranslations: TranslationFileContent = + // getDocsTranslations(version); return [ { @@ -268,13 +268,13 @@ function getVersionTranslationFiles(version: LoadedVersion): TranslationFiles { } function translateVersion( version: LoadedVersion, - translationFiles: Record, + translationFiles: {[fileName: string]: TranslationFile}, ): LoadedVersion { const versionTranslations = - translationFiles[getVersionFileName(version.versionName)].content; + translationFiles[getVersionFileName(version.versionName)]!.content; return { ...version, - versionLabel: versionTranslations['version.label']?.message, + label: versionTranslations['version.label']?.message ?? version.label, sidebars: translateSidebars(version, versionTranslations), // docs: translateDocs(version.docs, versionTranslations), }; @@ -282,26 +282,26 @@ function translateVersion( function getVersionsTranslationFiles( versions: LoadedVersion[], -): TranslationFiles { +): TranslationFile[] { return versions.flatMap(getVersionTranslationFiles); } function translateVersions( versions: LoadedVersion[], - translationFiles: Record, + translationFiles: {[fileName: string]: TranslationFile}, ): LoadedVersion[] { return versions.map((version) => translateVersion(version, translationFiles)); } export function getLoadedContentTranslationFiles( loadedContent: LoadedContent, -): TranslationFiles { +): TranslationFile[] { return getVersionsTranslationFiles(loadedContent.loadedVersions); } export function translateLoadedContent( loadedContent: LoadedContent, translationFiles: TranslationFile[], ): LoadedContent { - const translationFilesMap: Record = keyBy( + const translationFilesMap: {[fileName: string]: TranslationFile} = _.keyBy( translationFiles, (f) => f.path, ); diff --git a/packages/docusaurus-plugin-content-docs/src/types.ts b/packages/docusaurus-plugin-content-docs/src/types.ts index b32575772fbf..909a200da7e4 100644 --- a/packages/docusaurus-plugin-content-docs/src/types.ts +++ b/packages/docusaurus-plugin-content-docs/src/types.ts @@ -8,14 +8,12 @@ /// import type {Sidebars} from './sidebars/types'; -import type {Tag, FrontMatterTag, Slugger} from '@docusaurus/utils'; +import type {BrokenMarkdownLink, Tag} from '@docusaurus/utils'; import type { - BrokenMarkdownLink as IBrokenMarkdownLink, - ContentPaths, -} from '@docusaurus/utils/lib/markdownLinks'; -import type { - VersionBanner, - SidebarOptions, + VersionMetadata, + LastUpdateData, + DocMetadata, + CategoryGeneratedIndexMetadata, } from '@docusaurus/plugin-content-docs'; export type DocFile = { @@ -26,110 +24,19 @@ export type DocFile = { lastUpdate: LastUpdateData; }; -export type VersionMetadata = ContentPaths & { - versionName: string; // 1.0.0 - versionLabel: string; // Version 1.0.0 - versionPath: string; // /baseUrl/docs/1.0.0 - tagsPath: string; - versionEditUrl?: string | undefined; - versionEditUrlLocalized?: string | undefined; - versionBanner: VersionBanner | null; - versionBadge: boolean; - versionClassName: string; - isLast: boolean; - sidebarFilePath: string | false | undefined; // versioned_sidebars/1.0.0.json - routePriority: number | undefined; // -1 for the latest docs -}; - -export type NormalizeSidebarsParams = SidebarOptions & { - version: VersionMetadata; - categoryLabelSlugger: Slugger; -}; - -export type LastUpdateData = { - lastUpdatedAt?: number; - formattedLastUpdatedAt?: string; - lastUpdatedBy?: string; -}; - -export type DocFrontMatter = { - // Front matter uses snake case - id?: string; - title?: string; - tags?: FrontMatterTag[]; - hide_title?: boolean; - hide_table_of_contents?: boolean; - keywords?: string[]; - image?: string; - description?: string; - slug?: string; - sidebar_label?: string; - sidebar_position?: number; - sidebar_class_name?: string; - displayed_sidebar?: string | null; - pagination_label?: string; - custom_edit_url?: string | null; - parse_number_prefixes?: boolean; - toc_min_heading_level?: number; - toc_max_heading_level?: number; - pagination_next?: string | null; - pagination_prev?: string | null; -}; - -export type DocMetadataBase = LastUpdateData & { - id: string; // TODO legacy versioned id => try to remove - unversionedId: string; // TODO new unversioned id => try to rename to "id" - version: string; - title: string; - description: string; - source: string; // @site aliased posix source => "@site/docs/folder/subFolder/subSubFolder/myDoc.md" - sourceDirName: string; // posix path relative to the versioned docs folder (can be ".") => "folder/subFolder/subSubFolder" - slug: string; - permalink: string; - sidebarPosition?: number; - editUrl?: string | null; - tags: Tag[]; - frontMatter: DocFrontMatter & Record; -}; - -export type DocNavLink = { - title: string; - permalink: string; -}; - -export type DocMetadata = DocMetadataBase & { - sidebar?: string; - previous?: DocNavLink; - next?: DocNavLink; -}; - -export type CategoryGeneratedIndexMetadata = { - title: string; - description?: string; - slug: string; - permalink: string; - sidebar: string; - previous?: DocNavLink; - next?: DocNavLink; - image?: string; - keywords?: string | readonly string[]; -}; - export type SourceToPermalink = { [source: string]: string; }; -export type VersionTag = { - name: string; // normalized name/label of the tag - docIds: string[]; // all doc ids having this tag - permalink: string; // pathname of the tag +export type VersionTag = Tag & { + /** all doc ids having this tag. */ + docIds: string[]; }; export type VersionTags = { [key: string]: VersionTag; }; export type LoadedVersion = VersionMetadata & { - versionPath: string; mainDocId: string; docs: DocMetadata[]; sidebars: Sidebars; @@ -140,11 +47,11 @@ export type LoadedContent = { loadedVersions: LoadedVersion[]; }; -export type BrokenMarkdownLink = IBrokenMarkdownLink; +export type DocBrokenMarkdownLink = BrokenMarkdownLink; export type DocsMarkdownOption = { versionsMetadata: VersionMetadata[]; siteDir: string; sourceToPermalink: SourceToPermalink; - onBrokenMarkdownLink: (brokenMarkdownLink: BrokenMarkdownLink) => void; + onBrokenMarkdownLink: (brokenMarkdownLink: DocBrokenMarkdownLink) => void; }; diff --git a/packages/docusaurus-plugin-content-docs/src/versions.ts b/packages/docusaurus-plugin-content-docs/src/versions.ts index 7934a4e0b725..7b06aa0e089c 100644 --- a/packages/docusaurus-plugin-content-docs/src/versions.ts +++ b/packages/docusaurus-plugin-content-docs/src/versions.ts @@ -7,7 +7,6 @@ import path from 'path'; import fs from 'fs-extra'; -import type {VersionMetadata} from './types'; import { VERSIONS_JSON_FILE, VERSIONED_DOCS_DIR, @@ -17,8 +16,8 @@ import { import type { PluginOptions, VersionBanner, - VersionOptions, VersionsOptions, + VersionMetadata, } from '@docusaurus/plugin-content-docs'; import type {LoadContext} from '@docusaurus/types'; @@ -28,16 +27,14 @@ import { posixPath, DEFAULT_PLUGIN_ID, } from '@docusaurus/utils'; -import {difference} from 'lodash'; +import _ from 'lodash'; import {resolveSidebarPathOption} from './sidebars'; // retro-compatibility: no prefix for the default plugin id function addPluginIdPrefix(fileOrDir: string, pluginId: string): string { - if (pluginId === DEFAULT_PLUGIN_ID) { - return fileOrDir; - } else { - return `${pluginId}_${fileOrDir}`; - } + return pluginId === DEFAULT_PLUGIN_ID + ? fileOrDir + : `${pluginId}_${fileOrDir}`; } export function getVersionedDocsDirPath( @@ -76,9 +73,9 @@ function ensureValidVersionString(version: unknown): asserts version is string { function ensureValidVersionArray( versionArray: unknown, ): asserts versionArray is string[] { - if (!(versionArray instanceof Array)) { + if (!Array.isArray(versionArray)) { throw new Error( - `The versions file should contain an array of versions! Found content: ${JSON.stringify( + `The versions file should contain an array of version names! Found content: ${JSON.stringify( versionArray, )}`, ); @@ -87,7 +84,7 @@ function ensureValidVersionArray( versionArray.forEach(ensureValidVersionString); } -async function readVersionsFile( +export async function readVersionsFile( siteDir: string, pluginId: string, ): Promise { @@ -96,12 +93,11 @@ async function readVersionsFile( const content = JSON.parse(await fs.readFile(versionsFilePath, 'utf8')); ensureValidVersionArray(content); return content; - } else { - return null; } + return null; } -async function readVersionNames( +export async function readVersionNames( siteDir: string, options: Pick< PluginOptions, @@ -210,7 +206,12 @@ function getVersionEditUrls({ contentPath, contentPathLocalized, context: {siteDir, i18n}, - options: {id, path: currentVersionPath, editUrl, editCurrentVersion}, + options: { + id, + path: currentVersionPath, + editUrl: editUrlOption, + editCurrentVersion, + }, }: { contentPath: string; contentPathLocalized: string; @@ -219,15 +220,11 @@ function getVersionEditUrls({ PluginOptions, 'id' | 'path' | 'editUrl' | 'editCurrentVersion' >; -}): {versionEditUrl: string; versionEditUrlLocalized: string} | undefined { - if (!editUrl) { - return undefined; - } - - // if the user is using the functional form of editUrl, - // he has total freedom and we can't compute a "version edit url" - if (typeof editUrl === 'function') { - return undefined; +}): Pick { + // If the user is using the functional form of editUrl, + // she has total freedom and we can't compute a "version edit url" + if (!editUrlOption || typeof editUrlOption === 'function') { + return {editUrl: undefined, editUrlLocalized: undefined}; } const editDirPath = editCurrentVersion ? currentVersionPath : contentPath; @@ -247,20 +244,20 @@ function getVersionEditUrls({ path.relative(siteDir, path.resolve(siteDir, editDirPathLocalized)), ); - const versionEditUrl = normalizeUrl([editUrl, versionPathSegment]); + const editUrl = normalizeUrl([editUrlOption, versionPathSegment]); - const versionEditUrlLocalized = normalizeUrl([ - editUrl, + const editUrlLocalized = normalizeUrl([ + editUrlOption, versionPathSegmentLocalized, ]); return { - versionEditUrl, - versionEditUrlLocalized, + editUrl, + editUrlLocalized, }; } -function getDefaultVersionBanner({ +export function getDefaultVersionBanner({ versionName, versionNames, lastVersionName, @@ -274,18 +271,16 @@ function getDefaultVersionBanner({ return null; } // Upcoming versions: unreleased banner - else if ( + if ( versionNames.indexOf(versionName) < versionNames.indexOf(lastVersionName) ) { return 'unreleased'; } // Older versions: display unmaintained banner - else { - return 'unmaintained'; - } + return 'unmaintained'; } -function getVersionBanner({ +export function getVersionBanner({ versionName, versionNames, lastVersionName, @@ -307,7 +302,7 @@ function getVersionBanner({ }); } -function getVersionBadge({ +export function getVersionBadge({ versionName, versionNames, options, @@ -375,12 +370,12 @@ function createVersionMetadata({ } const defaultVersionPathPart = getDefaultVersionPathPart(); - const versionOptions: VersionOptions = options.versions[versionName] ?? {}; + const versionOptions = options.versions[versionName] ?? {}; - const versionLabel = versionOptions.label ?? defaultVersionLabel; + const label = versionOptions.label ?? defaultVersionLabel; const versionPathPart = versionOptions.path ?? defaultVersionPathPart; - const versionPath = normalizeUrl([ + const routePath = normalizeUrl([ context.baseUrl, options.routeBasePath, versionPathPart, @@ -393,28 +388,27 @@ function createVersionMetadata({ options, }); - // Because /docs/:route` should always be after `/docs/versionName/:route`. const routePriority = versionPathPart === '' ? -1 : undefined; // the path that will be used to refer the docs tags // example below will be using /docs/tags - const tagsPath = normalizeUrl([versionPath, options.tagsBasePath]); + const tagsPath = normalizeUrl([routePath, options.tagsBasePath]); return { versionName, - versionLabel, - versionPath, + label, + path: routePath, tagsPath, - versionEditUrl: versionEditUrls?.versionEditUrl, - versionEditUrlLocalized: versionEditUrls?.versionEditUrlLocalized, - versionBanner: getVersionBanner({ + editUrl: versionEditUrls.editUrl, + editUrlLocalized: versionEditUrls.editUrlLocalized, + banner: getVersionBanner({ versionName, versionNames, lastVersionName, options, }), - versionBadge: getVersionBadge({versionName, versionNames, options}), - versionClassName: getVersionClassName({versionName, options}), + badge: getVersionBadge({versionName, versionNames, options}), + className: getVersionClassName({versionName, options}), isLast, routePriority, sidebarFilePath, @@ -423,7 +417,7 @@ function createVersionMetadata({ }; } -function checkVersionMetadataPaths({ +async function checkVersionMetadataPaths({ versionMetadata, context, }: { @@ -434,7 +428,7 @@ function checkVersionMetadataPaths({ const {siteDir} = context; const isCurrentVersion = versionName === CURRENT_VERSION_NAME; - if (!fs.existsSync(contentPath)) { + if (!(await fs.pathExists(contentPath))) { throw new Error( `The docs folder does not exist for version "${versionName}". A docs folder is expected to be found at ${path.relative( siteDir, @@ -443,14 +437,15 @@ function checkVersionMetadataPaths({ ); } - // If the current version defines a path to a sidebar file that does not exist, we throw! - // Note: for versioned sidebars, the file may not exist (as we prefer to not create it rather than to create an empty file) + // If the current version defines a path to a sidebar file that does not + // exist, we throw! Note: for versioned sidebars, the file may not exist (as + // we prefer to not create it rather than to create an empty file) // See https://github.com/facebook/docusaurus/issues/3366 // See https://github.com/facebook/docusaurus/pull/4775 if ( isCurrentVersion && typeof sidebarFilePath === 'string' && - !fs.existsSync(sidebarFilePath) + !(await fs.pathExists(sidebarFilePath)) ) { throw new Error(`The path to the sidebar file does not exist at "${path.relative( siteDir, @@ -468,12 +463,11 @@ Please set the docs "sidebarPath" field in your config file to: // "last version" is not a very good concept nor api surface function getDefaultLastVersionName(versionNames: string[]) { if (versionNames.length === 1) { - return versionNames[0]; - } else { - return versionNames.filter( - (versionName) => versionName !== CURRENT_VERSION_NAME, - )[0]; + return versionNames[0]!; } + return versionNames.filter( + (versionName) => versionName !== CURRENT_VERSION_NAME, + )[0]!; } function checkVersionsOptions( @@ -491,7 +485,7 @@ function checkVersionsOptions( `Docs option lastVersion: ${options.lastVersion} is invalid. ${availableVersionNamesMsg}`, ); } - const unknownVersionConfigNames = difference( + const unknownVersionConfigNames = _.difference( Object.keys(options.versions), availableVersionNames, ); @@ -509,7 +503,7 @@ function checkVersionsOptions( `Invalid docs option "onlyIncludeVersions": an empty array is not allowed, at least one version is needed.`, ); } - const unknownOnlyIncludeVersionNames = difference( + const unknownOnlyIncludeVersionNames = _.difference( options.onlyIncludeVersions, availableVersionNames, ); @@ -536,17 +530,16 @@ function checkVersionsOptions( * Note: we preserve the order in which versions are provided; * the order of the onlyIncludeVersions array does not matter */ -function filterVersions( +export function filterVersions( versionNamesUnfiltered: string[], options: Pick, -) { +): string[] { if (options.onlyIncludeVersions) { return versionNamesUnfiltered.filter((name) => - (options.onlyIncludeVersions || []).includes(name), + options.onlyIncludeVersions!.includes(name), ); - } else { - return versionNamesUnfiltered; } + return versionNamesUnfiltered; } export async function readVersionsMetadata({ @@ -591,20 +584,10 @@ export async function readVersionsMetadata({ options, }), ); - versionsMetadata.forEach((versionMetadata) => - checkVersionMetadataPaths({versionMetadata, context}), + await Promise.all( + versionsMetadata.map((versionMetadata) => + checkVersionMetadataPaths({versionMetadata, context}), + ), ); return versionsMetadata; } - -// order matter! -// Read in priority the localized path, then the unlocalized one -// We want the localized doc to "override" the unlocalized one -export function getDocsDirPaths( - versionMetadata: Pick< - VersionMetadata, - 'contentPath' | 'contentPathLocalized' - >, -): [string, string] { - return [versionMetadata.contentPathLocalized, versionMetadata.contentPath]; -} diff --git a/packages/docusaurus-plugin-content-pages/package.json b/packages/docusaurus-plugin-content-pages/package.json index 6772e2f1692f..8cc6de8765f0 100644 --- a/packages/docusaurus-plugin-content-pages/package.json +++ b/packages/docusaurus-plugin-content-pages/package.json @@ -1,6 +1,6 @@ { "name": "@docusaurus/plugin-content-pages", - "version": "2.0.0-beta.14", + "version": "2.0.0-beta.18", "description": "Pages plugin for Docusaurus.", "main": "lib/index.js", "types": "src/plugin-content-pages.d.ts", @@ -18,18 +18,17 @@ }, "license": "MIT", "dependencies": { - "@docusaurus/core": "2.0.0-beta.14", - "@docusaurus/mdx-loader": "2.0.0-beta.14", - "@docusaurus/utils": "2.0.0-beta.14", - "@docusaurus/utils-validation": "2.0.0-beta.14", - "fs-extra": "^10.0.0", - "globby": "^11.0.2", + "@docusaurus/core": "2.0.0-beta.18", + "@docusaurus/mdx-loader": "2.0.0-beta.18", + "@docusaurus/utils": "2.0.0-beta.18", + "@docusaurus/utils-validation": "2.0.0-beta.18", + "fs-extra": "^10.0.1", "remark-admonitions": "^1.2.1", "tslib": "^2.3.1", - "webpack": "^5.61.0" + "webpack": "^5.70.0" }, "devDependencies": { - "@docusaurus/types": "2.0.0-beta.14" + "@docusaurus/types": "2.0.0-beta.18" }, "peerDependencies": { "react": "^16.8.4 || ^17.0.0", diff --git a/packages/docusaurus-plugin-content-pages/src/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus-plugin-content-pages/src/__tests__/__snapshots__/index.test.ts.snap new file mode 100644 index 000000000000..818f94da35f5 --- /dev/null +++ b/packages/docusaurus-plugin-content-pages/src/__tests__/__snapshots__/index.test.ts.snap @@ -0,0 +1,105 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`docusaurus-plugin-content-pages loads simple pages 1`] = ` +[ + { + "permalink": "/", + "source": "@site/src/pages/index.js", + "type": "jsx", + }, + { + "permalink": "/typescript", + "source": "@site/src/pages/typescript.tsx", + "type": "jsx", + }, + { + "description": "Markdown index page", + "frontMatter": {}, + "permalink": "/hello/", + "source": "@site/src/pages/hello/index.md", + "title": "Index", + "type": "mdx", + }, + { + "description": "my mdx page", + "frontMatter": { + "description": "my mdx page", + "title": "mdx page", + }, + "permalink": "/hello/mdxPage", + "source": "@site/src/pages/hello/mdxPage.mdx", + "title": "mdx page", + "type": "mdx", + }, + { + "permalink": "/hello/translatedJs", + "source": "@site/src/pages/hello/translatedJs.js", + "type": "jsx", + }, + { + "description": "translated markdown page", + "frontMatter": {}, + "permalink": "/hello/translatedMd", + "source": "@site/src/pages/hello/translatedMd.md", + "title": undefined, + "type": "mdx", + }, + { + "permalink": "/hello/world", + "source": "@site/src/pages/hello/world.js", + "type": "jsx", + }, +] +`; + +exports[`docusaurus-plugin-content-pages loads simple pages with french translations 1`] = ` +[ + { + "permalink": "/", + "source": "@site/src/pages/index.js", + "type": "jsx", + }, + { + "permalink": "/typescript", + "source": "@site/src/pages/typescript.tsx", + "type": "jsx", + }, + { + "description": "Markdown index page", + "frontMatter": {}, + "permalink": "/hello/", + "source": "@site/src/pages/hello/index.md", + "title": "Index", + "type": "mdx", + }, + { + "description": "my mdx page", + "frontMatter": { + "description": "my mdx page", + "title": "mdx page", + }, + "permalink": "/hello/mdxPage", + "source": "@site/src/pages/hello/mdxPage.mdx", + "title": "mdx page", + "type": "mdx", + }, + { + "permalink": "/hello/translatedJs", + "source": "@site/i18n/fr/docusaurus-plugin-content-pages/hello/translatedJs.js", + "type": "jsx", + }, + { + "description": "translated markdown page (fr)", + "frontMatter": {}, + "permalink": "/hello/translatedMd", + "source": "@site/i18n/fr/docusaurus-plugin-content-pages/hello/translatedMd.md", + "title": undefined, + "type": "mdx", + }, + { + "permalink": "/hello/world", + "source": "@site/src/pages/hello/world.js", + "type": "jsx", + }, +] +`; diff --git a/packages/docusaurus-plugin-content-pages/src/__tests__/index.test.ts b/packages/docusaurus-plugin-content-pages/src/__tests__/index.test.ts index db1979436e7e..89221c819bc8 100644 --- a/packages/docusaurus-plugin-content-pages/src/__tests__/index.test.ts +++ b/packages/docusaurus-plugin-content-pages/src/__tests__/index.test.ts @@ -9,86 +9,30 @@ import path from 'path'; import {loadContext} from '@docusaurus/core/lib/server'; import pluginContentPages from '../index'; -import {PluginOptionSchema} from '../pluginOptionSchema'; +import {validateOptions} from '../options'; +import {normalizePluginOptions} from '@docusaurus/utils-validation'; describe('docusaurus-plugin-content-pages', () => { - test('simple pages', async () => { + it('loads simple pages', async () => { const siteDir = path.join(__dirname, '__fixtures__', 'website'); - const context = await loadContext(siteDir); - const pluginPath = 'src/pages'; + const context = await loadContext({siteDir}); const plugin = await pluginContentPages( context, - PluginOptionSchema.validate({ - path: pluginPath, - }).value, + validateOptions({ + validate: normalizePluginOptions, + options: { + path: 'src/pages', + }, + }), ); - const pagesMetadata = await plugin.loadContent?.(); + const pagesMetadata = await plugin.loadContent!(); - expect(pagesMetadata).toEqual([ - { - type: 'jsx', - permalink: '/', - source: path.posix.join('@site', pluginPath, 'index.js'), - }, - { - type: 'jsx', - permalink: '/typescript', - source: path.posix.join('@site', pluginPath, 'typescript.tsx'), - }, - { - type: 'mdx', - permalink: '/hello/', - source: path.posix.join('@site', pluginPath, 'hello', 'index.md'), - description: 'Markdown index page', - frontMatter: {}, - title: 'Index', - }, - { - type: 'mdx', - permalink: '/hello/mdxPage', - source: path.posix.join('@site', pluginPath, 'hello', 'mdxPage.mdx'), - description: 'my mdx page', - title: 'mdx page', - frontMatter: { - description: 'my mdx page', - title: 'mdx page', - }, - }, - { - type: 'jsx', - permalink: '/hello/translatedJs', - source: path.posix.join( - '@site', - pluginPath, - 'hello', - 'translatedJs.js', - ), - }, - { - type: 'mdx', - permalink: '/hello/translatedMd', - source: path.posix.join( - '@site', - pluginPath, - 'hello', - 'translatedMd.md', - ), - description: 'translated markdown page', - frontMatter: {}, - title: undefined, - }, - { - type: 'jsx', - permalink: '/hello/world', - source: path.posix.join('@site', pluginPath, 'hello', 'world.js'), - }, - ]); + expect(pagesMetadata).toMatchSnapshot(); }); - test('simple pages with french translations', async () => { + it('loads simple pages with french translations', async () => { const siteDir = path.join(__dirname, '__fixtures__', 'website'); - const context = await loadContext(siteDir); - const pluginPath = 'src/pages'; + const context = await loadContext({siteDir}); const plugin = await pluginContentPages( { ...context, @@ -97,67 +41,15 @@ describe('docusaurus-plugin-content-pages', () => { currentLocale: 'fr', }, }, - PluginOptionSchema.validate({ - path: pluginPath, - }).value, - ); - const pagesMetadata = await plugin.loadContent?.(); - - const frTranslationsPath = path.posix.join( - '@site', - 'i18n', - 'fr', - 'docusaurus-plugin-content-pages', + validateOptions({ + validate: normalizePluginOptions, + options: { + path: 'src/pages', + }, + }), ); + const pagesMetadata = await plugin.loadContent!(); - expect(pagesMetadata).toEqual([ - { - type: 'jsx', - permalink: '/', - source: path.posix.join('@site', pluginPath, 'index.js'), - }, - { - type: 'jsx', - permalink: '/typescript', - source: path.posix.join('@site', pluginPath, 'typescript.tsx'), - }, - { - type: 'mdx', - permalink: '/hello/', - source: path.posix.join('@site', pluginPath, 'hello', 'index.md'), - description: 'Markdown index page', - frontMatter: {}, - title: 'Index', - }, - { - type: 'mdx', - permalink: '/hello/mdxPage', - source: path.posix.join('@site', pluginPath, 'hello', 'mdxPage.mdx'), - description: 'my mdx page', - title: 'mdx page', - frontMatter: { - description: 'my mdx page', - title: 'mdx page', - }, - }, - { - type: 'jsx', - permalink: '/hello/translatedJs', - source: path.posix.join(frTranslationsPath, 'hello', 'translatedJs.js'), - }, - { - type: 'mdx', - permalink: '/hello/translatedMd', - source: path.posix.join(frTranslationsPath, 'hello', 'translatedMd.md'), - description: 'translated markdown page (fr)', - frontMatter: {}, - title: undefined, - }, - { - type: 'jsx', - permalink: '/hello/world', - source: path.posix.join('@site', pluginPath, 'hello', 'world.js'), - }, - ]); + expect(pagesMetadata).toMatchSnapshot(); }); }); diff --git a/packages/docusaurus-plugin-content-pages/src/__tests__/options.test.ts b/packages/docusaurus-plugin-content-pages/src/__tests__/options.test.ts new file mode 100644 index 000000000000..5038527034cb --- /dev/null +++ b/packages/docusaurus-plugin-content-pages/src/__tests__/options.test.ts @@ -0,0 +1,54 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {validateOptions, DEFAULT_OPTIONS} from '../options'; +import {normalizePluginOptions} from '@docusaurus/utils-validation'; +import type {Options} from '@docusaurus/plugin-content-pages'; + +function testValidate(options: Options) { + return validateOptions({validate: normalizePluginOptions, options}); +} + +const defaultOptions = { + ...DEFAULT_OPTIONS, + id: 'default', +}; + +describe('normalizePagesPluginOptions', () => { + it('returns default options for undefined user options', () => { + expect(testValidate({})).toEqual(defaultOptions); + }); + + it('fills in default options for partially defined user options', () => { + expect(testValidate({path: 'src/foo'})).toEqual({ + ...defaultOptions, + path: 'src/foo', + }); + }); + + it('accepts correctly defined user options', () => { + const userOptions = { + path: 'src/my-pages', + routeBasePath: 'my-pages', + include: ['**/*.{js,jsx,ts,tsx}'], + exclude: ['**/$*/'], + }; + expect(testValidate(userOptions)).toEqual({ + ...defaultOptions, + ...userOptions, + }); + }); + + it('rejects bad path inputs', () => { + expect(() => { + testValidate({ + // @ts-expect-error: bad attribute + path: 42, + }); + }).toThrowErrorMatchingInlineSnapshot(`"\\"path\\" must be a string"`); + }); +}); diff --git a/packages/docusaurus-plugin-content-pages/src/__tests__/pluginOptionSchema.test.ts b/packages/docusaurus-plugin-content-pages/src/__tests__/pluginOptionSchema.test.ts deleted file mode 100644 index a891d20dae97..000000000000 --- a/packages/docusaurus-plugin-content-pages/src/__tests__/pluginOptionSchema.test.ts +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import {PluginOptionSchema, DEFAULT_OPTIONS} from '../pluginOptionSchema'; -import type {PluginOptions} from '@docusaurus/plugin-content-pages'; - -function normalizePluginOptions( - options: Partial, -): PluginOptions { - const {value, error} = PluginOptionSchema.validate(options, { - convert: false, - }); - if (error) { - throw error; - } else { - return value; - } -} - -describe('normalizePagesPluginOptions', () => { - test('should return default options for undefined user options', () => { - const value = normalizePluginOptions({}); - expect(value).toEqual(DEFAULT_OPTIONS); - }); - - test('should fill in default options for partially defined user options', () => { - const value = normalizePluginOptions({path: 'src/pages'}); - expect(value).toEqual(DEFAULT_OPTIONS); - }); - - test('should accept correctly defined user options', () => { - const userOptions = { - path: 'src/my-pages', - routeBasePath: 'my-pages', - include: ['**/*.{js,jsx,ts,tsx}'], - exclude: ['**/$*/'], - }; - const value = normalizePluginOptions(userOptions); - expect(value).toEqual({...DEFAULT_OPTIONS, ...userOptions}); - }); - - test('should reject bad path inputs', () => { - expect(() => { - normalizePluginOptions({ - // @ts-expect-error: bad attribute - path: 42, - }); - }).toThrowErrorMatchingInlineSnapshot(`"\\"path\\" must be a string"`); - }); -}); diff --git a/packages/docusaurus-plugin-content-pages/src/deps.d.ts b/packages/docusaurus-plugin-content-pages/src/deps.d.ts index c9976d8a584b..6b8b33906b54 100644 --- a/packages/docusaurus-plugin-content-pages/src/deps.d.ts +++ b/packages/docusaurus-plugin-content-pages/src/deps.d.ts @@ -6,7 +6,7 @@ */ declare module 'remark-admonitions' { - type Options = Record; + type Options = {[key: string]: unknown}; const plugin: (options?: Options) => void; export = plugin; diff --git a/packages/docusaurus-plugin-content-pages/src/pageFrontMatter.ts b/packages/docusaurus-plugin-content-pages/src/frontMatter.ts similarity index 86% rename from packages/docusaurus-plugin-content-pages/src/pageFrontMatter.ts rename to packages/docusaurus-plugin-content-pages/src/frontMatter.ts index 3f5162925594..8c9247da710a 100644 --- a/packages/docusaurus-plugin-content-pages/src/pageFrontMatter.ts +++ b/packages/docusaurus-plugin-content-pages/src/frontMatter.ts @@ -20,8 +20,8 @@ const PageFrontMatterSchema = Joi.object({ ...FrontMatterTOCHeadingLevels, }); -export function validatePageFrontMatter( - frontMatter: Record, -): FrontMatter { +export function validatePageFrontMatter(frontMatter: { + [key: string]: unknown; +}): FrontMatter { return validateFrontMatter(frontMatter, PageFrontMatterSchema); } diff --git a/packages/docusaurus-plugin-content-pages/src/index.ts b/packages/docusaurus-plugin-content-pages/src/index.ts index c553d084446f..c205743e6a00 100644 --- a/packages/docusaurus-plugin-content-pages/src/index.ts +++ b/packages/docusaurus-plugin-content-pages/src/index.ts @@ -21,17 +21,9 @@ import { DEFAULT_PLUGIN_ID, parseMarkdownString, } from '@docusaurus/utils'; -import type { - LoadContext, - Plugin, - OptionValidationContext, - ValidationResult, - ConfigureWebpackUtils, -} from '@docusaurus/types'; -import type {Configuration} from 'webpack'; +import type {LoadContext, Plugin} from '@docusaurus/types'; import admonitions from 'remark-admonitions'; -import {PluginOptionSchema} from './pluginOptionSchema'; -import {validatePageFrontMatter} from './pageFrontMatter'; +import {validatePageFrontMatter} from './frontMatter'; import type {LoadedContent, PagesContentPaths} from './types'; import type {PluginOptions, Metadata} from '@docusaurus/plugin-content-pages'; @@ -49,7 +41,7 @@ export default async function pluginContentPages( ): Promise> { if (options.admonitions) { options.remarkPlugins = options.remarkPlugins.concat([ - [admonitions, options.admonitions || {}], + [admonitions, options.admonitions], ]); } const { @@ -79,7 +71,7 @@ export default async function pluginContentPages( name: 'docusaurus-plugin-content-pages', getPathsToWatch() { - const {include = []} = options; + const {include} = options; return getContentPathList(contentPaths).flatMap((contentPath) => include.map((pattern) => `${contentPath}/${pattern}`), ); @@ -112,29 +104,28 @@ export default async function pluginContentPages( options.routeBasePath, encodePath(fileToPath(relativeSource)), ]); - if (isMarkdownSource(relativeSource)) { - const content = await fs.readFile(source, 'utf-8'); - const { - frontMatter: unsafeFrontMatter, - contentTitle, - excerpt, - } = parseMarkdownString(content); - const frontMatter = validatePageFrontMatter(unsafeFrontMatter); - return { - type: 'mdx', - permalink, - source: aliasedSourcePath, - title: frontMatter.title ?? contentTitle, - description: frontMatter.description ?? excerpt, - frontMatter, - }; - } else { + if (!isMarkdownSource(relativeSource)) { return { type: 'jsx', permalink, source: aliasedSourcePath, }; } + const content = await fs.readFile(source, 'utf-8'); + const { + frontMatter: unsafeFrontMatter, + contentTitle, + excerpt, + } = parseMarkdownString(content); + const frontMatter = validatePageFrontMatter(unsafeFrontMatter); + return { + type: 'mdx', + permalink, + source: aliasedSourcePath, + title: frontMatter.title ?? contentTitle, + description: frontMatter.description ?? excerpt, + frontMatter, + }; } return Promise.all(pagesFiles.map(toMetadata)); @@ -179,11 +170,7 @@ export default async function pluginContentPages( ); }, - configureWebpack( - _config: Configuration, - isServer: boolean, - {getJSLoader}: ConfigureWebpackUtils, - ) { + configureWebpack(config, isServer, {getJSLoader}) { const { rehypePlugins, remarkPlugins, @@ -200,7 +187,7 @@ export default async function pluginContentPages( module: { rules: [ { - test: /(\.mdx?)$/, + test: /\.mdx?$/i, include: contentDirs // Trailing slash is important, see https://github.com/facebook/docusaurus/pull/3970 .map(addTrailingPathSeparator), @@ -248,10 +235,4 @@ export default async function pluginContentPages( }; } -export function validateOptions({ - validate, - options, -}: OptionValidationContext): ValidationResult { - const validatedOptions = validate(PluginOptionSchema, options); - return validatedOptions; -} +export {validateOptions} from './options'; diff --git a/packages/docusaurus-plugin-content-pages/src/markdownLoader.ts b/packages/docusaurus-plugin-content-pages/src/markdownLoader.ts index f6a82dca6c83..e8ca79a21681 100644 --- a/packages/docusaurus-plugin-content-pages/src/markdownLoader.ts +++ b/packages/docusaurus-plugin-content-pages/src/markdownLoader.ts @@ -18,5 +18,5 @@ export default function markdownLoader( // TODO provide additional md processing here? like interlinking pages? // fileString = linkify(fileString) - return callback && callback(null, fileString); + return callback?.(null, fileString); } diff --git a/packages/docusaurus-plugin-content-pages/src/pluginOptionSchema.ts b/packages/docusaurus-plugin-content-pages/src/options.ts similarity index 79% rename from packages/docusaurus-plugin-content-pages/src/pluginOptionSchema.ts rename to packages/docusaurus-plugin-content-pages/src/options.ts index 662916af102c..9713fea810dd 100644 --- a/packages/docusaurus-plugin-content-pages/src/pluginOptionSchema.ts +++ b/packages/docusaurus-plugin-content-pages/src/options.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import type {PluginOptions} from '@docusaurus/plugin-content-pages'; +import type {PluginOptions, Options} from '@docusaurus/plugin-content-pages'; import { Joi, RemarkPluginsSchema, @@ -13,6 +13,7 @@ import { AdmonitionsSchema, } from '@docusaurus/utils-validation'; import {GlobExcludeDefault} from '@docusaurus/utils'; +import type {OptionValidationContext} from '@docusaurus/types'; export const DEFAULT_OPTIONS: PluginOptions = { path: 'src/pages', // Path to data on filesystem, relative to site dir. @@ -27,7 +28,7 @@ export const DEFAULT_OPTIONS: PluginOptions = { admonitions: {}, }; -export const PluginOptionSchema = Joi.object({ +const PluginOptionSchema = Joi.object({ path: Joi.string().default(DEFAULT_OPTIONS.path), routeBasePath: Joi.string().default(DEFAULT_OPTIONS.routeBasePath), include: Joi.array().items(Joi.string()).default(DEFAULT_OPTIONS.include), @@ -43,3 +44,11 @@ export const PluginOptionSchema = Joi.object({ ), admonitions: AdmonitionsSchema.default(DEFAULT_OPTIONS.admonitions), }); + +export function validateOptions({ + validate, + options, +}: OptionValidationContext): PluginOptions { + const validatedOptions = validate(PluginOptionSchema, options); + return validatedOptions; +} diff --git a/packages/docusaurus-plugin-content-pages/src/plugin-content-pages.d.ts b/packages/docusaurus-plugin-content-pages/src/plugin-content-pages.d.ts index e4991dea76ca..24fd413ca1f4 100644 --- a/packages/docusaurus-plugin-content-pages/src/plugin-content-pages.d.ts +++ b/packages/docusaurus-plugin-content-pages/src/plugin-content-pages.d.ts @@ -6,16 +6,16 @@ */ declare module '@docusaurus/plugin-content-pages' { - import type {RemarkAndRehypePluginOptions} from '@docusaurus/mdx-loader'; + import type {MDXOptions} from '@docusaurus/mdx-loader'; - export type PluginOptions = RemarkAndRehypePluginOptions & { + export type PluginOptions = MDXOptions & { id?: string; path: string; routeBasePath: string; include: string[]; exclude: string[]; mdxPageComponent: string; - admonitions: Record; + admonitions: {[key: string]: unknown}; }; export type Options = Partial; @@ -39,7 +39,7 @@ declare module '@docusaurus/plugin-content-pages' { type: 'mdx'; permalink: string; source: string; - frontMatter: FrontMatter & Record; + frontMatter: FrontMatter & {[key: string]: unknown}; title?: string; description?: string; }; @@ -63,6 +63,5 @@ declare module '@theme/MDXPage' { }; } - const MDXPage: (props: Props) => JSX.Element; - export default MDXPage; + export default function MDXPage(props: Props): JSX.Element; } diff --git a/packages/docusaurus-plugin-debug/package.json b/packages/docusaurus-plugin-debug/package.json index 5b2adfba397e..3a99d1b859a3 100644 --- a/packages/docusaurus-plugin-debug/package.json +++ b/packages/docusaurus-plugin-debug/package.json @@ -1,6 +1,6 @@ { "name": "@docusaurus/plugin-debug", - "version": "2.0.0-beta.14", + "version": "2.0.0-beta.18", "description": "Debug plugin for Docusaurus.", "main": "lib/index.js", "types": "src/plugin-debug.d.ts", @@ -18,14 +18,14 @@ }, "license": "MIT", "dependencies": { - "@docusaurus/core": "2.0.0-beta.14", - "@docusaurus/utils": "2.0.0-beta.14", - "fs-extra": "^10.0.0", + "@docusaurus/core": "2.0.0-beta.18", + "@docusaurus/utils": "2.0.0-beta.18", + "fs-extra": "^10.0.1", "react-json-view": "^1.21.3", "tslib": "^2.3.1" }, "devDependencies": { - "@docusaurus/types": "2.0.0-beta.14" + "@docusaurus/types": "2.0.0-beta.18" }, "peerDependencies": { "react": "^16.8.4 || ^17.0.0", diff --git a/packages/docusaurus-plugin-debug/src/index.ts b/packages/docusaurus-plugin-debug/src/index.ts index f85f3ba77238..01a9e0a4e03f 100644 --- a/packages/docusaurus-plugin-debug/src/index.ts +++ b/packages/docusaurus-plugin-debug/src/index.ts @@ -24,11 +24,10 @@ export default function pluginDebug({ name: 'docusaurus-plugin-debug', getThemePath() { - return path.resolve(__dirname, '../lib/theme'); + return '../lib/theme'; }, - getTypeScriptThemePath() { - return path.resolve(__dirname, '../src/theme'); + return '../src/theme'; }, async contentLoaded({actions: {createData, addRoute}, allContent}) { diff --git a/packages/docusaurus-plugin-debug/src/plugin-debug.d.ts b/packages/docusaurus-plugin-debug/src/plugin-debug.d.ts index 890a2b38a86f..93b666dd4f31 100644 --- a/packages/docusaurus-plugin-debug/src/plugin-debug.d.ts +++ b/packages/docusaurus-plugin-debug/src/plugin-debug.d.ts @@ -33,6 +33,8 @@ declare module '@theme/DebugJsonView' { } declare module '@theme/DebugLayout' { + import type {ReactNode} from 'react'; + export default function DebugLayout(props: { children: ReactNode; }): JSX.Element; diff --git a/packages/docusaurus-plugin-debug/src/theme/DebugConfig/index.tsx b/packages/docusaurus-plugin-debug/src/theme/DebugConfig/index.tsx index 7a233a4e38db..373fd530efff 100644 --- a/packages/docusaurus-plugin-debug/src/theme/DebugConfig/index.tsx +++ b/packages/docusaurus-plugin-debug/src/theme/DebugConfig/index.tsx @@ -12,7 +12,7 @@ import DebugJsonView from '@theme/DebugJsonView'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; -function DebugMetadata(): JSX.Element { +export default function DebugMetadata(): JSX.Element { const {siteConfig} = useDocusaurusContext(); return ( @@ -21,5 +21,3 @@ function DebugMetadata(): JSX.Element { ); } - -export default DebugMetadata; diff --git a/packages/docusaurus-plugin-debug/src/theme/DebugContent/index.tsx b/packages/docusaurus-plugin-debug/src/theme/DebugContent/index.tsx index 7c622b6a0124..ceac7dea5d19 100644 --- a/packages/docusaurus-plugin-debug/src/theme/DebugContent/index.tsx +++ b/packages/docusaurus-plugin-debug/src/theme/DebugContent/index.tsx @@ -31,7 +31,7 @@ function PluginContent({ pluginContent, }: { pluginName: string; - pluginContent: Record; + pluginContent: {[pluginId: string]: unknown}; }) { return (
@@ -39,9 +39,7 @@ function PluginContent({
{Object.entries(pluginContent) // filter plugin instances with no content - .filter( - ([_pluginId, pluginInstanceContent]) => !!pluginInstanceContent, - ) + .filter(([, pluginInstanceContent]) => !!pluginInstanceContent) .map(([pluginId, pluginInstanceContent]) => (

Plugin content

{Object.entries(allContent) // filter plugins with no content - .filter(([_pluginName, pluginContent]) => + .filter(([, pluginContent]) => Object.values(pluginContent).some( (instanceContent) => !!instanceContent, ), @@ -77,5 +75,3 @@ function DebugContent({allContent}: Props): JSX.Element { ); } - -export default DebugContent; diff --git a/packages/docusaurus-plugin-debug/src/theme/DebugGlobalData/index.tsx b/packages/docusaurus-plugin-debug/src/theme/DebugGlobalData/index.tsx index 1f75152b9ee9..2a1b28b73fa8 100644 --- a/packages/docusaurus-plugin-debug/src/theme/DebugGlobalData/index.tsx +++ b/packages/docusaurus-plugin-debug/src/theme/DebugGlobalData/index.tsx @@ -11,7 +11,7 @@ import DebugLayout from '@theme/DebugLayout'; import DebugJsonView from '@theme/DebugJsonView'; import useGlobalData from '@docusaurus/useGlobalData'; -function DebugMetadata(): JSX.Element { +export default function DebugMetadata(): JSX.Element { const globalData = useGlobalData(); return ( @@ -20,5 +20,3 @@ function DebugMetadata(): JSX.Element { ); } - -export default DebugMetadata; diff --git a/packages/docusaurus-plugin-debug/src/theme/DebugJsonView/index.tsx b/packages/docusaurus-plugin-debug/src/theme/DebugJsonView/index.tsx index eef8f994e430..783928cb7afc 100644 --- a/packages/docusaurus-plugin-debug/src/theme/DebugJsonView/index.tsx +++ b/packages/docusaurus-plugin-debug/src/theme/DebugJsonView/index.tsx @@ -27,7 +27,10 @@ function BrowserOnlyReactJson(props: ReactJsonViewProps) { ); } -function DebugJsonView({src, collapseDepth}: Props): JSX.Element { +export default function DebugJsonView({ + src, + collapseDepth, +}: Props): JSX.Element { return ( ); } - -export default DebugJsonView; diff --git a/packages/docusaurus-plugin-debug/src/theme/DebugLayout/index.tsx b/packages/docusaurus-plugin-debug/src/theme/DebugLayout/index.tsx index 53447eaaa80f..e8e57fde16de 100644 --- a/packages/docusaurus-plugin-debug/src/theme/DebugLayout/index.tsx +++ b/packages/docusaurus-plugin-debug/src/theme/DebugLayout/index.tsx @@ -25,7 +25,11 @@ function DebugNavLink({to, children}: {to: string; children: ReactNode}) { ); } -function DebugLayout({children}: {children: ReactNode}): JSX.Element { +export default function DebugLayout({ + children, +}: { + children: ReactNode; +}): JSX.Element { return ( <> @@ -53,5 +57,3 @@ function DebugLayout({children}: {children: ReactNode}): JSX.Element { ); } - -export default DebugLayout; diff --git a/packages/docusaurus-plugin-debug/src/theme/DebugRegistry/index.tsx b/packages/docusaurus-plugin-debug/src/theme/DebugRegistry/index.tsx index 24e49ed027ec..0fc809149035 100644 --- a/packages/docusaurus-plugin-debug/src/theme/DebugRegistry/index.tsx +++ b/packages/docusaurus-plugin-debug/src/theme/DebugRegistry/index.tsx @@ -11,7 +11,7 @@ import DebugLayout from '@theme/DebugLayout'; import registry from '@generated/registry'; import styles from './styles.module.css'; -function DebugRegistry(): JSX.Element { +export default function DebugRegistry(): JSX.Element { return (

Registry

@@ -30,5 +30,3 @@ function DebugRegistry(): JSX.Element {
); } - -export default DebugRegistry; diff --git a/packages/docusaurus-plugin-debug/src/theme/DebugRoutes/index.tsx b/packages/docusaurus-plugin-debug/src/theme/DebugRoutes/index.tsx index 0c8f37407ab5..962c9f508b06 100644 --- a/packages/docusaurus-plugin-debug/src/theme/DebugRoutes/index.tsx +++ b/packages/docusaurus-plugin-debug/src/theme/DebugRoutes/index.tsx @@ -12,7 +12,7 @@ import DebugJsonView from '@theme/DebugJsonView'; import routes from '@generated/routes'; import styles from './styles.module.css'; -function DebugRoutes(): JSX.Element { +export default function DebugRoutes(): JSX.Element { return (

Routes

@@ -37,5 +37,3 @@ function DebugRoutes(): JSX.Element {
); } - -export default DebugRoutes; diff --git a/packages/docusaurus-plugin-debug/src/theme/DebugSiteMetadata/index.tsx b/packages/docusaurus-plugin-debug/src/theme/DebugSiteMetadata/index.tsx index b72b4484360e..934e45b97e9c 100644 --- a/packages/docusaurus-plugin-debug/src/theme/DebugSiteMetadata/index.tsx +++ b/packages/docusaurus-plugin-debug/src/theme/DebugSiteMetadata/index.tsx @@ -11,7 +11,7 @@ import DebugLayout from '@theme/DebugLayout'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import styles from './styles.module.css'; -function DebugMetadata(): JSX.Element { +export default function DebugMetadata(): JSX.Element { const {siteMetadata} = useDocusaurusContext(); return ( @@ -43,5 +43,3 @@ function DebugMetadata(): JSX.Element { ); } - -export default DebugMetadata; diff --git a/packages/docusaurus-plugin-google-analytics/package.json b/packages/docusaurus-plugin-google-analytics/package.json index 9593476867f0..7dfbc3578232 100644 --- a/packages/docusaurus-plugin-google-analytics/package.json +++ b/packages/docusaurus-plugin-google-analytics/package.json @@ -1,6 +1,6 @@ { "name": "@docusaurus/plugin-google-analytics", - "version": "2.0.0-beta.14", + "version": "2.0.0-beta.18", "description": "Global analytics (analytics.js) plugin for Docusaurus.", "main": "lib/index.js", "types": "src/plugin-google-analytics.d.ts", @@ -18,12 +18,12 @@ }, "license": "MIT", "dependencies": { - "@docusaurus/core": "2.0.0-beta.14", - "@docusaurus/utils-validation": "2.0.0-beta.14", + "@docusaurus/core": "2.0.0-beta.18", + "@docusaurus/utils-validation": "2.0.0-beta.18", "tslib": "^2.3.1" }, "devDependencies": { - "@docusaurus/types": "2.0.0-beta.14" + "@docusaurus/types": "2.0.0-beta.18" }, "peerDependencies": { "react": "^16.8.4 || ^17.0.0", diff --git a/packages/docusaurus-plugin-google-analytics/src/analytics.ts b/packages/docusaurus-plugin-google-analytics/src/analytics.ts index ae5c6532fed4..e0196d77fbf1 100644 --- a/packages/docusaurus-plugin-google-analytics/src/analytics.ts +++ b/packages/docusaurus-plugin-google-analytics/src/analytics.ts @@ -7,7 +7,7 @@ import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment'; -export default (function () { +export default (function analyticsModule() { if (!ExecutionEnvironment.canUseDOM) { return null; } diff --git a/packages/docusaurus-plugin-google-analytics/src/index.ts b/packages/docusaurus-plugin-google-analytics/src/index.ts index 3abe326da3ce..0219c748f12c 100644 --- a/packages/docusaurus-plugin-google-analytics/src/index.ts +++ b/packages/docusaurus-plugin-google-analytics/src/index.ts @@ -5,17 +5,15 @@ * LICENSE file in the root directory of this source tree. */ -import path from 'path'; import {Joi} from '@docusaurus/utils-validation'; import type { LoadContext, Plugin, OptionValidationContext, - ValidationResult, ThemeConfig, ThemeConfigValidationContext, } from '@docusaurus/types'; -import type {PluginOptions} from '@docusaurus/plugin-google-analytics'; +import type {PluginOptions, Options} from '@docusaurus/plugin-google-analytics'; export default function pluginGoogleAnalytics( context: LoadContext, @@ -28,7 +26,7 @@ export default function pluginGoogleAnalytics( name: 'docusaurus-plugin-google-analytics', getClientModules() { - return isProd ? [path.resolve(__dirname, './analytics')] : []; + return isProd ? ['./analytics'] : []; }, injectHtmlTags() { @@ -75,13 +73,13 @@ const pluginOptionsSchema = Joi.object({ export function validateOptions({ validate, options, -}: OptionValidationContext): ValidationResult { +}: OptionValidationContext): PluginOptions { return validate(pluginOptionsSchema, options); } export function validateThemeConfig({ themeConfig, -}: ThemeConfigValidationContext): ValidationResult { +}: ThemeConfigValidationContext): ThemeConfig { if ('googleAnalytics' in themeConfig) { throw new Error( 'The "googleAnalytics" field in themeConfig should now be specified as option for plugin-google-analytics. More information at https://github.com/facebook/docusaurus/pull/5832.', diff --git a/packages/docusaurus-plugin-google-gtag/package.json b/packages/docusaurus-plugin-google-gtag/package.json index 2616f708cb0a..925a196a9e9e 100644 --- a/packages/docusaurus-plugin-google-gtag/package.json +++ b/packages/docusaurus-plugin-google-gtag/package.json @@ -1,6 +1,6 @@ { "name": "@docusaurus/plugin-google-gtag", - "version": "2.0.0-beta.14", + "version": "2.0.0-beta.18", "description": "Global Site Tag (gtag.js) plugin for Docusaurus.", "main": "lib/index.js", "types": "src/plugin-google-gtag.d.ts", @@ -18,12 +18,12 @@ }, "license": "MIT", "dependencies": { - "@docusaurus/core": "2.0.0-beta.14", - "@docusaurus/utils-validation": "2.0.0-beta.14", + "@docusaurus/core": "2.0.0-beta.18", + "@docusaurus/utils-validation": "2.0.0-beta.18", "tslib": "^2.3.1" }, "devDependencies": { - "@docusaurus/types": "2.0.0-beta.14" + "@docusaurus/types": "2.0.0-beta.18" }, "peerDependencies": { "react": "^16.8.4 || ^17.0.0", diff --git a/packages/docusaurus-plugin-google-gtag/src/gtag.ts b/packages/docusaurus-plugin-google-gtag/src/gtag.ts index 4e07afe4d8eb..77024f7bb5ed 100644 --- a/packages/docusaurus-plugin-google-gtag/src/gtag.ts +++ b/packages/docusaurus-plugin-google-gtag/src/gtag.ts @@ -9,17 +9,18 @@ import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment'; import globalData from '@generated/globalData'; import type {PluginOptions} from '@docusaurus/plugin-google-gtag'; -export default (function () { +export default (function gtagModule() { if (!ExecutionEnvironment.canUseDOM) { return null; } - const {trackingID} = globalData['docusaurus-plugin-google-gtag'] + const {trackingID} = globalData['docusaurus-plugin-google-gtag']! .default as PluginOptions; return { onRouteUpdate({location}: {location: Location}) { - // Always refer to the variable on window in-case it gets overridden elsewhere. + // Always refer to the variable on window in case it gets overridden + // elsewhere. window.gtag('config', trackingID, { page_path: location.pathname, page_title: document.title, diff --git a/packages/docusaurus-plugin-google-gtag/src/index.ts b/packages/docusaurus-plugin-google-gtag/src/index.ts index 5dacf0a1c369..66ed0365e0a9 100644 --- a/packages/docusaurus-plugin-google-gtag/src/index.ts +++ b/packages/docusaurus-plugin-google-gtag/src/index.ts @@ -5,17 +5,15 @@ * LICENSE file in the root directory of this source tree. */ -import path from 'path'; import {Joi} from '@docusaurus/utils-validation'; import type { LoadContext, Plugin, OptionValidationContext, - ValidationResult, ThemeConfig, ThemeConfigValidationContext, } from '@docusaurus/types'; -import type {PluginOptions} from '@docusaurus/plugin-google-gtag'; +import type {PluginOptions, Options} from '@docusaurus/plugin-google-gtag'; export default function pluginGoogleGtag( context: LoadContext, @@ -32,7 +30,7 @@ export default function pluginGoogleGtag( }, getClientModules() { - return isProd ? [path.resolve(__dirname, './gtag')] : []; + return isProd ? ['./gtag'] : []; }, injectHtmlTags() { @@ -40,7 +38,8 @@ export default function pluginGoogleGtag( return {}; } return { - // Gtag includes GA by default, so we also preconnect to google-analytics. + // Gtag includes GA by default, so we also preconnect to + // google-analytics. headTags: [ { tagName: 'link', @@ -88,13 +87,13 @@ const pluginOptionsSchema = Joi.object({ export function validateOptions({ validate, options, -}: OptionValidationContext): ValidationResult { +}: OptionValidationContext): PluginOptions { return validate(pluginOptionsSchema, options); } export function validateThemeConfig({ themeConfig, -}: ThemeConfigValidationContext): ValidationResult { +}: ThemeConfigValidationContext): ThemeConfig { if ('gtag' in themeConfig) { throw new Error( 'The "gtag" field in themeConfig should now be specified as option for plugin-google-gtag. More information at https://github.com/facebook/docusaurus/pull/5832.', diff --git a/packages/docusaurus-plugin-ideal-image/package.json b/packages/docusaurus-plugin-ideal-image/package.json index fee95e8e9356..18044e5c43ad 100644 --- a/packages/docusaurus-plugin-ideal-image/package.json +++ b/packages/docusaurus-plugin-ideal-image/package.json @@ -1,6 +1,6 @@ { "name": "@docusaurus/plugin-ideal-image", - "version": "2.0.0-beta.14", + "version": "2.0.0-beta.18", "description": "Docusaurus Plugin to generate an almost ideal image (responsive, lazy-loading, and low quality placeholder).", "main": "lib/index.js", "types": "src/plugin-ideal-image.d.ts", @@ -21,21 +21,21 @@ }, "license": "MIT", "dependencies": { - "@docusaurus/core": "2.0.0-beta.14", - "@docusaurus/lqip-loader": "2.0.0-beta.14", - "@docusaurus/responsive-loader": "1.5.0", - "@docusaurus/theme-translations": "2.0.0-beta.14", - "@docusaurus/utils-validation": "2.0.0-beta.14", + "@docusaurus/core": "2.0.0-beta.18", + "@docusaurus/lqip-loader": "2.0.0-beta.18", + "@docusaurus/responsive-loader": "^1.7.0", + "@docusaurus/theme-translations": "2.0.0-beta.18", + "@docusaurus/utils-validation": "2.0.0-beta.18", "@endiliey/react-ideal-image": "^0.0.11", "react-waypoint": "^10.1.0", - "sharp": "^0.29.1", + "sharp": "^0.30.3", "tslib": "^2.3.1", - "webpack": "^5.61.0" + "webpack": "^5.70.0" }, "devDependencies": { - "@docusaurus/module-type-aliases": "2.0.0-beta.14", - "@docusaurus/types": "2.0.0-beta.14", - "fs-extra": "^10.0.0" + "@docusaurus/module-type-aliases": "2.0.0-beta.18", + "@docusaurus/types": "2.0.0-beta.18", + "fs-extra": "^10.0.1" }, "peerDependencies": { "jimp": "*", diff --git a/packages/docusaurus-plugin-ideal-image/src/deps.d.ts b/packages/docusaurus-plugin-ideal-image/src/deps.d.ts index b77a092e3712..769d0561cc60 100644 --- a/packages/docusaurus-plugin-ideal-image/src/deps.d.ts +++ b/packages/docusaurus-plugin-ideal-image/src/deps.d.ts @@ -9,9 +9,12 @@ /** * @see https://github.com/endiliey/react-ideal-image/blob/master/index.d.ts - * Note: the original type definition is WRONG. getIcon & getMessage receive full state object. + * Note: the original type definition is WRONG. getIcon & getMessage receive + * full state object. */ declare module '@endiliey/react-ideal-image' { + import type {ComponentProps, ComponentType, CSSProperties} from 'react'; + export type LoadingState = 'initial' | 'loading' | 'loaded' | 'error'; export type State = { @@ -39,19 +42,21 @@ declare module '@endiliey/react-ideal-image' { type ThemeKey = 'placeholder' | 'img' | 'icon' | 'noscript'; - export interface ImageProps { + export interface ImageProps extends ComponentProps<'img'> { /** - * This function decides what icon to show based on the current state of the component. + * This function decides what icon to show based on the current state of the + * component. */ getIcon?: (state: State) => IconKey; /** - * This function decides what message to show based on the icon (returned from getIcon prop) and - * the current state of the component. + * This function decides what message to show based on the icon (returned + * from `getIcon` prop) and the current state of the component. */ - getMessage?: (icon: IconKey, state: State) => string; + getMessage?: (icon: IconKey, state: State) => string | null; /** - * This function is called as soon as the component enters the viewport and is used to generate urls - * based on width and format if props.srcSet doesn't provide src field. + * This function is called as soon as the component enters the viewport and + * is used to generate urls based on width and format if `props.srcSet` + * doesn't provide `src` field. */ getUrl?: (srcType: SrcType) => string; /** @@ -59,10 +64,11 @@ declare module '@endiliey/react-ideal-image' { */ height: number; /** - * This provides a map of the icons. By default, the component uses icons from material design, - * implemented as React components with the SVG element. You can customize icons + * This provides a map of the icons. By default, the component uses icons + * from material design, Implemented as React components with the SVG + * element. You can customize icons */ - icons: Partial>; + icons?: Partial<{[icon in IconKey]: ComponentType}>; /** * This prop takes one of the 2 options, xhr and image. * Read more about it: @@ -74,9 +80,10 @@ declare module '@endiliey/react-ideal-image' { */ placeholder: {color: string} | {lqip: string}; /** - * This function decides if image should be downloaded automatically. The default function - * returns false for a 2g network, for a 3g network it decides based on props.threshold - * and for a 4g network it returns true by default. + * This function decides if image should be downloaded automatically. The + * default function returns false for a 2g network, for a 3g network it + * decides based on `props.threshold` and for a 4g network it returns true + * by default. */ shouldAutoDownload?: (options: { connection?: 'slow-2g' | '2g' | '3g' | '4g'; @@ -85,18 +92,20 @@ declare module '@endiliey/react-ideal-image' { possiblySlowNetwork?: boolean; }) => boolean; /** - * This provides an array of sources of different format and size of the image. - * Read more about it: + * This provides an array of sources of different format and size of the + * image. Read more about it: * https://github.com/stereobooster/react-ideal-image/blob/master/introduction.md#srcset */ srcSet: SrcType[]; /** - * This provides a theme to the component. By default, the component uses inline styles, - * but it is also possible to use CSS modules and override all styles. + * This provides a theme to the component. By default, the component uses + * inline styles, but it is also possible to use CSS modules and override + * all styles. */ - theme?: Partial>; + theme?: Partial<{[key in ThemeKey]: string | CSSProperties}>; /** - * Tells how much to wait in milliseconds until consider the download to be slow. + * Tells how much to wait in milliseconds until consider the download to be + * slow. */ threshold?: number; /** @@ -105,8 +114,5 @@ declare module '@endiliey/react-ideal-image' { width: number; } - type IdealImageComponent = ComponentClass; - - declare const IdealImage: IdealImageComponent; - export default IdealImage; + export default function IdealImage(props: ImageProps): JSX.Element; } diff --git a/packages/docusaurus-plugin-ideal-image/src/index.ts b/packages/docusaurus-plugin-ideal-image/src/index.ts index 9b6630e14cdc..4ea9fa4675c7 100644 --- a/packages/docusaurus-plugin-ideal-image/src/index.ts +++ b/packages/docusaurus-plugin-ideal-image/src/index.ts @@ -9,15 +9,11 @@ import type { LoadContext, Plugin, OptionValidationContext, - ValidationResult, } from '@docusaurus/types'; import type {PluginOptions} from '@docusaurus/plugin-ideal-image'; -import type {Configuration} from 'webpack'; import {Joi} from '@docusaurus/utils-validation'; import {readDefaultCodeTranslationMessages} from '@docusaurus/theme-translations'; -import path from 'path'; - export default function pluginIdealImage( context: LoadContext, options: PluginOptions, @@ -30,11 +26,11 @@ export default function pluginIdealImage( name: 'docusaurus-plugin-ideal-image', getThemePath() { - return path.resolve(__dirname, '../lib/theme'); + return '../lib/theme'; }, getTypeScriptThemePath() { - return path.resolve(__dirname, '../src/theme'); + return '../src/theme'; }, getDefaultCodeTranslationMessages() { @@ -44,7 +40,7 @@ export default function pluginIdealImage( }); }, - configureWebpack(_config: Configuration, isServer: boolean) { + configureWebpack(_config, isServer) { const {disableInDev, ...loaderOptions} = options; if (disableInDev && process.env.NODE_ENV !== 'production') { return {}; @@ -57,7 +53,7 @@ export default function pluginIdealImage( module: { rules: [ { - test: /\.(png|jpe?g|gif)$/i, + test: /\.(?:png|jpe?g)$/i, use: [ require.resolve('@docusaurus/lqip-loader'), { @@ -82,7 +78,7 @@ export default function pluginIdealImage( export function validateOptions({ validate, options, -}: OptionValidationContext): ValidationResult { +}: OptionValidationContext): PluginOptions { const pluginOptionsSchema = Joi.object({ disableInDev: Joi.boolean().default(true), }).unknown(); diff --git a/packages/docusaurus-plugin-ideal-image/src/plugin-ideal-image.d.ts b/packages/docusaurus-plugin-ideal-image/src/plugin-ideal-image.d.ts index b992324e6df3..0c151f88cbf9 100644 --- a/packages/docusaurus-plugin-ideal-image/src/plugin-ideal-image.d.ts +++ b/packages/docusaurus-plugin-ideal-image/src/plugin-ideal-image.d.ts @@ -12,23 +12,29 @@ declare module '@docusaurus/plugin-ideal-image' { */ name?: string; /** - * Specify all widths you want to use; if a specified size exceeds the original image's width, the latter will be used (i.e. images won't be scaled up). You may also declare a default sizes array in the loader options in your webpack.config.js. + * Specify all widths you want to use; if a specified size exceeds the + * original image's width, the latter will be used (i.e. images won't be + * scaled up). */ sizes?: number[]; /** - * Specify one width you want to use; if the specified size exceeds the original image's width, the latter will be used (i.e. images won't be scaled up) + * Specify one width you want to use; if the specified size exceeds the + * original image's width, the latter will be used (i.e. images won't be + * scaled up) */ size?: number; /** - * As an alternative to manually specifying sizes, you can specify min, max and steps, and the sizes will be generated for you. + * As an alternative to manually specifying `sizes`, you can specify `min`, + * `max` and `steps`, and the `sizes` will be generated for you. */ min?: number; /** - * See min above + * @see {@link PluginOptions.min} */ max?: number; /** - * Configure the number of images generated between min and max (inclusive) + * Configure the number of images generated between `min` and `max` + * (inclusive) */ steps?: number; /** @@ -36,7 +42,8 @@ declare module '@docusaurus/plugin-ideal-image' { */ quality?: number; /** - * Just use regular images in dev mode + * You can test ideal image behavior in dev mode by setting this to `false`. + * Tip: use network throttling in your browser to simulate slow networks. */ disableInDev?: boolean; }; @@ -60,8 +67,8 @@ declare module '@theme/IdealImage' { images: SrcType[]; }; - export type Props = ComponentProps<'img'> & { - img: {default: string} | {src: SrcImage; preSrc: string} | string; - }; + export interface Props extends ComponentProps<'img'> { + readonly img: {default: string} | {src: SrcImage; preSrc: string} | string; + } export default function IdealImage(props: Props): JSX.Element; } diff --git a/packages/docusaurus-plugin-ideal-image/src/theme/IdealImage/index.tsx b/packages/docusaurus-plugin-ideal-image/src/theme/IdealImage/index.tsx index 60d4e323f9fb..6bc259b2b1ba 100644 --- a/packages/docusaurus-plugin-ideal-image/src/theme/IdealImage/index.tsx +++ b/packages/docusaurus-plugin-ideal-image/src/theme/IdealImage/index.tsx @@ -15,7 +15,7 @@ import {translate} from '@docusaurus/Translate'; import type {Props} from '@theme/IdealImage'; // Adopted from https://github.com/endiliey/react-ideal-image/blob/master/src/components/helpers.js#L59-L65 -const bytesToSize = (bytes: number) => { +function bytesToSize(bytes: number) { const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; if (bytes === 0) { return 'n/a'; @@ -25,10 +25,10 @@ const bytesToSize = (bytes: number) => { return `${bytes} ${sizes[scale]}`; } return `${(bytes / 1024 ** scale).toFixed(1)} ${sizes[scale]}`; -}; +} // Adopted from https://github.com/endiliey/react-ideal-image/blob/master/src/components/IdealImage/index.js#L43-L75 -const getMessage = (icon: IconKey, state: State) => { +function getMessage(icon: IconKey, state: State) { switch (icon) { case 'noicon': case 'loaded': @@ -68,20 +68,19 @@ const getMessage = (icon: IconKey, state: State) => { message: '404. Image not found', description: 'When the image is not found', }); - } else { - return translate({ - id: 'theme.IdealImageMessage.error', - message: 'Error. Click to reload', - description: 'When the image fails to load for unknown error', - }); } + return translate({ + id: 'theme.IdealImageMessage.error', + message: 'Error. Click to reload', + description: 'When the image fails to load for unknown error', + }); } default: throw new Error(`Wrong icon: ${icon}`); } -}; +} -function IdealImage(props: Props): JSX.Element { +export default function IdealImage(props: Props): JSX.Element { const {alt, className, img} = props; // In dev env just use regular img with original file @@ -113,5 +112,3 @@ function IdealImage(props: Props): JSX.Element { /> ); } - -export default IdealImage; diff --git a/packages/docusaurus-plugin-pwa/package.json b/packages/docusaurus-plugin-pwa/package.json index 50a603c33a1a..3150d85f7df8 100644 --- a/packages/docusaurus-plugin-pwa/package.json +++ b/packages/docusaurus-plugin-pwa/package.json @@ -1,6 +1,6 @@ { "name": "@docusaurus/plugin-pwa", - "version": "2.0.0-beta.14", + "version": "2.0.0-beta.18", "description": "Docusaurus Plugin to add PWA support.", "main": "lib/index.js", "types": "src/plugin-pwa.d.ts", @@ -20,31 +20,29 @@ }, "license": "MIT", "dependencies": { - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.0", - "@babel/plugin-proposal-optional-chaining": "^7.16.0", - "@babel/preset-env": "^7.16.4", - "@docusaurus/core": "2.0.0-beta.14", - "@docusaurus/theme-common": "2.0.0-beta.14", - "@docusaurus/theme-translations": "2.0.0-beta.14", - "@docusaurus/utils": "2.0.0-beta.14", - "@docusaurus/utils-validation": "2.0.0-beta.14", - "babel-loader": "^8.2.2", + "@babel/core": "^7.17.8", + "@babel/preset-env": "^7.16.11", + "@docusaurus/core": "2.0.0-beta.18", + "@docusaurus/theme-common": "2.0.0-beta.18", + "@docusaurus/theme-translations": "2.0.0-beta.18", + "@docusaurus/utils": "2.0.0-beta.18", + "@docusaurus/utils-validation": "2.0.0-beta.18", + "babel-loader": "^8.2.4", "clsx": "^1.1.1", - "core-js": "^3.18.0", - "terser-webpack-plugin": "^5.2.4", + "core-js": "^3.21.1", + "terser-webpack-plugin": "^5.3.1", "tslib": "^2.3.1", - "webpack": "^5.61.0", - "webpack-merge": "^5.7.3", - "workbox-build": "^6.1.1", - "workbox-precaching": "^6.1.1", - "workbox-window": "^6.1.1" + "webpack": "^5.70.0", + "webpack-merge": "^5.8.0", + "workbox-build": "^6.5.2", + "workbox-precaching": "^6.5.2", + "workbox-window": "^6.5.2" }, "devDependencies": { - "@docusaurus/module-type-aliases": "2.0.0-beta.14", - "fs-extra": "^10.0.0" + "@docusaurus/module-type-aliases": "2.0.0-beta.18", + "fs-extra": "^10.0.1" }, "peerDependencies": { - "@babel/core": "^7.0.0", "react": "^16.8.4 || ^17.0.0", "react-dom": "^16.8.4 || ^17.0.0" } diff --git a/packages/docusaurus-plugin-pwa/src/index.ts b/packages/docusaurus-plugin-pwa/src/index.ts index 64ee7bc02d5c..044f05c5492d 100644 --- a/packages/docusaurus-plugin-pwa/src/index.ts +++ b/packages/docusaurus-plugin-pwa/src/index.ts @@ -37,16 +37,11 @@ function getSWBabelLoader() { }, ], ], - plugins: [ - require.resolve('@babel/plugin-proposal-object-rest-spread'), - require.resolve('@babel/plugin-proposal-optional-chaining'), - require.resolve('@babel/plugin-proposal-nullish-coalescing-operator'), - ], }, }; } -export default function ( +export default function pluginPWA( context: LoadContext, options: PluginOptions, ): Plugin { @@ -69,11 +64,14 @@ export default function ( name: 'docusaurus-plugin-pwa', getThemePath() { - return path.resolve(__dirname, './theme'); + return '../lib/theme'; + }, + getTypeScriptThemePath() { + return '../src/theme'; }, getClientModules() { - return isProd ? [swRegister] : []; + return isProd && swRegister ? [swRegister] : []; }, getDefaultCodeTranslationMessages() { @@ -140,7 +138,7 @@ export default function ( const swSourceFileTest = /\.m?js$/; const swWebpackConfig: Configuration = { - entry: path.resolve(__dirname, 'sw.js'), + entry: require.resolve('./sw.js'), output: { path: outDir, filename: 'sw.js', @@ -174,7 +172,7 @@ export default function ( rules: [ { test: swSourceFileTest, - exclude: /(node_modules)/, + exclude: /node_modules/, use: getSWBabelLoader(), }, ], @@ -191,9 +189,8 @@ export default function ( '**/*.{js,json,css,html}', '**/*.{png,jpg,jpeg,gif,svg,ico}', '**/*.{woff,woff2,eot,ttf,otf}', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - ...(injectManifest.globPatterns || []), + // @ts-expect-error: internal API? + ...(injectManifest.globPatterns ?? []), ], // those attributes are not overrideable swDest, diff --git a/packages/docusaurus-plugin-pwa/src/options.ts b/packages/docusaurus-plugin-pwa/src/options.ts index ae5242b0b6c5..e96405756ad1 100644 --- a/packages/docusaurus-plugin-pwa/src/options.ts +++ b/packages/docusaurus-plugin-pwa/src/options.ts @@ -5,13 +5,8 @@ * LICENSE file in the root directory of this source tree. */ -import path from 'path'; import {Joi} from '@docusaurus/utils-validation'; -import type { - ThemeConfig, - ValidationResult, - OptionValidationContext, -} from '@docusaurus/types'; +import type {OptionValidationContext} from '@docusaurus/types'; import type {PluginOptions} from '@docusaurus/plugin-pwa'; const DEFAULT_OPTIONS = { @@ -24,11 +19,11 @@ const DEFAULT_OPTIONS = { injectManifestConfig: {}, pwaHead: [], swCustom: undefined, - swRegister: path.join(__dirname, 'registerSw.js'), + swRegister: './registerSw.js', reloadPopup: '@theme/PwaReloadPopup', }; -export const Schema = Joi.object({ +const Schema = Joi.object({ debug: Joi.bool().default(DEFAULT_OPTIONS.debug), offlineModeActivationStrategies: Joi.array() .items( @@ -62,6 +57,6 @@ export const Schema = Joi.object({ export function validateOptions({ validate, options, -}: OptionValidationContext): ValidationResult { +}: OptionValidationContext): PluginOptions { return validate(Schema, options); } diff --git a/packages/docusaurus-plugin-pwa/src/plugin-pwa.d.ts b/packages/docusaurus-plugin-pwa/src/plugin-pwa.d.ts index 86786b48f185..1e0128331039 100644 --- a/packages/docusaurus-plugin-pwa/src/plugin-pwa.d.ts +++ b/packages/docusaurus-plugin-pwa/src/plugin-pwa.d.ts @@ -6,29 +6,91 @@ */ declare module '@docusaurus/plugin-pwa' { - export type pwaHead = { - tagName: string; - href?: string; - content?: string; - [attributeName: string]: string | boolean; - }; + import type {InjectManifestOptions} from 'workbox-build'; export type PluginOptions = { + /** + * Turn debug mode on: + * + * - Workbox logs + * - Additional Docusaurus logs + * - Unoptimized SW file output + * - Source maps + */ debug?: boolean; - offlineModeActivationStrategies; - injectManifestConfig; - reloadPopup; - pwaHead: pwaHead[]; - swCustom; - swRegister; + /** + * Strategies used to turn the offline mode on: + * + * - `appInstalled`: activates for users having installed the site as an app + * (not 100% reliable) + * - `standalone`: activates for users running the app as standalone (often + * the case once a PWA is installed) + * - `queryString`: activates if queryString contains `offlineMode=true` + * (convenient for PWA debugging) + * - `mobile`: activates for mobile users (width <= 940px) + * - `saveData`: activates for users with `navigator.connection.saveData === + * true` + * - `always`: activates for all users + */ + offlineModeActivationStrategies: ( + | 'appInstalled' + | 'queryString' + | 'standalone' + | 'mobile' + | 'saveData' + | 'always' + )[]; + /** + * Workbox options to pass to `workbox.injectManifest()`. This gives you + * control over which assets will be precached, and be available offline. + * @see https://developers.google.com/web/tools/workbox/reference-docs/latest/module-workbox-build#.injectManifest + */ + injectManifestConfig: InjectManifestOptions; + /** + * Module path to reload popup component. This popup is rendered when a new + * service worker is waiting to be installed, and we suggest a reload to + * the user. + * + * Passing `false` will disable the popup, but this is not recommended: + * users won't have a way to get up-to-date content. + * @see {@link @theme/PwaReloadPopup} + */ + reloadPopup: string | false; + /** + * Array of objects containing `tagName` and key-value pairs for attributes + * to inject into the `` tag. Technically you can inject any head tag + * through this, but it's ideally used for tags to make your site PWA- + * compliant. + */ + pwaHead: { + tagName: string; + href?: string; + content?: string; + [attributeName: string]: string | boolean; + }[]; + /** + * Useful for additional Workbox rules. You can do whatever a service worker + * can do here, and use the full power of workbox libraries. The code is + * transpiled, so you can use modern ES6+ syntax here. + */ + swCustom?: string; + /** + * Adds an entry before the Docusaurus app so that registration can happen + * before the app runs. The default `registerSW.js` file is enough for + * simple registration. Passing `false` will disable registration entirely. + */ + swRegister: string | false; }; } declare module '@theme/PwaReloadPopup' { - export type PwaReloadPopupProps = { + export interface Props { + /** + * The popup should call this callback when the `reload` button is clicked. + * This will tell the service worker to install the waiting service worker + * and reload the page. + */ readonly onReload: () => void; - }; - - const PwaReloadPopup: (props: PwaReloadPopupProps) => JSX.Element; - export default PwaReloadPopup; + } + export default function PwaReloadPopup(props: Props): JSX.Element; } diff --git a/packages/docusaurus-plugin-pwa/src/registerSw.js b/packages/docusaurus-plugin-pwa/src/registerSw.js index d46bf6cdcc2b..36fc55c5a857 100644 --- a/packages/docusaurus-plugin-pwa/src/registerSw.js +++ b/packages/docusaurus-plugin-pwa/src/registerSw.js @@ -79,7 +79,7 @@ function isStandaloneDisplayMode() { const OfflineModeActivationStrategiesImplementations = { always: () => true, mobile: () => window.innerWidth <= MAX_MOBILE_WIDTH, - saveData: () => !!(navigator.connection && navigator.connection.saveData), + saveData: () => !!navigator.connection?.saveData, appInstalled: async () => { const installedEventFired = await isAppInstalledEventFired(); const installedRelatedApps = await isAppInstalledRelatedApps(); @@ -219,8 +219,8 @@ async function registerSW() { } } -// TODO these events still works in chrome but have been removed from the spec in 2019! -// See https://github.com/w3c/manifest/pull/836 +// TODO these events still works in chrome but have been removed from the spec +// in 2019! See https://github.com/w3c/manifest/pull/836 function addLegacyAppInstalledEventsListeners() { if (typeof window !== 'undefined') { if (debug) { @@ -248,7 +248,8 @@ function addLegacyAppInstalledEventsListeners() { await clearRegistrations(); }); - // TODO this event still works in chrome but has been removed from the spec in 2019!!! + // TODO this event still works in chrome but has been removed from the spec + // in 2019!!! window.addEventListener('beforeinstallprompt', async (event) => { if (debug) { console.log( @@ -256,7 +257,8 @@ function addLegacyAppInstalledEventsListeners() { event, ); } - // TODO instead of default browser install UI, show custom docusaurus prompt? + // TODO instead of default browser install UI, show custom docusaurus + // prompt? // event.preventDefault(); if (debug) { console.log( @@ -273,7 +275,7 @@ function addLegacyAppInstalledEventsListeners() { } // After uninstalling the app, if the user doesn't clear all data, then // the previous service worker will continue serving cached files. We - // need to clear registrations and reload, otherwise the popup will show. + // need to clear registrations and reload, otherwise the popup shows. await clearRegistrations(); } }); diff --git a/packages/docusaurus-plugin-pwa/src/sw.js b/packages/docusaurus-plugin-pwa/src/sw.js index 4834f2bcf074..6c30f239616f 100644 --- a/packages/docusaurus-plugin-pwa/src/sw.js +++ b/packages/docusaurus-plugin-pwa/src/sw.js @@ -18,10 +18,10 @@ function parseSwParams() { return params; } -// doc advise against dynamic imports in SW +// doc advises against dynamic imports in SW // https://developers.google.com/web/tools/workbox/guides/using-bundlers#code_splitting_and_dynamic_imports // https://twitter.com/sebastienlorber/status/1280155204575518720 -// but I think it's working fine as it's inlined by webpack, need to double check? +// but looks it's working fine as it's inlined by webpack, need to double check async function runSWCustomCode(params) { if (process.env.PWA_SW_CUSTOM) { const customSW = await import(process.env.PWA_SW_CUSTOM); @@ -70,6 +70,7 @@ function getPossibleURLs(url) { (async () => { const params = parseSwParams(); + // eslint-disable-next-line no-underscore-dangle const precacheManifest = self.__WB_MANIFEST; const controller = new PrecacheController({ fallbackToNetwork: true, // safer to turn this true? @@ -136,7 +137,7 @@ function getPossibleURLs(url) { }); } - const type = event.data && event.data.type; + const type = event.data?.type; if (type === 'SKIP_WAITING') { self.skipWaiting(); diff --git a/packages/docusaurus-plugin-pwa/src/theme/PwaReloadPopup/index.tsx b/packages/docusaurus-plugin-pwa/src/theme/PwaReloadPopup/index.tsx index 5938859679fb..20e8490fb82b 100644 --- a/packages/docusaurus-plugin-pwa/src/theme/PwaReloadPopup/index.tsx +++ b/packages/docusaurus-plugin-pwa/src/theme/PwaReloadPopup/index.tsx @@ -9,11 +9,11 @@ import React, {useState} from 'react'; import clsx from 'clsx'; import Translate, {translate} from '@docusaurus/Translate'; -import type {PwaReloadPopupProps} from '@theme/PwaReloadPopup'; +import type {Props} from '@theme/PwaReloadPopup'; import styles from './styles.module.css'; -function PwaReloadPopup({onReload}: PwaReloadPopupProps): JSX.Element | false { +export default function PwaReloadPopup({onReload}: Props): JSX.Element | false { const [isVisible, setIsVisible] = useState(true); return ( @@ -58,5 +58,3 @@ function PwaReloadPopup({onReload}: PwaReloadPopupProps): JSX.Element | false { ) ); } - -export default PwaReloadPopup; diff --git a/packages/docusaurus-plugin-sitemap/package.json b/packages/docusaurus-plugin-sitemap/package.json index c3f3b1115019..9eb611ed0126 100644 --- a/packages/docusaurus-plugin-sitemap/package.json +++ b/packages/docusaurus-plugin-sitemap/package.json @@ -1,6 +1,6 @@ { "name": "@docusaurus/plugin-sitemap", - "version": "2.0.0-beta.14", + "version": "2.0.0-beta.18", "description": "Simple sitemap generation plugin for Docusaurus.", "main": "lib/index.js", "types": "src/plugin-sitemap.d.ts", @@ -18,16 +18,16 @@ }, "license": "MIT", "dependencies": { - "@docusaurus/core": "2.0.0-beta.14", - "@docusaurus/utils": "2.0.0-beta.14", - "@docusaurus/utils-common": "2.0.0-beta.14", - "@docusaurus/utils-validation": "2.0.0-beta.14", - "fs-extra": "^10.0.0", - "sitemap": "^7.0.0", + "@docusaurus/core": "2.0.0-beta.18", + "@docusaurus/utils": "2.0.0-beta.18", + "@docusaurus/utils-common": "2.0.0-beta.18", + "@docusaurus/utils-validation": "2.0.0-beta.18", + "fs-extra": "^10.0.1", + "sitemap": "^7.1.1", "tslib": "^2.3.1" }, "devDependencies": { - "@docusaurus/types": "2.0.0-beta.14" + "@docusaurus/types": "2.0.0-beta.18" }, "peerDependencies": { "react": "^16.8.4 || ^17.0.0", diff --git a/packages/docusaurus-plugin-sitemap/src/__tests__/createSitemap.test.ts b/packages/docusaurus-plugin-sitemap/src/__tests__/createSitemap.test.ts index 36edf71a3411..c041bff79b6b 100644 --- a/packages/docusaurus-plugin-sitemap/src/__tests__/createSitemap.test.ts +++ b/packages/docusaurus-plugin-sitemap/src/__tests__/createSitemap.test.ts @@ -10,7 +10,7 @@ import type {DocusaurusConfig} from '@docusaurus/types'; import {EnumChangefreq} from 'sitemap'; describe('createSitemap', () => { - test('simple site', async () => { + it('simple site', async () => { const sitemap = await createSitemap( { url: 'https://example.com', @@ -26,19 +26,19 @@ describe('createSitemap', () => { ); }); - test('empty site', () => + it('empty site', () => expect(async () => { await createSitemap({} as DocusaurusConfig, [], {}); }).rejects.toThrow( 'URL in docusaurus.config.js cannot be empty/undefined.', )); - test('exclusion of 404 page', async () => { + it('exclusion of 404 page', async () => { const sitemap = await createSitemap( { url: 'https://example.com', } as DocusaurusConfig, - ['/', '/404.html', '/mypage'], + ['/', '/404.html', '/my-page'], { changefreq: EnumChangefreq.DAILY, priority: 0.7, @@ -47,7 +47,7 @@ describe('createSitemap', () => { expect(sitemap).not.toContain('404'); }); - test('keep trailing slash unchanged', async () => { + it('keep trailing slash unchanged', async () => { const sitemap = await createSitemap( { url: 'https://example.com', @@ -66,7 +66,7 @@ describe('createSitemap', () => { expect(sitemap).toContain('https://example.com/nested/test2/'); }); - test('add trailing slash', async () => { + it('add trailing slash', async () => { const sitemap = await createSitemap( { url: 'https://example.com', @@ -85,7 +85,7 @@ describe('createSitemap', () => { expect(sitemap).toContain('https://example.com/nested/test2/'); }); - test('remove trailing slash', async () => { + it('remove trailing slash', async () => { const sitemap = await createSitemap( { url: 'https://example.com', diff --git a/packages/docusaurus-plugin-sitemap/src/__tests__/options.test.ts b/packages/docusaurus-plugin-sitemap/src/__tests__/options.test.ts new file mode 100644 index 000000000000..e53a2adc6ba9 --- /dev/null +++ b/packages/docusaurus-plugin-sitemap/src/__tests__/options.test.ts @@ -0,0 +1,52 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {validateOptions, DEFAULT_OPTIONS} from '../options'; +import {normalizePluginOptions} from '@docusaurus/utils-validation'; +import type {Options} from '@docusaurus/plugin-sitemap'; + +function testValidate(options: Options) { + return validateOptions({validate: normalizePluginOptions, options}); +} + +const defaultOptions = { + ...DEFAULT_OPTIONS, + id: 'default', +}; + +describe('validateOptions', () => { + it('returns default values for empty user options', () => { + expect(testValidate({})).toEqual(defaultOptions); + }); + + it('accepts correctly defined user options', () => { + const userOptions = { + changefreq: 'yearly', + priority: 0.9, + }; + expect(testValidate(userOptions)).toEqual({ + ...defaultOptions, + ...userOptions, + }); + }); + + it('rejects out-of-range priority inputs', () => { + expect(() => + testValidate({priority: 2}), + ).toThrowErrorMatchingInlineSnapshot( + `"\\"priority\\" must be less than or equal to 1"`, + ); + }); + + it('rejects bad changefreq inputs', () => { + expect(() => + testValidate({changefreq: 'annually'}), + ).toThrowErrorMatchingInlineSnapshot( + `"\\"changefreq\\" must be one of [daily, monthly, always, hourly, weekly, yearly, never]"`, + ); + }); +}); diff --git a/packages/docusaurus-plugin-sitemap/src/__tests__/pluginOptionSchema.test.ts b/packages/docusaurus-plugin-sitemap/src/__tests__/pluginOptionSchema.test.ts deleted file mode 100644 index 9ac30edea870..000000000000 --- a/packages/docusaurus-plugin-sitemap/src/__tests__/pluginOptionSchema.test.ts +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import {PluginOptionSchema, DEFAULT_OPTIONS} from '../pluginOptionSchema'; - -function normalizePluginOptions(options) { - const {value, error} = PluginOptionSchema.validate(options, { - convert: false, - }); - if (error) { - throw error; - } else { - return value; - } -} - -describe('normalizeSitemapPluginOptions', () => { - test('should return default values for empty user options', async () => { - const {value} = await PluginOptionSchema.validate({}); - expect(value).toEqual(DEFAULT_OPTIONS); - }); - - test('should accept correctly defined user options', async () => { - const userOptions = { - changefreq: 'yearly', - priority: 0.9, - }; - const {value} = await PluginOptionSchema.validate(userOptions); - expect(value).toEqual(userOptions); - }); - - test('should reject out-of-range priority inputs', () => { - expect(() => { - normalizePluginOptions({ - priority: 2, - }); - }).toThrowErrorMatchingInlineSnapshot( - `"\\"priority\\" must be less than or equal to 1"`, - ); - }); - - test('should reject bad changefreq inputs', () => { - expect(() => { - normalizePluginOptions({ - changefreq: 'annually', - }); - }).toThrowErrorMatchingInlineSnapshot( - `"\\"changefreq\\" must be one of [daily, monthly, always, hourly, weekly, yearly, never]"`, - ); - }); -}); diff --git a/packages/docusaurus-plugin-sitemap/src/index.ts b/packages/docusaurus-plugin-sitemap/src/index.ts index 7533713acadf..0a6651029917 100644 --- a/packages/docusaurus-plugin-sitemap/src/index.ts +++ b/packages/docusaurus-plugin-sitemap/src/index.ts @@ -9,23 +9,16 @@ import fs from 'fs-extra'; import path from 'path'; import type {Options} from '@docusaurus/plugin-sitemap'; import createSitemap from './createSitemap'; -import type { - LoadContext, - Props, - OptionValidationContext, - ValidationResult, - Plugin, -} from '@docusaurus/types'; -import {PluginOptionSchema} from './pluginOptionSchema'; +import type {LoadContext, Plugin} from '@docusaurus/types'; export default function pluginSitemap( - _context: LoadContext, + context: LoadContext, options: Options, ): Plugin { return { name: 'docusaurus-plugin-sitemap', - async postBuild({siteConfig, routesPaths, outDir}: Props) { + async postBuild({siteConfig, routesPaths, outDir}) { if (siteConfig.noIndex) { return; } @@ -47,10 +40,4 @@ export default function pluginSitemap( }; } -export function validateOptions({ - validate, - options, -}: OptionValidationContext): ValidationResult { - const validatedOptions = validate(PluginOptionSchema, options); - return validatedOptions; -} +export {validateOptions} from './options'; diff --git a/packages/docusaurus-plugin-sitemap/src/pluginOptionSchema.ts b/packages/docusaurus-plugin-sitemap/src/options.ts similarity index 71% rename from packages/docusaurus-plugin-sitemap/src/pluginOptionSchema.ts rename to packages/docusaurus-plugin-sitemap/src/options.ts index 77df104d6c27..e7d7972153a5 100644 --- a/packages/docusaurus-plugin-sitemap/src/pluginOptionSchema.ts +++ b/packages/docusaurus-plugin-sitemap/src/options.ts @@ -8,14 +8,14 @@ import {Joi} from '@docusaurus/utils-validation'; import {EnumChangefreq} from 'sitemap'; import type {Options} from '@docusaurus/plugin-sitemap'; +import type {OptionValidationContext} from '@docusaurus/types'; -export const DEFAULT_OPTIONS: Required = { +export const DEFAULT_OPTIONS: Options = { changefreq: EnumChangefreq.WEEKLY, priority: 0.5, }; -export const PluginOptionSchema = Joi.object({ - // TODO temporary (@alpha-71) +const PluginOptionSchema = Joi.object({ cacheTime: Joi.forbidden().messages({ 'any.unknown': 'Option `cacheTime` in sitemap config is deprecated. Please remove it.', @@ -29,3 +29,11 @@ export const PluginOptionSchema = Joi.object({ 'Please use the new Docusaurus global trailingSlash config instead, and the sitemaps plugin will use it.', }), }); + +export function validateOptions({ + validate, + options, +}: OptionValidationContext): Options { + const validatedOptions = validate(PluginOptionSchema, options); + return validatedOptions; +} diff --git a/packages/docusaurus-plugin-sitemap/src/plugin-sitemap.d.ts b/packages/docusaurus-plugin-sitemap/src/plugin-sitemap.d.ts index 0c9b4a826c95..7f16b96a4c94 100644 --- a/packages/docusaurus-plugin-sitemap/src/plugin-sitemap.d.ts +++ b/packages/docusaurus-plugin-sitemap/src/plugin-sitemap.d.ts @@ -8,6 +8,9 @@ import type {EnumChangefreq} from 'sitemap'; export type Options = { + id?: string; + /** @see https://www.sitemaps.org/protocol.html#xmlTagDefinitions */ changefreq?: EnumChangefreq; + /** @see https://www.sitemaps.org/protocol.html#xmlTagDefinitions */ priority?: number; }; diff --git a/packages/docusaurus-preset-classic/README.md b/packages/docusaurus-preset-classic/README.md index dc92363be2bc..721150986b4a 100644 --- a/packages/docusaurus-preset-classic/README.md +++ b/packages/docusaurus-preset-classic/README.md @@ -4,4 +4,4 @@ Classic preset for Docusaurus. ## Usage -See [presets documentation](https://docusaurus.io/docs/presets). +See [presets documentation](https://docusaurus.io/docs/using-plugins#using-presets). diff --git a/packages/docusaurus-preset-classic/package.json b/packages/docusaurus-preset-classic/package.json index 8f582c562708..850c52f9e030 100644 --- a/packages/docusaurus-preset-classic/package.json +++ b/packages/docusaurus-preset-classic/package.json @@ -1,6 +1,6 @@ { "name": "@docusaurus/preset-classic", - "version": "2.0.0-beta.14", + "version": "2.0.0-beta.18", "description": "Classic preset for Docusaurus.", "main": "lib/index.js", "types": "src/preset-classic.d.ts", @@ -18,17 +18,17 @@ }, "license": "MIT", "dependencies": { - "@docusaurus/core": "2.0.0-beta.14", - "@docusaurus/plugin-content-blog": "2.0.0-beta.14", - "@docusaurus/plugin-content-docs": "2.0.0-beta.14", - "@docusaurus/plugin-content-pages": "2.0.0-beta.14", - "@docusaurus/plugin-debug": "2.0.0-beta.14", - "@docusaurus/plugin-google-analytics": "2.0.0-beta.14", - "@docusaurus/plugin-google-gtag": "2.0.0-beta.14", - "@docusaurus/plugin-sitemap": "2.0.0-beta.14", - "@docusaurus/theme-classic": "2.0.0-beta.14", - "@docusaurus/theme-common": "2.0.0-beta.14", - "@docusaurus/theme-search-algolia": "2.0.0-beta.14" + "@docusaurus/core": "2.0.0-beta.18", + "@docusaurus/plugin-content-blog": "2.0.0-beta.18", + "@docusaurus/plugin-content-docs": "2.0.0-beta.18", + "@docusaurus/plugin-content-pages": "2.0.0-beta.18", + "@docusaurus/plugin-debug": "2.0.0-beta.18", + "@docusaurus/plugin-google-analytics": "2.0.0-beta.18", + "@docusaurus/plugin-google-gtag": "2.0.0-beta.18", + "@docusaurus/plugin-sitemap": "2.0.0-beta.18", + "@docusaurus/theme-classic": "2.0.0-beta.18", + "@docusaurus/theme-common": "2.0.0-beta.18", + "@docusaurus/theme-search-algolia": "2.0.0-beta.18" }, "peerDependencies": { "react": "^16.8.4 || ^17.0.0", diff --git a/packages/docusaurus-remark-plugin-npm2yarn/package.json b/packages/docusaurus-remark-plugin-npm2yarn/package.json index 80ca1b43fcd7..30f3d7909d0b 100644 --- a/packages/docusaurus-remark-plugin-npm2yarn/package.json +++ b/packages/docusaurus-remark-plugin-npm2yarn/package.json @@ -1,6 +1,6 @@ { "name": "@docusaurus/remark-plugin-npm2yarn", - "version": "2.0.0-beta.14", + "version": "2.0.0-beta.18", "description": "Remark plugin for converting npm commands to Yarn commands as tabs.", "main": "lib/index.js", "publishConfig": { @@ -19,17 +19,13 @@ "dependencies": { "npm-to-yarn": "^1.0.1", "tslib": "^2.3.1", - "unist-util-visit": "^2.0.2" + "unist-util-visit": "^2.0.3" }, "devDependencies": { "@types/mdast": "^3.0.10", - "remark": "^12.0.0", + "remark": "^12.0.1", "remark-mdx": "^1.6.21", - "to-vfile": "^6.0.0" - }, - "peerDependencies": { - "react": "^16.8.4 || ^17.0.0", - "react-dom": "^16.8.4 || ^17.0.0" + "to-vfile": "^6.1.0" }, "engines": { "node": ">=14" diff --git a/packages/docusaurus-remark-plugin-npm2yarn/src/__tests__/fixtures/import-tabs-above.md b/packages/docusaurus-remark-plugin-npm2yarn/src/__tests__/__fixtures__/import-tabs-above.md similarity index 100% rename from packages/docusaurus-remark-plugin-npm2yarn/src/__tests__/fixtures/import-tabs-above.md rename to packages/docusaurus-remark-plugin-npm2yarn/src/__tests__/__fixtures__/import-tabs-above.md diff --git a/packages/docusaurus-remark-plugin-npm2yarn/src/__tests__/fixtures/import-tabs-below.md b/packages/docusaurus-remark-plugin-npm2yarn/src/__tests__/__fixtures__/import-tabs-below.md similarity index 100% rename from packages/docusaurus-remark-plugin-npm2yarn/src/__tests__/fixtures/import-tabs-below.md rename to packages/docusaurus-remark-plugin-npm2yarn/src/__tests__/__fixtures__/import-tabs-below.md diff --git a/packages/docusaurus-remark-plugin-npm2yarn/src/__tests__/fixtures/installation.md b/packages/docusaurus-remark-plugin-npm2yarn/src/__tests__/__fixtures__/installation.md similarity index 100% rename from packages/docusaurus-remark-plugin-npm2yarn/src/__tests__/fixtures/installation.md rename to packages/docusaurus-remark-plugin-npm2yarn/src/__tests__/__fixtures__/installation.md diff --git a/packages/docusaurus-remark-plugin-npm2yarn/src/__tests__/fixtures/plugin.md b/packages/docusaurus-remark-plugin-npm2yarn/src/__tests__/__fixtures__/plugin.md similarity index 100% rename from packages/docusaurus-remark-plugin-npm2yarn/src/__tests__/fixtures/plugin.md rename to packages/docusaurus-remark-plugin-npm2yarn/src/__tests__/__fixtures__/plugin.md diff --git a/packages/docusaurus-remark-plugin-npm2yarn/src/__tests__/__fixtures__/sync.md b/packages/docusaurus-remark-plugin-npm2yarn/src/__tests__/__fixtures__/sync.md new file mode 100644 index 000000000000..4da6d88d3410 --- /dev/null +++ b/packages/docusaurus-remark-plugin-npm2yarn/src/__tests__/__fixtures__/sync.md @@ -0,0 +1,11 @@ +```bash npm2yarn +npm install --global docusaurus +``` + +```bash npm2yarn +npm install --save docusaurus-plugin-name +``` + +```bash npm2yarn +npm run build +``` diff --git a/packages/docusaurus-remark-plugin-npm2yarn/src/__tests__/fixtures/syntax-not-properly-set.md b/packages/docusaurus-remark-plugin-npm2yarn/src/__tests__/__fixtures__/syntax-not-properly-set.md similarity index 100% rename from packages/docusaurus-remark-plugin-npm2yarn/src/__tests__/fixtures/syntax-not-properly-set.md rename to packages/docusaurus-remark-plugin-npm2yarn/src/__tests__/__fixtures__/syntax-not-properly-set.md diff --git a/packages/docusaurus-remark-plugin-npm2yarn/src/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus-remark-plugin-npm2yarn/src/__tests__/__snapshots__/index.test.ts.snap index 03e53282c43b..d00ba962a458 100644 --- a/packages/docusaurus-remark-plugin-npm2yarn/src/__tests__/__snapshots__/index.test.ts.snap +++ b/packages/docusaurus-remark-plugin-npm2yarn/src/__tests__/__snapshots__/index.test.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`npm2yarn plugin test: already imported tabs components above are not re-imported 1`] = ` +exports[`npm2yarn plugin does not re-import tabs components when already imported above 1`] = ` "import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; @@ -24,7 +24,7 @@ import TabItem from '@theme/TabItem'; " `; -exports[`npm2yarn plugin test: already imported tabs components below are not re-imported 1`] = ` +exports[`npm2yarn plugin does not re-import tabs components when already imported below 1`] = ` " @@ -48,7 +48,22 @@ import TabItem from '@theme/TabItem'; " `; -exports[`npm2yarn plugin test: installation file 1`] = ` +exports[`npm2yarn plugin does not work when language is not set 1`] = ` +"\`\`\`npm2yarn +npm install --save docusaurus-plugin-name +\`\`\` + +\`\`\`bash +npm install --save docusaurus-plugin-name +\`\`\` + +\`\`\`shell +npm install --save docusaurus-plugin-name +\`\`\` +" +`; + +exports[`npm2yarn plugin works on installation file 1`] = ` "import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; @@ -71,22 +86,34 @@ import TabItem from '@theme/TabItem'; " `; -exports[`npm2yarn plugin test: language was not set 1`] = ` -"\`\`\`npm2yarn -npm install --save docusaurus-plugin-name -\`\`\` +exports[`npm2yarn plugin works on plugin file 1`] = ` +"import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +## Installing a plugin + +A plugin is usually a npm package, so you install them like other npm packages using npm. + + + \`\`\`bash npm install --save docusaurus-plugin-name \`\`\` -\`\`\`shell -npm install --save docusaurus-plugin-name + + + +\`\`\`bash +yarn add docusaurus-plugin-name \`\`\` + + + " `; -exports[`npm2yarn plugin test: plugin file 1`] = ` +exports[`npm2yarn plugin works with sync option 1`] = ` "import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; @@ -94,7 +121,7 @@ import TabItem from '@theme/TabItem'; A plugin is usually a npm package, so you install them like other npm packages using npm. - + \`\`\`bash diff --git a/packages/docusaurus-remark-plugin-npm2yarn/src/__tests__/index.test.ts b/packages/docusaurus-remark-plugin-npm2yarn/src/__tests__/index.test.ts index 3fe327238ad0..4887b0ac9d23 100644 --- a/packages/docusaurus-remark-plugin-npm2yarn/src/__tests__/index.test.ts +++ b/packages/docusaurus-remark-plugin-npm2yarn/src/__tests__/index.test.ts @@ -6,7 +6,7 @@ */ import remark from 'remark'; -// import from the transpiled lib because Babel can't transpile `export =` syntax +// import from the transpiled lib because Babel can't transpile `export =` // TODO change to `../index` after migrating to ESM import npm2yarn from '../../lib/index'; import vfile from 'to-vfile'; @@ -14,42 +14,45 @@ import path from 'path'; import mdx from 'remark-mdx'; const processFixture = async (name: string, options?: {sync?: boolean}) => { - const filePath = path.join(__dirname, 'fixtures', `${name}.md`); + const filePath = path.join(__dirname, '__fixtures__', `${name}.md`); const file = await vfile.read(filePath); - const result = await remark() - .use(mdx) - .use(npm2yarn, {...options, filePath}) - .process(file); + const result = await remark().use(mdx).use(npm2yarn, options).process(file); return result.toString(); }; describe('npm2yarn plugin', () => { - test('test: installation file', async () => { + it('works on installation file', async () => { const result = await processFixture('installation'); expect(result).toMatchSnapshot(); }); - test('test: plugin file', async () => { + it('works on plugin file', async () => { const result = await processFixture('plugin'); expect(result).toMatchSnapshot(); }); - test('test: language was not set', async () => { + it('works with sync option', async () => { + const result = await processFixture('plugin', {sync: true}); + + expect(result).toMatchSnapshot(); + }); + + it('does not work when language is not set', async () => { const result = await processFixture('syntax-not-properly-set'); expect(result).toMatchSnapshot(); }); - test('test: already imported tabs components above are not re-imported', async () => { + it('does not re-import tabs components when already imported above', async () => { const result = await processFixture('import-tabs-above'); expect(result).toMatchSnapshot(); }); - test('test: already imported tabs components below are not re-imported', async () => { + it('does not re-import tabs components when already imported below', async () => { const result = await processFixture('import-tabs-below'); expect(result).toMatchSnapshot(); diff --git a/packages/docusaurus-remark-plugin-npm2yarn/src/index.ts b/packages/docusaurus-remark-plugin-npm2yarn/src/index.ts index 7f80ed367824..b16949dd72e4 100644 --- a/packages/docusaurus-remark-plugin-npm2yarn/src/index.ts +++ b/packages/docusaurus-remark-plugin-npm2yarn/src/index.ts @@ -71,7 +71,7 @@ const plugin: Plugin<[PluginOptions?]> = (options = {}) => { if (isParent(node)) { let index = 0; while (index < node.children.length) { - const child = node.children[index]; + const child = node.children[index]!; if (matchNode(child)) { const result = transformNode(child, sync); node.children.splice(index, 1, ...result); diff --git a/packages/docusaurus-theme-classic/babel.config.js b/packages/docusaurus-theme-classic/babel.config.js index 90c09d806fc4..0bbcfe267e45 100644 --- a/packages/docusaurus-theme-classic/babel.config.js +++ b/packages/docusaurus-theme-classic/babel.config.js @@ -12,14 +12,9 @@ module.exports = { // we mostly need to transpile some features so that node does not crash... lib: { presets: [ + ['@babel/preset-env', {targets: {node: 14}, modules: 'commonjs'}], ['@babel/preset-typescript', {isTSX: true, allExtensions: true}], ], - // Useful to transpile for older node versions - plugins: [ - '@babel/plugin-transform-modules-commonjs', - '@babel/plugin-proposal-nullish-coalescing-operator', - '@babel/plugin-proposal-optional-chaining', - ], }, // USED FOR JS SWIZZLE diff --git a/packages/docusaurus-theme-classic/package.json b/packages/docusaurus-theme-classic/package.json index 5a74747c454b..9536af7b3b17 100644 --- a/packages/docusaurus-theme-classic/package.json +++ b/packages/docusaurus-theme-classic/package.json @@ -1,6 +1,6 @@ { "name": "@docusaurus/theme-classic", - "version": "2.0.0-beta.14", + "version": "2.0.0-beta.18", "description": "Classic theme for Docusaurus", "main": "lib/index.js", "types": "src/theme-classic.d.ts", @@ -21,35 +21,34 @@ "format:lib-next": "prettier --config ../../.prettierrc --write \"lib-next/**/*.{js,ts,jsx,tsc}\"" }, "dependencies": { - "@docusaurus/core": "2.0.0-beta.14", - "@docusaurus/plugin-content-blog": "2.0.0-beta.14", - "@docusaurus/plugin-content-docs": "2.0.0-beta.14", - "@docusaurus/plugin-content-pages": "2.0.0-beta.14", - "@docusaurus/theme-common": "2.0.0-beta.14", - "@docusaurus/theme-translations": "2.0.0-beta.14", - "@docusaurus/utils": "2.0.0-beta.14", - "@docusaurus/utils-common": "2.0.0-beta.14", - "@docusaurus/utils-validation": "2.0.0-beta.14", - "@mdx-js/react": "^1.6.21", + "@docusaurus/core": "2.0.0-beta.18", + "@docusaurus/plugin-content-blog": "2.0.0-beta.18", + "@docusaurus/plugin-content-docs": "2.0.0-beta.18", + "@docusaurus/plugin-content-pages": "2.0.0-beta.18", + "@docusaurus/theme-common": "2.0.0-beta.18", + "@docusaurus/theme-translations": "2.0.0-beta.18", + "@docusaurus/utils": "2.0.0-beta.18", + "@docusaurus/utils-common": "2.0.0-beta.18", + "@docusaurus/utils-validation": "2.0.0-beta.18", + "@mdx-js/react": "^1.6.22", "clsx": "^1.1.1", "copy-text-to-clipboard": "^3.0.1", - "infima": "0.2.0-alpha.37", - "lodash": "^4.17.20", - "postcss": "^8.3.7", - "prism-react-renderer": "^1.2.1", - "prismjs": "^1.23.0", + "infima": "0.2.0-alpha.38", + "lodash": "^4.17.21", + "postcss": "^8.4.12", + "prism-react-renderer": "^1.3.1", + "prismjs": "^1.27.0", "react-router-dom": "^5.2.0", - "rtlcss": "^3.3.0" + "rtlcss": "^3.5.0" }, "devDependencies": { - "@docusaurus/module-type-aliases": "2.0.0-beta.14", - "@docusaurus/types": "2.0.0-beta.14", - "@types/mdx-js__react": "^1.5.4", - "@types/parse-numeric-range": "^0.0.1", - "@types/prismjs": "^1.16.2", - "@types/rtlcss": "^3.1.1", + "@docusaurus/module-type-aliases": "2.0.0-beta.18", + "@docusaurus/types": "2.0.0-beta.18", + "@types/mdx-js__react": "^1.5.5", + "@types/prismjs": "^1.26.0", + "@types/rtlcss": "^3.1.3", "cross-env": "^7.0.3", - "fs-extra": "^10.0.0", + "fs-extra": "^10.0.1", "react-test-renderer": "^17.0.2", "utility-types": "^3.10.0" }, diff --git a/packages/docusaurus-theme-classic/src/__tests__/__snapshots__/translations.test.ts.snap b/packages/docusaurus-theme-classic/src/__tests__/__snapshots__/translations.test.ts.snap index 3f3ae40ed41f..084840bf3967 100644 --- a/packages/docusaurus-theme-classic/src/__tests__/__snapshots__/translations.test.ts.snap +++ b/packages/docusaurus-theme-classic/src/__tests__/__snapshots__/translations.test.ts.snap @@ -1,51 +1,51 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`getTranslationFiles should return translation files matching snapshot 1`] = ` -Array [ - Object { - "content": Object { - "item.label.Dropdown": Object { +exports[`getTranslationFiles returns translation files matching snapshot 1`] = ` +[ + { + "content": { + "item.label.Dropdown": { "description": "Navbar item with label Dropdown", "message": "Dropdown", }, - "item.label.Dropdown item 1": Object { + "item.label.Dropdown item 1": { "description": "Navbar item with label Dropdown item 1", "message": "Dropdown item 1", }, - "item.label.Dropdown item 2": Object { + "item.label.Dropdown item 2": { "description": "Navbar item with label Dropdown item 2", "message": "Dropdown item 2", }, - "title": Object { + "title": { "description": "The title in the navbar", "message": "navbar title", }, }, "path": "navbar", }, - Object { - "content": Object { - "copyright": Object { + { + "content": { + "copyright": { "description": "The footer copyright", "message": "Copyright FB", }, - "link.item.label.Link 1": Object { + "link.item.label.Link 1": { "description": "The label of footer link with label=Link 1 linking to https://facebook.com", "message": "Link 1", }, - "link.item.label.Link 2": Object { + "link.item.label.Link 2": { "description": "The label of footer link with label=Link 2 linking to https://facebook.com", "message": "Link 2", }, - "link.item.label.Link 3": Object { + "link.item.label.Link 3": { "description": "The label of footer link with label=Link 3 linking to https://facebook.com", "message": "Link 3", }, - "link.title.Footer link column 1": Object { + "link.title.Footer link column 1": { "description": "The title of the footer links column with title=Footer link column 1 in the footer", "message": "Footer link column 1", }, - "link.title.Footer link column 2": Object { + "link.title.Footer link column 2": { "description": "The title of the footer links column with title=Footer link column 2 in the footer", "message": "Footer link column 2", }, @@ -55,40 +55,40 @@ Array [ ] `; -exports[`getTranslationFiles should return translation files matching snapshot 2`] = ` -Array [ - Object { - "content": Object { - "item.label.Dropdown": Object { +exports[`getTranslationFiles returns translation files matching snapshot 2`] = ` +[ + { + "content": { + "item.label.Dropdown": { "description": "Navbar item with label Dropdown", "message": "Dropdown", }, - "item.label.Dropdown item 1": Object { + "item.label.Dropdown item 1": { "description": "Navbar item with label Dropdown item 1", "message": "Dropdown item 1", }, - "item.label.Dropdown item 2": Object { + "item.label.Dropdown item 2": { "description": "Navbar item with label Dropdown item 2", "message": "Dropdown item 2", }, - "title": Object { + "title": { "description": "The title in the navbar", "message": "navbar title", }, }, "path": "navbar", }, - Object { - "content": Object { - "copyright": Object { + { + "content": { + "copyright": { "description": "The footer copyright", "message": "Copyright FB", }, - "link.item.label.Link 1": Object { + "link.item.label.Link 1": { "description": "The label of footer link with label=Link 1 linking to https://facebook.com", "message": "Link 1", }, - "link.item.label.Link 2": Object { + "link.item.label.Link 2": { "description": "The label of footer link with label=Link 2 linking to https://facebook.com", "message": "Link 2", }, @@ -98,32 +98,32 @@ Array [ ] `; -exports[`translateThemeConfig should return translated themeConfig matching snapshot 1`] = ` -Object { - "announcementBar": Object {}, - "colorMode": Object {}, - "docs": Object { +exports[`translateThemeConfig returns translated themeConfig 1`] = ` +{ + "announcementBar": {}, + "colorMode": {}, + "docs": { "versionPersistence": "none", }, - "footer": Object { + "footer": { "copyright": "Copyright FB (translated)", - "links": Array [ - Object { - "items": Array [ - Object { + "links": [ + { + "items": [ + { "label": "Link 1 (translated)", "to": "https://facebook.com", }, - Object { + { "label": "Link 2 (translated)", "to": "https://facebook.com", }, ], "title": "Footer link column 1 (translated)", }, - Object { - "items": Array [ - Object { + { + "items": [ + { "label": "Link 3 (translated)", "to": "https://facebook.com", }, @@ -134,17 +134,17 @@ Object { "style": "light", }, "hideableSidebar": true, - "navbar": Object { + "navbar": { "hideOnScroll": false, - "items": Array [ - Object { - "items": Array [ - Object { - "items": Array [], + "items": [ + { + "items": [ + { + "items": [], "label": "Dropdown item 1 (translated)", }, - Object { - "items": Array [], + { + "items": [], "label": "Dropdown item 2 (translated)", }, ], @@ -154,6 +154,6 @@ Object { "style": "dark", "title": "navbar title (translated)", }, - "prism": Object {}, + "prism": {}, } `; diff --git a/packages/docusaurus-theme-classic/src/__tests__/translations.test.ts b/packages/docusaurus-theme-classic/src/__tests__/translations.test.ts index 8c8b4d908951..66d738519e1e 100644 --- a/packages/docusaurus-theme-classic/src/__tests__/translations.test.ts +++ b/packages/docusaurus-theme-classic/src/__tests__/translations.test.ts @@ -79,7 +79,7 @@ function getSampleTranslationFilesTranslated(themeConfig: ThemeConfig) { } describe('getTranslationFiles', () => { - test('should return translation files matching snapshot', () => { + it('returns translation files matching snapshot', () => { expect(getSampleTranslationFiles(ThemeConfigSample)).toMatchSnapshot(); expect( getSampleTranslationFiles(ThemeConfigSampleSimpleFooter), @@ -88,7 +88,7 @@ describe('getTranslationFiles', () => { }); describe('translateThemeConfig', () => { - test('should not translate anything if translation files are untranslated', () => { + it('does not translate anything if translation files are untranslated', () => { expect( translateThemeConfig({ themeConfig: ThemeConfigSample, @@ -97,7 +97,7 @@ describe('translateThemeConfig', () => { ).toEqual(ThemeConfigSample); }); - test('should return translated themeConfig matching snapshot', () => { + it('returns translated themeConfig', () => { expect( translateThemeConfig({ themeConfig: ThemeConfigSample, @@ -117,17 +117,17 @@ describe('getTranslationFiles and translateThemeConfig isomorphism', () => { expect(translatedThemeConfig).toEqual(themeConfig); } - test('should be verified for sample', () => { + it('is verified for sample', () => { verifyIsomorphism(ThemeConfigSample); }); - test('should be verified for sample with simple footer', () => { + it('is verified for sample with simple footer', () => { verifyIsomorphism(ThemeConfigSampleSimpleFooter); }); // undefined footer should not make the translation code crash // See https://github.com/facebook/docusaurus/issues/3936 - test('should be verified for sample without footer', () => { + it('is verified for sample without footer', () => { verifyIsomorphism({...ThemeConfigSample, footer: undefined}); }); }); diff --git a/packages/docusaurus-theme-classic/src/__tests__/validateThemeConfig.test.ts b/packages/docusaurus-theme-classic/src/__tests__/validateThemeConfig.test.ts index 67a2ec87a285..d2cb4d7184bf 100644 --- a/packages/docusaurus-theme-classic/src/__tests__/validateThemeConfig.test.ts +++ b/packages/docusaurus-theme-classic/src/__tests__/validateThemeConfig.test.ts @@ -5,21 +5,21 @@ * LICENSE file in the root directory of this source tree. */ -import {merge} from 'lodash'; +import _ from 'lodash'; import {ThemeConfigSchema, DEFAULT_CONFIG} from '../validateThemeConfig'; import {normalizeThemeConfig} from '@docusaurus/utils-validation'; import theme from 'prism-react-renderer/themes/github'; import darkTheme from 'prism-react-renderer/themes/dracula'; -function testValidateThemeConfig(partialThemeConfig: Record) { +function testValidateThemeConfig(partialThemeConfig: {[key: string]: unknown}) { return normalizeThemeConfig(ThemeConfigSchema, { ...DEFAULT_CONFIG, ...partialThemeConfig, }); } -function testOk(partialThemeConfig: Record) { +function testOk(partialThemeConfig: {[key: string]: unknown}) { expect( testValidateThemeConfig({...DEFAULT_CONFIG, ...partialThemeConfig}), ).toEqual({ @@ -29,7 +29,7 @@ function testOk(partialThemeConfig: Record) { } describe('themeConfig', () => { - test('should accept valid theme config', () => { + it('accepts valid theme config', () => { const userConfig = { prism: { theme, @@ -38,7 +38,7 @@ describe('themeConfig', () => { additionalLanguages: ['kotlin', 'java'], }, announcementBar: { - id: 'supportus', + id: 'supports', content: 'pls support', backgroundColor: '#fff', textColor: '#000', @@ -101,7 +101,7 @@ describe('themeConfig', () => { }); }); - test('should allow possible types of navbar items', () => { + it('allows possible types of navbar items', () => { const config = { navbar: { items: [ @@ -199,14 +199,14 @@ describe('themeConfig', () => { }); }); - test('should reject unknown navbar item type', () => { + it('rejects unknown navbar item type', () => { const config = { navbar: { items: [ { type: 'joke', position: 'left', - label: 'haha', + label: 'hahaha', }, ], }, @@ -216,7 +216,7 @@ describe('themeConfig', () => { ).toThrowErrorMatchingInlineSnapshot(`"Bad navbar item type joke"`); }); - test('should reject nested dropdowns', () => { + it('rejects nested dropdowns', () => { const config = { navbar: { items: [ @@ -243,7 +243,7 @@ describe('themeConfig', () => { ).toThrowErrorMatchingInlineSnapshot(`"Nested dropdowns are not allowed"`); }); - test('should reject nested dropdowns 2', () => { + it('rejects nested dropdowns 2', () => { const config = { navbar: { items: [ @@ -260,7 +260,7 @@ describe('themeConfig', () => { ).toThrowErrorMatchingInlineSnapshot(`"Nested dropdowns are not allowed"`); }); - test('should reject position attribute within dropdown', () => { + it('rejects position attribute within dropdown', () => { const config = { navbar: { items: [ @@ -285,7 +285,7 @@ describe('themeConfig', () => { ); }); - test('should give friendly error when href and to coexist', () => { + it('gives friendly error when href and to coexist', () => { const config = { navbar: { items: [ @@ -305,7 +305,7 @@ describe('themeConfig', () => { ); }); - test('should allow empty alt tags for the logo image in the header', () => { + it('allows empty alt tags for the logo image in the header', () => { const altTagConfig = { navbar: { logo: { @@ -324,7 +324,7 @@ describe('themeConfig', () => { }); }); - test('should allow empty alt tags for the logo image in the footer', () => { + it('allows empty alt tags for the logo image in the footer', () => { const partialConfig = { footer: { logo: { @@ -344,7 +344,7 @@ describe('themeConfig', () => { }); }); - test('should allow simple links in footer', () => { + it('allows simple links in footer', () => { const partialConfig = { footer: { links: [ @@ -378,7 +378,7 @@ describe('themeConfig', () => { }); }); - test('should allow footer column with no title', () => { + it('allows footer column with no title', () => { const partialConfig = { footer: { links: [ @@ -414,7 +414,7 @@ describe('themeConfig', () => { }); }); - test('should reject mix of simple and multi-column links in footer', () => { + it('rejects mix of simple and multi-column links in footer', () => { const partialConfig = { footer: { links: [ @@ -442,7 +442,7 @@ describe('themeConfig', () => { ); }); - test('should allow width and height specification for logo', () => { + it('allows width and height specification for logo', () => { const altTagConfig = { navbar: { logo: { @@ -463,7 +463,7 @@ describe('themeConfig', () => { }); }); - test('should accept valid prism config', () => { + it('accepts valid prism config', () => { const prismConfig = { prism: { additionalLanguages: ['kotlin', 'java'], @@ -476,25 +476,25 @@ describe('themeConfig', () => { }); describe('customCss config', () => { - test('should accept customCss undefined', () => { + it('accepts customCss undefined', () => { testOk({ customCss: undefined, }); }); - test('should accept customCss string', () => { + it('accepts customCss string', () => { testOk({ customCss: './path/to/cssFile.css', }); }); - test('should accept customCss string array', () => { + it('accepts customCss string array', () => { testOk({ customCss: ['./path/to/cssFile.css', './path/to/cssFile2.css'], }); }); - test('should reject customCss number', () => { + it('rejects customCss number', () => { expect(() => testValidateThemeConfig({ customCss: 42, @@ -507,36 +507,26 @@ describe('themeConfig', () => { describe('color mode config', () => { const withDefaultValues = (colorMode) => - merge({}, DEFAULT_CONFIG.colorMode, colorMode); + _.merge({}, DEFAULT_CONFIG.colorMode, colorMode); - test('minimal config', () => { + it('switch config', () => { const colorMode = { switchConfig: { darkIcon: '🌙', }, }; - expect(testValidateThemeConfig({colorMode})).toEqual({ - ...DEFAULT_CONFIG, - colorMode: withDefaultValues(colorMode), - }); + expect(() => + testValidateThemeConfig({colorMode}), + ).toThrowErrorMatchingInlineSnapshot( + `"colorMode.switchConfig is deprecated. If you want to customize the icons for light and dark mode, swizzle IconLightMode, IconDarkMode, or ColorModeToggle instead."`, + ); }); - test('max config', () => { + it('max config', () => { const colorMode = { defaultMode: 'dark', disableSwitch: false, respectPrefersColorScheme: true, - switchConfig: { - darkIcon: '🌙', - darkIconStyle: { - marginTop: '1px', - marginLeft: '2px', - }, - lightIcon: '☀️', - lightIconStyle: { - marginLeft: '1px', - }, - }, }; expect(testValidateThemeConfig({colorMode})).toEqual({ ...DEFAULT_CONFIG, @@ -544,7 +534,7 @@ describe('themeConfig', () => { }); }); - test('undefined config', () => { + it('undefined config', () => { const colorMode = undefined; expect(testValidateThemeConfig({colorMode})).toEqual({ ...DEFAULT_CONFIG, @@ -552,7 +542,7 @@ describe('themeConfig', () => { }); }); - test('empty config', () => { + it('empty config', () => { const colorMode = {}; expect(testValidateThemeConfig({colorMode})).toEqual({ ...DEFAULT_CONFIG, @@ -562,143 +552,133 @@ describe('themeConfig', () => { }, }); }); + }); - test('empty switch config', () => { - const colorMode = { - switchConfig: {}, - }; - expect(testValidateThemeConfig({colorMode})).toEqual({ + describe('tableOfContents', () => { + it('accepts undefined', () => { + const tableOfContents = undefined; + expect(testValidateThemeConfig({tableOfContents})).toEqual({ ...DEFAULT_CONFIG, - colorMode: withDefaultValues(colorMode), + tableOfContents: { + minHeadingLevel: DEFAULT_CONFIG.tableOfContents.minHeadingLevel, + maxHeadingLevel: DEFAULT_CONFIG.tableOfContents.maxHeadingLevel, + }, }); }); - }); -}); -describe('themeConfig tableOfContents', () => { - test('toc undefined', () => { - const tableOfContents = undefined; - expect(testValidateThemeConfig({tableOfContents})).toEqual({ - ...DEFAULT_CONFIG, - tableOfContents: { - minHeadingLevel: DEFAULT_CONFIG.tableOfContents.minHeadingLevel, - maxHeadingLevel: DEFAULT_CONFIG.tableOfContents.maxHeadingLevel, - }, - }); - }); - - test('toc empty', () => { - const tableOfContents = {}; - expect(testValidateThemeConfig({tableOfContents})).toEqual({ - ...DEFAULT_CONFIG, - tableOfContents: { - minHeadingLevel: DEFAULT_CONFIG.tableOfContents.minHeadingLevel, - maxHeadingLevel: DEFAULT_CONFIG.tableOfContents.maxHeadingLevel, - }, + it('accepts empty', () => { + const tableOfContents = {}; + expect(testValidateThemeConfig({tableOfContents})).toEqual({ + ...DEFAULT_CONFIG, + tableOfContents: { + minHeadingLevel: DEFAULT_CONFIG.tableOfContents.minHeadingLevel, + maxHeadingLevel: DEFAULT_CONFIG.tableOfContents.maxHeadingLevel, + }, + }); }); - }); - test('toc with min', () => { - const tableOfContents = { - minHeadingLevel: 3, - }; - expect(testValidateThemeConfig({tableOfContents})).toEqual({ - ...DEFAULT_CONFIG, - tableOfContents: { + it('accepts min', () => { + const tableOfContents = { minHeadingLevel: 3, - maxHeadingLevel: DEFAULT_CONFIG.tableOfContents.maxHeadingLevel, - }, + }; + expect(testValidateThemeConfig({tableOfContents})).toEqual({ + ...DEFAULT_CONFIG, + tableOfContents: { + minHeadingLevel: 3, + maxHeadingLevel: DEFAULT_CONFIG.tableOfContents.maxHeadingLevel, + }, + }); }); - }); - test('toc with max', () => { - const tableOfContents = { - maxHeadingLevel: 5, - }; - expect(testValidateThemeConfig({tableOfContents})).toEqual({ - ...DEFAULT_CONFIG, - tableOfContents: { - minHeadingLevel: DEFAULT_CONFIG.tableOfContents.minHeadingLevel, + it('accepts max', () => { + const tableOfContents = { maxHeadingLevel: 5, - }, + }; + expect(testValidateThemeConfig({tableOfContents})).toEqual({ + ...DEFAULT_CONFIG, + tableOfContents: { + minHeadingLevel: DEFAULT_CONFIG.tableOfContents.minHeadingLevel, + maxHeadingLevel: 5, + }, + }); }); - }); - test('toc with min 2.5', () => { - const tableOfContents = { - minHeadingLevel: 2.5, - }; - expect(() => - testValidateThemeConfig({tableOfContents}), - ).toThrowErrorMatchingInlineSnapshot( - `"\\"tableOfContents.minHeadingLevel\\" must be an integer"`, - ); - }); + it('rejects min 2.5', () => { + const tableOfContents = { + minHeadingLevel: 2.5, + }; + expect(() => + testValidateThemeConfig({tableOfContents}), + ).toThrowErrorMatchingInlineSnapshot( + `"\\"tableOfContents.minHeadingLevel\\" must be an integer"`, + ); + }); - test('toc with max 2.5', () => { - const tableOfContents = { - maxHeadingLevel: 2.5, - }; - expect(() => - testValidateThemeConfig({tableOfContents}), - ).toThrowErrorMatchingInlineSnapshot( - `"\\"tableOfContents.maxHeadingLevel\\" must be an integer"`, - ); - }); + it('rejects max 2.5', () => { + const tableOfContents = { + maxHeadingLevel: 2.5, + }; + expect(() => + testValidateThemeConfig({tableOfContents}), + ).toThrowErrorMatchingInlineSnapshot( + `"\\"tableOfContents.maxHeadingLevel\\" must be an integer"`, + ); + }); - test('toc with min 1', () => { - const tableOfContents = { - minHeadingLevel: 1, - }; - expect(() => - testValidateThemeConfig({tableOfContents}), - ).toThrowErrorMatchingInlineSnapshot( - `"\\"tableOfContents.minHeadingLevel\\" must be greater than or equal to 2"`, - ); - }); + it('rejects min 1', () => { + const tableOfContents = { + minHeadingLevel: 1, + }; + expect(() => + testValidateThemeConfig({tableOfContents}), + ).toThrowErrorMatchingInlineSnapshot( + `"\\"tableOfContents.minHeadingLevel\\" must be greater than or equal to 2"`, + ); + }); - test('toc with min 7', () => { - const tableOfContents = { - minHeadingLevel: 7, - }; - expect(() => - testValidateThemeConfig({tableOfContents}), - ).toThrowErrorMatchingInlineSnapshot( - `"\\"tableOfContents.minHeadingLevel\\" must be less than or equal to ref:maxHeadingLevel"`, - ); - }); + it('rejects min 7', () => { + const tableOfContents = { + minHeadingLevel: 7, + }; + expect(() => + testValidateThemeConfig({tableOfContents}), + ).toThrowErrorMatchingInlineSnapshot( + `"\\"tableOfContents.minHeadingLevel\\" must be less than or equal to ref:maxHeadingLevel"`, + ); + }); - test('toc with max 1', () => { - const tableOfContents = { - maxHeadingLevel: 1, - }; - expect(() => - testValidateThemeConfig({tableOfContents}), - ).toThrowErrorMatchingInlineSnapshot( - `"\\"tableOfContents.maxHeadingLevel\\" must be greater than or equal to 2"`, - ); - }); + it('rejects max 1', () => { + const tableOfContents = { + maxHeadingLevel: 1, + }; + expect(() => + testValidateThemeConfig({tableOfContents}), + ).toThrowErrorMatchingInlineSnapshot( + `"\\"tableOfContents.maxHeadingLevel\\" must be greater than or equal to 2"`, + ); + }); - test('toc with max 7', () => { - const tableOfContents = { - maxHeadingLevel: 7, - }; - expect(() => - testValidateThemeConfig({tableOfContents}), - ).toThrowErrorMatchingInlineSnapshot( - `"\\"tableOfContents.maxHeadingLevel\\" must be less than or equal to 6"`, - ); - }); + it('rejects max 7', () => { + const tableOfContents = { + maxHeadingLevel: 7, + }; + expect(() => + testValidateThemeConfig({tableOfContents}), + ).toThrowErrorMatchingInlineSnapshot( + `"\\"tableOfContents.maxHeadingLevel\\" must be less than or equal to 6"`, + ); + }); - test('toc with bad min 5 + max 3', () => { - const tableOfContents = { - minHeadingLevel: 5, - maxHeadingLevel: 3, - }; - expect(() => - testValidateThemeConfig({tableOfContents}), - ).toThrowErrorMatchingInlineSnapshot( - `"\\"tableOfContents.minHeadingLevel\\" must be less than or equal to ref:maxHeadingLevel"`, - ); + it('rejects min 5 + max 3', () => { + const tableOfContents = { + minHeadingLevel: 5, + maxHeadingLevel: 3, + }; + expect(() => + testValidateThemeConfig({tableOfContents}), + ).toThrowErrorMatchingInlineSnapshot( + `"\\"tableOfContents.minHeadingLevel\\" must be less than or equal to ref:maxHeadingLevel"`, + ); + }); }); }); diff --git a/packages/docusaurus-theme-classic/src/getSwizzleConfig.ts b/packages/docusaurus-theme-classic/src/getSwizzleConfig.ts new file mode 100644 index 000000000000..13b9fde846f9 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/getSwizzleConfig.ts @@ -0,0 +1,244 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import type {SwizzleConfig} from '@docusaurus/types'; + +/* eslint sort-keys: "error" */ + +export default function getSwizzleConfig(): SwizzleConfig { + return { + components: { + CodeBlock: { + actions: { + eject: 'safe', + wrap: 'safe', + }, + description: + 'The component used to render multi-line code blocks, generally used in Markdown files.', + }, + ColorModeToggle: { + actions: { + eject: 'safe', + wrap: 'safe', + }, + description: + 'The color mode toggle to switch between light and dark mode.', + }, + DocSidebar: { + actions: { + eject: 'unsafe', // too much technical code in sidebar, not very safe atm + wrap: 'safe', + }, + description: 'The sidebar component on docs pages', + }, + Footer: { + actions: { + eject: 'safe', + wrap: 'safe', + }, + description: "The footer component of you site's layout", + }, + 'Footer/Copyright': { + actions: { + eject: 'safe', + wrap: 'safe', + }, + description: 'The footer copyright', + }, + 'Footer/Layout': { + actions: { + eject: 'safe', + wrap: 'safe', + }, + description: 'The footer main layout component', + }, + 'Footer/LinkItem': { + actions: { + eject: 'safe', + wrap: 'safe', + }, + description: 'The footer link item component', + }, + 'Footer/Links': { + actions: { + eject: 'safe', + wrap: 'safe', + }, + description: 'The footer component rendering the footer links', + }, + 'Footer/Links/MultiColumn': { + actions: { + eject: 'safe', + wrap: 'safe', + }, + description: + 'The footer component rendering the footer links with a multi-column layout', + }, + 'Footer/Links/Simple': { + actions: { + eject: 'safe', + wrap: 'safe', + }, + description: + 'The footer component rendering the footer links with a simple layout (single row)', + }, + 'Footer/Logo': { + actions: { + eject: 'safe', + wrap: 'safe', + }, + description: 'The footer logo', + }, + IconArrow: { + actions: { + eject: 'safe', + wrap: 'safe', + }, + description: 'The arrow icon component', + }, + IconDarkMode: { + actions: { + eject: 'safe', + wrap: 'safe', + }, + description: 'The dark mode icon component.', + }, + IconEdit: { + actions: { + eject: 'safe', + wrap: 'safe', + }, + description: 'The edit icon component', + }, + IconLightMode: { + actions: { + eject: 'safe', + wrap: 'safe', + }, + description: 'The light mode icon component.', + }, + IconMenu: { + actions: { + eject: 'safe', + wrap: 'safe', + }, + description: 'The menu icon component', + }, + MDXComponents: { + actions: { + eject: 'safe', + wrap: 'forbidden', /// TODO allow wrapping objects??? + }, + description: + 'The MDX components to use for rendering MDX files. Meant to be ejected.', + }, + 'MDXComponents/A': { + actions: { + eject: 'safe', + wrap: 'safe', + }, + description: + 'The component used to render tags and Markdown links in MDX', + }, + 'MDXComponents/Code': { + actions: { + eject: 'safe', + wrap: 'safe', + }, + description: + 'The component used to render tags and Markdown code blocks in MDX', + }, + 'MDXComponents/Details': { + actions: { + eject: 'safe', + wrap: 'safe', + }, + description: 'The component used to render
tags in MDX', + }, + 'MDXComponents/Head': { + actions: { + eject: 'forbidden', + wrap: 'forbidden', + }, + description: + 'Technical component used to assign metadata (generally for SEO purpose) to the current MDX document', + }, + 'MDXComponents/Heading': { + actions: { + eject: 'safe', + wrap: 'safe', + }, + description: + 'The component used to render heading tags (

,

...) and Markdown headings in MDX', + }, + 'MDXComponents/Img': { + actions: { + eject: 'safe', + wrap: 'safe', + }, + description: + 'The component used to render tags and Markdown images in MDX', + }, + 'MDXComponents/Pre': { + actions: { + eject: 'safe', + wrap: 'safe', + }, + description: 'The component used to render
 tags in MDX',
+      },
+      'MDXComponents/Ul': {
+        actions: {
+          eject: 'safe',
+          wrap: 'safe',
+        },
+        description:
+          'The component used to render 
    tags and Markdown unordered lists in MDX', + }, + MDXContent: { + actions: { + eject: 'safe', + wrap: 'safe', + }, + description: + 'A component wrapping all MDX content and providing the MDXComponents to the MDX context', + }, + // TODO should probably not even appear here + 'NavbarItem/utils': { + actions: { + eject: 'forbidden', + wrap: 'forbidden', + }, + }, + NotFound: { + actions: { + eject: 'safe', + wrap: 'safe', + }, + description: + 'The global 404 page of your site, meant to be ejected and customized', + }, + SearchBar: { + actions: { + eject: 'safe', + wrap: 'safe', + }, + // TODO how to describe this one properly? + // By default it's an empty placeholder for the user to fill + description: + 'The search bar component of your site, appearing in the navbar.', + }, + 'prism-include-languages': { + actions: { + eject: 'safe', + wrap: 'forbidden', // not a component! + }, + description: + 'The Prism languages to include for code block syntax highlighting. Meant to be ejected.', + }, + }, + }; +} diff --git a/packages/docusaurus-theme-classic/src/index.ts b/packages/docusaurus-theme-classic/src/index.ts index a902c96f2cd9..64f56392a45e 100644 --- a/packages/docusaurus-theme-classic/src/index.ts +++ b/packages/docusaurus-theme-classic/src/index.ts @@ -5,10 +5,9 @@ * LICENSE file in the root directory of this source tree. */ -import type {LoadContext, Plugin, PostCssOptions} from '@docusaurus/types'; +import type {LoadContext, Plugin} from '@docusaurus/types'; import type {ThemeConfig} from '@docusaurus/theme-common'; import {getTranslationFiles, translateThemeConfig} from './translations'; -import path from 'path'; import {createRequire} from 'module'; import type {Plugin as PostCssPlugin} from 'postcss'; import rtlcss from 'rtlcss'; @@ -23,7 +22,7 @@ const ContextReplacementPlugin: typeof webpack.ContextReplacementPlugin = requireFromDocusaurusCore('webpack/lib/ContextReplacementPlugin'); // Need to be inlined to prevent dark mode FOUC -// Make sure that the 'storageKey' is the same as the one in `/theme/hooks/useTheme.js` +// Make sure the key is the same as the one in `/theme/hooks/useTheme.js` const ThemeStorageKey = 'theme'; const noFlashColorMode = ({ defaultMode, @@ -64,15 +63,17 @@ const noFlashColorMode = ({ } })();`; -// Duplicated constant. Unfortunately we can't import it from theme-common, as we need to support older nodejs versions without ESM support +// Duplicated constant. Unfortunately we can't import it from theme-common, as +// we need to support older nodejs versions without ESM support // TODO: import from theme-common once we only support Node.js with ESM support // + move all those announcementBar stuff there too export const AnnouncementBarDismissStorageKey = 'docusaurus.announcement.dismiss'; const AnnouncementBarDismissDataAttribute = 'data-announcement-bar-initially-dismissed'; -// We always render the announcement bar html on the server, to prevent layout shifts on React hydration -// The theme can use CSS + the data attribute to hide the announcement bar asap (before React hydration) +// We always render the announcement bar html on the server, to prevent layout +// shifts on React hydration. The theme can use CSS + the data attribute to hide +// the announcement bar asap (before React hydration) const AnnouncementBarInlineJavaScript = ` (function() { function isDismissed() { @@ -95,27 +96,26 @@ export default function docusaurusThemeClassic( options: Options, ): Plugin { const { - siteConfig: {themeConfig: roughlyTypedThemeConfig}, i18n: {currentLocale, localeConfigs}, } = context; - const themeConfig = (roughlyTypedThemeConfig || {}) as ThemeConfig; + const themeConfig = context.siteConfig.themeConfig as ThemeConfig; const { announcementBar, colorMode, - prism: {additionalLanguages = []} = {}, + prism: {additionalLanguages}, } = themeConfig; - const {customCss} = options || {}; - const {direction} = localeConfigs[currentLocale]; + const {customCss} = options ?? {}; + const {direction} = localeConfigs[currentLocale]!; return { name: 'docusaurus-theme-classic', getThemePath() { - return path.join(__dirname, '../lib-next/theme'); + return '../lib-next/theme'; }, getTypeScriptThemePath() { - return path.resolve(__dirname, '../src/theme'); + return '../src/theme'; }, getTranslationFiles: async () => getTranslationFiles({themeConfig}), @@ -136,8 +136,8 @@ export default function docusaurusThemeClassic( getClientModules() { const modules = [ require.resolve(getInfimaCSSFile(direction)), - path.resolve(__dirname, './prism-include-languages'), - path.resolve(__dirname, './admonitions.css'), + './prism-include-languages', + './admonitions.css', ]; if (customCss) { @@ -169,7 +169,7 @@ export default function docusaurusThemeClassic( }; }, - configurePostCss(postCssOptions: PostCssOptions) { + configurePostCss(postCssOptions) { if (direction === 'rtl') { const resolvedInfimaFile = require.resolve(getInfimaCSSFile(direction)); const plugin: PostCssPlugin = { @@ -205,21 +205,5 @@ ${announcementBar ? AnnouncementBarInlineJavaScript : ''} }; } -const swizzleAllowedComponents = [ - 'CodeBlock', - 'DocSidebar', - 'Footer', - 'NotFound', - 'SearchBar', - 'IconArrow', - 'IconEdit', - 'IconMenu', - 'hooks/useTheme', - 'prism-include-languages', -]; - -export function getSwizzleComponentList(): string[] { - return swizzleAllowedComponents; -} - +export {default as getSwizzleConfig} from './getSwizzleConfig'; export {validateThemeConfig} from './validateThemeConfig'; diff --git a/packages/docusaurus-theme-classic/src/theme-classic.d.ts b/packages/docusaurus-theme-classic/src/theme-classic.d.ts index 23bb1fdf9d80..9f3c48ed3da0 100644 --- a/packages/docusaurus-theme-classic/src/theme-classic.d.ts +++ b/packages/docusaurus-theme-classic/src/theme-classic.d.ts @@ -5,6 +5,13 @@ * LICENSE file in the root directory of this source tree. */ +/* eslint-disable @typescript-eslint/triple-slash-reference */ + +/// +/// +/// +/// + declare module '@docusaurus/theme-classic' { export type Options = { customCss?: string | string[]; @@ -24,8 +31,7 @@ declare module '@theme/Admonition' { } declare module '@theme/AnnouncementBar' { - const AnnouncementBar: () => JSX.Element | null; - export default AnnouncementBar; + export default function AnnouncementBar(): JSX.Element | null; } declare module '@theme/BackToTopButton' { @@ -38,9 +44,17 @@ declare module '@theme/BlogListPaginator' { export interface Props { readonly metadata: Metadata; } + export default function BlogListPaginator(props: Props): JSX.Element; +} + +declare module '@theme/BlogSidebar' { + import type {BlogSidebar} from '@docusaurus/plugin-content-blog'; + + export interface Props { + readonly sidebar: BlogSidebar; + } - const BlogListPaginator: (props: Props) => JSX.Element; - export default BlogListPaginator; + export default function BlogSidebar(props: Props): JSX.Element; } declare module '@theme/BlogPostItem' { @@ -56,8 +70,7 @@ declare module '@theme/BlogPostItem' { readonly children: JSX.Element; } - const BlogPostItem: (props: Props) => JSX.Element; - export default BlogPostItem; + export default function BlogPostItem(props: Props): JSX.Element; } declare module '@theme/BlogPostAuthor' { @@ -90,22 +103,20 @@ declare module '@theme/BlogPostPaginator' { readonly prevItem?: Item; } - const BlogPostPaginator: (props: Props) => JSX.Element; - export default BlogPostPaginator; + export default function BlogPostPaginator(props: Props): JSX.Element; } declare module '@theme/BlogLayout' { import type {ReactNode} from 'react'; import type {Props as LayoutProps} from '@theme/Layout'; - import type {BlogSidebar} from '@theme/BlogSidebar'; + import type {BlogSidebar} from '@docusaurus/plugin-content-blog'; export interface Props extends LayoutProps { readonly sidebar?: BlogSidebar; readonly toc?: ReactNode; } - const BlogLayout: (props: Props) => JSX.Element; - export default BlogLayout; + export default function BlogLayout(props: Props): JSX.Element; } declare module '@theme/CodeBlock' { @@ -119,8 +130,41 @@ declare module '@theme/CodeBlock' { readonly language?: string; } - const CodeBlock: (props: Props) => JSX.Element; - export default CodeBlock; + export default function CodeBlock(props: Props): JSX.Element; +} + +declare module '@theme/CodeBlock/CopyButton' { + export interface Props { + readonly code: string; + } + + export default function CopyButton(props: Props): JSX.Element; +} + +declare module '@theme/DocCard' { + import type {PropSidebarItem} from '@docusaurus/plugin-content-docs'; + + export interface Props { + readonly item: PropSidebarItem; + } + + export default function DocCard(props: Props): JSX.Element; +} + +declare module '@theme/DocCardList' { + import type {PropSidebarItem} from '@docusaurus/plugin-content-docs'; + + export interface Props { + readonly items: PropSidebarItem[]; + } + + export default function DocCardList(props: Props): JSX.Element; +} + +declare module '@theme/DocItemFooter' { + import type {Props} from '@theme/DocItem'; + + export default function DocItemFooter(props: Props): JSX.Element; } declare module '@theme/DocPaginator' { @@ -144,8 +188,43 @@ declare module '@theme/DocSidebar' { readonly [key: string]: unknown; } - const DocSidebar: (props: Props) => JSX.Element; - export default DocSidebar; + export default function DocSidebar(props: Props): JSX.Element; +} + +declare module '@theme/DocSidebar/Mobile' { + import type {Props as DocSidebarProps} from '@theme/DocSidebar'; + + export interface Props extends DocSidebarProps {} + + export default function DocSidebarMobile(props: Props): JSX.Element; +} + +declare module '@theme/DocSidebar/Desktop' { + import type {Props as DocSidebarProps} from '@theme/DocSidebar'; + + export interface Props extends DocSidebarProps {} + + export default function DocSidebarDesktop(props: Props): JSX.Element; +} + +declare module '@theme/DocSidebar/Desktop/Content' { + import type {PropSidebarItem} from '@docusaurus/plugin-content-docs'; + + export interface Props { + readonly className?: string; + readonly path: string; + readonly sidebar: readonly PropSidebarItem[]; + } + + export default function Content(props: Props): JSX.Element; +} + +declare module '@theme/DocSidebar/Desktop/CollapseButton' { + export interface Props { + onClick: React.MouseEventHandler; + } + + export default function CollapseButton(props: Props): JSX.Element; } declare module '@theme/DocSidebarItem' { @@ -163,28 +242,76 @@ declare module '@theme/DocSidebarItem' { export default function DocSidebarItem(props: Props): JSX.Element; } +declare module '@theme/DocSidebarItem/Link' { + import type {Props as DocSidebarItemProps} from '@theme/DocSidebarItem'; + + import type {PropSidebarItemLink} from '@docusaurus/plugin-content-docs'; + + export interface Props extends DocSidebarItemProps { + item: PropSidebarItemLink; + } + + export default function DocSidebarItemLink(props: Props): JSX.Element; +} + +declare module '@theme/DocSidebarItem/Html' { + import type {Props as DocSidebarItemProps} from '@theme/DocSidebarItem'; + import type {PropSidebarItemHtml} from '@docusaurus/plugin-content-docs'; + + export interface Props extends DocSidebarItemProps { + item: PropSidebarItemHtml; + } + + export default function DocSidebarItemHtml(props: Props): JSX.Element; +} + +declare module '@theme/DocSidebarItem/Category' { + import type {Props as DocSidebarItemProps} from '@theme/DocSidebarItem'; + import type {PropSidebarItemCategory} from '@docusaurus/plugin-content-docs'; + + export interface Props extends DocSidebarItemProps { + item: PropSidebarItemCategory; + } + + export default function DocSidebarItemCategory(props: Props): JSX.Element; +} + declare module '@theme/DocSidebarItems' { import type {Props as DocSidebarItemProps} from '@theme/DocSidebarItem'; import type {PropSidebarItem} from '@docusaurus/plugin-content-docs'; - export type Props = Omit & { + export interface Props extends Omit { readonly items: readonly PropSidebarItem[]; - }; + } export default function DocSidebarItems(props: Props): JSX.Element; } +declare module '@theme/DocVersionBanner' { + export interface Props { + readonly className?: string; + } + + export default function DocVersionBanner(props: Props): JSX.Element; +} + +declare module '@theme/DocVersionBadge' { + export interface Props { + readonly className?: string; + } + + export default function DocVersionBadge(props: Props): JSX.Element; +} + declare module '@theme/DocVersionSuggestions' { - const DocVersionSuggestions: () => JSX.Element; - export default DocVersionSuggestions; + export default function DocVersionSuggestions(): JSX.Element; } declare module '@theme/EditThisPage' { export interface Props { readonly editUrl: string; } - const EditThisPage: (props: Props) => JSX.Element; - export default EditThisPage; + export default function EditThisPage(props: Props): JSX.Element; } declare module '@theme/ErrorPageContent' { @@ -195,8 +322,78 @@ declare module '@theme/ErrorPageContent' { } declare module '@theme/Footer' { - const Footer: () => JSX.Element | null; - export default Footer; + export default function Footer(): JSX.Element | null; +} + +declare module '@theme/Footer/Logo' { + import type {FooterLogo} from '@docusaurus/theme-common'; + + export interface Props { + logo: FooterLogo; + } + + export default function FooterLogo(props: Props): JSX.Element; +} + +declare module '@theme/Footer/Copyright' { + export interface Props { + copyright: string; + } + + export default function FooterCopyright(props: Props): JSX.Element; +} + +declare module '@theme/Footer/LinkItem' { + import type {FooterLinkItem} from '@docusaurus/theme-common'; + + export interface Props { + item: FooterLinkItem; + } + + export default function FooterLinkItem(props: Props): JSX.Element; +} + +declare module '@theme/Footer/Layout' { + import type {ReactNode} from 'react'; + + export interface Props { + style: 'light' | 'dark'; + links: ReactNode; + logo: ReactNode; + copyright: ReactNode; + } + + export default function FooterLayout(props: Props): JSX.Element; +} + +declare module '@theme/Footer/Links' { + import type {Footer} from '@docusaurus/theme-common'; + + export interface Props { + links: Footer['links']; + } + + export default function FooterLinks(props: Props): JSX.Element; +} + +declare module '@theme/Footer/Links/MultiColumn' { + import type {MultiColumnFooter} from '@docusaurus/theme-common'; + + export interface Props { + columns: MultiColumnFooter['links']; + } + + export default function FooterLinksMultiColumn(props: Props): JSX.Element; +} + +declare module '@theme/Footer/Links/Simple' { + import type {SimpleFooter} from '@docusaurus/theme-common'; + + export interface Props { + links: SimpleFooter['links']; + } + + export default function FooterLinksSimple(props: Props): JSX.Element; } declare module '@theme/Heading' { @@ -216,31 +413,17 @@ declare module '@theme/Layout' { export interface Props { readonly children?: ReactNode; - readonly title?: string; readonly noFooter?: boolean; - readonly description?: string; - readonly image?: string; - readonly keywords?: string | string[]; - readonly permalink?: string; readonly wrapperClassName?: string; - readonly pageClassName?: string; - readonly searchMetadata?: { - readonly version?: string; - readonly tag?: string; - }; + + // Not really layout-related, but kept for convenience/retro-compatibility + readonly title?: string; + readonly description?: string; } export default function Layout(props: Props): JSX.Element; } -declare module '@theme/LayoutHead' { - import type {Props as LayoutProps} from '@theme/Layout'; - - export interface Props extends Omit {} - - export default function LayoutHead(props: Props): JSX.Element; -} - declare module '@theme/LayoutProviders' { import type {ReactNode} from 'react'; @@ -258,8 +441,7 @@ declare module '@theme/SearchMetadata' { readonly tag?: string; } - const SearchMetadata: (props: Props) => JSX.Element; - export default SearchMetadata; + export default function SearchMetadata(props: Props): JSX.Element; } declare module '@theme/LastUpdated' { @@ -269,41 +451,179 @@ declare module '@theme/LastUpdated' { readonly lastUpdatedBy?: string; } - const LastUpdated: (props: Props) => JSX.Element; - export default LastUpdated; + export default function LastUpdated(props: Props): JSX.Element; } declare module '@theme/SkipToContent' { - const SkipToContent: () => JSX.Element; - export default SkipToContent; + export default function SkipToContent(): JSX.Element; } -declare module '@theme/MDXComponents' { +declare module '@theme/MDXComponents/A' { + import type {ComponentProps} from 'react'; + + export interface Props extends ComponentProps<'a'> {} + + export default function MDXA(props: Props): JSX.Element; +} + +declare module '@theme/MDXComponents/Code' { + import type {ComponentProps} from 'react'; + + export interface Props extends ComponentProps<'code'> {} + + export default function MDXCode(props: Props): JSX.Element; +} + +declare module '@theme/MDXComponents/Details' { + import type {ComponentProps} from 'react'; + + export interface Props extends ComponentProps<'details'> {} + + export default function MDXDetails(props: Props): JSX.Element; +} + +declare module '@theme/MDXComponents/Ul' { + import type {ComponentProps} from 'react'; + + export interface Props extends ComponentProps<'ul'> {} + + export default function MDXUl(props: Props): JSX.Element; +} + +declare module '@theme/MDXComponents/Img' { import type {ComponentProps} from 'react'; - import type CodeBlock from '@theme/CodeBlock'; - import type Head from '@docusaurus/Head'; + + export interface Props extends ComponentProps<'img'> {} + + export default function MDXImg(props: Props): JSX.Element; +} + +declare module '@theme/MDXComponents/Head' { + import type {ComponentProps} from 'react'; + + export interface Props extends ComponentProps<'head'> {} + + export default function MDXHead(props: Props): JSX.Element; +} + +declare module '@theme/MDXComponents/Heading' { + import type {ComponentProps} from 'react'; + import type Heading from '@theme/Heading'; + + export interface Props extends ComponentProps {} + + export default function MDXHeading(props: Props): JSX.Element; +} + +declare module '@theme/MDXComponents/Pre' { + import type {ComponentProps} from 'react'; + + export interface Props extends ComponentProps<'pre'> {} + + export default function MDXPre(props: Props): JSX.Element; +} + +declare module '@theme/MDXComponents' { + import type {ComponentType, ComponentProps} from 'react'; + + import type MDXHead from '@theme/MDXComponents/Head'; + import type MDXCode from '@theme/MDXComponents/Code'; + import type MDXA from '@theme/MDXComponents/A'; + import type MDXPre from '@theme/MDXComponents/Pre'; + import type MDXDetails from '@theme/MDXComponents/Details'; + import type MDXUl from '@theme/MDXComponents/Ul'; + import type MDXImg from '@theme/MDXComponents/Img'; export type MDXComponentsObject = { - readonly head: typeof Head; - readonly code: typeof CodeBlock; - readonly a: (props: ComponentProps<'a'>) => JSX.Element; - readonly pre: typeof CodeBlock; - readonly details: (props: ComponentProps<'details'>) => JSX.Element; + readonly head: typeof MDXHead; + readonly code: typeof MDXCode; + readonly a: typeof MDXA; + readonly pre: typeof MDXPre; + readonly details: typeof MDXDetails; + readonly ul: typeof MDXUl; + readonly img: typeof MDXImg; readonly h1: (props: ComponentProps<'h1'>) => JSX.Element; readonly h2: (props: ComponentProps<'h2'>) => JSX.Element; readonly h3: (props: ComponentProps<'h3'>) => JSX.Element; readonly h4: (props: ComponentProps<'h4'>) => JSX.Element; readonly h5: (props: ComponentProps<'h5'>) => JSX.Element; readonly h6: (props: ComponentProps<'h6'>) => JSX.Element; - }; + } & {[tagName: string]: ComponentType}; const MDXComponents: MDXComponentsObject; export default MDXComponents; } +declare module '@theme/MDXContent' { + import type {ReactNode} from 'react'; + + export interface Props { + readonly children: ReactNode; + } + + export default function MDXContent(props: Props): JSX.Element; +} + declare module '@theme/Navbar' { - const Navbar: () => JSX.Element; - export default Navbar; + export default function Navbar(): JSX.Element; +} + +declare module '@theme/Navbar/ColorModeToggle' { + export interface Props { + readonly className?: string; + } + + export default function NavbarColorModeToggle( + props: Props, + ): JSX.Element | null; +} + +declare module '@theme/Navbar/Logo' { + export default function NavbarLogo(): JSX.Element; +} + +declare module '@theme/Navbar/Content' { + export default function NavbarContent(): JSX.Element; +} + +declare module '@theme/Navbar/Layout' { + export interface Props { + readonly children: React.ReactNode; + } + + export default function NavbarLayout(props: Props): JSX.Element; +} + +declare module '@theme/Navbar/MobileSidebar' { + export default function NavbarMobileSidebar(): JSX.Element; +} + +declare module '@theme/Navbar/MobileSidebar/Layout' { + import type {ReactNode} from 'react'; + + interface Props { + header: ReactNode; + primaryMenu: ReactNode; + secondaryMenu: ReactNode; + } + + export default function NavbarMobileSidebarLayout(props: Props): JSX.Element; +} + +declare module '@theme/Navbar/MobileSidebar/Toggle' { + export default function NavbarMobileSidebarToggle(): JSX.Element; +} + +declare module '@theme/Navbar/MobileSidebar/PrimaryMenu' { + export default function NavbarMobileSidebarPrimaryMenu(): JSX.Element; +} + +declare module '@theme/Navbar/MobileSidebar/SecondaryMenu' { + export default function NavbarMobileSidebarSecondaryMenu(): JSX.Element; +} + +declare module '@theme/Navbar/MobileSidebar/Header' { + export default function NavbarMobileSidebarHeader(): JSX.Element; } declare module '@theme/NavbarItem/DefaultNavbarItem' { @@ -324,15 +644,15 @@ declare module '@theme/NavbarItem/DefaultNavbarItem' { declare module '@theme/NavbarItem/NavbarNavLink' { import type {ReactNode} from 'react'; - import type {LinkProps} from '@docusaurus/Link'; + import type {Props as LinkProps} from '@docusaurus/Link'; - export type Props = LinkProps & { + export interface Props extends LinkProps { readonly activeBasePath?: string; readonly activeBaseRegex?: string; readonly exact?: boolean; readonly label?: ReactNode; readonly prependBaseUrlToHref?: string; - }; + } export default function NavbarNavLink(props: Props): JSX.Element; } @@ -352,8 +672,7 @@ declare module '@theme/NavbarItem/DropdownNavbarItem' { readonly mobile?: boolean; } - const DropdownNavbarItem: (props: Props) => JSX.Element; - export default DropdownNavbarItem; + export default function DropdownNavbarItem(props: Props): JSX.Element; } declare module '@theme/NavbarItem/SearchNavbarItem' { @@ -361,8 +680,7 @@ declare module '@theme/NavbarItem/SearchNavbarItem' { readonly mobile?: boolean; } - const SearchNavbarItem: (props: Props) => JSX.Element; - export default SearchNavbarItem; + export default function SearchNavbarItem(props: Props): JSX.Element; } declare module '@theme/NavbarItem/LocaleDropdownNavbarItem' { @@ -374,8 +692,7 @@ declare module '@theme/NavbarItem/LocaleDropdownNavbarItem' { readonly dropdownItemsAfter: LinkLikeNavbarItemProps[]; } - const LocaleDropdownNavbarItem: (props: Props) => JSX.Element; - export default LocaleDropdownNavbarItem; + export default function LocaleDropdownNavbarItem(props: Props): JSX.Element; } declare module '@theme/NavbarItem/DocsVersionDropdownNavbarItem' { @@ -389,8 +706,9 @@ declare module '@theme/NavbarItem/DocsVersionDropdownNavbarItem' { readonly dropdownItemsAfter: LinkLikeNavbarItemProps[]; } - const DocsVersionDropdownNavbarItem: (props: Props) => JSX.Element; - export default DocsVersionDropdownNavbarItem; + export default function DocsVersionDropdownNavbarItem( + props: Props, + ): JSX.Element; } declare module '@theme/NavbarItem/DocsVersionNavbarItem' { @@ -400,8 +718,7 @@ declare module '@theme/NavbarItem/DocsVersionNavbarItem' { readonly docsPluginId?: string; } - const DocsVersionNavbarItem: (props: Props) => JSX.Element; - export default DocsVersionNavbarItem; + export default function DocsVersionNavbarItem(props: Props): JSX.Element; } declare module '@theme/NavbarItem/DocNavbarItem' { @@ -412,8 +729,7 @@ declare module '@theme/NavbarItem/DocNavbarItem' { readonly docsPluginId?: string; } - const DocsSidebarNavbarItem: (props: Props) => JSX.Element; - export default DocsSidebarNavbarItem; + export default function DocsSidebarNavbarItem(props: Props): JSX.Element; } declare module '@theme/NavbarItem/DocSidebarNavbarItem' { @@ -424,8 +740,7 @@ declare module '@theme/NavbarItem/DocSidebarNavbarItem' { readonly docsPluginId?: string; } - const DocSidebarNavbarItem: (props: Props) => JSX.Element; - export default DocSidebarNavbarItem; + export default function DocSidebarNavbarItem(props: Props): JSX.Element; } declare module '@theme/NavbarItem' { @@ -461,12 +776,18 @@ declare module '@theme/NavbarItem' { export type Types = Props['type']; - const NavbarItem: (props: Props) => JSX.Element; - export default NavbarItem; + export default function NavbarItem(props: Props): JSX.Element; } declare module '@theme/NavbarItem/utils' { - export function getInfimaActiveClassName(mobile?: boolean): string; + /** + * On desktop and mobile, we would apply different class names for dropdown + * items. + * @see https://github.com/facebook/docusaurus/pull/5431 + */ + export function getInfimaActiveClassName( + mobile?: boolean, + ): `${'menu' | 'navbar'}__link--active`; } declare module '@theme/PaginatorNavLink' { @@ -495,11 +816,10 @@ declare module '@theme/TabItem' { readonly label?: string; readonly hidden?: boolean; readonly className?: string; - readonly attributes?: Record; + readonly attributes?: {[key: string]: unknown}; } - const TabItem: (props: Props) => JSX.Element; - export default TabItem; + export default function TabItem(props: Props): JSX.Element; } declare module '@theme/Tabs' { @@ -514,14 +834,13 @@ declare module '@theme/Tabs' { readonly values?: readonly { value: string; label?: string; - attributes?: Record; + attributes?: {[key: string]: unknown}; }[]; readonly groupId?: string; readonly className?: string; } - const Tabs: (props: Props) => JSX.Element; - export default Tabs; + export default function Tabs(props: Props): JSX.Element; } declare module '@theme/ThemedImage' { @@ -534,8 +853,7 @@ declare module '@theme/ThemedImage' { }; } - const ThemedImage: (props: Props) => JSX.Element; - export default ThemedImage; + export default function ThemedImage(props: Props): JSX.Element; } declare module '@theme/Details' { @@ -548,81 +866,72 @@ declare module '@theme/Details' { declare module '@theme/TOCItems' { import type {TOCItem} from '@docusaurus/types'; - export type TOCItemsProps = { + export interface Props { readonly toc: readonly TOCItem[]; readonly minHeadingLevel?: number; readonly maxHeadingLevel?: number; readonly className?: string; readonly linkClassName?: string | null; readonly linkActiveClassName?: string; - }; + } - export default function TOCItems(props: TOCItemsProps): JSX.Element; + export default function TOCItems(props: Props): JSX.Element; } declare module '@theme/TOC' { import type {TOCItem} from '@docusaurus/types'; - // minHeadingLevel only exists as a per-doc option, - // and won't have a default set by Joi. See TOC, TOCInline, - // TOCCollapsible for examples - export type TOCProps = { + // minHeadingLevel only exists as a per-doc option, and won't have a default + // set by Joi. See TOC, TOCInline, TOCCollapsible for examples + export interface Props { readonly toc: readonly TOCItem[]; readonly minHeadingLevel?: number; readonly maxHeadingLevel?: number; readonly className?: string; - }; - - export type TOCHeadingsProps = { - readonly toc: readonly TOCItem[]; - readonly minHeadingLevel?: number; - readonly maxHeadingLevel?: number; - }; - - export const TOCHeadings: (props: TOCHeadingsProps) => JSX.Element; + } - const TOC: (props: TOCProps) => JSX.Element; - export default TOC; + export default function TOC(props: Props): JSX.Element; } declare module '@theme/TOCInline' { import type {TOCItem} from '@docusaurus/types'; - export type TOCInlineProps = { + export interface Props { readonly toc: readonly TOCItem[]; readonly minHeadingLevel?: number; readonly maxHeadingLevel?: number; - }; + } - const TOCInline: (props: TOCInlineProps) => JSX.Element; - export default TOCInline; + export default function TOCInline(props: Props): JSX.Element; } declare module '@theme/TOCCollapsible' { import type {TOCItem} from '@docusaurus/types'; - export type TOCCollapsibleProps = { + export interface Props { readonly className?: string; readonly minHeadingLevel?: number; readonly maxHeadingLevel?: number; readonly toc: readonly TOCItem[]; - }; + } - const TOCCollapsible: (props: TOCCollapsibleProps) => JSX.Element; - export default TOCCollapsible; + export default function TOCCollapsible(props: Props): JSX.Element; } -declare module '@theme/Toggle' { - import type {SyntheticEvent} from 'react'; +declare module '@theme/ColorModeToggle' { + import type {ColorMode} from '@docusaurus/theme-common'; export interface Props { readonly className?: string; - readonly checked: boolean; - readonly onChange: (e: SyntheticEvent) => void; + readonly value: ColorMode; + /** + * The parameter represents the "to-be" value. For example, if currently in + * dark mode, clicking the button should call `onChange("light")` + */ + readonly onChange: (colorMode: ColorMode) => void; } - const Toggle: (props: Props) => JSX.Element; - export default Toggle; + export default function ColorModeToggle(props: Props): JSX.Element; } declare module '@theme/Logo' { @@ -633,8 +942,7 @@ declare module '@theme/Logo' { readonly titleClassName?: string; } - const Logo: (props: Props) => JSX.Element; - export default Logo; + export default function Logo(props: Props): JSX.Element; } declare module '@theme/IconArrow' { @@ -642,8 +950,15 @@ declare module '@theme/IconArrow' { export interface Props extends ComponentProps<'svg'> {} - const IconArrow: (props: Props) => JSX.Element; - export default IconArrow; + export default function IconArrow(props: Props): JSX.Element; +} + +declare module '@theme/IconDarkMode' { + import type {ComponentProps} from 'react'; + + export interface Props extends ComponentProps<'svg'> {} + + export default function IconDarkMode(props: Props): JSX.Element; } declare module '@theme/IconEdit' { @@ -651,8 +966,15 @@ declare module '@theme/IconEdit' { export interface Props extends ComponentProps<'svg'> {} - const IconEdit: (props: Props) => JSX.Element; - export default IconEdit; + export default function IconEdit(props: Props): JSX.Element; +} + +declare module '@theme/IconLightMode' { + import type {ComponentProps} from 'react'; + + export interface Props extends ComponentProps<'svg'> {} + + export default function IconLightMode(props: Props): JSX.Element; } declare module '@theme/IconMenu' { @@ -660,8 +982,7 @@ declare module '@theme/IconMenu' { export interface Props extends ComponentProps<'svg'> {} - const IconMenu: (props: Props) => JSX.Element; - export default IconMenu; + export default function IconMenu(props: Props): JSX.Element; } declare module '@theme/IconClose' { @@ -669,8 +990,7 @@ declare module '@theme/IconClose' { export interface Props extends ComponentProps<'svg'> {} - const IconClose: (props: Props) => JSX.Element; - export default IconClose; + export default function IconClose(props: Props): JSX.Element; } declare module '@theme/IconLanguage' { @@ -678,8 +998,7 @@ declare module '@theme/IconLanguage' { export interface Props extends ComponentProps<'svg'> {} - const IconLanguage: (props: Props) => JSX.Element; - export default IconLanguage; + export default function IconLanguage(props: Props): JSX.Element; } declare module '@theme/IconExternalLink' { @@ -687,16 +1006,12 @@ declare module '@theme/IconExternalLink' { export interface Props extends ComponentProps<'svg'> {} - const IconExternalLink: (props: Props) => JSX.Element; - export default IconExternalLink; + export default function IconExternalLink(props: Props): JSX.Element; } declare module '@theme/TagsListByLetter' { - export type TagsListItem = Readonly<{ - name: string; - permalink: string; - count: number; - }>; + import type {TagsListItem} from '@docusaurus/theme-common'; + export interface Props { readonly tags: readonly TagsListItem[]; } @@ -704,7 +1019,8 @@ declare module '@theme/TagsListByLetter' { } declare module '@theme/TagsListInline' { - export type Tag = Readonly<{label: string; permalink: string}>; + import type {Tag} from '@docusaurus/utils'; + export interface Props { readonly tags: readonly Tag[]; } @@ -712,7 +1028,7 @@ declare module '@theme/TagsListInline' { } declare module '@theme/Tag' { - import type {TagsListItem} from '@theme/TagsListByLetter'; + import type {TagsListItem} from '@docusaurus/theme-common'; import type {Optional} from 'utility-types'; export interface Props extends Optional {} diff --git a/packages/docusaurus-theme-classic/src/theme/AnnouncementBar/index.tsx b/packages/docusaurus-theme-classic/src/theme/AnnouncementBar/index.tsx index f42acb6a197d..26ad85727639 100644 --- a/packages/docusaurus-theme-classic/src/theme/AnnouncementBar/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/AnnouncementBar/index.tsx @@ -13,7 +13,7 @@ import IconClose from '@theme/IconClose'; import styles from './styles.module.css'; -function AnnouncementBar(): JSX.Element | null { +export default function AnnouncementBar(): JSX.Element | null { const {isActive, close} = useAnnouncementBar(); const {announcementBar} = useThemeConfig(); @@ -51,5 +51,3 @@ function AnnouncementBar(): JSX.Element | null {

); } - -export default AnnouncementBar; diff --git a/packages/docusaurus-theme-classic/src/theme/BackToTopButton/index.tsx b/packages/docusaurus-theme-classic/src/theme/BackToTopButton/index.tsx index 1b88c9ed626f..d5e4edfc4134 100644 --- a/packages/docusaurus-theme-classic/src/theme/BackToTopButton/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/BackToTopButton/index.tsx @@ -5,119 +5,15 @@ * LICENSE file in the root directory of this source tree. */ -import React, {useRef, useState} from 'react'; +import React from 'react'; import clsx from 'clsx'; import {translate} from '@docusaurus/Translate'; +import {ThemeClassNames, useBackToTopButton} from '@docusaurus/theme-common'; import styles from './styles.module.css'; -import { - ThemeClassNames, - useScrollPosition, - useLocationChange, -} from '@docusaurus/theme-common'; - -const threshold = 300; - -// Not all have support for smooth scrolling (particularly Safari mobile iOS) -// TODO proper detection is currently unreliable! -// see https://github.com/wessberg/scroll-behavior-polyfill/issues/16 -const SupportsNativeSmoothScrolling = false; -// const SupportsNativeSmoothScrolling = ExecutionEnvironment.canUseDOM && 'scrollBehavior' in document.documentElement.style; - -type CancelScrollTop = () => void; - -function smoothScrollTopNative(): CancelScrollTop { - window.scrollTo({top: 0, behavior: 'smooth'}); - return () => { - // Nothing to cancel, it's natively cancelled if user tries to scroll down - }; -} - -function smoothScrollTopPolyfill(): CancelScrollTop { - let raf: number | null = null; - function rafRecursion() { - const currentScroll = document.documentElement.scrollTop; - if (currentScroll > 0) { - raf = requestAnimationFrame(rafRecursion); - window.scrollTo(0, Math.floor(currentScroll * 0.85)); - } - } - rafRecursion(); - - // Break the recursion - // Prevents the user from "fighting" against that recursion producing a weird UX - return () => raf && cancelAnimationFrame(raf); -} - -type UseSmoothScrollTopReturn = { - // We use a cancel function because the non-native smooth scroll-top implementation must be interrupted if user scroll down - smoothScrollTop: () => void; - cancelScrollToTop: CancelScrollTop; -}; - -function useSmoothScrollToTop(): UseSmoothScrollTopReturn { - const lastCancelRef = useRef(null); - - function smoothScrollTop(): void { - lastCancelRef.current = SupportsNativeSmoothScrolling - ? smoothScrollTopNative() - : smoothScrollTopPolyfill(); - } - - return { - smoothScrollTop, - cancelScrollToTop: () => lastCancelRef.current?.(), - }; -} - -function BackToTopButton(): JSX.Element { - const [show, setShow] = useState(false); - const isFocusedAnchor = useRef(false); - const {smoothScrollTop, cancelScrollToTop} = useSmoothScrollToTop(); - - useScrollPosition(({scrollY: scrollTop}, lastPosition) => { - const lastScrollTop = lastPosition?.scrollY; - - // No lastScrollTop means component is just being mounted. - // Not really a scroll event from the user, so we ignore it - if (!lastScrollTop) { - return; - } - - if (isFocusedAnchor.current) { - isFocusedAnchor.current = false; - return; - } - - const isScrollingUp = scrollTop < lastScrollTop; - - if (!isScrollingUp) { - cancelScrollToTop(); - } - - if (scrollTop < threshold) { - setShow(false); - return; - } - - if (isScrollingUp) { - const documentHeight = document.documentElement.scrollHeight; - const windowHeight = window.innerHeight; - if (scrollTop + windowHeight < documentHeight) { - setShow(true); - } - } else { - setShow(false); - } - }); - - useLocationChange((locationChangeEvent) => { - if (locationChangeEvent.location.hash) { - isFocusedAnchor.current = true; - setShow(false); - } - }); +export default function BackToTopButton(): JSX.Element { + const {shown, scrollToTop} = useBackToTopButton({threshold: 300}); return ( + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/CodeBlock/CopyButton/styles.module.css b/packages/docusaurus-theme-classic/src/theme/CodeBlock/CopyButton/styles.module.css new file mode 100644 index 000000000000..f91a3d6aacfd --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/CodeBlock/CopyButton/styles.module.css @@ -0,0 +1,66 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +.copyButton { + display: flex; + background: inherit; + border: 1px solid var(--ifm-color-emphasis-300); + border-radius: var(--ifm-global-radius); + padding: 0.4rem; + position: absolute; + right: calc(var(--ifm-pre-padding) / 2); + top: calc(var(--ifm-pre-padding) / 2); + transition: opacity 200ms ease-in-out; + opacity: 0; +} + +.copyButton:focus-visible, +.copyButton:hover, +:global(.theme-code-block:hover) .copyButtonCopied { + opacity: 1 !important; +} + +:global(.theme-code-block:hover) .copyButton:not(.copyButtonCopied) { + opacity: 0.4; +} + +.copyButtonIcons { + position: relative; + width: 1.125rem; + height: 1.125rem; +} + +.copyButtonIcon, +.copyButtonSuccessIcon { + position: absolute; + top: 0; + left: 0; + fill: currentColor; + opacity: inherit; + width: inherit; + height: inherit; + transition: all 0.15s ease; +} + +.copyButtonSuccessIcon { + top: 50%; + left: 50%; + transform: translate(-50%, -50%) scale(0.33); + opacity: 0; + color: #00d600; +} + +.copyButtonCopied .copyButtonIcon { + transform: scale(0.33); + opacity: 0; +} + +.copyButtonCopied .copyButtonSuccessIcon { + transform: translate(-50%, -50%) scale(1); + opacity: 1; + transition-delay: 0.075s; +} diff --git a/packages/docusaurus-theme-classic/src/theme/CodeBlock/index.tsx b/packages/docusaurus-theme-classic/src/theme/CodeBlock/index.tsx index b33421105f41..40ab6581fddb 100644 --- a/packages/docusaurus-theme-classic/src/theme/CodeBlock/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/CodeBlock/index.tsx @@ -8,8 +8,6 @@ import React, {isValidElement, useEffect, useState} from 'react'; import clsx from 'clsx'; import Highlight, {defaultProps, type Language} from 'prism-react-renderer'; -import copy from 'copy-text-to-clipboard'; -import Translate, {translate} from '@docusaurus/Translate'; import { useThemeConfig, parseCodeBlockTitle, @@ -18,6 +16,7 @@ import { ThemeClassNames, usePrismTheme, } from '@docusaurus/theme-common'; +import CopyButton from '@theme/CodeBlock/CopyButton'; import type {Props} from '@theme/CodeBlock'; import styles from './styles.module.css'; @@ -31,7 +30,6 @@ export default function CodeBlock({ }: Props): JSX.Element { const {prism} = useThemeConfig(); - const [showCopied, setShowCopied] = useState(false); const [mounted, setMounted] = useState(false); // The Prism theme on SSR is always the default theme but the site theme // can be in a different mode. React hydration doesn't update DOM styles @@ -51,7 +49,8 @@ export default function CodeBlock({ const prismTheme = usePrismTheme(); //
 tags in markdown map to CodeBlocks and they may contain JSX children.
-  // When the children is not a simple string, we just return a styled block without actually highlighting.
+  // When the children is not a simple string, we just return a styled block
+  // without actually highlighting.
   if (React.Children.toArray(children).some((el) => isValidElement(el))) {
     return (
        {
-    copy(code);
-    setShowCopied(true);
-
-    setTimeout(() => setShowCopied(false), 2000);
-  };
-
   return (
     
           )}
-          
+
+              className={clsx(className, styles.codeBlock, 'thin-scrollbar')}>
               
                 {tokens.map((line, i) => {
-                  if (line.length === 1 && line[0].content === '\n') {
-                    line[0].content = '';
+                  if (line.length === 1 && line[0]!.content === '\n') {
+                    line[0]!.content = '';
                   }
 
                   const lineProps = getLineProps({line, key: i});
@@ -149,29 +140,7 @@ export default function CodeBlock({
               
             
- +
)} diff --git a/packages/docusaurus-theme-classic/src/theme/CodeBlock/styles.module.css b/packages/docusaurus-theme-classic/src/theme/CodeBlock/styles.module.css index a1bd413658d8..c74136412f0e 100644 --- a/packages/docusaurus-theme-classic/src/theme/CodeBlock/styles.module.css +++ b/packages/docusaurus-theme-classic/src/theme/CodeBlock/styles.module.css @@ -7,14 +7,15 @@ .codeBlockContainer { margin-bottom: var(--ifm-leading); - border-radius: var(--ifm-global-radius); box-shadow: var(--ifm-global-shadow-lw); + border-radius: var(--ifm-code-border-radius); } .codeBlockContent { position: relative; /* rtl:ignore */ direction: ltr; + border-radius: inherit; } .codeBlockTitle { @@ -22,14 +23,14 @@ font-size: var(--ifm-code-font-size); font-weight: 500; padding: 0.75rem var(--ifm-pre-padding); - border-top-left-radius: var(--ifm-global-radius); - border-top-right-radius: var(--ifm-global-radius); + border-top-left-radius: inherit; + border-top-right-radius: inherit; } .codeBlock { margin: 0; padding: 0; - border-radius: var(--ifm-global-radius); + background-color: inherit; } .codeBlockTitle + .codeBlockContent .codeBlock { @@ -39,26 +40,6 @@ .codeBlockStandalone { padding: 0; - border-radius: var(--ifm-global-radius); -} - -.copyButton { - background: rgb(0 0 0 / 30%); - border-radius: var(--ifm-global-radius); - color: var(--ifm-color-white); - opacity: 0; - user-select: none; - padding: 0.4rem 0.5rem; - position: absolute; - right: calc(var(--ifm-pre-padding) / 2); - top: calc(var(--ifm-pre-padding) / 2); - transition: opacity 200ms ease-in-out; -} - -.copyButton:focus, -.codeBlockContent:hover > .copyButton, -.codeBlockTitle:hover + .codeBlockContent .copyButton { - opacity: 1; } .codeBlockLines { diff --git a/packages/docusaurus-theme-classic/src/theme/ColorModeToggle/index.tsx b/packages/docusaurus-theme-classic/src/theme/ColorModeToggle/index.tsx new file mode 100644 index 000000000000..e119f2ae33ed --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/ColorModeToggle/index.tsx @@ -0,0 +1,67 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import type {Props} from '@theme/ColorModeToggle'; +import useIsBrowser from '@docusaurus/useIsBrowser'; +import {translate} from '@docusaurus/Translate'; +import IconLightMode from '@theme/IconLightMode'; +import IconDarkMode from '@theme/IconDarkMode'; + +import clsx from 'clsx'; +import styles from './styles.module.css'; + +function ColorModeToggle({className, value, onChange}: Props): JSX.Element { + const isBrowser = useIsBrowser(); + + const title = translate( + { + message: 'Switch between dark and light mode (currently {mode})', + id: 'theme.colorToggle.ariaLabel', + description: 'The ARIA label for the navbar color mode toggle', + }, + { + mode: + value === 'dark' + ? translate({ + message: 'dark mode', + id: 'theme.colorToggle.ariaLabel.mode.dark', + description: 'The name for the dark color mode', + }) + : translate({ + message: 'light mode', + id: 'theme.colorToggle.ariaLabel.mode.light', + description: 'The name for the light color mode', + }), + }, + ); + + return ( +
+ +
+ ); +} + +export default React.memo(ColorModeToggle); diff --git a/packages/docusaurus-theme-classic/src/theme/ColorModeToggle/styles.module.css b/packages/docusaurus-theme-classic/src/theme/ColorModeToggle/styles.module.css new file mode 100644 index 000000000000..37463e0fb330 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/ColorModeToggle/styles.module.css @@ -0,0 +1,35 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +.toggle { + width: 2rem; + height: 2rem; +} + +.toggleButton { + -webkit-tap-highlight-color: transparent; + align-items: center; + display: flex; + justify-content: center; + width: 100%; + height: 100%; + border-radius: 50%; + transition: background var(--ifm-transition-fast); +} + +.toggleButton:hover { + background: var(--ifm-color-emphasis-200); +} + +[data-theme='light'] .darkToggleIcon, +[data-theme='dark'] .lightToggleIcon { + display: none; +} + +.toggleButtonDisabled { + cursor: not-allowed; +} diff --git a/packages/docusaurus-theme-classic/src/theme/Details/index.tsx b/packages/docusaurus-theme-classic/src/theme/Details/index.tsx index 2a870227b6a4..724863d73088 100644 --- a/packages/docusaurus-theme-classic/src/theme/Details/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/Details/index.tsx @@ -11,7 +11,8 @@ import {Details as DetailsGeneric} from '@docusaurus/theme-common'; import type {Props} from '@theme/Details'; import styles from './styles.module.css'; -// Should we have a custom details/summary comp in Infima instead of reusing alert classes? +// Should we have a custom details/summary comp in Infima instead of reusing +// alert classes? const InfimaClasses = 'alert alert--info'; export default function Details({...props}: Props): JSX.Element { diff --git a/packages/docusaurus-theme-classic/src/theme/DocBreadcrumbs/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocBreadcrumbs/index.tsx new file mode 100644 index 000000000000..780579774851 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/DocBreadcrumbs/index.tsx @@ -0,0 +1,110 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, {type ReactNode} from 'react'; +import { + ThemeClassNames, + useSidebarBreadcrumbs, + useHomePageRoute, +} from '@docusaurus/theme-common'; +import styles from './styles.module.css'; +import clsx from 'clsx'; +import Link from '@docusaurus/Link'; +import useBaseUrl from '@docusaurus/useBaseUrl'; + +// TODO move to design system folder +function BreadcrumbsItemLink({ + children, + href, +}: { + children: ReactNode; + href?: string; +}): JSX.Element { + const className = 'breadcrumbs__link'; + return href ? ( + + {children} + + ) : ( + + {children} + + ); +} + +// TODO move to design system folder +function BreadcrumbsItem({ + children, + active, + index, +}: { + children: ReactNode; + active?: boolean; + index: number; +}): JSX.Element { + return ( +
  • + {children} + +
  • + ); +} + +function HomeBreadcrumbItem() { + const homeHref = useBaseUrl('/'); + return ( +
  • + + 🏠 + +
  • + ); +} + +export default function DocBreadcrumbs(): JSX.Element | null { + const breadcrumbs = useSidebarBreadcrumbs(); + const homePageRoute = useHomePageRoute(); + + if (!breadcrumbs) { + return null; + } + + return ( + + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/DocBreadcrumbs/styles.module.css b/packages/docusaurus-theme-classic/src/theme/DocBreadcrumbs/styles.module.css new file mode 100644 index 000000000000..a400c5d9e622 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/DocBreadcrumbs/styles.module.css @@ -0,0 +1,11 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +.breadcrumbsContainer { + --ifm-breadcrumb-size-multiplier: 0.8; + margin-bottom: 0.8rem; +} diff --git a/packages/docusaurus-theme-classic/src/theme/DocCard/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocCard/index.tsx index 9f96259f576e..15c8ac155d51 100644 --- a/packages/docusaurus-theme-classic/src/theme/DocCard/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/DocCard/index.tsx @@ -16,25 +16,21 @@ import {findFirstCategoryLink, useDocById} from '@docusaurus/theme-common'; import clsx from 'clsx'; import styles from './styles.module.css'; import isInternalUrl from '@docusaurus/isInternalUrl'; +import {translate} from '@docusaurus/Translate'; function CardContainer({ href, children, }: { - href?: string; + href: string; children: ReactNode; }): JSX.Element { - const className = clsx( - 'card margin-bottom--lg padding--lg', - styles.cardContainer, - href && styles.cardContainerLink, - ); - return href ? ( - + return ( + {children} - ) : ( -
    {children}
    ); } @@ -44,7 +40,7 @@ function CardLayout({ title, description, }: { - href?: string; + href: string; icon: ReactNode; title: string; description?: string; @@ -54,23 +50,43 @@ function CardLayout({

    {icon} {title}

    -
    - {description} -
    + {description && ( +

    + {description} +

    + )} ); } -function CardCategory({item}: {item: PropSidebarItemCategory}): JSX.Element { +function CardCategory({ + item, +}: { + item: PropSidebarItemCategory; +}): JSX.Element | null { const href = findFirstCategoryLink(item); + + // Unexpected: categories that don't have a link have been filtered upfront + if (!href) { + return null; + } + return ( ); } diff --git a/packages/docusaurus-theme-classic/src/theme/DocCard/styles.module.css b/packages/docusaurus-theme-classic/src/theme/DocCard/styles.module.css index 92145f2d68a5..63c3d9856b70 100644 --- a/packages/docusaurus-theme-classic/src/theme/DocCard/styles.module.css +++ b/packages/docusaurus-theme-classic/src/theme/DocCard/styles.module.css @@ -6,38 +6,29 @@ */ .cardContainer { - height: 8rem; - color: var(--ifm-color-emphasis-800); --ifm-link-color: var(--ifm-color-emphasis-800); - --ifm-link-hover-color: var(--ifm-color-emphasis-800); + --ifm-link-hover-color: var(--ifm-color-emphasis-700); --ifm-link-hover-decoration: none; - /* box-shadow: var(--ifm-global-shadow-lw); */ box-shadow: 0 1.5px 3px 0 rgb(0 0 0 / 15%); border: 1px solid var(--ifm-color-emphasis-200); - transition: box-shadow var(--ifm-transition-fast) ease, - background-color var(--ifm-transition-fast) ease; + transition: all var(--ifm-transition-fast) ease; + transition-property: border, box-shadow; } -.cardContainer.cardContainerLink:hover { - /* box-shadow: var(--ifm-global-shadow-md); */ - box-shadow: 0 4px 8px 0 rgb(0 0 0 / 20%); +.cardContainer:hover { + border-color: var(--ifm-color-primary); + box-shadow: 0 3px 6px 0 rgb(0 0 0 / 20%); } -html[data-theme='dark'] .cardContainer.cardContainerLink:hover { - --ifm-card-background-color: #2d2d2d; /* original, non-hovered color is #242526 */ -} - -.cardContainer:not(.cardContainerLink) { - cursor: not-allowed; +.cardContainer *:last-child { + margin-bottom: 0; } .cardTitle { font-size: 1.2rem; - min-height: 1.2rem; } .cardDescription { font-size: 0.8rem; - min-height: 0.8rem; } diff --git a/packages/docusaurus-theme-classic/src/theme/DocCardList/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocCardList/index.tsx index fec4a34eb948..052e88292523 100644 --- a/packages/docusaurus-theme-classic/src/theme/DocCardList/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/DocCardList/index.tsx @@ -9,6 +9,17 @@ import React from 'react'; import DocCard from '@theme/DocCard'; import type {PropSidebarItem} from '@docusaurus/plugin-content-docs'; +import {findFirstCategoryLink} from '@docusaurus/theme-common'; + +// Filter categories that don't have a link. +function filterItems(items: PropSidebarItem[]): PropSidebarItem[] { + return items.filter((item) => { + if (item.type === 'category') { + return !!findFirstCategoryLink(item); + } + return true; + }); +} export default function DocCardList({ items, @@ -17,9 +28,9 @@ export default function DocCardList({ }): JSX.Element { return (
    - {items.map((item, index) => ( -
    - + {filterItems(items).map((item, index) => ( +
    +
    ))}
    diff --git a/packages/docusaurus-theme-classic/src/theme/DocCategoryGeneratedIndexPage/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocCategoryGeneratedIndexPage/index.tsx index 1214a86aab3f..179839ac09cc 100644 --- a/packages/docusaurus-theme-classic/src/theme/DocCategoryGeneratedIndexPage/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/DocCategoryGeneratedIndexPage/index.tsx @@ -6,25 +6,42 @@ */ import React from 'react'; -import {useCurrentSidebarCategory} from '@docusaurus/theme-common'; +import { + PageMetadata, + useCurrentSidebarCategory, +} from '@docusaurus/theme-common'; import type {Props} from '@theme/DocCategoryGeneratedIndexPage'; import DocCardList from '@theme/DocCardList'; import DocPaginator from '@theme/DocPaginator'; -import Seo from '@theme/Seo'; import DocVersionBanner from '@theme/DocVersionBanner'; import DocVersionBadge from '@theme/DocVersionBadge'; +import DocBreadcrumbs from '@theme/DocBreadcrumbs'; import Heading from '@theme/Heading'; import useBaseUrl from '@docusaurus/useBaseUrl'; import styles from './styles.module.css'; -export default function DocCategoryGeneratedIndexPage({ +function DocCategoryGeneratedIndexPageMetadata({ + categoryGeneratedIndex, +}: Props): JSX.Element { + return ( + + ); +} + +function DocCategoryGeneratedIndexPageContent({ categoryGeneratedIndex, }: Props): JSX.Element { const category = useCurrentSidebarCategory(); return ( <> -
    +
    @@ -55,3 +73,14 @@ export default function DocCategoryGeneratedIndexPage({ ); } + +export default function DocCategoryGeneratedIndexPage( + props: Props, +): JSX.Element { + return ( + <> + + + + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/DocItem/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocItem/index.tsx index ef83ca041fad..77265efccbd2 100644 --- a/packages/docusaurus-theme-classic/src/theme/DocItem/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/DocItem/index.tsx @@ -10,30 +10,44 @@ import clsx from 'clsx'; import DocPaginator from '@theme/DocPaginator'; import DocVersionBanner from '@theme/DocVersionBanner'; import DocVersionBadge from '@theme/DocVersionBadge'; -import Seo from '@theme/Seo'; import type {Props} from '@theme/DocItem'; import DocItemFooter from '@theme/DocItemFooter'; import TOC from '@theme/TOC'; import TOCCollapsible from '@theme/TOCCollapsible'; import Heading from '@theme/Heading'; import styles from './styles.module.css'; -import {ThemeClassNames, useWindowSize} from '@docusaurus/theme-common'; +import { + PageMetadata, + HtmlClassNameProvider, + ThemeClassNames, + useWindowSize, +} from '@docusaurus/theme-common'; +import DocBreadcrumbs from '@theme/DocBreadcrumbs'; +import MDXContent from '@theme/MDXContent'; -export default function DocItem(props: Props): JSX.Element { +function DocItemMetadata(props: Props): JSX.Element { + const {content: DocContent} = props; + const {metadata, frontMatter, assets} = DocContent; + const {keywords} = frontMatter; + const {description, title} = metadata; + const image = assets.image ?? frontMatter.image; + + return ; +} + +function DocItemContent(props: Props): JSX.Element { const {content: DocContent} = props; const {metadata, frontMatter} = DocContent; const { - image, - keywords, hide_title: hideTitle, hide_table_of_contents: hideTableOfContents, toc_min_heading_level: tocMinHeadingLevel, toc_max_heading_level: tocMaxHeadingLevel, } = frontMatter; - const {description, title} = metadata; + const {title} = metadata; // We only add a title if: - // - user asks to hide it with front matter + // - user doesn't ask to hide it with front matter // - the markdown content does not already contain a top-level h1 heading const shouldAddTitle = !hideTitle && typeof DocContent.contentTitle === 'undefined'; @@ -47,64 +61,69 @@ export default function DocItem(props: Props): JSX.Element { canRenderTOC && (windowSize === 'desktop' || windowSize === 'ssr'); return ( - <> - - -
    -
    - -
    -
    - +
    +
    + +
    +
    + + - {canRenderTOC && ( - - )} + {canRenderTOC && ( + + )} -
    - {/* - Title can be declared inside md content or declared through front matter and added manually - To make both cases consistent, the added title is added under the same div.markdown block +
    + {/* + Title can be declared inside md content or declared through + front matter and added manually. To make both cases consistent, + the added title is added under the same div.markdown block See https://github.com/facebook/docusaurus/pull/4882#issuecomment-853021120 */} - {shouldAddTitle && ( -
    - {title} -
    - )} - + {shouldAddTitle && ( +
    + {title} +
    + )} + -
    + +
    - -
    + +
    - -
    +
    - {renderTocDesktop && ( -
    - -
    - )}
    - + {renderTocDesktop && ( +
    + +
    + )} +
    + ); +} + +export default function DocItem(props: Props): JSX.Element { + const docHtmlClassName = `docs-doc-id-${props.content.metadata.unversionedId}`; + return ( + + + + ); } diff --git a/packages/docusaurus-theme-classic/src/theme/DocPage/Layout/Aside.tsx b/packages/docusaurus-theme-classic/src/theme/DocPage/Layout/Aside.tsx new file mode 100644 index 000000000000..236382eae4ff --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/DocPage/Layout/Aside.tsx @@ -0,0 +1,100 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, {type ReactNode, useState, useCallback} from 'react'; +import DocSidebar from '@theme/DocSidebar'; +import IconArrow from '@theme/IconArrow'; +import {translate} from '@docusaurus/Translate'; +import {useLocation} from '@docusaurus/router'; +import type {Props} from '@theme/DocPage/Layout/Aside'; + +import clsx from 'clsx'; +import styles from './styles.module.css'; + +import {ThemeClassNames, useDocsSidebar} from '@docusaurus/theme-common'; + +function SidebarExpandButton({toggleSidebar}: {toggleSidebar: () => void}) { + return ( +
    + +
    + ); +} + +// Reset sidebar state when sidebar changes +// Use React key to unmount/remount the children +// See https://github.com/facebook/docusaurus/issues/3414 +function ResetOnSidebarChange({children}: {children: ReactNode}) { + const sidebar = useDocsSidebar(); + return ( + + {children} + + ); +} + +export default function DocPageLayoutAside({ + sidebar, + hiddenSidebarContainer, + setHiddenSidebarContainer, +}: Props): JSX.Element { + const {pathname} = useLocation(); + + const [hiddenSidebar, setHiddenSidebar] = useState(false); + const toggleSidebar = useCallback(() => { + if (hiddenSidebar) { + setHiddenSidebar(false); + } + setHiddenSidebarContainer((value) => !value); + }, [setHiddenSidebarContainer, hiddenSidebar]); + + return ( + + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/DocPage/Layout/Main.tsx b/packages/docusaurus-theme-classic/src/theme/DocPage/Layout/Main.tsx new file mode 100644 index 000000000000..3cc4f0479e00 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/DocPage/Layout/Main.tsx @@ -0,0 +1,37 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; + +import {useDocsSidebar} from '@docusaurus/theme-common'; + +import clsx from 'clsx'; +import styles from './styles.module.css'; +import type {Props} from '@theme/DocPage/Layout/Main'; + +export default function DocPageLayoutMain({ + hiddenSidebarContainer, + children, +}: Props): JSX.Element { + const sidebar = useDocsSidebar(); + return ( +
    +
    + {children} +
    +
    + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/DocPage/Layout/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocPage/Layout/index.tsx new file mode 100644 index 000000000000..858a24e39183 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/DocPage/Layout/index.tsx @@ -0,0 +1,39 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, {useState} from 'react'; +import Layout from '@theme/Layout'; +import BackToTopButton from '@theme/BackToTopButton'; +import type {Props} from '@theme/DocPage/Layout'; +import DocPageLayoutAside from '@theme/DocPage/Layout/Aside'; +import DocPageLayoutMain from '@theme/DocPage/Layout/Main'; + +import styles from './styles.module.css'; + +import {useDocsSidebar} from '@docusaurus/theme-common'; + +export default function DocPageLayout({children}: Props): JSX.Element { + const sidebar = useDocsSidebar(); + const [hiddenSidebarContainer, setHiddenSidebarContainer] = useState(false); + return ( + + +
    + {sidebar && ( + + )} + + {children} + +
    +
    + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/DocPage/styles.module.css b/packages/docusaurus-theme-classic/src/theme/DocPage/Layout/styles.module.css similarity index 89% rename from packages/docusaurus-theme-classic/src/theme/DocPage/styles.module.css rename to packages/docusaurus-theme-classic/src/theme/DocPage/Layout/styles.module.css index 5d6f675ee8d0..bcd30b7193b2 100644 --- a/packages/docusaurus-theme-classic/src/theme/DocPage/styles.module.css +++ b/packages/docusaurus-theme-classic/src/theme/DocPage/Layout/styles.module.css @@ -10,14 +10,15 @@ --doc-sidebar-hidden-width: 30px; } -:global(.docs-wrapper) { - display: flex; +.docPage, +.docMainContainer { + width: 100%; } +.docsWrapper, .docPage, .docMainContainer { display: flex; - width: 100%; } .docSidebarContainer { @@ -69,12 +70,12 @@ transform: rotate(0); } - html[dir='rtl'] .expandSidebarButtonIcon { + [dir='rtl'] .expandSidebarButtonIcon { transform: rotate(180deg); } - html[data-theme='dark'] .collapsedDocSidebar:hover, - html[data-theme='dark'] .collapsedDocSidebar:focus { + [data-theme='dark'] .collapsedDocSidebar:hover, + [data-theme='dark'] .collapsedDocSidebar:focus { background-color: var(--collapse-button-bg-color-dark); } diff --git a/packages/docusaurus-theme-classic/src/theme/DocPage/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocPage/index.tsx index 27648505a92b..2c908cc05535 100644 --- a/packages/docusaurus-theme-classic/src/theme/DocPage/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/DocPage/index.tsx @@ -5,147 +5,30 @@ * LICENSE file in the root directory of this source tree. */ -import React, {type ReactNode, useState, useCallback} from 'react'; -import {MDXProvider} from '@mdx-js/react'; - +import React from 'react'; import renderRoutes from '@docusaurus/renderRoutes'; -import type {PropVersionMetadata} from '@docusaurus/plugin-content-docs'; -import Layout from '@theme/Layout'; -import DocSidebar from '@theme/DocSidebar'; -import MDXComponents from '@theme/MDXComponents'; +import type {PropSidebar} from '@docusaurus/plugin-content-docs'; import NotFound from '@theme/NotFound'; -import type {DocumentRoute} from '@theme/DocItem'; import type {Props} from '@theme/DocPage'; -import IconArrow from '@theme/IconArrow'; -import BackToTopButton from '@theme/BackToTopButton'; +import DocPageLayout from '@theme/DocPage/Layout'; import {matchPath} from '@docusaurus/router'; -import {translate} from '@docusaurus/Translate'; import clsx from 'clsx'; -import styles from './styles.module.css'; + import { + HtmlClassNameProvider, ThemeClassNames, docVersionSearchTag, DocsSidebarProvider, - useDocsSidebar, DocsVersionProvider, } from '@docusaurus/theme-common'; -import Head from '@docusaurus/Head'; - -type DocPageContentProps = { - readonly currentDocRoute: DocumentRoute; - readonly versionMetadata: PropVersionMetadata; - readonly children: ReactNode; - readonly sidebarName: string | undefined; -}; - -function DocPageContent({ - currentDocRoute, - versionMetadata, - children, - sidebarName, -}: DocPageContentProps): JSX.Element { - const sidebar = useDocsSidebar(); - const {pluginId, version} = versionMetadata; - const [hiddenSidebarContainer, setHiddenSidebarContainer] = useState(false); - const [hiddenSidebar, setHiddenSidebar] = useState(false); - const toggleSidebar = useCallback(() => { - if (hiddenSidebar) { - setHiddenSidebar(false); - } - - setHiddenSidebarContainer((value) => !value); - }, [hiddenSidebar]); - - return ( - -
    - - - {sidebar && ( - - )} -
    -
    - {children} -
    -
    -
    -
    - ); -} +import SearchMetadata from '@theme/SearchMetadata'; -function DocPage(props: Props): JSX.Element { +function extractDocRouteMetadata(props: Props): null | { + docElement: JSX.Element; + sidebarName: string | undefined; + sidebarItems: PropSidebar | undefined; +} { const { route: {routes: docRoutes}, versionMetadata, @@ -155,34 +38,56 @@ function DocPage(props: Props): JSX.Element { matchPath(location.pathname, docRoute), ); if (!currentDocRoute) { - return ; + return null; } // For now, the sidebarName is added as route config: not ideal! const sidebarName = currentDocRoute.sidebar; - const sidebar = sidebarName + const sidebarItems = sidebarName ? versionMetadata.docsSidebars[sidebarName] - : null; + : undefined; + const docElement = renderRoutes(props.route.routes, { + versionMetadata, + }); + + return { + docElement, + sidebarName, + sidebarItems, + }; +} + +export default function DocPage(props: Props): JSX.Element { + const {versionMetadata} = props; + const currentDocRouteMetadata = extractDocRouteMetadata(props); + if (!currentDocRouteMetadata) { + return ; + } + const {docElement, sidebarName, sidebarItems} = currentDocRouteMetadata; return ( <> - - {/* TODO we should add a core addRoute({htmlClassName}) generic plugin option */} - - - - - - {renderRoutes(docRoutes, {versionMetadata})} - - - + + + + + {docElement} + + + ); } - -export default DocPage; diff --git a/packages/docusaurus-theme-classic/src/theme/DocPaginator/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocPaginator/index.tsx index b30cee81369a..3b5a9d814213 100644 --- a/packages/docusaurus-theme-classic/src/theme/DocPaginator/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/DocPaginator/index.tsx @@ -10,7 +10,7 @@ import Translate, {translate} from '@docusaurus/Translate'; import PaginatorNavLink from '@theme/PaginatorNavLink'; import type {Props} from '@theme/DocPaginator'; -function DocPaginator(props: Props): JSX.Element { +export default function DocPaginator(props: Props): JSX.Element { const {previous, next} = props; return ( @@ -52,5 +52,3 @@ function DocPaginator(props: Props): JSX.Element { ); } - -export default DocPaginator; diff --git a/packages/docusaurus-theme-classic/src/theme/DocSidebar/Desktop/CollapseButton/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocSidebar/Desktop/CollapseButton/index.tsx new file mode 100644 index 000000000000..c902ac90d6ca --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/DocSidebar/Desktop/CollapseButton/index.tsx @@ -0,0 +1,38 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import clsx from 'clsx'; +import IconArrow from '@theme/IconArrow'; +import {translate} from '@docusaurus/Translate'; +import type {Props} from '@theme/DocSidebar/Desktop/CollapseButton'; + +import styles from './styles.module.css'; + +export default function CollapseButton({onClick}: Props): JSX.Element { + return ( + + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/DocSidebar/Desktop/CollapseButton/styles.module.css b/packages/docusaurus-theme-classic/src/theme/DocSidebar/Desktop/CollapseButton/styles.module.css new file mode 100644 index 000000000000..4e225b0bdd93 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/DocSidebar/Desktop/CollapseButton/styles.module.css @@ -0,0 +1,44 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +:root { + --collapse-button-bg-color-dark: #2e333a; +} + +@media (min-width: 997px) { + .collapseSidebarButton { + display: block !important; + background-color: var(--ifm-button-background-color); + height: 40px; + position: sticky; + bottom: 0; + border-radius: 0; + border: 1px solid var(--ifm-toc-border-color); + } + + .collapseSidebarButtonIcon { + transform: rotate(180deg); + margin-top: 4px; + } + + [dir='rtl'] .collapseSidebarButtonIcon { + transform: rotate(0); + } + + [data-theme='dark'] .collapseSidebarButton { + background-color: var(--collapse-button-bg-color-dark); + } + + [data-theme='dark'] .collapseSidebarButton:hover, + [data-theme='dark'] .collapseSidebarButton:focus { + background-color: var(--ifm-color-emphasis-200); + } +} + +.collapseSidebarButton { + display: none; +} diff --git a/packages/docusaurus-theme-classic/src/theme/DocSidebar/Desktop/Content/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocSidebar/Desktop/Content/index.tsx new file mode 100644 index 000000000000..f72a9c27a3d0 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/DocSidebar/Desktop/Content/index.tsx @@ -0,0 +1,55 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, {useState} from 'react'; +import clsx from 'clsx'; +import { + ThemeClassNames, + useAnnouncementBar, + useScrollPosition, +} from '@docusaurus/theme-common'; +import DocSidebarItems from '@theme/DocSidebarItems'; +import type {Props} from '@theme/DocSidebar/Desktop/Content'; + +import styles from './styles.module.css'; + +function useShowAnnouncementBar() { + const {isActive} = useAnnouncementBar(); + const [showAnnouncementBar, setShowAnnouncementBar] = useState(isActive); + + useScrollPosition( + ({scrollY}) => { + if (isActive) { + setShowAnnouncementBar(scrollY === 0); + } + }, + [isActive], + ); + return isActive && showAnnouncementBar; +} + +export default function DocSidebarDesktopContent({ + path, + sidebar, + className, +}: Props): JSX.Element { + const showAnnouncementBar = useShowAnnouncementBar(); + + return ( + + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/DocSidebarItem/styles.module.css b/packages/docusaurus-theme-classic/src/theme/DocSidebar/Desktop/Content/styles.module.css similarity index 62% rename from packages/docusaurus-theme-classic/src/theme/DocSidebarItem/styles.module.css rename to packages/docusaurus-theme-classic/src/theme/DocSidebar/Desktop/Content/styles.module.css index e197c843f82a..1ee104b73adb 100644 --- a/packages/docusaurus-theme-classic/src/theme/DocSidebarItem/styles.module.css +++ b/packages/docusaurus-theme-classic/src/theme/DocSidebar/Desktop/Content/styles.module.css @@ -6,15 +6,12 @@ */ @media (min-width: 997px) { - .menuLinkText { - cursor: initial; + .menu { + flex-grow: 1; + padding: 0.5rem; } - .menuLinkText:hover { - background: none; - } - - .menuLinkText.hasHref { - cursor: pointer; + .menuWithAnnouncementBar { + margin-bottom: var(--docusaurus-announcement-bar-height); } } diff --git a/packages/docusaurus-theme-classic/src/theme/DocSidebar/Desktop/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocSidebar/Desktop/index.tsx new file mode 100644 index 000000000000..b2b9357d9195 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/DocSidebar/Desktop/index.tsx @@ -0,0 +1,38 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import clsx from 'clsx'; +import {useThemeConfig} from '@docusaurus/theme-common'; +import Logo from '@theme/Logo'; +import CollapseButton from '@theme/DocSidebar/Desktop/CollapseButton'; +import Content from '@theme/DocSidebar/Desktop/Content'; +import type {Props} from '@theme/DocSidebar/Desktop'; + +import styles from './styles.module.css'; + +function DocSidebarDesktop({path, sidebar, onCollapse, isHidden}: Props) { + const { + navbar: {hideOnScroll}, + hideableSidebar, + } = useThemeConfig(); + + return ( +
    + {hideOnScroll && } + + {hideableSidebar && } +
    + ); +} + +export default React.memo(DocSidebarDesktop); diff --git a/packages/docusaurus-theme-classic/src/theme/DocSidebar/Desktop/styles.module.css b/packages/docusaurus-theme-classic/src/theme/DocSidebar/Desktop/styles.module.css new file mode 100644 index 000000000000..8d7734af15d7 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/DocSidebar/Desktop/styles.module.css @@ -0,0 +1,50 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +@media (min-width: 997px) { + .sidebar { + display: flex; + flex-direction: column; + max-height: 100vh; + height: 100%; + position: sticky; + top: 0; + padding-top: var(--ifm-navbar-height); + width: var(--doc-sidebar-width); + transition: opacity 50ms ease; + } + + .sidebarWithHideableNavbar { + padding-top: 0; + } + + .sidebarHidden { + opacity: 0; + height: 0; + overflow: hidden; + visibility: hidden; + } + + .sidebarLogo { + display: flex !important; + align-items: center; + margin: 0 var(--ifm-navbar-padding-horizontal); + min-height: var(--ifm-navbar-height); + max-height: var(--ifm-navbar-height); + color: inherit !important; + text-decoration: none !important; + } + + .sidebarLogo img { + margin-right: 0.5rem; + height: 2rem; + } +} + +.sidebarLogo { + display: none; +} diff --git a/packages/docusaurus-theme-classic/src/theme/DocSidebar/Mobile/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocSidebar/Mobile/index.tsx new file mode 100644 index 000000000000..48314ab6b12e --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/DocSidebar/Mobile/index.tsx @@ -0,0 +1,54 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import clsx from 'clsx'; +import { + NavbarSecondaryMenuFiller, + type NavbarSecondaryMenuComponent, + ThemeClassNames, + useNavbarMobileSidebar, +} from '@docusaurus/theme-common'; +import DocSidebarItems from '@theme/DocSidebarItems'; +import type {Props} from '@theme/DocSidebar/Mobile'; + +// eslint-disable-next-line react/function-component-definition +const DocSidebarMobileSecondaryMenu: NavbarSecondaryMenuComponent = ({ + sidebar, + path, +}) => { + const mobileSidebar = useNavbarMobileSidebar(); + return ( +
      + { + // Mobile sidebar should only be closed if the category has a link + if (item.type === 'category' && item.href) { + mobileSidebar.toggle(); + } + if (item.type === 'link') { + mobileSidebar.toggle(); + } + }} + level={1} + /> +
    + ); +}; + +function DocSidebarMobile(props: Props) { + return ( + + ); +} + +export default React.memo(DocSidebarMobile); diff --git a/packages/docusaurus-theme-classic/src/theme/DocSidebar/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocSidebar/index.tsx index 77f6e0aae312..5bbd9e2eb710 100644 --- a/packages/docusaurus-theme-classic/src/theme/DocSidebar/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/DocSidebar/index.tsx @@ -5,126 +5,11 @@ * LICENSE file in the root directory of this source tree. */ -import React, {useState} from 'react'; -import clsx from 'clsx'; -import { - useThemeConfig, - useAnnouncementBar, - MobileSecondaryMenuFiller, - type MobileSecondaryMenuComponent, - ThemeClassNames, - useScrollPosition, - useWindowSize, -} from '@docusaurus/theme-common'; -import Logo from '@theme/Logo'; -import IconArrow from '@theme/IconArrow'; -import {translate} from '@docusaurus/Translate'; -import DocSidebarItems from '@theme/DocSidebarItems'; +import React from 'react'; +import {useWindowSize} from '@docusaurus/theme-common'; import type {Props} from '@theme/DocSidebar'; - -import styles from './styles.module.css'; - -function useShowAnnouncementBar() { - const {isActive} = useAnnouncementBar(); - const [showAnnouncementBar, setShowAnnouncementBar] = useState(isActive); - - useScrollPosition( - ({scrollY}) => { - if (isActive) { - setShowAnnouncementBar(scrollY === 0); - } - }, - [isActive], - ); - return isActive && showAnnouncementBar; -} - -function HideableSidebarButton({onClick}: {onClick: React.MouseEventHandler}) { - return ( - - ); -} - -function DocSidebarDesktop({path, sidebar, onCollapse, isHidden}: Props) { - const showAnnouncementBar = useShowAnnouncementBar(); - const { - navbar: {hideOnScroll}, - hideableSidebar, - } = useThemeConfig(); - - return ( -
    - {hideOnScroll && } - - {hideableSidebar && } -
    - ); -} - -// eslint-disable-next-line react/function-component-definition -const DocSidebarMobileSecondaryMenu: MobileSecondaryMenuComponent = ({ - toggleSidebar, - sidebar, - path, -}) => ( -
      - { - // Mobile sidebar should only be closed if the category has a link - if (item.type === 'category' && item.href) { - toggleSidebar(); - } - if (item.type === 'link') { - toggleSidebar(); - } - }} - level={1} - /> -
    -); - -function DocSidebarMobile(props: Props) { - return ( - - ); -} - -const DocSidebarDesktopMemo = React.memo(DocSidebarDesktop); -const DocSidebarMobileMemo = React.memo(DocSidebarMobile); +import DocSidebarDesktop from '@theme/DocSidebar/Desktop'; +import DocSidebarMobile from '@theme/DocSidebar/Mobile'; export default function DocSidebar(props: Props): JSX.Element { const windowSize = useWindowSize(); @@ -138,8 +23,8 @@ export default function DocSidebar(props: Props): JSX.Element { return ( <> - {shouldRenderSidebarDesktop && } - {shouldRenderSidebarMobile && } + {shouldRenderSidebarDesktop && } + {shouldRenderSidebarMobile && } ); } diff --git a/packages/docusaurus-theme-classic/src/theme/DocSidebar/styles.module.css b/packages/docusaurus-theme-classic/src/theme/DocSidebar/styles.module.css deleted file mode 100644 index f9a09be722f4..000000000000 --- a/packages/docusaurus-theme-classic/src/theme/DocSidebar/styles.module.css +++ /dev/null @@ -1,107 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -:root { - --collapse-button-bg-color-dark: #2e333a; -} - -@media (min-width: 997px) { - .sidebar { - display: flex; - flex-direction: column; - max-height: 100vh; - height: 100%; - position: sticky; - top: 0; - padding-top: var(--ifm-navbar-height); - width: var(--doc-sidebar-width); - transition: opacity 50ms ease; - } - - .sidebarWithHideableNavbar { - padding-top: 0; - } - - .sidebarHidden { - opacity: 0; - height: 0; - overflow: hidden; - visibility: hidden; - } - - .sidebarLogo { - display: flex !important; - align-items: center; - margin: 0 var(--ifm-navbar-padding-horizontal); - min-height: var(--ifm-navbar-height); - max-height: var(--ifm-navbar-height); - color: inherit !important; - text-decoration: none !important; - } - - .sidebarLogo img { - margin-right: 0.5rem; - height: 2rem; - } - - .menu { - flex-grow: 1; - padding: 0.5rem; - } - - .menuWithAnnouncementBar { - margin-bottom: var(--docusaurus-announcement-bar-height); - } - - .collapseSidebarButton { - display: block !important; - background-color: var(--ifm-button-background-color); - height: 40px; - position: sticky; - bottom: 0; - border-radius: 0; - border: 1px solid var(--ifm-toc-border-color); - } - - .collapseSidebarButtonIcon { - transform: rotate(180deg); - margin-top: 4px; - } - - html[dir='rtl'] .collapseSidebarButtonIcon { - transform: rotate(0); - } - - html[data-theme='dark'] .collapseSidebarButton { - background-color: var(--collapse-button-bg-color-dark); - } - - html[data-theme='dark'] .collapseSidebarButton:hover, - html[data-theme='dark'] .collapseSidebarButton:focus { - background-color: var(--ifm-color-emphasis-200); - } -} - -.sidebarLogo, -.collapseSidebarButton { - display: none; -} - -.sidebarMenuIcon { - vertical-align: middle; -} - -.sidebarMenuCloseIcon { - display: inline-flex; - justify-content: center; - align-items: center; - height: 24px; - font-size: 1.5rem; - font-weight: var(--ifm-font-weight-bold); - line-height: 0.9; - width: 24px; -} diff --git a/packages/docusaurus-theme-classic/src/theme/DocSidebarItem/Category.tsx b/packages/docusaurus-theme-classic/src/theme/DocSidebarItem/Category.tsx new file mode 100644 index 000000000000..202a9dae09c3 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/DocSidebarItem/Category.tsx @@ -0,0 +1,212 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, {type ComponentProps, useEffect, useMemo} from 'react'; +import clsx from 'clsx'; +import { + isActiveSidebarItem, + usePrevious, + Collapsible, + useCollapsible, + findFirstCategoryLink, + ThemeClassNames, + useThemeConfig, + useDocSidebarItemsExpandedState, + isSamePath, +} from '@docusaurus/theme-common'; +import Link from '@docusaurus/Link'; +import {translate} from '@docusaurus/Translate'; + +import DocSidebarItems from '@theme/DocSidebarItems'; +import type {Props} from '@theme/DocSidebarItem/Category'; + +import useIsBrowser from '@docusaurus/useIsBrowser'; + +// If we navigate to a category and it becomes active, it should automatically +// expand itself +function useAutoExpandActiveCategory({ + isActive, + collapsed, + setCollapsed, +}: { + isActive: boolean; + collapsed: boolean; + setCollapsed: (b: boolean) => void; +}) { + const wasActive = usePrevious(isActive); + useEffect(() => { + const justBecameActive = isActive && !wasActive; + if (justBecameActive && collapsed) { + setCollapsed(false); + } + }, [isActive, wasActive, collapsed, setCollapsed]); +} + +/** + * When a collapsible category has no link, we still link it to its first child + * during SSR as a temporary fallback. This allows to be able to navigate inside + * the category even when JS fails to load, is delayed or simply disabled + * React hydration becomes an optional progressive enhancement + * see https://github.com/facebookincubator/infima/issues/36#issuecomment-772543188 + * see https://github.com/facebook/docusaurus/issues/3030 + */ +function useCategoryHrefWithSSRFallback( + item: Props['item'], +): string | undefined { + const isBrowser = useIsBrowser(); + return useMemo(() => { + if (item.href) { + return item.href; + } + // In these cases, it's not necessary to render a fallback + // We skip the "findFirstCategoryLink" computation + if (isBrowser || !item.collapsible) { + return undefined; + } + return findFirstCategoryLink(item); + }, [item, isBrowser]); +} + +function CollapseButton({ + categoryLabel, + onClick, +}: { + categoryLabel: string; + onClick: ComponentProps<'button'>['onClick']; +}) { + return ( +
    - - - - - - ); -} - -function DocSidebarItemLink({ - item, - onItemClick, - activePath, - level, - index, - ...props -}: Props & {item: PropSidebarItemLink}) { - const {href, label, className} = item; - const isActive = isActiveSidebarItem(item, activePath); - return ( -
  • - onItemClick(item) : undefined, - })} - {...props}> - {isInternalUrl(href) ? ( - label - ) : ( - - {label} - - - )} - -
  • - ); -} diff --git a/packages/docusaurus-theme-classic/src/theme/DocTagDocListPage/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocTagDocListPage/index.tsx index 23025464b636..512eabc4a892 100644 --- a/packages/docusaurus-theme-classic/src/theme/DocTagDocListPage/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/DocTagDocListPage/index.tsx @@ -9,10 +9,17 @@ import React from 'react'; import Layout from '@theme/Layout'; import Link from '@docusaurus/Link'; -import {ThemeClassNames, usePluralForm} from '@docusaurus/theme-common'; +import { + PageMetadata, + HtmlClassNameProvider, + ThemeClassNames, + usePluralForm, +} from '@docusaurus/theme-common'; import type {PropTagDocListDoc} from '@docusaurus/plugin-content-docs'; import Translate, {translate} from '@docusaurus/Translate'; import type {Props} from '@theme/DocTagDocListPage'; +import SearchMetadata from '@theme/SearchMetadata'; +import clsx from 'clsx'; // Very simple pluralization: probably good enough for now function useNDocsTaggedPlural() { @@ -55,35 +62,36 @@ export default function DocTagDocListPage({tag}: Props): JSX.Element { ); return ( - -
    -
    -
    -
    -

    {title}

    - - - View All Tags - - -
    -
    - {tag.docs.map((doc) => ( - - ))} -
    -
    + + + + +
    +
    +
    +
    +

    {title}

    + + + View All Tags + + +
    +
    + {tag.docs.map((doc) => ( + + ))} +
    +
    +
    -
    - + + ); } diff --git a/packages/docusaurus-theme-classic/src/theme/DocTagsListPage/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocTagsListPage/index.tsx index eec9e8afc998..5ab4f5a44db9 100644 --- a/packages/docusaurus-theme-classic/src/theme/DocTagsListPage/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/DocTagsListPage/index.tsx @@ -9,33 +9,36 @@ import React from 'react'; import Layout from '@theme/Layout'; import { + PageMetadata, + HtmlClassNameProvider, ThemeClassNames, translateTagsPageTitle, } from '@docusaurus/theme-common'; import TagsListByLetter from '@theme/TagsListByLetter'; import type {Props} from '@theme/DocTagsListPage'; +import SearchMetadata from '@theme/SearchMetadata'; +import clsx from 'clsx'; -function DocTagsListPage({tags}: Props): JSX.Element { +export default function DocTagsListPage({tags}: Props): JSX.Element { const title = translateTagsPageTitle(); return ( - -
    -
    -
    -

    {title}

    - -
    + + + + +
    +
    +
    +

    {title}

    + +
    +
    -
    - + + ); } - -export default DocTagsListPage; diff --git a/packages/docusaurus-theme-classic/src/theme/DocVersionBadge/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocVersionBadge/index.tsx index 65510487915f..862a33e5e543 100644 --- a/packages/docusaurus-theme-classic/src/theme/DocVersionBadge/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/DocVersionBadge/index.tsx @@ -6,6 +6,7 @@ */ import React from 'react'; +import Translate from '@docusaurus/Translate'; import {ThemeClassNames, useDocsVersion} from '@docusaurus/theme-common'; import clsx from 'clsx'; import type {Props} from '@theme/DocVersionBadge'; @@ -22,7 +23,11 @@ export default function DocVersionBadge({ ThemeClassNames.docs.docVersionBadge, 'badge badge--secondary', )}> - Version: {versionMetadata.label} + + {'Version: {versionLabel}'} + ); } diff --git a/packages/docusaurus-theme-classic/src/theme/DocVersionBanner/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocVersionBanner/index.tsx index 679feaf85897..daadce8000be 100644 --- a/packages/docusaurus-theme-classic/src/theme/DocVersionBanner/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/DocVersionBanner/index.tsx @@ -70,10 +70,9 @@ function UnmaintainedVersionLabel({ ); } -const BannerLabelComponents: Record< - VersionBanner, - ComponentType -> = { +const BannerLabelComponents: { + [banner in VersionBanner]: ComponentType; +} = { unreleased: UnreleasedVersionLabel, unmaintained: UnmaintainedVersionLabel, }; diff --git a/packages/docusaurus-theme-classic/src/theme/Footer/Copyright/index.tsx b/packages/docusaurus-theme-classic/src/theme/Footer/Copyright/index.tsx new file mode 100644 index 000000000000..ab1657d97811 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/Footer/Copyright/index.tsx @@ -0,0 +1,20 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import type {Props} from '@theme/Footer/Copyright'; + +export default function FooterCopyright({copyright}: Props): JSX.Element { + return ( +
    + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/Footer/Layout/index.tsx b/packages/docusaurus-theme-classic/src/theme/Footer/Layout/index.tsx new file mode 100644 index 000000000000..bb22f31f5b39 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/Footer/Layout/index.tsx @@ -0,0 +1,34 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import clsx from 'clsx'; +import type {Props} from '@theme/Footer/Layout'; + +export default function FooterLayout({ + style, + links, + logo, + copyright, +}: Props): JSX.Element { + return ( +
    +
    + {links} + {(logo || copyright) && ( +
    + {logo &&
    {logo}
    } + {copyright} +
    + )} +
    +
    + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/Footer/LinkItem/index.tsx b/packages/docusaurus-theme-classic/src/theme/Footer/LinkItem/index.tsx new file mode 100644 index 000000000000..f5e334acefb8 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/Footer/LinkItem/index.tsx @@ -0,0 +1,36 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; + +import Link from '@docusaurus/Link'; +import useBaseUrl from '@docusaurus/useBaseUrl'; +import isInternalUrl from '@docusaurus/isInternalUrl'; +import IconExternalLink from '@theme/IconExternalLink'; +import type {Props} from '@theme/Footer/LinkItem'; + +export default function FooterLinkItem({item}: Props): JSX.Element { + const {to, href, label, prependBaseUrlToHref, ...props} = item; + const toUrl = useBaseUrl(to); + const normalizedHref = useBaseUrl(href, {forcePrependBaseUrl: true}); + + return ( + + {label} + {href && !isInternalUrl(href) && } + + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/Footer/Links/MultiColumn/index.tsx b/packages/docusaurus-theme-classic/src/theme/Footer/Links/MultiColumn/index.tsx new file mode 100644 index 000000000000..75c8f3a64328 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/Footer/Links/MultiColumn/index.tsx @@ -0,0 +1,51 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import type {Props} from '@theme/Footer/Links/MultiColumn'; +import LinkItem from '@theme/Footer/LinkItem'; + +type ColumnType = Props['columns'][number]; +type ColumnItemType = ColumnType['items'][number]; + +function ColumnLinkItem({item}: {item: ColumnItemType}) { + return item.html ? ( +
  • + ) : ( +
  • + +
  • + ); +} + +function Column({column}: {column: ColumnType}) { + return ( +
    +
    {column.title}
    +
      + {column.items.map((item, i) => ( + + ))} +
    +
    + ); +} + +export default function FooterLinksMultiColumn({columns}: Props): JSX.Element { + return ( +
    + {columns.map((column, i) => ( + + ))} +
    + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/Footer/Links/Simple/index.tsx b/packages/docusaurus-theme-classic/src/theme/Footer/Links/Simple/index.tsx new file mode 100644 index 000000000000..72fcc0a74a53 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/Footer/Links/Simple/index.tsx @@ -0,0 +1,42 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import type {Props} from '@theme/Footer/Links/Simple'; +import LinkItem from '@theme/Footer/LinkItem'; + +function Separator() { + return ·; +} + +function SimpleLinkItem({item}: {item: Props['links'][number]}) { + return item.html ? ( + + ) : ( + + ); +} + +export default function FooterLinksSimple({links}: Props): JSX.Element { + return ( +
    +
    + {links.map((item, i) => ( + + + {links.length !== i + 1 && } + + ))} +
    +
    + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/Footer/Links/index.tsx b/packages/docusaurus-theme-classic/src/theme/Footer/Links/index.tsx new file mode 100644 index 000000000000..44e553e5390e --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/Footer/Links/index.tsx @@ -0,0 +1,21 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; + +import {isMultiColumnFooterLinks} from '@docusaurus/theme-common'; +import type {Props} from '@theme/Footer/Links'; +import FooterLinksMultiColumn from '@theme/Footer/Links/MultiColumn'; +import FooterLinksSimple from '@theme/Footer/Links/Simple'; + +export default function FooterLinks({links}: Props): JSX.Element { + return isMultiColumnFooterLinks(links) ? ( + + ) : ( + + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/Footer/Logo/index.tsx b/packages/docusaurus-theme-classic/src/theme/Footer/Logo/index.tsx new file mode 100644 index 000000000000..f566555a1791 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/Footer/Logo/index.tsx @@ -0,0 +1,41 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; + +import Link from '@docusaurus/Link'; +import {useBaseUrlUtils} from '@docusaurus/useBaseUrl'; +import styles from './styles.module.css'; +import ThemedImage from '@theme/ThemedImage'; +import type {Props} from '@theme/Footer/Logo'; + +function LogoImage({logo}: Props) { + const {withBaseUrl} = useBaseUrlUtils(); + const sources = { + light: withBaseUrl(logo.src), + dark: withBaseUrl(logo.srcDark ?? logo.src), + }; + return ( + + ); +} + +export default function FooterLogo({logo}: Props): JSX.Element { + return logo.href ? ( + + + + ) : ( + + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/Footer/styles.module.css b/packages/docusaurus-theme-classic/src/theme/Footer/Logo/styles.module.css similarity index 100% rename from packages/docusaurus-theme-classic/src/theme/Footer/styles.module.css rename to packages/docusaurus-theme-classic/src/theme/Footer/Logo/styles.module.css diff --git a/packages/docusaurus-theme-classic/src/theme/Footer/index.tsx b/packages/docusaurus-theme-classic/src/theme/Footer/index.tsx index 75719b0f4e93..5106ca241c7f 100644 --- a/packages/docusaurus-theme-classic/src/theme/Footer/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/Footer/index.tsx @@ -6,197 +6,27 @@ */ import React from 'react'; -import clsx from 'clsx'; -import Link from '@docusaurus/Link'; -import { - type FooterLinkItem, - useThemeConfig, - type MultiColumnFooter, - type SimpleFooter, -} from '@docusaurus/theme-common'; -import useBaseUrl from '@docusaurus/useBaseUrl'; -import isInternalUrl from '@docusaurus/isInternalUrl'; -import styles from './styles.module.css'; -import ThemedImage, {type Props as ThemedImageProps} from '@theme/ThemedImage'; -import IconExternalLink from '@theme/IconExternalLink'; - -function FooterLink({ - to, - href, - label, - prependBaseUrlToHref, - ...props -}: FooterLinkItem) { - const toUrl = useBaseUrl(to); - const normalizedHref = useBaseUrl(href, {forcePrependBaseUrl: true}); - - return ( - - {href && !isInternalUrl(href) ? ( - - {label} - - - ) : ( - label - )} - - ); -} - -function FooterLogo({ - sources, - alt, - width, - height, -}: Pick) { - return ( - - ); -} - -function MultiColumnLinks({links}: {links: MultiColumnFooter['links']}) { - return ( - <> - {links.map((linkItem, i) => ( -
    -
    {linkItem.title}
    -
      - {linkItem.items.map((item, key) => - item.html ? ( -
    • - ) : ( -
    • - -
    • - ), - )} -
    -
    - ))} - - ); -} - -function SimpleLinks({links}: {links: SimpleFooter['links']}) { - return ( -
    - {links.map((item, key) => ( - <> - {item.html ? ( - - ) : ( - - )} - {links.length !== key + 1 && ( - · - )} - - ))} -
    - ); -} - -function isMultiColumnFooterLinks( - links: MultiColumnFooter['links'] | SimpleFooter['links'], -): links is MultiColumnFooter['links'] { - return 'title' in links[0]; -} +import {useThemeConfig} from '@docusaurus/theme-common'; +import FooterLinks from '@theme/Footer/Links'; +import FooterLogo from '@theme/Footer/Logo'; +import FooterCopyright from '@theme/Footer/Copyright'; +import FooterLayout from '@theme/Footer/Layout'; function Footer(): JSX.Element | null { const {footer} = useThemeConfig(); - - const {copyright, links = [], logo = {}} = footer || {}; - const sources = { - light: useBaseUrl(logo.src), - dark: useBaseUrl(logo.srcDark || logo.src), - }; - if (!footer) { return null; } + const {copyright, links, logo, style} = footer; return ( -
    -
    - {links && - links.length > 0 && - (isMultiColumnFooterLinks(links) ? ( -
    - -
    - ) : ( -
    - -
    - ))} - {(logo || copyright) && ( -
    - {logo && (logo.src || logo.srcDark) && ( -
    - {logo.href ? ( - - - - ) : ( - - )} -
    - )} - {copyright ? ( -
    - ) : null} -
    - )} -
    -
    + 0 && } + logo={logo && } + copyright={copyright && } + /> ); } diff --git a/packages/docusaurus-theme-classic/src/theme/Heading/index.tsx b/packages/docusaurus-theme-classic/src/theme/Heading/index.tsx index 982b51990187..51e1ee7d9377 100644 --- a/packages/docusaurus-theme-classic/src/theme/Heading/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/Heading/index.tsx @@ -11,7 +11,6 @@ import type {Props} from '@theme/Heading'; import {translate} from '@docusaurus/Translate'; import {useThemeConfig} from '@docusaurus/theme-common'; -import './styles.css'; import styles from './styles.module.css'; function AnchorHeading({as: As, id, ...props}: Props) { @@ -26,10 +25,12 @@ function AnchorHeading({as: As, id, ...props}: Props) { return ( {props.children}
    .hash-link { - opacity: 1; -} diff --git a/packages/docusaurus-theme-classic/src/theme/Heading/styles.module.css b/packages/docusaurus-theme-classic/src/theme/Heading/styles.module.css index 03a6a9d15ef5..1b7c5cdbd605 100644 --- a/packages/docusaurus-theme-classic/src/theme/Heading/styles.module.css +++ b/packages/docusaurus-theme-classic/src/theme/Heading/styles.module.css @@ -17,3 +17,19 @@ See https://twitter.com/JoshWComeau/status/1332015868725891076 .anchorWithHideOnScrollNavbar { scroll-margin-top: 0.5rem; } + +:global(.hash-link) { + opacity: 0; + padding-left: 0.5rem; + transition: opacity var(--ifm-transition-fast); + user-select: none; +} + +:global(.hash-link::before) { + content: '#'; +} + +:global(.hash-link:focus), +:global(*:hover > .hash-link) { + opacity: 1; +} diff --git a/packages/docusaurus-theme-classic/src/theme/IconArrow/index.tsx b/packages/docusaurus-theme-classic/src/theme/IconArrow/index.tsx index aea5099f7a17..e358e5edc001 100644 --- a/packages/docusaurus-theme-classic/src/theme/IconArrow/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/IconArrow/index.tsx @@ -8,7 +8,7 @@ import React from 'react'; import type {Props} from '@theme/IconArrow'; -function IconArrow(props: Props): JSX.Element { +export default function IconArrow(props: Props): JSX.Element { return ( ); } - -export default IconArrow; diff --git a/packages/docusaurus-theme-classic/src/theme/IconDarkMode/index.tsx b/packages/docusaurus-theme-classic/src/theme/IconDarkMode/index.tsx new file mode 100644 index 000000000000..d319096eeab3 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/IconDarkMode/index.tsx @@ -0,0 +1,20 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import type {Props} from '@theme/IconDarkMode'; + +export default function IconDarkMode(props: Props): JSX.Element { + return ( + + + + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/IconEdit/index.tsx b/packages/docusaurus-theme-classic/src/theme/IconEdit/index.tsx index 616e21b092d9..76f8a8168b1f 100644 --- a/packages/docusaurus-theme-classic/src/theme/IconEdit/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/IconEdit/index.tsx @@ -12,7 +12,10 @@ import type {Props} from '@theme/IconEdit'; import styles from './styles.module.css'; -function IconEdit({className, ...restProps}: Props): JSX.Element { +export default function IconEdit({ + className, + ...restProps +}: Props): JSX.Element { return ( ); } - -export default IconEdit; diff --git a/packages/docusaurus-theme-classic/src/theme/IconExternalLink/index.tsx b/packages/docusaurus-theme-classic/src/theme/IconExternalLink/index.tsx index 21eab8abd218..6f1ecb6239c5 100644 --- a/packages/docusaurus-theme-classic/src/theme/IconExternalLink/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/IconExternalLink/index.tsx @@ -10,7 +10,10 @@ import type {Props} from '@theme/IconExternalLink'; import styles from './styles.module.css'; -function IconExternalLink({width = 13.5, height = 13.5}: Props): JSX.Element { +export default function IconExternalLink({ + width = 13.5, + height = 13.5, +}: Props): JSX.Element { return ( ); } - -export default IconExternalLink; diff --git a/packages/docusaurus-theme-classic/src/theme/IconExternalLink/styles.module.css b/packages/docusaurus-theme-classic/src/theme/IconExternalLink/styles.module.css index 1e8964ae9a9c..7b0a5ad13d39 100644 --- a/packages/docusaurus-theme-classic/src/theme/IconExternalLink/styles.module.css +++ b/packages/docusaurus-theme-classic/src/theme/IconExternalLink/styles.module.css @@ -7,6 +7,4 @@ .iconExternalLink { margin-left: 0.3rem; - position: relative; - top: 1px; } diff --git a/packages/docusaurus-theme-classic/src/theme/IconLanguage/index.tsx b/packages/docusaurus-theme-classic/src/theme/IconLanguage/index.tsx index d61b5730778e..fa3f3f969f88 100644 --- a/packages/docusaurus-theme-classic/src/theme/IconLanguage/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/IconLanguage/index.tsx @@ -8,20 +8,22 @@ import React from 'react'; import type {Props} from '@theme/IconLanguage'; -function IconLanguage({width = 20, height = 20, ...props}: Props): JSX.Element { +export default function IconLanguage({ + width = 20, + height = 20, + ...props +}: Props): JSX.Element { return ( ); } - -export default IconLanguage; diff --git a/packages/docusaurus-theme-classic/src/theme/IconLightMode/index.tsx b/packages/docusaurus-theme-classic/src/theme/IconLightMode/index.tsx new file mode 100644 index 000000000000..9c080c966791 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/IconLightMode/index.tsx @@ -0,0 +1,20 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import type {Props} from '@theme/IconLightMode'; + +export default function IconLightMode(props: Props): JSX.Element { + return ( + + + + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/IconMenu/index.tsx b/packages/docusaurus-theme-classic/src/theme/IconMenu/index.tsx index 6bf99b1a0651..d1c4e8cdf2b4 100644 --- a/packages/docusaurus-theme-classic/src/theme/IconMenu/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/IconMenu/index.tsx @@ -8,7 +8,7 @@ import React from 'react'; import type {Props} from '@theme/IconMenu'; -function IconMenu({ +export default function IconMenu({ width = 30, height = 30, className, @@ -32,5 +32,3 @@ function IconMenu({ ); } - -export default IconMenu; diff --git a/packages/docusaurus-theme-classic/src/theme/Layout/index.tsx b/packages/docusaurus-theme-classic/src/theme/Layout/index.tsx index 2353118cea8d..35592ef869f7 100644 --- a/packages/docusaurus-theme-classic/src/theme/Layout/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/Layout/index.tsx @@ -13,20 +13,30 @@ import AnnouncementBar from '@theme/AnnouncementBar'; import Navbar from '@theme/Navbar'; import Footer from '@theme/Footer'; import LayoutProviders from '@theme/LayoutProviders'; -import LayoutHead from '@theme/LayoutHead'; import type {Props} from '@theme/Layout'; -import {ThemeClassNames, useKeyboardNavigation} from '@docusaurus/theme-common'; +import { + PageMetadata, + ThemeClassNames, + useKeyboardNavigation, +} from '@docusaurus/theme-common'; import ErrorPageContent from '@theme/ErrorPageContent'; import './styles.css'; -function Layout(props: Props): JSX.Element { - const {children, noFooter, wrapperClassName, pageClassName} = props; +export default function Layout(props: Props): JSX.Element { + const { + children, + noFooter, + wrapperClassName, + // not really layout-related, but kept for convenience/retro-compatibility + title, + description, + } = props; useKeyboardNavigation(); return ( - + @@ -34,12 +44,7 @@ function Layout(props: Props): JSX.Element { -
    +
    {children}
    @@ -47,5 +52,3 @@ function Layout(props: Props): JSX.Element { ); } - -export default Layout; diff --git a/packages/docusaurus-theme-classic/src/theme/LayoutProviders/index.tsx b/packages/docusaurus-theme-classic/src/theme/LayoutProviders/index.tsx index 5d0d2bf859d0..9eba0ff42336 100644 --- a/packages/docusaurus-theme-classic/src/theme/LayoutProviders/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/LayoutProviders/index.tsx @@ -11,8 +11,9 @@ import { TabGroupChoiceProvider, AnnouncementBarProvider, DocsPreferredVersionContextProvider, - MobileSecondaryMenuProvider, ScrollControllerProvider, + NavbarProvider, + PluginHtmlClassNameProvider, } from '@docusaurus/theme-common'; import type {Props} from '@theme/LayoutProviders'; @@ -23,9 +24,9 @@ export default function LayoutProviders({children}: Props): JSX.Element { - - {children} - + + {children} + diff --git a/packages/docusaurus-theme-classic/src/theme/Logo/index.tsx b/packages/docusaurus-theme-classic/src/theme/Logo/index.tsx index 5d1051cd6b23..eaa89cc1e0cc 100644 --- a/packages/docusaurus-theme-classic/src/theme/Logo/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/Logo/index.tsx @@ -14,7 +14,7 @@ import useBaseUrl from '@docusaurus/useBaseUrl'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import {useThemeConfig} from '@docusaurus/theme-common'; -function Logo(props: Props): JSX.Element { +export default function Logo(props: Props): JSX.Element { const { siteConfig: {title}, } = useDocusaurusContext(); @@ -52,5 +52,3 @@ function Logo(props: Props): JSX.Element { ); } - -export default Logo; diff --git a/packages/docusaurus-theme-classic/src/theme/MDXComponents/A.tsx b/packages/docusaurus-theme-classic/src/theme/MDXComponents/A.tsx new file mode 100644 index 000000000000..9ee8333e6c80 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/MDXComponents/A.tsx @@ -0,0 +1,14 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import Link from '@docusaurus/Link'; +import type {Props} from '@theme/MDXComponents/A'; + +export default function MDXA(props: Props): JSX.Element { + return ; +} diff --git a/packages/docusaurus-theme-classic/src/theme/MDXComponents/Code.tsx b/packages/docusaurus-theme-classic/src/theme/MDXComponents/Code.tsx new file mode 100644 index 000000000000..4d5680ed0adb --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/MDXComponents/Code.tsx @@ -0,0 +1,37 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import type {ComponentProps} from 'react'; +import React, {isValidElement} from 'react'; +import CodeBlock from '@theme/CodeBlock'; +import type {Props} from '@theme/MDXComponents/Code'; + +export default function MDXCode(props: Props): JSX.Element { + const inlineElements = [ + 'a', + 'b', + 'big', + 'i', + 'span', + 'em', + 'strong', + 'sup', + 'sub', + 'small', + ]; + const shouldBeInline = React.Children.toArray(props.children).every( + (el) => + (typeof el === 'string' && !el.includes('\n')) || + (isValidElement(el) && inlineElements.includes(el.props.mdxType)), + ); + + return shouldBeInline ? ( + + ) : ( + )} /> + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/MDXComponents/Details.tsx b/packages/docusaurus-theme-classic/src/theme/MDXComponents/Details.tsx new file mode 100644 index 000000000000..d09f2d923f63 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/MDXComponents/Details.tsx @@ -0,0 +1,26 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, {type ComponentProps, type ReactElement} from 'react'; +import Details from '@theme/Details'; +import type {Props} from '@theme/MDXComponents/Details'; + +export default function MDXDetails(props: Props): JSX.Element { + const items = React.Children.toArray(props.children) as ReactElement[]; + // Split summary item from the rest to pass it as a separate prop to the + // Details theme component + const summary: ReactElement> = items.find( + (item) => item?.props?.mdxType === 'summary', + )!; + const children = <>{items.filter((item) => item !== summary)}; + + return ( +
    + {children} +
    + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/MDXComponents/Head.tsx b/packages/docusaurus-theme-classic/src/theme/MDXComponents/Head.tsx new file mode 100644 index 000000000000..c21608e69e36 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/MDXComponents/Head.tsx @@ -0,0 +1,29 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, {type ReactElement, type ComponentProps} from 'react'; +import Head from '@docusaurus/Head'; +import type {Props} from '@theme/MDXComponents/Head'; + +// MDX elements are wrapped through the MDX pragma. In some cases (notably usage +// with Head/Helmet) we need to unwrap those elements. +function unwrapMDXElement(element: ReactElement) { + if (element?.props?.mdxType && element?.props?.originalType) { + const {mdxType, originalType, ...newProps} = element.props; + return React.createElement(element.props.originalType, newProps); + } + return element; +} + +export default function MDXHead(props: Props): JSX.Element { + const unwrappedChildren = React.Children.map(props.children, (child) => + unwrapMDXElement(child as ReactElement), + ); + return ( + )}>{unwrappedChildren} + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/MDXComponents/Heading.tsx b/packages/docusaurus-theme-classic/src/theme/MDXComponents/Heading.tsx new file mode 100644 index 000000000000..2b6c433bc5fc --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/MDXComponents/Heading.tsx @@ -0,0 +1,14 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import Heading from '@theme/Heading'; +import type {Props} from '@theme/MDXComponents/Heading'; + +export default function MDXHeading(props: Props): JSX.Element { + return ; +} diff --git a/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/plugin-empty.js b/packages/docusaurus-theme-classic/src/theme/MDXComponents/Img.module.css similarity index 71% rename from packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/plugin-empty.js rename to packages/docusaurus-theme-classic/src/theme/MDXComponents/Img.module.css index 6b8398c7d40e..76644d485ec3 100644 --- a/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/plugin-empty.js +++ b/packages/docusaurus-theme-classic/src/theme/MDXComponents/Img.module.css @@ -5,8 +5,6 @@ * LICENSE file in the root directory of this source tree. */ -module.exports = function() { - return { - name: 'plugin-empty', - }; -}; +.img { + height: auto; +} diff --git a/packages/docusaurus-theme-classic/src/theme/MDXComponents/Img.tsx b/packages/docusaurus-theme-classic/src/theme/MDXComponents/Img.tsx new file mode 100644 index 000000000000..a97bedc3e028 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/MDXComponents/Img.tsx @@ -0,0 +1,26 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import type {Props} from '@theme/MDXComponents/Img'; +import styles from './Img.module.css'; +import clsx from 'clsx'; + +function transformImgClassName(className?: string): string { + return clsx(className, styles.img); +} + +export default function MDXImg(props: Props): JSX.Element { + return ( + // eslint-disable-next-line jsx-a11y/alt-text + + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/MDXComponents/Pre.tsx b/packages/docusaurus-theme-classic/src/theme/MDXComponents/Pre.tsx new file mode 100644 index 000000000000..bfaf413fd04b --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/MDXComponents/Pre.tsx @@ -0,0 +1,22 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, {isValidElement} from 'react'; +import CodeBlock from '@theme/CodeBlock'; +import type {Props} from '@theme/MDXComponents/Pre'; + +export default function MDXPre(props: Props): JSX.Element { + return ( + + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/MDXComponents/styles.css b/packages/docusaurus-theme-classic/src/theme/MDXComponents/Ul.module.css similarity index 82% rename from packages/docusaurus-theme-classic/src/theme/MDXComponents/styles.css rename to packages/docusaurus-theme-classic/src/theme/MDXComponents/Ul.module.css index 3c263a233477..af91b8ee535d 100644 --- a/packages/docusaurus-theme-classic/src/theme/MDXComponents/styles.css +++ b/packages/docusaurus-theme-classic/src/theme/MDXComponents/Ul.module.css @@ -5,11 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -ul.contains-task-list { +.contains-task-list { padding-left: 0; list-style: none; } - -img { - height: auto; -} diff --git a/packages/docusaurus-theme-classic/src/theme/MDXComponents/Ul.tsx b/packages/docusaurus-theme-classic/src/theme/MDXComponents/Ul.tsx new file mode 100644 index 000000000000..c8bf10e62b9b --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/MDXComponents/Ul.tsx @@ -0,0 +1,28 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import clsx from 'clsx'; +import type {Props} from '@theme/MDXComponents/Ul'; + +import styles from './Ul.module.css'; + +const containsClassListLocalClass = styles['contains-task-list']; + +function transformUlClassName(className?: string): string { + return clsx( + className, + // This class is set globally by GitHub/MDX + // We keep the global class, but apply scoped CSS + // See https://github.com/syntax-tree/mdast-util-to-hast/issues/28 + className?.includes('contains-task-list') && containsClassListLocalClass, + ); +} + +export default function MDXUl(props: Props): JSX.Element { + return
      ; +} diff --git a/packages/docusaurus-theme-classic/src/theme/MDXComponents/index.tsx b/packages/docusaurus-theme-classic/src/theme/MDXComponents/index.tsx index 17c34a9b06dc..82e7c5ac6b12 100644 --- a/packages/docusaurus-theme-classic/src/theme/MDXComponents/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/MDXComponents/index.tsx @@ -5,74 +5,32 @@ * LICENSE file in the root directory of this source tree. */ -import React, { - type ComponentProps, - isValidElement, - type ReactElement, -} from 'react'; -import Head from '@docusaurus/Head'; -import Link from '@docusaurus/Link'; -import CodeBlock from '@theme/CodeBlock'; -import Heading from '@theme/Heading'; -import Details from '@theme/Details'; -import type {MDXComponentsObject} from '@theme/MDXComponents'; - -import './styles.css'; +import React from 'react'; +import MDXHead from '@theme/MDXComponents/Head'; +import MDXCode from '@theme/MDXComponents/Code'; +import MDXA from '@theme/MDXComponents/A'; +import MDXPre from '@theme/MDXComponents/Pre'; +import MDXDetails from '@theme/MDXComponents/Details'; +import MDXHeading from '@theme/MDXComponents/Heading'; +import MDXUl from '@theme/MDXComponents/Ul'; +import MDXImg from '@theme/MDXComponents/Img'; -// MDX elements are wrapped through the MDX pragma -// In some cases (notably usage with Head/Helmet) we need to unwrap those elements. -function unwrapMDXElement(element: ReactElement) { - if (element?.props?.mdxType && element?.props?.originalType) { - const {mdxType, originalType, ...newProps} = element.props; - return React.createElement(element.props.originalType, newProps); - } - return element; -} +import type {MDXComponentsObject} from '@theme/MDXComponents'; const MDXComponents: MDXComponentsObject = { - head: (props) => { - const unwrappedChildren = React.Children.map(props.children, (child) => - unwrapMDXElement(child as ReactElement), - ); - return {unwrappedChildren}; - }, - code: (props) => { - const shouldBeInline = React.Children.toArray(props.children).every( - (el) => typeof el === 'string' && !el.includes('\n'), - ); - - return shouldBeInline ? : ; - }, - a: (props) => , - pre: (props) => ( - - ), - details: (props): JSX.Element => { - const items = React.Children.toArray(props.children) as ReactElement[]; - // Split summary item from the rest to pass it as a separate prop to the Details theme component - const summary: ReactElement> = items.find( - (item) => item?.props?.mdxType === 'summary', - )!; - const children = <>{items.filter((item) => item !== summary)}; - - return ( -
      - {children} -
      - ); - }, - h1: (props) => , - h2: (props) => , - h3: (props) => , - h4: (props) => , - h5: (props) => , - h6: (props) => , + head: MDXHead, + code: MDXCode, + a: MDXA, + pre: MDXPre, + details: MDXDetails, + ul: MDXUl, + img: MDXImg, + h1: (props) => , + h2: (props) => , + h3: (props) => , + h4: (props) => , + h5: (props) => , + h6: (props) => , }; export default MDXComponents; diff --git a/packages/docusaurus-theme-classic/src/theme/MDXContent/index.tsx b/packages/docusaurus-theme-classic/src/theme/MDXContent/index.tsx new file mode 100644 index 000000000000..ad70107193db --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/MDXContent/index.tsx @@ -0,0 +1,15 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import {MDXProvider} from '@mdx-js/react'; +import MDXComponents from '@theme/MDXComponents'; +import type {Props} from '@theme/MDXContent'; + +export default function MDXContent({children}: Props): JSX.Element { + return {children}; +} diff --git a/packages/docusaurus-theme-classic/src/theme/MDXPage/index.tsx b/packages/docusaurus-theme-classic/src/theme/MDXPage/index.tsx index ccd233a092d1..44ba2a5c0ffa 100644 --- a/packages/docusaurus-theme-classic/src/theme/MDXPage/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/MDXPage/index.tsx @@ -8,49 +8,52 @@ import React from 'react'; import clsx from 'clsx'; import Layout from '@theme/Layout'; -import {MDXProvider} from '@mdx-js/react'; -import MDXComponents from '@theme/MDXComponents'; +import MDXContent from '@theme/MDXContent'; import type {Props} from '@theme/MDXPage'; import TOC from '@theme/TOC'; -import {ThemeClassNames} from '@docusaurus/theme-common'; +import { + PageMetadata, + HtmlClassNameProvider, + ThemeClassNames, +} from '@docusaurus/theme-common'; import styles from './styles.module.css'; -function MDXPage(props: Props): JSX.Element { +export default function MDXPage(props: Props): JSX.Element { const {content: MDXPageContent} = props; const { - metadata: {title, description, permalink, frontMatter}, + metadata: {title, description, frontMatter}, } = MDXPageContent; const {wrapperClassName, hide_table_of_contents: hideTableOfContents} = frontMatter; return ( - -
      -
      -
      - - - -
      - {!hideTableOfContents && MDXPageContent.toc && ( -
      - + + + +
      +
      +
      + + +
      - )} -
      -
      -
      + {!hideTableOfContents && MDXPageContent.toc && ( +
      + +
      + )} +
      +
      +
      + ); } - -export default MDXPage; diff --git a/packages/docusaurus-theme-classic/src/theme/Navbar/ColorModeToggle/index.tsx b/packages/docusaurus-theme-classic/src/theme/Navbar/ColorModeToggle/index.tsx new file mode 100644 index 000000000000..5c33ac4a3bed --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/Navbar/ColorModeToggle/index.tsx @@ -0,0 +1,30 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {useColorMode, useThemeConfig} from '@docusaurus/theme-common'; +import ColorModeToggle from '@theme/ColorModeToggle'; +import type {Props} from '@theme/Navbar/ColorModeToggle'; +import React from 'react'; + +export default function NavbarColorModeToggle({ + className, +}: Props): JSX.Element | null { + const disabled = useThemeConfig().colorMode.disableSwitch; + const {colorMode, setColorMode} = useColorMode(); + + if (disabled) { + return null; + } + + return ( + + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/Navbar/Content/index.tsx b/packages/docusaurus-theme-classic/src/theme/Navbar/Content/index.tsx new file mode 100644 index 000000000000..a71cd478fa02 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/Navbar/Content/index.tsx @@ -0,0 +1,81 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, {type ReactNode} from 'react'; +import type {Props as NavbarItemConfig} from '@theme/NavbarItem'; +import NavbarItem from '@theme/NavbarItem'; +import NavbarColorModeToggle from '@theme/Navbar/ColorModeToggle'; +import SearchBar from '@theme/SearchBar'; +import { + splitNavbarItems, + useNavbarMobileSidebar, + useThemeConfig, +} from '@docusaurus/theme-common'; +import NavbarMobileSidebarToggle from '@theme/Navbar/MobileSidebar/Toggle'; +import NavbarLogo from '@theme/Navbar/Logo'; +import styles from './styles.module.css'; + +function useNavbarItems() { + // TODO temporary casting until ThemeConfig type is improved + return useThemeConfig().navbar.items as NavbarItemConfig[]; +} + +function NavbarItems({items}: {items: NavbarItemConfig[]}): JSX.Element { + return ( + <> + {items.map((item, i) => ( + + ))} + + ); +} + +function NavbarContentLayout({ + left, + right, +}: { + left: ReactNode; + right: ReactNode; +}) { + return ( +
      +
      {left}
      +
      {right}
      +
      + ); +} + +export default function NavbarContent(): JSX.Element { + const mobileSidebar = useNavbarMobileSidebar(); + + const items = useNavbarItems(); + const [leftItems, rightItems] = splitNavbarItems(items); + + const autoAddSearchBar = !items.some((item) => item.type === 'search'); + + return ( + + {!mobileSidebar.disabled && } + + + + } + right={ + // TODO stop hardcoding items? + // Ask the user to add the respective navbar items => more flexible + <> + + + {autoAddSearchBar && } + + } + /> + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/Navbar/Content/styles.module.css b/packages/docusaurus-theme-classic/src/theme/Navbar/Content/styles.module.css new file mode 100644 index 000000000000..3cbe701f247e --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/Navbar/Content/styles.module.css @@ -0,0 +1,15 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/* +Hide color mode toggle in small viewports + */ +@media (max-width: 996px) { + .colorModeToggle { + display: none; + } +} diff --git a/packages/docusaurus-theme-classic/src/theme/Navbar/Layout/index.tsx b/packages/docusaurus-theme-classic/src/theme/Navbar/Layout/index.tsx new file mode 100644 index 000000000000..00e5736bd8fd --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/Navbar/Layout/index.tsx @@ -0,0 +1,57 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, {type ComponentProps} from 'react'; +import clsx from 'clsx'; +import NavbarMobileSidebar from '@theme/Navbar/MobileSidebar'; +import type {Props} from '@theme/Navbar/Layout'; +import { + useThemeConfig, + useHideableNavbar, + useNavbarMobileSidebar, +} from '@docusaurus/theme-common'; + +import styles from './styles.module.css'; + +function NavbarBackdrop(props: ComponentProps<'div'>) { + return ( +
      + ); +} + +export default function NavbarLayout({children}: Props): JSX.Element { + const { + navbar: {hideOnScroll, style}, + } = useThemeConfig(); + const mobileSidebar = useNavbarMobileSidebar(); + const {navbarRef, isNavbarVisible} = useHideableNavbar(hideOnScroll); + return ( + + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/Navbar/styles.module.css b/packages/docusaurus-theme-classic/src/theme/Navbar/Layout/styles.module.css similarity index 68% rename from packages/docusaurus-theme-classic/src/theme/Navbar/styles.module.css rename to packages/docusaurus-theme-classic/src/theme/Navbar/Layout/styles.module.css index 6dcfe1c7bb7a..8258c17f2cfd 100644 --- a/packages/docusaurus-theme-classic/src/theme/Navbar/styles.module.css +++ b/packages/docusaurus-theme-classic/src/theme/Navbar/Layout/styles.module.css @@ -5,15 +5,6 @@ * LICENSE file in the root directory of this source tree. */ -/* -Hide toggle in small viewports - */ -@media (max-width: 996px) { - .toggle { - display: none; - } -} - .navbarHideable { transition: transform var(--ifm-transition-fast) ease; } @@ -21,7 +12,3 @@ Hide toggle in small viewports .navbarHidden { transform: translate3d(0, calc(-100% - 2px), 0); } - -.navbarSidebarToggle { - margin-right: 1rem; -} diff --git a/packages/docusaurus-theme-classic/src/theme/Navbar/Logo/index.tsx b/packages/docusaurus-theme-classic/src/theme/Navbar/Logo/index.tsx new file mode 100644 index 000000000000..e1967b955f06 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/Navbar/Logo/index.tsx @@ -0,0 +1,19 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import Logo from '@theme/Logo'; + +export default function NavbarLogo(): JSX.Element { + return ( + + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/Navbar/MobileSidebar/Header/index.tsx b/packages/docusaurus-theme-classic/src/theme/Navbar/MobileSidebar/Header/index.tsx new file mode 100644 index 000000000000..c58b42eb014a --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/Navbar/MobileSidebar/Header/index.tsx @@ -0,0 +1,34 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import NavbarColorModeToggle from '@theme/Navbar/ColorModeToggle'; +import IconClose from '@theme/IconClose'; +import NavbarLogo from '@theme/Navbar/Logo'; +import {useNavbarMobileSidebar} from '@docusaurus/theme-common'; + +function CloseButton() { + const mobileSidebar = useNavbarMobileSidebar(); + return ( + + ); +} + +export default function NavbarMobileSidebarHeader(): JSX.Element { + return ( +
      + + + +
      + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/Navbar/MobileSidebar/Layout/index.tsx b/packages/docusaurus-theme-classic/src/theme/Navbar/MobileSidebar/Layout/index.tsx new file mode 100644 index 000000000000..4fe7a4f9984c --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/Navbar/MobileSidebar/Layout/index.tsx @@ -0,0 +1,31 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import clsx from 'clsx'; +import type {Props} from '@theme/Navbar/MobileSidebar/Layout'; +import {useNavbarSecondaryMenu} from '@docusaurus/theme-common'; + +export default function NavbarMobileSidebarLayout({ + header, + primaryMenu, + secondaryMenu, +}: Props): JSX.Element { + const {shown: secondaryMenuShown} = useNavbarSecondaryMenu(); + return ( +
      + {header} +
      +
      {primaryMenu}
      +
      {secondaryMenu}
      +
      +
      + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/Navbar/MobileSidebar/PrimaryMenu/index.tsx b/packages/docusaurus-theme-classic/src/theme/Navbar/MobileSidebar/PrimaryMenu/index.tsx new file mode 100644 index 000000000000..dbde63edb558 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/Navbar/MobileSidebar/PrimaryMenu/index.tsx @@ -0,0 +1,38 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import {useNavbarMobileSidebar, useThemeConfig} from '@docusaurus/theme-common'; +import type {Props as NavbarItemConfig} from '@theme/NavbarItem'; +import NavbarItem from '../../../NavbarItem'; + +function useNavbarItems() { + // TODO temporary casting until ThemeConfig type is improved + return useThemeConfig().navbar.items as NavbarItemConfig[]; +} + +// The primary menu displays the navbar items +export default function NavbarMobilePrimaryMenu(): JSX.Element { + const mobileSidebar = useNavbarMobileSidebar(); + + // TODO how can the order be defined for mobile? + // Should we allow providing a different list of items? + const items = useNavbarItems(); + + return ( +
        + {items.map((item, i) => ( + mobileSidebar.toggle()} + key={i} + /> + ))} +
      + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/Navbar/MobileSidebar/SecondaryMenu/index.tsx b/packages/docusaurus-theme-classic/src/theme/Navbar/MobileSidebar/SecondaryMenu/index.tsx new file mode 100644 index 000000000000..a15ba55c5684 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/Navbar/MobileSidebar/SecondaryMenu/index.tsx @@ -0,0 +1,38 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, {type ComponentProps} from 'react'; +import {useNavbarSecondaryMenu, useThemeConfig} from '@docusaurus/theme-common'; +import Translate from '@docusaurus/Translate'; + +function SecondaryMenuBackButton(props: ComponentProps<'button'>) { + return ( + + ); +} + +// The secondary menu slides from the right and shows contextual information +// such as the docs sidebar +export default function NavbarMobileSidebarSecondaryMenu(): JSX.Element | null { + const isPrimaryMenuEmpty = useThemeConfig().navbar.items.length === 0; + const secondaryMenu = useNavbarSecondaryMenu(); + return ( + <> + {/* edge-case: prevent returning to the primaryMenu when it's empty */} + {!isPrimaryMenuEmpty && ( + secondaryMenu.hide()} /> + )} + {secondaryMenu.content} + + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/Navbar/MobileSidebar/Toggle/index.tsx b/packages/docusaurus-theme-classic/src/theme/Navbar/MobileSidebar/Toggle/index.tsx new file mode 100644 index 000000000000..e4ac62a50584 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/Navbar/MobileSidebar/Toggle/index.tsx @@ -0,0 +1,25 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import IconMenu from '@theme/IconMenu'; +import {useNavbarMobileSidebar} from '@docusaurus/theme-common'; + +export default function MobileSidebarToggle(): JSX.Element { + const mobileSidebar = useNavbarMobileSidebar(); + return ( + + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/Navbar/MobileSidebar/index.tsx b/packages/docusaurus-theme-classic/src/theme/Navbar/MobileSidebar/index.tsx new file mode 100644 index 000000000000..4a29c4901525 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/Navbar/MobileSidebar/index.tsx @@ -0,0 +1,33 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import NavbarMobileSidebarLayout from '@theme/Navbar/MobileSidebar/Layout'; +import NavbarMobileSidebarHeader from '@theme/Navbar/MobileSidebar/Header'; +import { + useLockBodyScroll, + useNavbarMobileSidebar, +} from '@docusaurus/theme-common'; +import NavbarMobileSidebarPrimaryMenu from '@theme/Navbar/MobileSidebar/PrimaryMenu'; +import NavbarMobileSidebarSecondaryMenu from '@theme/Navbar/MobileSidebar/SecondaryMenu'; + +export default function NavbarMobileSidebar(): JSX.Element | null { + const mobileSidebar = useNavbarMobileSidebar(); + useLockBodyScroll(mobileSidebar.shown); + + if (!mobileSidebar.shouldRender) { + return null; + } + + return ( + } + primaryMenu={} + secondaryMenu={} + /> + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/Navbar/index.tsx b/packages/docusaurus-theme-classic/src/theme/Navbar/index.tsx index 136f08a27f47..878f2047ad40 100644 --- a/packages/docusaurus-theme-classic/src/theme/Navbar/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/Navbar/index.tsx @@ -5,296 +5,14 @@ * LICENSE file in the root directory of this source tree. */ -import React, {useCallback, useState, useEffect} from 'react'; -import clsx from 'clsx'; -import Translate from '@docusaurus/Translate'; -import SearchBar from '@theme/SearchBar'; -import Toggle from '@theme/Toggle'; -import { - useThemeConfig, - useMobileSecondaryMenuRenderer, - usePrevious, - useHistoryPopHandler, - useHideableNavbar, - useLockBodyScroll, - useWindowSize, - useColorMode, -} from '@docusaurus/theme-common'; -import {useActivePlugin} from '@docusaurus/plugin-content-docs/client'; -import NavbarItem, {type Props as NavbarItemConfig} from '@theme/NavbarItem'; -import Logo from '@theme/Logo'; -import IconMenu from '@theme/IconMenu'; -import IconClose from '@theme/IconClose'; - -import styles from './styles.module.css'; - -// retrocompatible with v1 -const DefaultNavItemPosition = 'right'; - -function useNavbarItems() { - // TODO temporary casting until ThemeConfig type is improved - return useThemeConfig().navbar.items as NavbarItemConfig[]; -} - -// If split links by left/right -// if position is unspecified, fallback to right (as v1) -function splitNavItemsByPosition(items: NavbarItemConfig[]) { - const leftItems = items.filter( - (item) => (item.position ?? DefaultNavItemPosition) === 'left', - ); - const rightItems = items.filter( - (item) => (item.position ?? DefaultNavItemPosition) === 'right', - ); - return { - leftItems, - rightItems, - }; -} - -function useMobileSidebar() { - const windowSize = useWindowSize(); - - // Mobile sidebar not visible on hydration: can avoid SSR rendering - const shouldRender = windowSize === 'mobile'; // || windowSize === 'ssr'; - - const [shown, setShown] = useState(false); - - // Close mobile sidebar on navigation pop - // Most likely firing when using the Android back button (but not only) - useHistoryPopHandler(() => { - if (shown) { - setShown(false); - // Should we prevent the navigation here? - // See https://github.com/facebook/docusaurus/pull/5462#issuecomment-911699846 - return false; // prevent pop navigation - } - return undefined; - }); - - const toggle = useCallback(() => { - setShown((s) => !s); - }, []); - - useEffect(() => { - if (windowSize === 'desktop') { - setShown(false); - } - }, [windowSize]); - - return {shouldRender, toggle, shown}; -} - -function useColorModeToggle() { - const { - colorMode: {disableSwitch}, - } = useThemeConfig(); - const {isDarkTheme, setLightTheme, setDarkTheme} = useColorMode(); - const toggle = useCallback( - (e) => (e.target.checked ? setDarkTheme() : setLightTheme()), - [setLightTheme, setDarkTheme], - ); - return {isDarkTheme, toggle, disabled: disableSwitch}; -} - -function useSecondaryMenu({ - sidebarShown, - toggleSidebar, -}: NavbarMobileSidebarProps) { - const content = useMobileSecondaryMenuRenderer()?.({ - toggleSidebar, - }); - const previousContent = usePrevious(content); - - const [shown, setShown] = useState( - () => - // /!\ content is set with useEffect, - // so it's not available on mount anyway - // "return !!content" => always returns false - false, - ); - - // When content is become available for the first time (set in useEffect) - // we set this content to be shown! - useEffect(() => { - const contentBecameAvailable = content && !previousContent; - if (contentBecameAvailable) { - setShown(true); - } - }, [content, previousContent]); - - const hasContent = !!content; - - // On sidebar close, secondary menu is set to be shown on next re-opening - // (if any secondary menu content available) - useEffect(() => { - if (!hasContent) { - setShown(false); - return; - } - if (!sidebarShown) { - setShown(true); - } - }, [sidebarShown, hasContent]); - - const hide = useCallback(() => { - setShown(false); - }, []); - - return {shown, hide, content}; -} - -type NavbarMobileSidebarProps = { - sidebarShown: boolean; - toggleSidebar: () => void; -}; - -function NavbarMobileSidebar({ - sidebarShown, - toggleSidebar, -}: NavbarMobileSidebarProps) { - useLockBodyScroll(sidebarShown); - const items = useNavbarItems(); - - const colorModeToggle = useColorModeToggle(); - - const secondaryMenu = useSecondaryMenu({ - sidebarShown, - toggleSidebar, - }); - - return ( -
      -
      - - {!colorModeToggle.disabled && ( - - )} - -
      - -
      -
      -
        - {items.map((item, i) => ( - - ))} -
      -
      - -
      - {items.length > 0 && ( - - )} - {secondaryMenu.content} -
      -
      -
      - ); -} - -function Navbar(): JSX.Element { - const { - navbar: {hideOnScroll, style}, - } = useThemeConfig(); - - const mobileSidebar = useMobileSidebar(); - const colorModeToggle = useColorModeToggle(); - const activeDocPlugin = useActivePlugin(); - const {navbarRef, isNavbarVisible} = useHideableNavbar(hideOnScroll); - - const items = useNavbarItems(); - const hasSearchNavbarItem = items.some((item) => item.type === 'search'); - const {leftItems, rightItems} = splitNavItemsByPosition(items); +import React from 'react'; +import NavbarLayout from '@theme/Navbar/Layout'; +import NavbarContent from '@theme/Navbar/Content'; +export default function Navbar(): JSX.Element { return ( - + + + ); } - -export default Navbar; diff --git a/packages/docusaurus-theme-classic/src/theme/NavbarItem/DefaultNavbarItem.tsx b/packages/docusaurus-theme-classic/src/theme/NavbarItem/DefaultNavbarItem.tsx index cb4207afc631..2c5044682012 100644 --- a/packages/docusaurus-theme-classic/src/theme/NavbarItem/DefaultNavbarItem.tsx +++ b/packages/docusaurus-theme-classic/src/theme/NavbarItem/DefaultNavbarItem.tsx @@ -41,7 +41,7 @@ function DefaultNavbarItemDesktop({ function DefaultNavbarItemMobile({ className, - isDropdownItem: _isDropdownItem, + isDropdownItem, ...props }: DesktopOrMobileNavBarItemProps) { return ( @@ -51,9 +51,9 @@ function DefaultNavbarItemMobile({ ); } -function DefaultNavbarItem({ +export default function DefaultNavbarItem({ mobile = false, - position: _position, // Need to destructure position from props so that it doesn't get passed on. + position, // Need to destructure position from props so that it doesn't get passed on. ...props }: Props): JSX.Element { const Comp = mobile ? DefaultNavbarItemMobile : DefaultNavbarItemDesktop; @@ -66,5 +66,3 @@ function DefaultNavbarItem({ /> ); } - -export default DefaultNavbarItem; diff --git a/packages/docusaurus-theme-classic/src/theme/NavbarItem/DocNavbarItem.tsx b/packages/docusaurus-theme-classic/src/theme/NavbarItem/DocNavbarItem.tsx index 8c56cebf24fd..a5e9b06f02bc 100644 --- a/packages/docusaurus-theme-classic/src/theme/NavbarItem/DocNavbarItem.tsx +++ b/packages/docusaurus-theme-classic/src/theme/NavbarItem/DocNavbarItem.tsx @@ -7,30 +7,11 @@ import React from 'react'; import DefaultNavbarItem from '@theme/NavbarItem/DefaultNavbarItem'; -import { - useLatestVersion, - useActiveDocContext, -} from '@docusaurus/plugin-content-docs/client'; +import {useActiveDocContext} from '@docusaurus/plugin-content-docs/client'; import clsx from 'clsx'; import {getInfimaActiveClassName} from '@theme/NavbarItem/utils'; import type {Props} from '@theme/NavbarItem/DocNavbarItem'; -import {useDocsPreferredVersion, uniq} from '@docusaurus/theme-common'; -import type {GlobalVersion} from '@docusaurus/plugin-content-docs/client'; - -function getDocInVersions(versions: GlobalVersion[], docId: string) { - const allDocs = versions.flatMap((version) => version.docs); - const doc = allDocs.find((versionDoc) => versionDoc.id === docId); - if (!doc) { - const docIds = allDocs.map((versionDoc) => versionDoc.id).join('\n- '); - throw new Error( - `DocNavbarItem: couldn't find any doc with id "${docId}" in version${ - versions.length ? 's' : '' - } ${versions.map((version) => version.name).join(', ')}". -Available doc ids are:\n- ${docIds}`, - ); - } - return doc; -} +import {useLayoutDoc} from '@docusaurus/theme-common'; export default function DocNavbarItem({ docId, @@ -38,17 +19,8 @@ export default function DocNavbarItem({ docsPluginId, ...props }: Props): JSX.Element { - const {activeVersion, activeDoc} = useActiveDocContext(docsPluginId); - const {preferredVersion} = useDocsPreferredVersion(docsPluginId); - const latestVersion = useLatestVersion(docsPluginId); - - // Versions used to look for the doc to link to, ordered + no duplicate - const versions = uniq( - [activeVersion, preferredVersion, latestVersion].filter( - Boolean, - ) as GlobalVersion[], - ); - const doc = getDocInVersions(versions, docId); + const {activeDoc} = useActiveDocContext(docsPluginId); + const doc = useLayoutDoc(docId, docsPluginId); const activeDocInfimaClassName = getInfimaActiveClassName(props.mobile); return ( @@ -57,6 +29,9 @@ export default function DocNavbarItem({ {...props} className={clsx(props.className, { [activeDocInfimaClassName]: + // Do not make the item active if the active doc doesn't have sidebar. + // If `activeDoc === doc` react-router will make it active anyways, + // regardless of the existence of a sidebar activeDoc?.sidebar && activeDoc.sidebar === doc.sidebar, })} activeClassName={activeDocInfimaClassName} diff --git a/packages/docusaurus-theme-classic/src/theme/NavbarItem/DocSidebarNavbarItem.tsx b/packages/docusaurus-theme-classic/src/theme/NavbarItem/DocSidebarNavbarItem.tsx index ca6ec3d86687..965664855603 100644 --- a/packages/docusaurus-theme-classic/src/theme/NavbarItem/DocSidebarNavbarItem.tsx +++ b/packages/docusaurus-theme-classic/src/theme/NavbarItem/DocSidebarNavbarItem.tsx @@ -7,48 +7,12 @@ import React from 'react'; import DefaultNavbarItem from '@theme/NavbarItem/DefaultNavbarItem'; -import { - useLatestVersion, - useActiveDocContext, -} from '@docusaurus/plugin-content-docs/client'; +import {useActiveDocContext} from '@docusaurus/plugin-content-docs/client'; import clsx from 'clsx'; import {getInfimaActiveClassName} from '@theme/NavbarItem/utils'; -import {useDocsPreferredVersion, uniq} from '@docusaurus/theme-common'; +import {useLayoutDocsSidebar} from '@docusaurus/theme-common'; import type {Props} from '@theme/NavbarItem/DocSidebarNavbarItem'; -import type { - GlobalVersion, - GlobalSidebar, -} from '@docusaurus/plugin-content-docs/client'; - -function getSidebarLink(versions: GlobalVersion[], sidebarId: string) { - const allSidebars = versions - .flatMap((version) => { - if (version.sidebars) { - return Object.entries(version.sidebars); - } - return undefined; - }) - .filter( - (sidebarItem): sidebarItem is [string, GlobalSidebar] => !!sidebarItem, - ); - const sidebarEntry = allSidebars.find((sidebar) => sidebar[0] === sidebarId); - if (!sidebarEntry) { - throw new Error( - `DocSidebarNavbarItem: couldn't find any sidebar with id "${sidebarId}" in version${ - versions.length ? 's' : '' - } ${versions.map((version) => version.name).join(', ')}". -Available sidebar ids are: -- ${Object.keys(allSidebars).join('\n- ')}`, - ); - } - if (!sidebarEntry[1].link) { - throw new Error( - `DocSidebarNavbarItem: couldn't find any document for sidebar with id "${sidebarId}"`, - ); - } - return sidebarEntry[1].link; -} export default function DocSidebarNavbarItem({ sidebarId, @@ -56,17 +20,13 @@ export default function DocSidebarNavbarItem({ docsPluginId, ...props }: Props): JSX.Element { - const {activeVersion, activeDoc} = useActiveDocContext(docsPluginId); - const {preferredVersion} = useDocsPreferredVersion(docsPluginId); - const latestVersion = useLatestVersion(docsPluginId); - - // Versions used to look for the doc to link to, ordered + no duplicate - const versions = uniq( - [activeVersion, preferredVersion, latestVersion].filter( - Boolean, - ) as GlobalVersion[], - ); - const sidebarLink = getSidebarLink(versions, sidebarId); + const {activeDoc} = useActiveDocContext(docsPluginId); + const sidebarLink = useLayoutDocsSidebar(sidebarId, docsPluginId).link; + if (!sidebarLink) { + throw new Error( + `DocSidebarNavbarItem: Sidebar with ID "${sidebarId}" doesn't have anything to be linked to.`, + ); + } const activeDocInfimaClassName = getInfimaActiveClassName(props.mobile); return ( diff --git a/packages/docusaurus-theme-classic/src/theme/NavbarItem/DocsVersionDropdownNavbarItem.tsx b/packages/docusaurus-theme-classic/src/theme/NavbarItem/DocsVersionDropdownNavbarItem.tsx index 3349f81cc14b..9624dc3ddedb 100644 --- a/packages/docusaurus-theme-classic/src/theme/NavbarItem/DocsVersionDropdownNavbarItem.tsx +++ b/packages/docusaurus-theme-classic/src/theme/NavbarItem/DocsVersionDropdownNavbarItem.tsx @@ -10,14 +10,15 @@ import DefaultNavbarItem from '@theme/NavbarItem/DefaultNavbarItem'; import DropdownNavbarItem from '@theme/NavbarItem/DropdownNavbarItem'; import { useVersions, - useLatestVersion, useActiveDocContext, } from '@docusaurus/plugin-content-docs/client'; -import type {Props} from '@theme/NavbarItem/DocsVersionDropdownNavbarItem'; -import {useDocsPreferredVersion} from '@docusaurus/theme-common'; +import { + useDocsPreferredVersion, + useDocsVersionCandidates, +} from '@docusaurus/theme-common'; import {translate} from '@docusaurus/Translate'; import type {GlobalVersion} from '@docusaurus/plugin-content-docs/client'; -import type {LinkLikeNavbarItemProps} from '@theme/NavbarItem'; +import type {Props} from '@theme/NavbarItem/DocsVersionDropdownNavbarItem'; const getVersionMainDoc = (version: GlobalVersion) => version.docs.find((doc) => doc.id === version.mainDocId)!; @@ -32,36 +33,28 @@ export default function DocsVersionDropdownNavbarItem({ }: Props): JSX.Element { const activeDocContext = useActiveDocContext(docsPluginId); const versions = useVersions(docsPluginId); - const latestVersion = useLatestVersion(docsPluginId); - - const {preferredVersion, savePreferredVersionName} = - useDocsPreferredVersion(docsPluginId); - - function getItems(): LinkLikeNavbarItemProps[] { - const versionLinks = versions.map((version) => { - // We try to link to the same doc, in another version - // When not possible, fallback to the "main doc" of the version - const versionDoc = - activeDocContext?.alternateDocVersions[version.name] || - getVersionMainDoc(version); - return { - isNavLink: true, - label: version.label, - to: versionDoc.path, - isActive: () => version === activeDocContext?.activeVersion, - onClick: () => { - savePreferredVersionName(version.name); - }, - }; - }); - - return [...dropdownItemsBefore, ...versionLinks, ...dropdownItemsAfter]; - } - - const items = getItems(); + const {savePreferredVersionName} = useDocsPreferredVersion(docsPluginId); + const versionLinks = versions.map((version) => { + // We try to link to the same doc, in another version + // When not possible, fallback to the "main doc" of the version + const versionDoc = + activeDocContext?.alternateDocVersions[version.name] ?? + getVersionMainDoc(version); + return { + isNavLink: true, + label: version.label, + to: versionDoc.path, + isActive: () => version === activeDocContext?.activeVersion, + onClick: () => savePreferredVersionName(version.name), + }; + }); + const items = [ + ...dropdownItemsBefore, + ...versionLinks, + ...dropdownItemsAfter, + ]; - const dropdownVersion = - activeDocContext.activeVersion ?? preferredVersion ?? latestVersion; + const dropdownVersion = useDocsVersionCandidates(docsPluginId)[0]; // Mobile dropdown is handled a bit differently const dropdownLabel = @@ -78,8 +71,8 @@ export default function DocsVersionDropdownNavbarItem({ ? undefined : getVersionMainDoc(dropdownVersion).path; - // We don't want to render a version dropdown with 0 or 1 item - // If we build the site with a single docs version (onlyIncludeVersions: ['1.0.0']) + // We don't want to render a version dropdown with 0 or 1 item. If we build + // the site with a single docs version (onlyIncludeVersions: ['1.0.0']), // We'd rather render a button instead of a dropdown if (items.length <= 1) { return ( diff --git a/packages/docusaurus-theme-classic/src/theme/NavbarItem/DocsVersionNavbarItem.tsx b/packages/docusaurus-theme-classic/src/theme/NavbarItem/DocsVersionNavbarItem.tsx index 5d88a534ae3b..db6873764319 100644 --- a/packages/docusaurus-theme-classic/src/theme/NavbarItem/DocsVersionNavbarItem.tsx +++ b/packages/docusaurus-theme-classic/src/theme/NavbarItem/DocsVersionNavbarItem.tsx @@ -7,13 +7,9 @@ import React from 'react'; import DefaultNavbarItem from '@theme/NavbarItem/DefaultNavbarItem'; -import { - useActiveVersion, - useLatestVersion, - type GlobalVersion, -} from '@docusaurus/plugin-content-docs/client'; +import {useDocsVersionCandidates} from '@docusaurus/theme-common'; +import type {GlobalVersion} from '@docusaurus/plugin-content-docs/client'; import type {Props} from '@theme/NavbarItem/DocsVersionNavbarItem'; -import {useDocsPreferredVersion} from '@docusaurus/theme-common'; const getVersionMainDoc = (version: GlobalVersion) => version.docs.find((doc) => doc.id === version.mainDocId)!; @@ -24,10 +20,7 @@ export default function DocsVersionNavbarItem({ docsPluginId, ...props }: Props): JSX.Element { - const activeVersion = useActiveVersion(docsPluginId); - const {preferredVersion} = useDocsPreferredVersion(docsPluginId); - const latestVersion = useLatestVersion(docsPluginId); - const version = activeVersion ?? preferredVersion ?? latestVersion; + const version = useDocsVersionCandidates(docsPluginId)[0]; const label = staticLabel ?? version.label; const path = staticTo ?? getVersionMainDoc(version).path; return ; diff --git a/packages/docusaurus-theme-classic/src/theme/NavbarItem/DropdownNavbarItem.tsx b/packages/docusaurus-theme-classic/src/theme/NavbarItem/DropdownNavbarItem.tsx index cd04e822f992..37dd48692452 100644 --- a/packages/docusaurus-theme-classic/src/theme/NavbarItem/DropdownNavbarItem.tsx +++ b/packages/docusaurus-theme-classic/src/theme/NavbarItem/DropdownNavbarItem.tsx @@ -85,6 +85,9 @@ function DropdownNavbarItemDesktop({ 'dropdown--show': showDropdown, })}> { e.preventDefault(); @@ -172,9 +184,10 @@ function DropdownNavbarItemMobile({ ); } -function DropdownNavbarItem({mobile = false, ...props}: Props): JSX.Element { +export default function DropdownNavbarItem({ + mobile = false, + ...props +}: Props): JSX.Element { const Comp = mobile ? DropdownNavbarItemMobile : DropdownNavbarItemDesktop; return ; } - -export default DropdownNavbarItem; diff --git a/packages/docusaurus-theme-classic/src/theme/NavbarItem/LocaleDropdownNavbarItem/index.tsx b/packages/docusaurus-theme-classic/src/theme/NavbarItem/LocaleDropdownNavbarItem/index.tsx index 7309d0f4dc2b..bec77b1a309b 100644 --- a/packages/docusaurus-theme-classic/src/theme/NavbarItem/LocaleDropdownNavbarItem/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/NavbarItem/LocaleDropdownNavbarItem/index.tsx @@ -11,6 +11,7 @@ import IconLanguage from '@theme/IconLanguage'; import type {Props} from '@theme/NavbarItem/LocaleDropdownNavbarItem'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import {useAlternatePageUtils} from '@docusaurus/theme-common'; +import {translate} from '@docusaurus/Translate'; import type {LinkLikeNavbarItemProps} from '@theme/NavbarItem'; import styles from './styles.module.css'; @@ -26,10 +27,6 @@ export default function LocaleDropdownNavbarItem({ } = useDocusaurusContext(); const alternatePageUtils = useAlternatePageUtils(); - function getLocaleLabel(locale: string) { - return localeConfigs[locale].label; - } - const localeItems = locales.map((locale): LinkLikeNavbarItemProps => { const to = `pathname://${alternatePageUtils.createUrl({ locale, @@ -37,7 +34,7 @@ export default function LocaleDropdownNavbarItem({ })}`; return { isNavLink: true, - label: getLocaleLabel(locale), + label: localeConfigs[locale]!.label, to, target: '_self', autoAddBaseUrl: false, @@ -48,7 +45,13 @@ export default function LocaleDropdownNavbarItem({ const items = [...dropdownItemsBefore, ...localeItems, ...dropdownItemsAfter]; // Mobile is handled a bit differently - const dropdownLabel = mobile ? 'Languages' : getLocaleLabel(currentLocale); + const dropdownLabel = mobile + ? translate({ + message: 'Languages', + id: 'theme.navbar.mobileLanguageDropdown.label', + description: 'The label for the mobile language switcher dropdown', + }) + : localeConfigs[currentLocale]!.label; return ( - {isExternalLink ? ( - - {label} - - - ) : ( - label + {label} + {isExternalLink && ( + )} ); diff --git a/packages/docusaurus-theme-classic/src/theme/NavbarItem/index.tsx b/packages/docusaurus-theme-classic/src/theme/NavbarItem/index.tsx index da7606057f44..d9c5e9d30df8 100644 --- a/packages/docusaurus-theme-classic/src/theme/NavbarItem/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/NavbarItem/index.tsx @@ -14,19 +14,18 @@ import LocaleDropdownNavbarItem from '@theme/NavbarItem/LocaleDropdownNavbarItem import SearchNavbarItem from '@theme/NavbarItem/SearchNavbarItem'; import type {Types, Props} from '@theme/NavbarItem'; -const NavbarItemComponents: Record< - Exclude, +const NavbarItemComponents: { // Not really worth typing, as we pass all props down immediately // eslint-disable-next-line @typescript-eslint/no-explicit-any - () => (props: any) => JSX.Element -> = { + [type in Exclude]: () => (props: any) => JSX.Element; +} = { default: () => DefaultNavbarItem, localeDropdown: () => LocaleDropdownNavbarItem, search: () => SearchNavbarItem, dropdown: () => DropdownNavbarItem, - // Need to lazy load these items as we don't know for sure the docs plugin is loaded - // See https://github.com/facebook/docusaurus/issues/3360 + // Need to lazy load these items as we don't know for sure the docs plugin is + // loaded. See https://github.com/facebook/docusaurus/issues/3360 /* eslint-disable @typescript-eslint/no-var-requires, global-require */ docsVersion: () => require('@theme/NavbarItem/DocsVersionNavbarItem').default, docsVersionDropdown: () => diff --git a/packages/docusaurus-theme-classic/src/theme/NavbarItem/utils.ts b/packages/docusaurus-theme-classic/src/theme/NavbarItem/utils.ts index b3f9cce11440..a98db455f25e 100644 --- a/packages/docusaurus-theme-classic/src/theme/NavbarItem/utils.ts +++ b/packages/docusaurus-theme-classic/src/theme/NavbarItem/utils.ts @@ -5,6 +5,9 @@ * LICENSE file in the root directory of this source tree. */ -// eslint-disable-next-line import/no-named-export -export const getInfimaActiveClassName = (mobile?: boolean): string => +/* eslint-disable import/no-named-export */ + +export const getInfimaActiveClassName = ( + mobile?: boolean, +): `${'menu' | 'navbar'}__link--active` => mobile ? 'menu__link--active' : 'navbar__link--active'; diff --git a/packages/docusaurus-theme-classic/src/theme/NotFound.tsx b/packages/docusaurus-theme-classic/src/theme/NotFound.tsx index 20a600f8274d..2213158d52a6 100644 --- a/packages/docusaurus-theme-classic/src/theme/NotFound.tsx +++ b/packages/docusaurus-theme-classic/src/theme/NotFound.tsx @@ -8,44 +8,47 @@ import React from 'react'; import Layout from '@theme/Layout'; import Translate, {translate} from '@docusaurus/Translate'; +import {PageMetadata} from '@docusaurus/theme-common'; -function NotFound(): JSX.Element { +export default function NotFound(): JSX.Element { return ( - -
      -
      -
      -

      - - Page Not Found - -

      -

      - - We could not find what you were looking for. - -

      -

      - - Please contact the owner of the site that linked you to the - original URL and let them know their link is broken. - -

      + <> + + +
      +
      +
      +

      + + Page Not Found + +

      +

      + + We could not find what you were looking for. + +

      +

      + + Please contact the owner of the site that linked you to the + original URL and let them know their link is broken. + +

      +
      -
      -
      -
      + + + ); } - -export default NotFound; diff --git a/packages/docusaurus-theme-classic/src/theme/PaginatorNavLink/index.tsx b/packages/docusaurus-theme-classic/src/theme/PaginatorNavLink/index.tsx index 7ea21ee7efa7..5d34daf13328 100644 --- a/packages/docusaurus-theme-classic/src/theme/PaginatorNavLink/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/PaginatorNavLink/index.tsx @@ -9,7 +9,7 @@ import React from 'react'; import Link from '@docusaurus/Link'; import type {Props} from '@theme/PaginatorNavLink'; -function PaginatorNavLink(props: Props): JSX.Element { +export default function PaginatorNavLink(props: Props): JSX.Element { const {permalink, title, subLabel} = props; return ( @@ -18,5 +18,3 @@ function PaginatorNavLink(props: Props): JSX.Element { ); } - -export default PaginatorNavLink; diff --git a/packages/docusaurus-theme-classic/src/theme/SearchBar.tsx b/packages/docusaurus-theme-classic/src/theme/SearchBar.tsx index 2c59cbfea88a..08ee42e30956 100644 --- a/packages/docusaurus-theme-classic/src/theme/SearchBar.tsx +++ b/packages/docusaurus-theme-classic/src/theme/SearchBar.tsx @@ -6,7 +6,8 @@ */ // By default, the classic theme does not provide any SearchBar implementation -// If you swizzled this file, it is your responsibility to provide an implementation +// If you swizzled this, it is your responsibility to provide an implementation // Tip: swizzle the SearchBar from the Algolia theme for inspiration: // npm run swizzle @docusaurus/theme-search-algolia SearchBar + export {default} from '@docusaurus/Noop'; diff --git a/packages/docusaurus-theme-classic/src/theme/SearchMetadata/index.tsx b/packages/docusaurus-theme-classic/src/theme/SearchMetadata/index.tsx index 00ab2092c543..4a5d72a28afe 100644 --- a/packages/docusaurus-theme-classic/src/theme/SearchMetadata/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/SearchMetadata/index.tsx @@ -10,19 +10,34 @@ import React from 'react'; import Head from '@docusaurus/Head'; import type {Props} from '@theme/SearchMetadata'; -// Note: we don't couple this to Algolia/DocSearch on purpose -// We may want to support other search engine plugins too -// Search plugins should swizzle/override this comp to add their behavior +// Note: we bias toward using Algolia metadata on purpose +// Not doing so leads to confusion in the community, +// as it requires to first crawl the site with the Algolia plugin enabled first +// - https://github.com/facebook/docusaurus/issues/6693 +// - https://github.com/facebook/docusaurus/issues/4555 export default function SearchMetadata({ locale, version, tag, }: Props): JSX.Element { + // Seems safe to consider here the locale is the language, as the existing + // docsearch:language filter is afaik a regular string-based filter + const language = locale; + return ( + {/* + Docusaurus metadata, used by third-party search plugin + See https://github.com/cmfcmf/docusaurus-search-local/issues/99 + */} {locale && } {version && } {tag && } + + {/* Algolia DocSearch metadata */} + {language && } + {version && } + {tag && } ); } diff --git a/packages/docusaurus-theme-classic/src/theme/Seo/index.tsx b/packages/docusaurus-theme-classic/src/theme/Seo/index.tsx deleted file mode 100644 index 6acdbe086465..000000000000 --- a/packages/docusaurus-theme-classic/src/theme/Seo/index.tsx +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import React from 'react'; -import Head from '@docusaurus/Head'; -import {useTitleFormatter} from '@docusaurus/theme-common'; -import {useBaseUrlUtils} from '@docusaurus/useBaseUrl'; - -import type {Props} from '@theme/Seo'; - -export default function Seo({ - title, - description, - keywords, - image, - children, -}: Props): JSX.Element { - const pageTitle = useTitleFormatter(title); - const {withBaseUrl} = useBaseUrlUtils(); - const pageImage = image ? withBaseUrl(image, {absolute: true}) : undefined; - - return ( - - {title && {pageTitle}} - {title && } - - {description && } - {description && } - - {keywords && ( - - )} - - {pageImage && } - {pageImage && } - - {children} - - ); -} diff --git a/packages/docusaurus-theme-classic/src/theme/LayoutHead/index.tsx b/packages/docusaurus-theme-classic/src/theme/SiteMetadata/index.tsx similarity index 75% rename from packages/docusaurus-theme-classic/src/theme/LayoutHead/index.tsx rename to packages/docusaurus-theme-classic/src/theme/SiteMetadata/index.tsx index 5e91606c75db..e21668aa90f2 100644 --- a/packages/docusaurus-theme-classic/src/theme/LayoutHead/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/SiteMetadata/index.tsx @@ -9,18 +9,18 @@ import React from 'react'; import Head from '@docusaurus/Head'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import useBaseUrl from '@docusaurus/useBaseUrl'; -import type {Props} from '@theme/Layout'; import SearchMetadata from '@theme/SearchMetadata'; -import Seo from '@theme/Seo'; import { + PageMetadata, DEFAULT_SEARCH_TAG, - useTitleFormatter, useAlternatePageUtils, useThemeConfig, + keyboardFocusedClassName, } from '@docusaurus/theme-common'; import {useLocation} from '@docusaurus/router'; -// Useful for SEO +// TODO move to SiteMetadataDefaults or theme-common ? +// Useful for i18n/SEO // See https://developers.google.com/search/docs/advanced/crawling/localized-versions // See https://github.com/facebook/docusaurus/issues/3317 function AlternateLangHeaders(): JSX.Element { @@ -65,6 +65,7 @@ function useDefaultCanonicalUrl() { return siteUrl + useBaseUrl(pathname); } +// TODO move to SiteMetadataDefaults or theme-common ? function CanonicalUrlHeaders({permalink}: {permalink?: string}) { const { siteConfig: {url: siteUrl}, @@ -82,42 +83,31 @@ function CanonicalUrlHeaders({permalink}: {permalink?: string}) { ); } -export default function LayoutHead(props: Props): JSX.Element { +export default function SiteMetadata(): JSX.Element { const { - siteConfig: {favicon}, - i18n: {currentLocale, localeConfigs}, + i18n: {currentLocale}, } = useDocusaurusContext(); + + // TODO maybe move these 2 themeConfig to siteConfig? + // These seems useful for other themes as well const {metadata, image: defaultImage} = useThemeConfig(); - const {title, description, image, keywords, searchMetadata} = props; - const faviconUrl = useBaseUrl(favicon); - const pageTitle = useTitleFormatter(title); - const {htmlLang, direction: htmlDir} = localeConfigs[currentLocale]; return ( <> - - {favicon && } - {pageTitle} - + {/* The keyboard focus class name need to be applied when SSR so links + are outlined when JS is disabled */} + - {/* image can override the default image */} - {defaultImage && } - {image && } - - + {defaultImage && } - + element here, diff --git a/packages/docusaurus-theme-classic/src/theme/SkipToContent/index.tsx b/packages/docusaurus-theme-classic/src/theme/SkipToContent/index.tsx index a65cd2ec1c36..52710f088139 100644 --- a/packages/docusaurus-theme-classic/src/theme/SkipToContent/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/SkipToContent/index.tsx @@ -18,7 +18,7 @@ function programmaticFocus(el: HTMLElement) { el.removeAttribute('tabindex'); } -function SkipToContent(): JSX.Element { +export default function SkipToContent(): JSX.Element { const containerRef = useRef(null); const {action} = useHistory(); const handleSkip = (e: React.MouseEvent) => { @@ -52,5 +52,3 @@ function SkipToContent(): JSX.Element {
      ); } - -export default SkipToContent; diff --git a/packages/docusaurus-theme-classic/src/theme/TOC/index.tsx b/packages/docusaurus-theme-classic/src/theme/TOC/index.tsx index efc6beebc53e..79ca736645e1 100644 --- a/packages/docusaurus-theme-classic/src/theme/TOC/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/TOC/index.tsx @@ -7,16 +7,16 @@ import React from 'react'; import clsx from 'clsx'; -import type {TOCProps} from '@theme/TOC'; +import type {Props} from '@theme/TOC'; import TOCItems from '@theme/TOCItems'; import styles from './styles.module.css'; // Using a custom className -// This prevents TOC highlighting to highlight TOCInline/TOCCollapsible by mistake +// This prevents TOCInline/TOCCollapsible getting highlighted by mistake const LINK_CLASS_NAME = 'table-of-contents__link toc-highlight'; const LINK_ACTIVE_CLASS_NAME = 'table-of-contents__link--active'; -function TOC({className, ...props}: TOCProps): JSX.Element { +export default function TOC({className, ...props}: Props): JSX.Element { return (
      ); } - -export default TOC; diff --git a/packages/docusaurus-theme-classic/src/theme/TOCCollapsible/index.tsx b/packages/docusaurus-theme-classic/src/theme/TOCCollapsible/index.tsx index 507f6be105e5..115dc112ca42 100644 --- a/packages/docusaurus-theme-classic/src/theme/TOCCollapsible/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/TOCCollapsible/index.tsx @@ -11,14 +11,14 @@ import Translate from '@docusaurus/Translate'; import {useCollapsible, Collapsible} from '@docusaurus/theme-common'; import styles from './styles.module.css'; import TOCItems from '@theme/TOCItems'; -import type {TOCCollapsibleProps} from '@theme/TOCCollapsible'; +import type {Props} from '@theme/TOCCollapsible'; export default function TOCCollapsible({ toc, className, minHeadingLevel, maxHeadingLevel, -}: TOCCollapsibleProps): JSX.Element { +}: Props): JSX.Element { const {collapsed, toggleCollapsed} = useCollapsible({ initialState: true, }); @@ -27,9 +27,7 @@ export default function TOCCollapsible({
      ); } - -export default TabItem; diff --git a/packages/docusaurus-theme-classic/src/theme/Tabs/__tests__/index.test.tsx b/packages/docusaurus-theme-classic/src/theme/Tabs/__tests__/index.test.tsx index c65e4687ea3b..52966b9c2fa6 100644 --- a/packages/docusaurus-theme-classic/src/theme/Tabs/__tests__/index.test.tsx +++ b/packages/docusaurus-theme-classic/src/theme/Tabs/__tests__/index.test.tsx @@ -15,7 +15,7 @@ import { } from '@docusaurus/theme-common'; describe('Tabs', () => { - test('Should reject bad Tabs child', () => { + it('rejects bad Tabs child', () => { expect(() => { renderer.create( @@ -27,7 +27,7 @@ describe('Tabs', () => { `"Docusaurus error: Bad child
      : all children of the component should be , and every should have a unique \\"value\\" prop."`, ); }); - test('Should reject bad Tabs defaultValue', () => { + it('rejects bad Tabs defaultValue', () => { expect(() => { renderer.create( @@ -39,7 +39,7 @@ describe('Tabs', () => { `"Docusaurus error: The has a defaultValue \\"bad\\" but none of its children has the corresponding value. Available values are: v1, v2. If you intend to show no default tab, use defaultValue={null} instead."`, ); }); - test('Should reject duplicate values', () => { + it('rejects duplicate values', () => { expect(() => { renderer.create( @@ -55,7 +55,7 @@ describe('Tabs', () => { `"Docusaurus error: Duplicate values \\"v1, v2\\" found in . Every value needs to be unique."`, ); }); - test('Should accept valid Tabs config', () => { + it('accepts valid Tabs config', () => { expect(() => { renderer.create( @@ -110,7 +110,7 @@ describe('Tabs', () => { }).not.toThrow(); // TODO Better Jest infrastructure to mock the Layout }); // https://github.com/facebook/docusaurus/issues/5729 - test('Should accept dynamic Tabs with number values', () => { + it('accepts dynamic Tabs with number values', () => { expect(() => { const tabs = ['Apple', 'Banana', 'Carrot']; renderer.create( @@ -120,7 +120,9 @@ describe('Tabs', () => { values={tabs.map((t, idx) => ({label: t, value: idx}))} defaultValue={0}> {tabs.map((t, idx) => ( - {t} + + {t} + ))} diff --git a/packages/docusaurus-theme-classic/src/theme/Tabs/index.tsx b/packages/docusaurus-theme-classic/src/theme/Tabs/index.tsx index 9fe416477d25..efaae4c05501 100644 --- a/packages/docusaurus-theme-classic/src/theme/Tabs/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/Tabs/index.tsx @@ -8,7 +8,6 @@ import React, { useState, cloneElement, - Children, isValidElement, type ReactElement, } from 'react'; @@ -40,7 +39,7 @@ function TabsComponent(props: Props): JSX.Element { groupId, className, } = props; - const children = Children.map(props.children, (child) => { + const children = React.Children.map(props.children, (child) => { if (isValidElement(child) && isTabItem(child)) { return child; } @@ -55,7 +54,7 @@ function TabsComponent(props: Props): JSX.Element { }); const values = valuesProp ?? - // We only pick keys that we recognize. MDX would inject some keys by default + // Only pick keys that we recognize. MDX would inject some keys by default children.map(({props: {value, label, attributes}}) => ({ value, label, @@ -108,7 +107,7 @@ function TabsComponent(props: Props): JSX.Element { ) => { const newTab = event.currentTarget; const newTabIndex = tabRefs.indexOf(newTab); - const newTabValue = values[newTabIndex].value; + const newTabValue = values[newTabIndex]!.value; if (newTabValue !== selectedValue) { blockElementScrollPositionUntilNextRender(newTab); @@ -126,12 +125,12 @@ function TabsComponent(props: Props): JSX.Element { switch (event.key) { case 'ArrowRight': { const nextTab = tabRefs.indexOf(event.currentTarget) + 1; - focusElement = tabRefs[nextTab] || tabRefs[0]; + focusElement = tabRefs[nextTab] || tabRefs[0]!; break; } case 'ArrowLeft': { const prevTab = tabRefs.indexOf(event.currentTarget) - 1; - focusElement = tabRefs[prevTab] || tabRefs[tabRefs.length - 1]; + focusElement = tabRefs[prevTab] || tabRefs[tabRefs.length - 1]!; break; } default: @@ -181,7 +180,7 @@ function TabsComponent(props: Props): JSX.Element { cloneElement( children.filter( (tabItem) => tabItem.props.value === selectedValue, - )[0], + )[0]!, {className: 'margin-vert--md'}, ) ) : ( diff --git a/packages/docusaurus-theme-classic/src/theme/Tag/index.tsx b/packages/docusaurus-theme-classic/src/theme/Tag/index.tsx index c2871fb3352e..ffe3ea717df3 100644 --- a/packages/docusaurus-theme-classic/src/theme/Tag/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/Tag/index.tsx @@ -12,20 +12,18 @@ import type {Props} from '@theme/Tag'; import styles from './styles.module.css'; -function Tag(props: Props): JSX.Element { +export default function Tag(props: Props): JSX.Element { const {permalink, name, count} = props; return ( + className={clsx( + styles.tag, + count ? styles.tagWithCount : styles.tagRegular, + )}> {name} {count && {count}} ); } - -export default Tag; diff --git a/packages/docusaurus-theme-classic/src/theme/TagsListByLetter/index.tsx b/packages/docusaurus-theme-classic/src/theme/TagsListByLetter/index.tsx index 47543dbcb042..c301c12e1ac4 100644 --- a/packages/docusaurus-theme-classic/src/theme/TagsListByLetter/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/TagsListByLetter/index.tsx @@ -28,7 +28,7 @@ function TagLetterEntryItem({letterEntry}: {letterEntry: TagLetterEntry}) { ); } -function TagsListByLetter({tags}: Props): JSX.Element { +export default function TagsListByLetter({tags}: Props): JSX.Element { const letterList = listTagsByLetters(tags); return (
      @@ -41,5 +41,3 @@ function TagsListByLetter({tags}: Props): JSX.Element {
      ); } - -export default TagsListByLetter; diff --git a/packages/docusaurus-theme-classic/src/theme/ThemedImage/index.tsx b/packages/docusaurus-theme-classic/src/theme/ThemedImage/index.tsx index 179b4f0e3706..0255e04805bb 100644 --- a/packages/docusaurus-theme-classic/src/theme/ThemedImage/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/ThemedImage/index.tsx @@ -14,14 +14,15 @@ import type {Props} from '@theme/ThemedImage'; import styles from './styles.module.css'; -function ThemedImage(props: Props): JSX.Element { +export default function ThemedImage(props: Props): JSX.Element { const isBrowser = useIsBrowser(); - const {isDarkTheme} = useColorMode(); - const {sources, className, alt = '', ...propsRest} = props; + const {colorMode} = useColorMode(); + const {sources, className, alt, ...propsRest} = props; type SourceName = keyof Props['sources']; - const clientThemes: SourceName[] = isDarkTheme ? ['dark'] : ['light']; + const clientThemes: SourceName[] = + colorMode === 'dark' ? ['dark'] : ['light']; const renderedSourceNames: SourceName[] = isBrowser ? clientThemes @@ -47,5 +48,3 @@ function ThemedImage(props: Props): JSX.Element { ); } - -export default ThemedImage; diff --git a/packages/docusaurus-theme-classic/src/theme/ThemedImage/styles.module.css b/packages/docusaurus-theme-classic/src/theme/ThemedImage/styles.module.css index 2f8648d5d9e4..876398ec3941 100644 --- a/packages/docusaurus-theme-classic/src/theme/ThemedImage/styles.module.css +++ b/packages/docusaurus-theme-classic/src/theme/ThemedImage/styles.module.css @@ -9,10 +9,10 @@ display: none; } -html[data-theme='light'] .themedImage--light { +[data-theme='light'] .themedImage--light { display: initial; } -html[data-theme='dark'] .themedImage--dark { +[data-theme='dark'] .themedImage--dark { display: initial; } diff --git a/packages/docusaurus-theme-classic/src/theme/Toggle/index.tsx b/packages/docusaurus-theme-classic/src/theme/Toggle/index.tsx deleted file mode 100644 index 168171306d22..000000000000 --- a/packages/docusaurus-theme-classic/src/theme/Toggle/index.tsx +++ /dev/null @@ -1,93 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import React, {useState, useRef, memo} from 'react'; -import type {Props} from '@theme/Toggle'; -import {useThemeConfig, type ColorModeConfig} from '@docusaurus/theme-common'; -import useIsBrowser from '@docusaurus/useIsBrowser'; - -import clsx from 'clsx'; -import styles from './styles.module.css'; - -// Based on react-toggle (https://github.com/aaronshaf/react-toggle/). -const ToggleComponent = memo( - ({ - className, - switchConfig, - checked: defaultChecked, - disabled, - onChange, - }: Props & { - switchConfig: ColorModeConfig['switchConfig']; - disabled: boolean; - }): JSX.Element => { - const {darkIcon, darkIconStyle, lightIcon, lightIconStyle} = switchConfig; - const [checked, setChecked] = useState(defaultChecked); - const [focused, setFocused] = useState(false); - const inputRef = useRef(null); - - return ( -
      - {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events */} -
      inputRef.current?.click()}> -
      - - {darkIcon} - -
      -
      - - {lightIcon} - -
      -
      -
      - - setChecked(!checked)} - onFocus={() => setFocused(true)} - onBlur={() => setFocused(false)} - onKeyDown={(e) => { - if (e.key === 'Enter') { - inputRef.current?.click(); - } - }} - /> -
      - ); - }, -); - -export default function Toggle(props: Props): JSX.Element { - const { - colorMode: {switchConfig}, - } = useThemeConfig(); - const isBrowser = useIsBrowser(); - - return ( - - ); -} diff --git a/packages/docusaurus-theme-classic/src/theme/Toggle/styles.module.css b/packages/docusaurus-theme-classic/src/theme/Toggle/styles.module.css deleted file mode 100644 index c293660b85c5..000000000000 --- a/packages/docusaurus-theme-classic/src/theme/Toggle/styles.module.css +++ /dev/null @@ -1,106 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -.toggle { - touch-action: pan-x; - position: relative; - cursor: pointer; - user-select: none; - -webkit-tap-highlight-color: transparent; -} - -.toggleScreenReader { - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - position: absolute; - width: 1px; -} - -.toggleDisabled { - cursor: not-allowed; -} - -.toggleTrack { - width: 50px; - height: 24px; - border-radius: 30px; - background-color: #4d4d4d; - transition: all 0.2s ease; -} - -.toggleTrackCheck { - position: absolute; - width: 14px; - height: 10px; - top: 0; - bottom: 0; - margin: auto 0; - left: 8px; - opacity: 0; - transition: opacity 0.25s ease; -} - -.toggleChecked .toggleTrackCheck, -[data-theme='dark'] .toggle .toggleTrackCheck { - opacity: 1; - transition: opacity 0.25s ease; -} - -.toggleTrackX { - position: absolute; - width: 10px; - height: 10px; - top: 0; - bottom: 0; - margin: auto 0; - right: 10px; - opacity: 1; - transition: opacity 0.25s ease; -} - -.toggleChecked .toggleTrackX, -[data-theme='dark'] .toggle .toggleTrackX { - opacity: 0; -} - -.toggleTrackThumb { - position: absolute; - top: 1px; - left: 1px; - width: 22px; - height: 22px; - border: 1px solid #4d4d4d; - border-radius: 50%; - background-color: #fafafa; - transition: all 0.25s ease; -} - -.toggleFocused .toggleTrackThumb, -.toggle:hover .toggleTrackThumb { - box-shadow: 0 0 2px 3px var(--ifm-color-primary); -} - -/* stylelint-disable-next-line no-descending-specificity */ -.toggleChecked .toggleTrackThumb, -[data-theme='dark'] .toggle .toggleTrackThumb { - left: 27px; -} - -.toggle:active:not(.toggleDisabled) .toggleTrackThumb { - box-shadow: 0 0 5px 5px var(--ifm-color-primary); -} - -.toggleIcon { - align-items: center; - display: flex; - height: 10px; - justify-content: center; - width: 10px; -} diff --git a/packages/docusaurus-theme-classic/src/theme/prism-include-languages.ts b/packages/docusaurus-theme-classic/src/theme/prism-include-languages.ts index 418e95dc3f33..9d9ed5e065be 100644 --- a/packages/docusaurus-theme-classic/src/theme/prism-include-languages.ts +++ b/packages/docusaurus-theme-classic/src/theme/prism-include-languages.ts @@ -5,32 +5,29 @@ * LICENSE file in the root directory of this source tree. */ -import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment'; import siteConfig from '@generated/docusaurus.config'; import type * as PrismNamespace from 'prismjs'; -const prismIncludeLanguages = (PrismObject: typeof PrismNamespace): void => { - if (ExecutionEnvironment.canUseDOM) { - const { - themeConfig: {prism}, - } = siteConfig; - const {additionalLanguages} = prism as {additionalLanguages: string[]}; +export default function prismIncludeLanguages( + PrismObject: typeof PrismNamespace, +): void { + const { + themeConfig: {prism}, + } = siteConfig; + const {additionalLanguages} = prism as {additionalLanguages: string[]}; - // Prism components work on the Prism instance on the window, while - // prism-react-renderer uses its own Prism instance. We temporarily mount - // the instance onto window, import components to enhance it, then remove it - // to avoid polluting global namespace. - // You can mutate this object: registering plugins, deleting languages... As - // long as you don't re-assign it - window.Prism = PrismObject; + // Prism components work on the Prism instance on the window, while prism- + // react-renderer uses its own Prism instance. We temporarily mount the + // instance onto window, import components to enhance it, then remove it to + // avoid polluting global namespace. + // You can mutate PrismObject: registering plugins, deleting languages... As + // long as you don't re-assign it + globalThis.Prism = PrismObject; - additionalLanguages.forEach((lang) => { - // eslint-disable-next-line global-require, import/no-dynamic-require - require(`prismjs/components/prism-${lang}`); - }); + additionalLanguages.forEach((lang) => { + // eslint-disable-next-line global-require, import/no-dynamic-require + require(`prismjs/components/prism-${lang}`); + }); - delete (window as Window & {Prism?: typeof PrismNamespace}).Prism; - } -}; - -export default prismIncludeLanguages; + delete (globalThis as Global & {Prism?: typeof PrismNamespace}).Prism; +} diff --git a/packages/docusaurus-theme-classic/src/translations.ts b/packages/docusaurus-theme-classic/src/translations.ts index 190a8188c79f..8d8bd673b48d 100644 --- a/packages/docusaurus-theme-classic/src/translations.ts +++ b/packages/docusaurus-theme-classic/src/translations.ts @@ -15,7 +15,7 @@ import type { SimpleFooter, } from '@docusaurus/theme-common'; -import {keyBy, chain} from 'lodash'; +import _ from 'lodash'; import {mergeTranslations} from '@docusaurus/utils'; function getNavbarTranslationFile(navbar: Navbar): TranslationFileContent { @@ -30,15 +30,17 @@ function getNavbarTranslationFile(navbar: Navbar): TranslationFileContent { const allNavbarItems = flattenNavbarItems(navbar.items); - const navbarItemsTranslations: TranslationFileContent = chain( - allNavbarItems.filter((navbarItem) => !!navbarItem.label), - ) - .keyBy((navbarItem) => `item.label.${navbarItem.label}`) - .mapValues((navbarItem) => ({ - message: navbarItem.label!, - description: `Navbar item with label ${navbarItem.label}`, - })) - .value(); + const navbarItemsTranslations: TranslationFileContent = Object.fromEntries( + allNavbarItems + .filter((navbarItem) => navbarItem.label) + .map((navbarItem) => [ + `item.label.${navbarItem.label}`, + { + message: navbarItem.label!, + description: `Navbar item with label ${navbarItem.label}`, + }, + ]), + ); const titleTranslations: TranslationFileContent = navbar.title ? {title: {message: navbar.title, description: 'The title in the navbar'}} @@ -48,8 +50,11 @@ function getNavbarTranslationFile(navbar: Navbar): TranslationFileContent { } function translateNavbar( navbar: Navbar, - navbarTranslations: TranslationFileContent, + navbarTranslations: TranslationFileContent | undefined, ): Navbar { + if (!navbarTranslations) { + return navbar; + } return { ...navbar, title: navbarTranslations.title?.message ?? navbar.title, @@ -74,37 +79,37 @@ function translateNavbar( function isMultiColumnFooterLinks( links: MultiColumnFooter['links'] | SimpleFooter['links'], ): links is MultiColumnFooter['links'] { - return links.length > 0 && 'title' in links[0]; + return links.length > 0 && 'title' in links[0]!; } function getFooterTranslationFile(footer: Footer): TranslationFileContent { - const footerLinkTitles: TranslationFileContent = chain( - isMultiColumnFooterLinks(footer.links) - ? footer.links.filter((link) => !!link.title) - : [], - ) - .keyBy((link) => `link.title.${link.title}`) - .mapValues((link) => ({ - message: link.title!, - description: `The title of the footer links column with title=${link.title} in the footer`, - })) - .value(); - - const footerLinkLabels: TranslationFileContent = chain( - isMultiColumnFooterLinks(footer.links) - ? footer.links - .flatMap((link) => link.items) - .filter((link) => !!link.label) - : footer.links.filter((link) => !!link.label), - ) - .keyBy((linkItem) => `link.item.label.${linkItem.label}`) - .mapValues((linkItem) => ({ - message: linkItem.label!, - description: `The label of footer link with label=${ - linkItem.label - } linking to ${linkItem.to ?? linkItem.href}`, - })) - .value(); + const footerLinkTitles: TranslationFileContent = Object.fromEntries( + (isMultiColumnFooterLinks(footer.links) + ? footer.links.filter((link) => link.title) + : [] + ).map((link) => [ + `link.title.${link.title}`, + { + message: link.title!, + description: `The title of the footer links column with title=${link.title} in the footer`, + }, + ]), + ); + + const footerLinkLabels: TranslationFileContent = Object.fromEntries( + (isMultiColumnFooterLinks(footer.links) + ? footer.links.flatMap((link) => link.items).filter((link) => link.label) + : footer.links.filter((link) => link.label) + ).map((link) => [ + `link.item.label.${link.label}`, + { + message: link.label!, + description: `The label of footer link with label=${ + link.label + } linking to ${link.to ?? link.href}`, + }, + ]), + ); const copyright: TranslationFileContent = footer.copyright ? { @@ -119,8 +124,11 @@ function getFooterTranslationFile(footer: Footer): TranslationFileContent { } function translateFooter( footer: Footer, - footerTranslations: TranslationFileContent, + footerTranslations: TranslationFileContent | undefined, ): Footer { + if (!footerTranslations) { + return footer; + } const links = isMultiColumnFooterLinks(footer.links) ? footer.links.map((link) => ({ ...link, @@ -171,12 +179,12 @@ export function translateThemeConfig({ themeConfig, translationFiles, }: { - // Why partial? To make TS correctly figure out the contravariance in parameter. + // To make TS correctly figure out the contravariance in parameter. // In practice it's always normalized themeConfig: ThemeConfig; translationFiles: TranslationFile[]; }): ThemeConfig { - const translationFilesMap: Record = keyBy( + const translationFilesMap: {[fileName: string]: TranslationFile} = _.keyBy( translationFiles, (f) => f.path, ); @@ -185,10 +193,10 @@ export function translateThemeConfig({ ...themeConfig, navbar: translateNavbar( themeConfig.navbar, - translationFilesMap.navbar.content, + translationFilesMap.navbar?.content, ), footer: themeConfig.footer - ? translateFooter(themeConfig.footer, translationFilesMap.footer.content) + ? translateFooter(themeConfig.footer, translationFilesMap.footer?.content) : undefined, }; } diff --git a/packages/docusaurus-theme-classic/src/types.d.ts b/packages/docusaurus-theme-classic/src/types.d.ts deleted file mode 100644 index db5e128fd3d1..000000000000 --- a/packages/docusaurus-theme-classic/src/types.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -/// -/// -/// -/// diff --git a/packages/docusaurus-theme-classic/src/validateThemeConfig.ts b/packages/docusaurus-theme-classic/src/validateThemeConfig.ts index 9fe30dee7f95..8de448ea652a 100644 --- a/packages/docusaurus-theme-classic/src/validateThemeConfig.ts +++ b/packages/docusaurus-theme-classic/src/validateThemeConfig.ts @@ -6,7 +6,10 @@ */ import {Joi, URISchema} from '@docusaurus/utils-validation'; -import type {ThemeConfig, Validate, ValidationResult} from '@docusaurus/types'; +import type { + ThemeConfig, + ThemeConfigValidationContext, +} from '@docusaurus/types'; const DEFAULT_DOCS_CONFIG = { versionPersistence: 'localStorage', @@ -21,15 +24,9 @@ const DEFAULT_COLOR_MODE_CONFIG = { defaultMode: 'light', disableSwitch: false, respectPrefersColorScheme: false, - switchConfig: { - darkIcon: '🌜', - darkIconStyle: {}, - lightIcon: '🌞', - lightIconStyle: {}, - }, }; -const DEFAULT_CONFIG = { +export const DEFAULT_CONFIG = { colorMode: DEFAULT_COLOR_MODE_CONFIG, docs: DEFAULT_DOCS_CONFIG, metadata: [], @@ -54,8 +51,8 @@ const NavbarItemBaseSchema = Joi.object({ label: Joi.string(), className: Joi.string(), }) - // We allow any unknown attributes on the links - // (users may need additional attributes like target, aria-role, data-customAttribute...) + // We allow any unknown attributes on the links (users may need additional + // attributes like target, aria-role, data-customAttribute...) .unknown(); const DefaultNavbarItemSchema = NavbarItemBaseSchema.append({ @@ -116,6 +113,10 @@ const DropdownSubitemSchema = Joi.object({ is: itemWithType('doc'), then: DocItemSchema, }, + { + is: itemWithType('docSidebar'), + then: DocSidebarItemSchema, + }, { is: itemWithType(undefined), then: DefaultNavbarItemSchema, @@ -216,20 +217,10 @@ const ColorModeSchema = Joi.object({ respectPrefersColorScheme: Joi.bool().default( DEFAULT_COLOR_MODE_CONFIG.respectPrefersColorScheme, ), - switchConfig: Joi.object({ - darkIcon: Joi.string().default( - DEFAULT_COLOR_MODE_CONFIG.switchConfig.darkIcon, - ), - darkIconStyle: Joi.object().default( - DEFAULT_COLOR_MODE_CONFIG.switchConfig.darkIconStyle, - ), - lightIcon: Joi.string().default( - DEFAULT_COLOR_MODE_CONFIG.switchConfig.lightIcon, - ), - lightIconStyle: Joi.object().default( - DEFAULT_COLOR_MODE_CONFIG.switchConfig.lightIconStyle, - ), - }).default(DEFAULT_COLOR_MODE_CONFIG.switchConfig), + switchConfig: Joi.any().forbidden().messages({ + 'any.unknown': + 'colorMode.switchConfig is deprecated. If you want to customize the icons for light and dark mode, swizzle IconLightMode, IconDarkMode, or ColorModeToggle instead.', + }), }).default(DEFAULT_COLOR_MODE_CONFIG); // schema can probably be improved @@ -251,15 +242,15 @@ const FooterLinkItemSchema = Joi.object({ .with('to', 'label') .with('href', 'label') .nand('html', 'label') - // We allow any unknown attributes on the links - // (users may need additional attributes like target, aria-role, data-customAttribute...) + // We allow any unknown attributes on the links (users may need additional + // attributes like target, aria-role, data-customAttribute...) .unknown(); const CustomCssSchema = Joi.alternatives() .try(Joi.array().items(Joi.string().required()), Joi.string().required()) .optional(); -const ThemeConfigSchema = Joi.object({ +export const ThemeConfigSchema = Joi.object({ // TODO temporary (@alpha-58) disableDarkMode: Joi.any().forbidden().messages({ 'any.unknown': @@ -316,7 +307,7 @@ const ThemeConfigSchema = Joi.object({ style: Joi.string().equal('dark', 'light').default('light'), logo: Joi.object({ alt: Joi.string().allow(''), - src: Joi.string(), + src: Joi.string().required(), srcDark: Joi.string(), // TODO infer this from reading the image width: Joi.alternatives().try(Joi.string(), Joi.number()), @@ -382,14 +373,9 @@ const ThemeConfigSchema = Joi.object({ }).default(DEFAULT_CONFIG.tableOfContents), }); -export {DEFAULT_CONFIG, ThemeConfigSchema}; - export function validateThemeConfig({ validate, themeConfig, -}: { - validate: Validate; - themeConfig: ThemeConfig; -}): ValidationResult { +}: ThemeConfigValidationContext): ThemeConfig { return validate(ThemeConfigSchema, themeConfig); } diff --git a/packages/docusaurus-theme-classic/tsconfig.json b/packages/docusaurus-theme-classic/tsconfig.json index 9840db7cfb76..ab5531d36d62 100644 --- a/packages/docusaurus-theme-classic/tsconfig.json +++ b/packages/docusaurus-theme-classic/tsconfig.json @@ -4,7 +4,7 @@ "lib": ["DOM", "ES2019"], "module": "esnext", "noEmit": true, - "jsx": "react", + "jsx": "react-native", "baseUrl": "src" }, "include": ["src/"] diff --git a/packages/docusaurus-theme-common/package.json b/packages/docusaurus-theme-common/package.json index ac6dd3a14c51..9c77f8eb95b9 100644 --- a/packages/docusaurus-theme-common/package.json +++ b/packages/docusaurus-theme-common/package.json @@ -1,6 +1,6 @@ { "name": "@docusaurus/theme-common", - "version": "2.0.0-beta.14", + "version": "2.0.0-beta.18", "description": "Common code for Docusaurus themes.", "main": "./lib/index.js", "types": "./lib/index.d.ts", @@ -18,24 +18,23 @@ }, "license": "MIT", "dependencies": { - "@docusaurus/plugin-content-blog": "2.0.0-beta.14", - "@docusaurus/plugin-content-docs": "2.0.0-beta.14", - "@docusaurus/plugin-content-pages": "2.0.0-beta.14", + "@docusaurus/module-type-aliases": "2.0.0-beta.18", + "@docusaurus/plugin-content-blog": "2.0.0-beta.18", + "@docusaurus/plugin-content-docs": "2.0.0-beta.18", + "@docusaurus/plugin-content-pages": "2.0.0-beta.18", "clsx": "^1.1.1", "parse-numeric-range": "^1.3.0", + "prism-react-renderer": "^1.3.1", "tslib": "^2.3.1", "utility-types": "^3.10.0" }, "devDependencies": { - "@docusaurus/core": "2.0.0-beta.14", - "@docusaurus/module-type-aliases": "2.0.0-beta.14", - "@docusaurus/types": "2.0.0-beta.14", - "@testing-library/react-hooks": "^7.0.2", - "fs-extra": "^10.0.0", - "lodash": "^4.17.20" + "@docusaurus/core": "2.0.0-beta.18", + "@docusaurus/types": "2.0.0-beta.18", + "fs-extra": "^10.0.1", + "lodash": "^4.17.21" }, "peerDependencies": { - "prism-react-renderer": "^1.2.1", "react": "^16.8.4 || ^17.0.0", "react-dom": "^16.8.4 || ^17.0.0" }, diff --git a/packages/docusaurus-theme-common/src/components/Collapsible/index.tsx b/packages/docusaurus-theme-common/src/components/Collapsible/index.tsx index 57dff7b2f19d..9185ac91c16f 100644 --- a/packages/docusaurus-theme-common/src/components/Collapsible/index.tsx +++ b/packages/docusaurus-theme-common/src/components/Collapsible/index.tsx @@ -11,29 +11,28 @@ import React, { useEffect, useRef, useCallback, + useLayoutEffect, type RefObject, type Dispatch, type SetStateAction, type ReactNode, - useLayoutEffect, } from 'react'; const DefaultAnimationEasing = 'ease-in-out'; -export type UseCollapsibleConfig = { +/** + * This hook is a very thin wrapper around a `useState`. + */ +export function useCollapsible({ + initialState, +}: { + /** The initial state. Will be non-collapsed by default. */ initialState: boolean | (() => boolean); -}; - -export type UseCollapsibleReturns = { +}): { collapsed: boolean; setCollapsed: Dispatch>; toggleCollapsed: () => void; -}; - -// This hook just define the state -export function useCollapsible({ - initialState, -}: UseCollapsibleConfig): UseCollapsibleReturns { +} { const [collapsed, setCollapsed] = useState(initialState ?? false); const toggleCollapsed = useCallback(() => { @@ -67,7 +66,8 @@ function applyCollapsedStyle(el: HTMLElement, collapsed: boolean) { } /* -Lex111: Dynamic transition duration is used in Material design, this technique is good for a large number of items. +Lex111: Dynamic transition duration is used in Material design, this technique +is good for a large number of items. https://material.io/archive/guidelines/motion/duration-easing.html#duration-easing-dynamic-durations https://github.com/mui-org/material-ui/blob/e724d98eba018e55e1a684236a2037e24bcf050c/packages/material-ui/src/styles/createTransitions.js#L40-L43 */ @@ -151,7 +151,10 @@ type CollapsibleElementType = React.ElementType< Pick, 'className' | 'onTransitionEnd' | 'style'> >; -// Prevent hydration layout shift before animations are handled imperatively with JS +/** + * Prevent hydration layout shift before animations are handled imperatively + * with JS + */ function getSSRStyle(collapsed: boolean) { if (ExecutionEnvironment.canUseDOM) { return undefined; @@ -160,15 +163,27 @@ function getSSRStyle(collapsed: boolean) { } type CollapsibleBaseProps = { + /** The actual DOM element to be used in the markup. */ as?: CollapsibleElementType; + /** Initial collapsed state. */ collapsed: boolean; children: ReactNode; + /** Configuration of animation, like `duration` and `easing` */ animation?: CollapsibleAnimationConfig; + /** + * A callback fired when the collapse transition animation ends. Receives + * the **new** collapsed state: e.g. when + * expanding, `collapsed` will be `false`. You can use this for some "cleanup" + * like applying new styles when the container is fully expanded. + */ onCollapseTransitionEnd?: (collapsed: boolean) => void; + /** Class name for the underlying DOM element. */ className?: string; - - // This is mostly useful for details/summary component where ssrStyle is not needed (as details are hidden natively) - // and can mess-up with the default native behavior of the browser when JS fails to load or is disabled + /** + * This is mostly useful for details/summary component where ssrStyle is not + * needed (as details are hidden natively) and can mess up with the browser's + * native behavior when JS fails to load or is disabled + */ disableSSRStyle?: boolean; }; @@ -189,7 +204,8 @@ function CollapsibleBase({ return ( { @@ -215,7 +231,7 @@ function CollapsibleLazy({collapsed, ...props}: CollapsibleBaseProps) { } }, [collapsed]); - // lazyCollapsed updated in effect so that the first expansion transition can work + // lazyCollapsed updated in effect so that first expansion transition can work const [lazyCollapsed, setLazyCollapsed] = useState(collapsed); useLayoutEffect(() => { if (mounted) { @@ -229,13 +245,20 @@ function CollapsibleLazy({collapsed, ...props}: CollapsibleBaseProps) { } type CollapsibleProps = CollapsibleBaseProps & { - // Lazy allows to delay the rendering when collapsed => it will render children only after hydration, on first expansion - // Required prop: it forces to think if content should be server-rendered or not! - // This has perf impact on the SSR output and html file sizes - // See https://github.com/facebook/docusaurus/issues/4753 + /** + * Delay rendering of the content till first expansion. Marked as required to + * force us to think if content should be server-rendered or not. This has + * perf impact since it reduces html file sizes, but could undermine SEO. + * @see https://github.com/facebook/docusaurus/issues/4753 + */ lazy: boolean; }; +/** + * A headless component providing smooth and uniform collapsing behavior. The + * component will be invisible (zero height) when collapsed. Doesn't provide + * interactivity by itself: collapse state is toggled through props. + */ export function Collapsible({lazy, ...props}: CollapsibleProps): JSX.Element { const Comp = lazy ? CollapsibleLazy : CollapsibleBase; return ; diff --git a/packages/docusaurus-theme-common/src/components/Details/index.tsx b/packages/docusaurus-theme-common/src/components/Details/index.tsx index ce9f177aaca5..ccf7e10b7fb4 100644 --- a/packages/docusaurus-theme-common/src/components/Details/index.tsx +++ b/packages/docusaurus-theme-common/src/components/Details/index.tsx @@ -6,10 +6,10 @@ */ import React, { - type ComponentProps, - type ReactElement, useRef, useState, + type ComponentProps, + type ReactElement, } from 'react'; import useIsBrowser from '@docusaurus/useIsBrowser'; import clsx from 'clsx'; @@ -31,21 +31,31 @@ function hasParent(node: HTMLElement | null, parent: HTMLElement): boolean { } export type DetailsProps = { + /** Summary is provided as props, including the wrapping `` tag */ summary?: ReactElement; } & ComponentProps<'details'>; -function Details({summary, children, ...props}: DetailsProps): JSX.Element { +/** + * A mostly un-styled `
      ` element with smooth collapsing. Provides some + * very lightweight styles, but you should bring your UI. + */ +export function Details({ + summary, + children, + ...props +}: DetailsProps): JSX.Element { const isBrowser = useIsBrowser(); const detailsRef = useRef(null); const {collapsed, setCollapsed} = useCollapsible({ initialState: !props.open, }); - // We use a separate prop because it must be set only after animation completes - // Otherwise close anim won't work + // Use a separate state for the actual details prop, because it must be set + // only after animation completes, otherwise close animations won't work const [open, setOpen] = useState(props.open); return ( + // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions
      { @@ -95,5 +105,3 @@ function Details({summary, children, ...props}: DetailsProps): JSX.Element {
      ); } - -export default Details; diff --git a/packages/docusaurus-theme-common/src/contexts/__tests__/docsSidebar.test.tsx b/packages/docusaurus-theme-common/src/contexts/__tests__/docsSidebar.test.tsx new file mode 100644 index 000000000000..2082a9779aef --- /dev/null +++ b/packages/docusaurus-theme-common/src/contexts/__tests__/docsSidebar.test.tsx @@ -0,0 +1,36 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import {renderHook} from '@testing-library/react-hooks'; +import {useDocsSidebar, DocsSidebarProvider} from '../docsSidebar'; +import type {PropSidebar} from '@docusaurus/plugin-content-docs'; + +describe('useDocsSidebar', () => { + it('throws if context provider is missing', () => { + expect( + () => renderHook(() => useDocsSidebar()).result.current?.items, + ).toThrowErrorMatchingInlineSnapshot( + `"Hook useDocsSidebar is called outside the . "`, + ); + }); + + it('reads value from context provider', () => { + const name = 'mySidebarName'; + const items: PropSidebar = []; + const {result} = renderHook(() => useDocsSidebar(), { + wrapper: ({children}) => ( + + {children} + + ), + }); + expect(result.current).toBeDefined(); + expect(result.current!.name).toBe(name); + expect(result.current!.items).toBe(items); + }); +}); diff --git a/packages/docusaurus-theme-common/src/contexts/__tests__/docsVersion.test.tsx b/packages/docusaurus-theme-common/src/contexts/__tests__/docsVersion.test.tsx new file mode 100644 index 000000000000..972a824cd31f --- /dev/null +++ b/packages/docusaurus-theme-common/src/contexts/__tests__/docsVersion.test.tsx @@ -0,0 +1,46 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import {renderHook} from '@testing-library/react-hooks'; +import {useDocsVersion, DocsVersionProvider} from '../docsVersion'; +import type {PropVersionMetadata} from '@docusaurus/plugin-content-docs'; + +function testVersion(data?: Partial): PropVersionMetadata { + return { + version: 'versionName', + label: 'Version Label', + className: 'version className', + badge: true, + banner: 'unreleased', + docs: {}, + docsSidebars: {}, + isLast: false, + pluginId: 'default', + ...data, + }; +} + +describe('useDocsVersion', () => { + it('throws if context provider is missing', () => { + expect( + () => renderHook(() => useDocsVersion()).result.current, + ).toThrowErrorMatchingInlineSnapshot( + `"Hook useDocsVersion is called outside the . "`, + ); + }); + + it('reads value from context provider', () => { + const version = testVersion(); + const {result} = renderHook(() => useDocsVersion(), { + wrapper: ({children}) => ( + {children} + ), + }); + expect(result.current).toBe(version); + }); +}); diff --git a/packages/docusaurus-theme-common/src/utils/announcementBarUtils.tsx b/packages/docusaurus-theme-common/src/contexts/announcementBar.tsx similarity index 76% rename from packages/docusaurus-theme-common/src/utils/announcementBarUtils.tsx rename to packages/docusaurus-theme-common/src/contexts/announcementBar.tsx index 81067f6df3ac..f244be2bee2e 100644 --- a/packages/docusaurus-theme-common/src/utils/announcementBarUtils.tsx +++ b/packages/docusaurus-theme-common/src/contexts/announcementBar.tsx @@ -10,13 +10,13 @@ import React, { useEffect, useCallback, useMemo, - type ReactNode, useContext, - createContext, + type ReactNode, } from 'react'; import useIsBrowser from '@docusaurus/useIsBrowser'; -import {createStorageSlot} from './storageUtils'; -import {useThemeConfig} from './useThemeConfig'; +import {createStorageSlot} from '../utils/storageUtils'; +import {ReactContextError} from '../utils/reactUtils'; +import {useThemeConfig} from '../utils/useThemeConfig'; export const AnnouncementBarDismissStorageKey = 'docusaurus.announcement.dismiss'; @@ -32,12 +32,18 @@ const isDismissedInStorage = () => const setDismissedInStorage = (bool: boolean) => AnnouncementBarDismissStorage.set(String(bool)); -type AnnouncementBarAPI = { +type ContextValue = { + /** Whether the announcement bar should be displayed. */ readonly isActive: boolean; + /** + * Callback fired when the user closes the announcement. Will be saved. + */ readonly close: () => void; }; -const useAnnouncementBarContextValue = (): AnnouncementBarAPI => { +const Context = React.createContext(null); + +function useContextValue(): ContextValue { const {announcementBar} = useThemeConfig(); const isBrowser = useIsBrowser(); @@ -93,29 +99,21 @@ const useAnnouncementBarContextValue = (): AnnouncementBarAPI => { }), [announcementBar, isClosed, handleClose], ); -}; - -const AnnouncementBarContext = createContext(null); +} export function AnnouncementBarProvider({ children, }: { children: ReactNode; }): JSX.Element { - const value = useAnnouncementBarContextValue(); - return ( - - {children} - - ); + const value = useContextValue(); + return {children}; } -export const useAnnouncementBar = (): AnnouncementBarAPI => { - const api = useContext(AnnouncementBarContext); +export function useAnnouncementBar(): ContextValue { + const api = useContext(Context); if (!api) { - throw new Error( - 'useAnnouncementBar(): AnnouncementBar not found in React context: make sure to use the AnnouncementBarProvider on top of the tree', - ); + throw new ReactContextError('AnnouncementBarProvider'); } return api; -}; +} diff --git a/packages/docusaurus-theme-common/src/contexts/colorMode.tsx b/packages/docusaurus-theme-common/src/contexts/colorMode.tsx new file mode 100644 index 000000000000..ee7d4eb54f54 --- /dev/null +++ b/packages/docusaurus-theme-common/src/contexts/colorMode.tsx @@ -0,0 +1,176 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, { + useState, + useCallback, + useEffect, + useContext, + useMemo, + useRef, + type ReactNode, +} from 'react'; +import {ReactContextError} from '../utils/reactUtils'; + +import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment'; +import {createStorageSlot} from '../utils/storageUtils'; +import {useThemeConfig} from '../utils/useThemeConfig'; + +type ContextValue = { + /** Current color mode. */ + readonly colorMode: ColorMode; + /** Set new color mode. */ + readonly setColorMode: (colorMode: ColorMode) => void; + + // TODO legacy APIs kept for retro-compatibility: deprecate them + readonly isDarkTheme: boolean; + readonly setLightTheme: () => void; + readonly setDarkTheme: () => void; +}; + +const Context = React.createContext(undefined); + +const ColorModeStorageKey = 'theme'; +const ColorModeStorage = createStorageSlot(ColorModeStorageKey); + +const ColorModes = { + light: 'light', + dark: 'dark', +} as const; + +export type ColorMode = typeof ColorModes[keyof typeof ColorModes]; + +// Ensure to always return a valid colorMode even if input is invalid +const coerceToColorMode = (colorMode?: string | null): ColorMode => + colorMode === ColorModes.dark ? ColorModes.dark : ColorModes.light; + +const getInitialColorMode = (defaultMode: ColorMode | undefined): ColorMode => + ExecutionEnvironment.canUseDOM + ? coerceToColorMode(document.documentElement.getAttribute('data-theme')) + : coerceToColorMode(defaultMode); + +const storeColorMode = (newColorMode: ColorMode) => { + ColorModeStorage.set(coerceToColorMode(newColorMode)); +}; + +function useContextValue(): ContextValue { + const { + colorMode: {defaultMode, disableSwitch, respectPrefersColorScheme}, + } = useThemeConfig(); + const [colorMode, setColorModeState] = useState( + getInitialColorMode(defaultMode), + ); + + const setColorMode = useCallback((newColorMode: ColorMode) => { + setColorModeState(newColorMode); + storeColorMode(newColorMode); + }, []); + + useEffect(() => { + document.documentElement.setAttribute( + 'data-theme', + coerceToColorMode(colorMode), + ); + }, [colorMode]); + + useEffect(() => { + if (disableSwitch) { + return undefined; + } + const onChange = (e: StorageEvent) => { + if (e.key !== ColorModeStorageKey) { + return; + } + try { + const storedColorMode = ColorModeStorage.get(); + if (storedColorMode !== null) { + setColorMode(coerceToColorMode(storedColorMode)); + } + } catch (err) { + console.error(err); + } + }; + window.addEventListener('storage', onChange); + return () => window.removeEventListener('storage', onChange); + }, [disableSwitch, setColorMode]); + + // PCS is coerced to light mode when printing, which causes the color mode to + // be reset to dark when exiting print mode, disregarding user settings. When + // the listener fires only because of a print/screen switch, we don't change + // color mode. See https://github.com/facebook/docusaurus/pull/6490 + const previousMediaIsPrint = useRef(false); + + useEffect(() => { + if (disableSwitch && !respectPrefersColorScheme) { + return undefined; + } + const mql = window.matchMedia('(prefers-color-scheme: dark)'); + const onChange = ({matches}: MediaQueryListEvent) => { + if (window.matchMedia('print').matches || previousMediaIsPrint.current) { + previousMediaIsPrint.current = window.matchMedia('print').matches; + return; + } + setColorMode(matches ? ColorModes.dark : ColorModes.light); + }; + mql.addListener(onChange); + return () => mql.removeListener(onChange); + }, [setColorMode, disableSwitch, respectPrefersColorScheme]); + + return useMemo( + () => ({ + colorMode, + setColorMode, + get isDarkTheme() { + if (process.env.NODE_ENV === 'development') { + console.error( + '`useColorMode().isDarkTheme` is deprecated. Please use `useColorMode().colorMode === "dark"` instead.', + ); + } + return colorMode === ColorModes.dark; + }, + setLightTheme() { + if (process.env.NODE_ENV === 'development') { + console.error( + '`useColorMode().setLightTheme` is deprecated. Please use `useColorMode().setColorMode("light")` instead.', + ); + } + setColorMode(ColorModes.light); + storeColorMode(ColorModes.light); + }, + setDarkTheme() { + if (process.env.NODE_ENV === 'development') { + console.error( + '`useColorMode().setDarkTheme` is deprecated. Please use `useColorMode().setColorMode("dark")` instead.', + ); + } + setColorMode(ColorModes.dark); + storeColorMode(ColorModes.dark); + }, + }), + [colorMode, setColorMode], + ); +} + +export function ColorModeProvider({ + children, +}: { + children: ReactNode; +}): JSX.Element { + const value = useContextValue(); + return {children}; +} + +export function useColorMode(): ContextValue { + const context = useContext(Context); + if (context == null) { + throw new ReactContextError( + 'ColorModeProvider', + 'Please see https://docusaurus.io/docs/api/themes/configuration#use-color-mode.', + ); + } + return context; +} diff --git a/packages/docusaurus-theme-common/src/contexts/docSidebarItemsExpandedState.tsx b/packages/docusaurus-theme-common/src/contexts/docSidebarItemsExpandedState.tsx new file mode 100644 index 000000000000..b3d84a78389d --- /dev/null +++ b/packages/docusaurus-theme-common/src/contexts/docSidebarItemsExpandedState.tsx @@ -0,0 +1,55 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, {type ReactNode, useMemo, useState, useContext} from 'react'; +import {ReactContextError} from '../utils/reactUtils'; + +type ContextValue = { + /** + * The item that the user last opened, `null` when there's none open. On + * initial render, it will always be `null`, which doesn't necessarily mean + * there's no category open (can have 0, 1, or many being initially open). + */ + expandedItem: number | null; + /** + * Set the currently expanded item, when the user opens one. Set the value to + * `null` when the user closes an open category. + */ + setExpandedItem: (a: number | null) => void; +}; + +const EmptyContext: unique symbol = Symbol('EmptyContext'); +const Context = React.createContext( + EmptyContext, +); + +/** + * Should be used to wrap one sidebar category level. This provider syncs the + * expanded states of all sibling categories, and categories can choose to + * collapse itself if another one is expanded. + */ +export function DocSidebarItemsExpandedStateProvider({ + children, +}: { + children: ReactNode; +}): JSX.Element { + const [expandedItem, setExpandedItem] = useState(null); + const contextValue = useMemo( + () => ({expandedItem, setExpandedItem}), + [expandedItem], + ); + + return {children}; +} + +export function useDocSidebarItemsExpandedState(): ContextValue { + const value = useContext(Context); + if (value === EmptyContext) { + throw new ReactContextError('DocSidebarItemsExpandedStateProvider'); + } + return value; +} diff --git a/packages/docusaurus-theme-common/src/contexts/docsPreferredVersion.tsx b/packages/docusaurus-theme-common/src/contexts/docsPreferredVersion.tsx new file mode 100644 index 000000000000..0922c2c33c89 --- /dev/null +++ b/packages/docusaurus-theme-common/src/contexts/docsPreferredVersion.tsx @@ -0,0 +1,253 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, { + useContext, + useEffect, + useMemo, + useState, + useCallback, + type ReactNode, +} from 'react'; +import { + useThemeConfig, + type DocsVersionPersistence, +} from '../utils/useThemeConfig'; +import {isDocsPluginEnabled} from '../utils/docsUtils'; +import {ReactContextError} from '../utils/reactUtils'; +import {createStorageSlot} from '../utils/storageUtils'; + +import { + useAllDocsData, + useDocsData, + type GlobalPluginData, + type GlobalVersion, +} from '@docusaurus/plugin-content-docs/client'; + +import {DEFAULT_PLUGIN_ID} from '@docusaurus/constants'; + +const storageKey = (pluginId: string) => `docs-preferred-version-${pluginId}`; + +const DocsPreferredVersionStorage = { + save: ( + pluginId: string, + persistence: DocsVersionPersistence, + versionName: string, + ): void => { + createStorageSlot(storageKey(pluginId), {persistence}).set(versionName); + }, + + read: ( + pluginId: string, + persistence: DocsVersionPersistence, + ): string | null => + createStorageSlot(storageKey(pluginId), {persistence}).get(), + + clear: (pluginId: string, persistence: DocsVersionPersistence): void => { + createStorageSlot(storageKey(pluginId), {persistence}).del(); + }, +}; + +type DocsPreferredVersionName = string | null; + +/** State for a single docs plugin instance */ +type DocsPreferredVersionPluginState = { + preferredVersionName: DocsPreferredVersionName; +}; + +/** + * We need to store the state in storage globally, with one preferred version + * per docs plugin instance. + */ +type DocsPreferredVersionState = { + [pluginId: string]: DocsPreferredVersionPluginState; +}; + +/** + * Initial state is always null as we can't read local storage from node SSR + */ +const getInitialState = (pluginIds: string[]): DocsPreferredVersionState => + Object.fromEntries(pluginIds.map((id) => [id, {preferredVersionName: null}])); + +/** + * Read storage for all docs plugins, assigning each doc plugin a preferred + * version (if found) + */ +function readStorageState({ + pluginIds, + versionPersistence, + allDocsData, +}: { + pluginIds: string[]; + versionPersistence: DocsVersionPersistence; + allDocsData: {[pluginId: string]: GlobalPluginData}; +}): DocsPreferredVersionState { + /** + * The storage value we read might be stale, and belong to a version that does + * not exist in the site anymore. In such case, we remove the storage value to + * avoid downstream errors. + */ + function restorePluginState( + pluginId: string, + ): DocsPreferredVersionPluginState { + const preferredVersionNameUnsafe = DocsPreferredVersionStorage.read( + pluginId, + versionPersistence, + ); + const pluginData = allDocsData[pluginId]!; + const versionExists = pluginData.versions.some( + (version) => version.name === preferredVersionNameUnsafe, + ); + if (versionExists) { + return {preferredVersionName: preferredVersionNameUnsafe}; + } + DocsPreferredVersionStorage.clear(pluginId, versionPersistence); + return {preferredVersionName: null}; + } + return Object.fromEntries( + pluginIds.map((id) => [id, restorePluginState(id)]), + ); +} + +function useVersionPersistence(): DocsVersionPersistence { + return useThemeConfig().docs.versionPersistence; +} + +type ContextValue = [ + state: DocsPreferredVersionState, + api: { + savePreferredVersion: (pluginId: string, versionName: string) => void; + }, +]; + +const Context = React.createContext(null); + +function useContextValue(): ContextValue { + const allDocsData = useAllDocsData(); + const versionPersistence = useVersionPersistence(); + const pluginIds = useMemo(() => Object.keys(allDocsData), [allDocsData]); + + // Initial state is empty, as we can't read browser storage in node/SSR + const [state, setState] = useState(() => getInitialState(pluginIds)); + + // On mount, we set the state read from browser storage + useEffect(() => { + setState(readStorageState({allDocsData, versionPersistence, pluginIds})); + }, [allDocsData, versionPersistence, pluginIds]); + + // The API that we expose to consumer hooks (memo for constant object) + const api = useMemo(() => { + function savePreferredVersion(pluginId: string, versionName: string) { + DocsPreferredVersionStorage.save( + pluginId, + versionPersistence, + versionName, + ); + setState((s) => ({ + ...s, + [pluginId]: {preferredVersionName: versionName}, + })); + } + + return { + savePreferredVersion, + }; + }, [versionPersistence]); + + return [state, api]; +} + +function DocsPreferredVersionContextProviderUnsafe({ + children, +}: { + children: ReactNode; +}): JSX.Element { + const value = useContextValue(); + return {children}; +} + +/** + * This is a maybe-layer. If the docs plugin is not enabled, this provider is a + * simple pass-through. + */ +export function DocsPreferredVersionContextProvider({ + children, +}: { + children: JSX.Element; +}): JSX.Element { + if (isDocsPluginEnabled) { + return ( + + {children} + + ); + } + return children; +} + +function useDocsPreferredVersionContext(): ContextValue { + const value = useContext(Context); + if (!value) { + throw new ReactContextError('DocsPreferredVersionContextProvider'); + } + return value; +} + +/** + * Returns a read-write interface to a plugin's preferred version. The + * "preferred version" is defined as the last version that the user visited. + * For example, if a user is using v3, even when v4 is later published, the user + * would still be browsing v3 docs when she opens the website next time. Note, + * the `preferredVersion` attribute will always be `null` before mount. + */ +export function useDocsPreferredVersion( + pluginId: string | undefined = DEFAULT_PLUGIN_ID, +): { + preferredVersion: GlobalVersion | null; + savePreferredVersionName: (versionName: string) => void; +} { + const docsData = useDocsData(pluginId); + const [state, api] = useDocsPreferredVersionContext(); + + const {preferredVersionName} = state[pluginId]!; + + const preferredVersion = + docsData.versions.find( + (version) => version.name === preferredVersionName, + ) ?? null; + + const savePreferredVersionName = useCallback( + (versionName: string) => { + api.savePreferredVersion(pluginId, versionName); + }, + [api, pluginId], + ); + + return {preferredVersion, savePreferredVersionName}; +} + +export function useDocsPreferredVersionByPluginId(): { + [pluginId: string]: GlobalVersion | null; +} { + const allDocsData = useAllDocsData(); + const [state] = useDocsPreferredVersionContext(); + + function getPluginIdPreferredVersion(pluginId: string) { + const docsData = allDocsData[pluginId]!; + const {preferredVersionName} = state[pluginId]!; + + return ( + docsData.versions.find( + (version) => version.name === preferredVersionName, + ) ?? null + ); + } + const pluginIds = Object.keys(allDocsData); + return Object.fromEntries( + pluginIds.map((id) => [id, getPluginIdPreferredVersion(id)]), + ); +} diff --git a/packages/docusaurus-theme-common/src/contexts/docsSidebar.tsx b/packages/docusaurus-theme-common/src/contexts/docsSidebar.tsx new file mode 100644 index 000000000000..2fb4d1cb0f19 --- /dev/null +++ b/packages/docusaurus-theme-common/src/contexts/docsSidebar.tsx @@ -0,0 +1,56 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, {useMemo, useContext, type ReactNode} from 'react'; +import type {PropSidebar} from '@docusaurus/plugin-content-docs'; +import {ReactContextError} from '../utils/reactUtils'; + +// Using a Symbol because null is a valid context value (a doc with no sidebar) +// Inspired by https://github.com/jamiebuilds/unstated-next/blob/master/src/unstated-next.tsx +const EmptyContext: unique symbol = Symbol('EmptyContext'); + +type SidebarContextValue = {name: string; items: PropSidebar}; + +const Context = React.createContext< + SidebarContextValue | null | typeof EmptyContext +>(EmptyContext); + +/** + * Provide the current sidebar to your children. + */ +export function DocsSidebarProvider({ + children, + name, + items, +}: { + children: ReactNode; + name: string | undefined; + items: PropSidebar | undefined; +}): JSX.Element { + const stableValue: SidebarContextValue | null = useMemo( + () => + name && items + ? { + name, + items, + } + : null, + [name, items], + ); + return {children}; +} + +/** + * Gets the sidebar data that's currently displayed, or `null` if there isn't one + */ +export function useDocsSidebar(): SidebarContextValue | null { + const value = useContext(Context); + if (value === EmptyContext) { + throw new ReactContextError('DocsSidebarProvider'); + } + return value; +} diff --git a/packages/docusaurus-theme-common/src/contexts/docsVersion.tsx b/packages/docusaurus-theme-common/src/contexts/docsVersion.tsx new file mode 100644 index 000000000000..efa8ffc147d6 --- /dev/null +++ b/packages/docusaurus-theme-common/src/contexts/docsVersion.tsx @@ -0,0 +1,36 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, {type ReactNode, useContext} from 'react'; +import type {PropVersionMetadata} from '@docusaurus/plugin-content-docs'; +import {ReactContextError} from '../utils/reactUtils'; + +const Context = React.createContext(null); + +/** + * Provide the current version's metadata to your children. + */ +export function DocsVersionProvider({ + children, + version, +}: { + children: ReactNode; + version: PropVersionMetadata | null; +}): JSX.Element { + return {children}; +} + +/** + * Gets the version metadata of the current doc page. + */ +export function useDocsVersion(): PropVersionMetadata { + const version = useContext(Context); + if (version === null) { + throw new ReactContextError('DocsVersionProvider'); + } + return version; +} diff --git a/packages/docusaurus-theme-common/src/contexts/navbarMobileSidebar.tsx b/packages/docusaurus-theme-common/src/contexts/navbarMobileSidebar.tsx new file mode 100644 index 000000000000..c798605edb6c --- /dev/null +++ b/packages/docusaurus-theme-common/src/contexts/navbarMobileSidebar.tsx @@ -0,0 +1,99 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, { + useCallback, + useEffect, + useState, + useMemo, + type ReactNode, +} from 'react'; +import {useNavbarSecondaryMenuContent} from './navbarSecondaryMenu/content'; +import {useWindowSize} from '../hooks/useWindowSize'; +import {useHistoryPopHandler} from '../utils/historyUtils'; +import {useThemeConfig} from '../utils/useThemeConfig'; +import {ReactContextError} from '../utils/reactUtils'; + +type ContextValue = { + /** + * Mobile sidebar should be disabled in case it's empty, i.e. no secondary + * menu + no navbar items). If disabled, the toggle button should not be + * displayed at all. + */ + disabled: boolean; + /** + * Signals whether the actual sidebar should be displayed (contrary to + * `disabled` which is about the toggle button). Sidebar should not visible + * until user interaction to avoid SSR rendering. + */ + shouldRender: boolean; + /** The displayed state. Can be toggled with the `toggle` callback. */ + shown: boolean; + /** Toggle the `shown` attribute. */ + toggle: () => void; +}; + +const Context = React.createContext(undefined); + +function useIsNavbarMobileSidebarDisabled() { + const secondaryMenuContent = useNavbarSecondaryMenuContent(); + const {items} = useThemeConfig().navbar; + return items.length === 0 && !secondaryMenuContent.component; +} + +function useContextValue(): ContextValue { + const disabled = useIsNavbarMobileSidebarDisabled(); + const windowSize = useWindowSize(); + + const shouldRender = !disabled && windowSize === 'mobile'; + + const [shown, setShown] = useState(false); + + // Close mobile sidebar on navigation pop + // Most likely firing when using the Android back button (but not only) + useHistoryPopHandler(() => { + if (shown) { + setShown(false); + // Should we prevent the navigation here? + // See https://github.com/facebook/docusaurus/pull/5462#issuecomment-911699846 + return false; // prevent pop navigation + } + return undefined; + }); + + const toggle = useCallback(() => { + setShown((s) => !s); + }, []); + + useEffect(() => { + if (windowSize === 'desktop') { + setShown(false); + } + }, [windowSize]); + + return useMemo( + () => ({disabled, shouldRender, toggle, shown}), + [disabled, shouldRender, toggle, shown], + ); +} + +export function NavbarMobileSidebarProvider({ + children, +}: { + children: ReactNode; +}): JSX.Element { + const value = useContextValue(); + return {children}; +} + +export function useNavbarMobileSidebar(): ContextValue { + const context = React.useContext(Context); + if (context === undefined) { + throw new ReactContextError('NavbarMobileSidebarProvider'); + } + return context; +} diff --git a/packages/docusaurus-theme-common/src/contexts/navbarSecondaryMenu/content.tsx b/packages/docusaurus-theme-common/src/contexts/navbarSecondaryMenu/content.tsx new file mode 100644 index 000000000000..1183362b2349 --- /dev/null +++ b/packages/docusaurus-theme-common/src/contexts/navbarSecondaryMenu/content.tsx @@ -0,0 +1,110 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, { + useState, + useContext, + useEffect, + useMemo, + type ReactNode, + type ComponentType, +} from 'react'; +import {ReactContextError} from '../../utils/reactUtils'; + +// This context represents a "global layout store". A component (usually a +// layout component) can request filling this store through +// `NavbarSecondaryMenuFiller`. It doesn't actually control rendering by itself, +// and this context should be considered internal implementation. The user- +// facing value comes from `display.tsx`, which takes the `component` and +// `props` stored here and renders the actual element. + +export type NavbarSecondaryMenuComponent = ComponentType; + +/** @internal */ +export type Content = + | { + component: NavbarSecondaryMenuComponent; + props: object; + } + | {component: null; props: null}; + +type ContextValue = [ + content: Content, + setContent: React.Dispatch>, +]; + +const Context = React.createContext(null); + +/** @internal */ +export function NavbarSecondaryMenuContentProvider({ + children, +}: { + children: ReactNode; +}): JSX.Element { + const value = useState({component: null, props: null}); + return ( + // @ts-expect-error: this context is hard to type + {children} + ); +} + +/** @internal */ +export function useNavbarSecondaryMenuContent(): Content { + const value = useContext(Context); + if (!value) { + throw new ReactContextError('NavbarSecondaryMenuContentProvider'); + } + return value[0]; +} + +function useShallowMemoizedObject(obj: O) { + return useMemo( + () => obj, + // Is this safe? + // eslint-disable-next-line react-hooks/exhaustive-deps + [...Object.keys(obj), ...Object.values(obj)], + ); +} + +/** + * This component renders nothing by itself, but it fills the placeholder in the + * generic secondary menu layout. This reduces coupling between the main layout + * and the specific page. + * + * This kind of feature is often called portal/teleport/gateway/outlet... + * Various unmaintained React libs exist. Most up-to-date one: + * https://github.com/gregberge/react-teleporter + * Not sure any of those is safe regarding concurrent mode. + */ +export function NavbarSecondaryMenuFiller

      ({ + component, + props, +}: { + component: NavbarSecondaryMenuComponent

      ; + props: P; +}): JSX.Element | null { + const context = useContext(Context); + if (!context) { + throw new ReactContextError('NavbarSecondaryMenuContentProvider'); + } + const [, setContent] = context; + + // To avoid useless context re-renders, props are memoized shallowly + const memoizedProps = useShallowMemoizedObject(props); + + useEffect(() => { + // @ts-expect-error: this context is hard to type + setContent({component, props: memoizedProps}); + }, [setContent, component, memoizedProps]); + + useEffect( + () => () => setContent({component: null, props: null}), + [setContent], + ); + + return null; +} diff --git a/packages/docusaurus-theme-common/src/contexts/navbarSecondaryMenu/display.tsx b/packages/docusaurus-theme-common/src/contexts/navbarSecondaryMenu/display.tsx new file mode 100644 index 000000000000..06d39f93b4da --- /dev/null +++ b/packages/docusaurus-theme-common/src/contexts/navbarSecondaryMenu/display.tsx @@ -0,0 +1,102 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, { + useState, + useContext, + useEffect, + useMemo, + useCallback, + type ReactNode, +} from 'react'; +import {ReactContextError, usePrevious} from '../../utils/reactUtils'; +import {useNavbarMobileSidebar} from '../navbarMobileSidebar'; +import {useNavbarSecondaryMenuContent, type Content} from './content'; + +type ContextValue = [ + shown: boolean, + setShown: React.Dispatch>, +]; + +const Context = React.createContext(null); + +function useContextValue(): ContextValue { + const mobileSidebar = useNavbarMobileSidebar(); + const content = useNavbarSecondaryMenuContent(); + + const [shown, setShown] = useState(false); + + const hasContent = content.component !== null; + const previousHasContent = usePrevious(hasContent); + + // When content is become available for the first time (set in useEffect) + // we set this content to be shown! + useEffect(() => { + const contentBecameAvailable = hasContent && !previousHasContent; + if (contentBecameAvailable) { + setShown(true); + } + }, [hasContent, previousHasContent]); + + // On sidebar close, secondary menu is set to be shown on next re-opening + // (if any secondary menu content available) + useEffect(() => { + if (!hasContent) { + setShown(false); + return; + } + if (!mobileSidebar.shown) { + setShown(true); + } + }, [mobileSidebar.shown, hasContent]); + + return useMemo(() => [shown, setShown], [shown]); +} + +/** @internal */ +export function NavbarSecondaryMenuDisplayProvider({ + children, +}: { + children: ReactNode; +}): JSX.Element { + const value = useContextValue(); + return {children}; +} + +function renderElement(content: Content): JSX.Element | undefined { + if (content.component) { + const Comp = content.component; + return ; + } + return undefined; +} + +/** Wires the logic for rendering the mobile navbar secondary menu. */ +export function useNavbarSecondaryMenu(): { + /** Whether secondary menu is displayed. */ + shown: boolean; + /** + * Hide the secondary menu; fired either when hiding the entire sidebar, or + * when going back to the primary menu. + */ + hide: () => void; + /** The content returned from the current secondary menu filler. */ + content: JSX.Element | undefined; +} { + const value = useContext(Context); + if (!value) { + throw new ReactContextError('NavbarSecondaryMenuDisplayProvider'); + } + const [shown, setShown] = value; + const hide = useCallback(() => setShown(false), [setShown]); + const content = useNavbarSecondaryMenuContent(); + + return useMemo( + () => ({shown, hide, content: renderElement(content)}), + [hide, content, shown], + ); +} diff --git a/packages/docusaurus-theme-common/src/utils/tabGroupChoiceUtils.tsx b/packages/docusaurus-theme-common/src/contexts/tabGroupChoice.tsx similarity index 60% rename from packages/docusaurus-theme-common/src/utils/tabGroupChoiceUtils.tsx rename to packages/docusaurus-theme-common/src/contexts/tabGroupChoice.tsx index e11df2de181c..47919921e851 100644 --- a/packages/docusaurus-theme-common/src/utils/tabGroupChoiceUtils.tsx +++ b/packages/docusaurus-theme-common/src/contexts/tabGroupChoice.tsx @@ -9,25 +9,25 @@ import React, { useState, useCallback, useEffect, - createContext, useMemo, useContext, type ReactNode, } from 'react'; -import {createStorageSlot, listStorageKeys} from './storageUtils'; +import {createStorageSlot, listStorageKeys} from '../utils/storageUtils'; +import {ReactContextError} from '../utils/reactUtils'; const TAB_CHOICE_PREFIX = 'docusaurus.tab.'; -type TabGroupChoiceContextValue = { +type ContextValue = { + /** A map from `groupId` to the `value` of the saved choice. */ readonly tabGroupChoices: {readonly [groupId: string]: string}; + /** Set the new choice value of a group. */ readonly setTabGroupChoices: (groupId: string, newChoice: string) => void; }; -const TabGroupChoiceContext = createContext< - TabGroupChoiceContextValue | undefined ->(undefined); +const Context = React.createContext(undefined); -function useTabGroupChoiceContextValue(): TabGroupChoiceContextValue { +function useContextValue(): ContextValue { const [tabGroupChoices, setChoices] = useState<{ readonly [groupId: string]: string; }>({}); @@ -37,7 +37,7 @@ function useTabGroupChoiceContextValue(): TabGroupChoiceContextValue { useEffect(() => { try { - const localStorageChoices: Record = {}; + const localStorageChoices: {[groupId: string]: string} = {}; listStorageKeys().forEach((storageKey) => { if (storageKey.startsWith(TAB_CHOICE_PREFIX)) { const groupId = storageKey.substring(TAB_CHOICE_PREFIX.length); @@ -50,13 +50,18 @@ function useTabGroupChoiceContextValue(): TabGroupChoiceContextValue { } }, []); - return { - tabGroupChoices, - setTabGroupChoices: (groupId: string, newChoice: string) => { + const setTabGroupChoices = useCallback( + (groupId: string, newChoice: string) => { setChoices((oldChoices) => ({...oldChoices, [groupId]: newChoice})); setChoiceSyncWithLocalStorage(groupId, newChoice); }, - }; + [setChoiceSyncWithLocalStorage], + ); + + return useMemo( + () => ({tabGroupChoices, setTabGroupChoices}), + [tabGroupChoices, setTabGroupChoices], + ); } export function TabGroupChoiceProvider({ @@ -64,27 +69,14 @@ export function TabGroupChoiceProvider({ }: { children: ReactNode; }): JSX.Element { - const {tabGroupChoices, setTabGroupChoices} = useTabGroupChoiceContextValue(); - const contextValue = useMemo( - () => ({ - tabGroupChoices, - setTabGroupChoices, - }), - [tabGroupChoices, setTabGroupChoices], - ); - return ( - - {children} - - ); + const value = useContextValue(); + return {children}; } -export function useTabGroupChoice(): TabGroupChoiceContextValue { - const context = useContext(TabGroupChoiceContext); +export function useTabGroupChoice(): ContextValue { + const context = useContext(Context); if (context == null) { - throw new Error( - '"useUserPreferencesContext" is used outside of "Layout" component.', - ); + throw new ReactContextError('TabGroupChoiceProvider'); } return context; } diff --git a/packages/docusaurus-theme-common/src/hooks/useBackToTopButton.ts b/packages/docusaurus-theme-common/src/hooks/useBackToTopButton.ts new file mode 100644 index 000000000000..aa7e2fd7b7dc --- /dev/null +++ b/packages/docusaurus-theme-common/src/hooks/useBackToTopButton.ts @@ -0,0 +1,73 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {useRef, useState} from 'react'; +import {useScrollPosition, useSmoothScrollTo} from '../utils/scrollUtils'; +import {useLocationChange} from '../utils/useLocationChange'; + +/** Wires the logic for the back to top button. */ +export function useBackToTopButton({ + threshold, +}: { + /** + * The minimum vertical scroll position, above which a scroll-up would not + * cause `shown` to become `true`. This is because BTT is only useful if the + * user is far down the page. + */ + threshold: number; +}): { + /** + * Whether the button should be displayed. We only show if the user has + * scrolled up and is on a vertical position greater than `threshold`. + */ + shown: boolean; + /** + * A (memoized) handle for starting the scroll, which you can directly plug + * into the props. + */ + scrollToTop: () => void; +} { + const [shown, setShown] = useState(false); + const isFocusedAnchor = useRef(false); + const {startScroll, cancelScroll} = useSmoothScrollTo(); + + useScrollPosition(({scrollY: scrollTop}, lastPosition) => { + const lastScrollTop = lastPosition?.scrollY; + // Component is just being mounted. Not really a scroll event from the user. + // Ignore it. + if (!lastScrollTop) { + return; + } + if (isFocusedAnchor.current) { + // This scroll position change is triggered by navigating to an anchor. + // Ignore it. + isFocusedAnchor.current = false; + } else if (scrollTop >= lastScrollTop) { + // The user has scrolled down to "fight against" the animation. Cancel any + // animation under progress. + cancelScroll(); + setShown(false); + } else if (scrollTop < threshold) { + // Scrolled to the minimum position; hide the button. + setShown(false); + } else if ( + scrollTop + window.innerHeight < + document.documentElement.scrollHeight + ) { + setShown(true); + } + }); + + useLocationChange((locationChangeEvent) => { + if (locationChangeEvent.location.hash) { + isFocusedAnchor.current = true; + setShown(false); + } + }); + + return {shown, scrollToTop: () => startScroll(0)}; +} diff --git a/packages/docusaurus-theme-common/src/hooks/useHideableNavbar.ts b/packages/docusaurus-theme-common/src/hooks/useHideableNavbar.ts index f83486f2c1b6..70ef84e3d10b 100644 --- a/packages/docusaurus-theme-common/src/hooks/useHideableNavbar.ts +++ b/packages/docusaurus-theme-common/src/hooks/useHideableNavbar.ts @@ -9,14 +9,16 @@ import {useState, useCallback, useRef} from 'react'; import {useLocationChange} from '../utils/useLocationChange'; import {useScrollPosition} from '../utils/scrollUtils'; -type UseHideableNavbarReturns = { +/** + * Wires the imperative logic of a hideable navbar. + * @param hideOnScroll If `false`, this hook is basically a no-op. + */ +export function useHideableNavbar(hideOnScroll: boolean): { + /** A ref to the navbar component. Plug this into the actual element. */ readonly navbarRef: (node: HTMLElement | null) => void; + /** If `false`, the navbar component should not be rendered. */ readonly isNavbarVisible: boolean; -}; - -export default function useHideableNavbar( - hideOnScroll: boolean, -): UseHideableNavbarReturns { +} { const [isNavbarVisible, setIsNavbarVisible] = useState(hideOnScroll); const isFocusedAnchor = useRef(false); const navbarHeight = useRef(0); @@ -26,14 +28,13 @@ export default function useHideableNavbar( } }, []); - useScrollPosition((currentPosition, lastPosition) => { + useScrollPosition(({scrollY: scrollTop}, lastPosition) => { if (!hideOnScroll) { return; } - const scrollTop = currentPosition.scrollY; - - // It needed for mostly to handle rubber band scrolling + // Needed mostly for handling rubber band scrolling. + // See https://github.com/facebook/docusaurus/pull/5721 if (scrollTop < navbarHeight.current) { setIsNavbarVisible(true); return; @@ -70,8 +71,5 @@ export default function useHideableNavbar( setIsNavbarVisible(true); }); - return { - navbarRef, - isNavbarVisible, - }; + return {navbarRef, isNavbarVisible}; } diff --git a/packages/docusaurus-theme-common/src/hooks/useKeyboardNavigation.ts b/packages/docusaurus-theme-common/src/hooks/useKeyboardNavigation.ts index 80fe791f107d..7a2ebfcc7a3f 100644 --- a/packages/docusaurus-theme-common/src/hooks/useKeyboardNavigation.ts +++ b/packages/docusaurus-theme-common/src/hooks/useKeyboardNavigation.ts @@ -9,12 +9,20 @@ import {useEffect} from 'react'; import './styles.css'; -// This hook detect keyboard focus indicator to not show outline for mouse users -// Inspired by https://hackernoon.com/removing-that-ugly-focus-ring-and-keeping-it-too-6c8727fefcd2 -export default function useKeyboardNavigation(): void { - useEffect(() => { - const keyboardFocusedClassName = 'navigation-with-keyboard'; +export const keyboardFocusedClassName = 'navigation-with-keyboard'; +/** + * Side-effect that adds the `keyboardFocusedClassName` to the body element when + * the keyboard has been pressed, or removes it when the mouse is clicked. + * + * The presence of this class name signals that the user may be using keyboard + * for navigation, and the theme **must** add focus outline when this class name + * is present. (And optionally not if it's absent, for design purposes) + * + * Inspired by https://hackernoon.com/removing-that-ugly-focus-ring-and-keeping-it-too-6c8727fefcd2 + */ +export function useKeyboardNavigation(): void { + useEffect(() => { function handleOutlineStyles(e: MouseEvent | KeyboardEvent) { if (e.type === 'keydown' && (e as KeyboardEvent).key === 'Tab') { document.body.classList.add(keyboardFocusedClassName); diff --git a/packages/docusaurus-theme-common/src/hooks/useLockBodyScroll.ts b/packages/docusaurus-theme-common/src/hooks/useLockBodyScroll.ts index 2ac498d69e5f..649eddff447f 100644 --- a/packages/docusaurus-theme-common/src/hooks/useLockBodyScroll.ts +++ b/packages/docusaurus-theme-common/src/hooks/useLockBodyScroll.ts @@ -7,10 +7,13 @@ import {useEffect} from 'react'; -export default function useLockBodyScroll(lock: boolean = true): void { +/** + * Side-effect that locks the document body's scroll throughout the lifetime of + * the containing component. e.g. when the mobile sidebar is expanded. + */ +export function useLockBodyScroll(lock: boolean = true): void { useEffect(() => { document.body.style.overflow = lock ? 'hidden' : 'visible'; - return () => { document.body.style.overflow = 'visible'; }; diff --git a/packages/docusaurus-theme-common/src/hooks/usePrismTheme.ts b/packages/docusaurus-theme-common/src/hooks/usePrismTheme.ts index 88b7c1298149..bb8034657baa 100644 --- a/packages/docusaurus-theme-common/src/hooks/usePrismTheme.ts +++ b/packages/docusaurus-theme-common/src/hooks/usePrismTheme.ts @@ -6,15 +6,19 @@ */ import defaultTheme from 'prism-react-renderer/themes/palenight'; -import {useColorMode} from '../utils/colorModeUtils'; +import {useColorMode} from '../contexts/colorMode'; import {useThemeConfig} from '../utils/useThemeConfig'; -export default function usePrismTheme(): typeof defaultTheme { +/** + * Returns a color-mode-dependent Prism theme: whatever the user specified in + * the config. Falls back to `palenight`. + */ +export function usePrismTheme(): typeof defaultTheme { const {prism} = useThemeConfig(); - const {isDarkTheme} = useColorMode(); + const {colorMode} = useColorMode(); const lightModeTheme = prism.theme || defaultTheme; const darkModeTheme = prism.darkTheme || lightModeTheme; - const prismTheme = isDarkTheme ? darkModeTheme : lightModeTheme; + const prismTheme = colorMode === 'dark' ? darkModeTheme : lightModeTheme; return prismTheme; } diff --git a/packages/docusaurus-theme-common/src/hooks/useSearchPage.ts b/packages/docusaurus-theme-common/src/hooks/useSearchPage.ts index 62271f89a04f..36c1cdaacf0b 100644 --- a/packages/docusaurus-theme-common/src/hooks/useSearchPage.ts +++ b/packages/docusaurus-theme-common/src/hooks/useSearchPage.ts @@ -11,13 +11,24 @@ import {useCallback, useEffect, useState} from 'react'; const SEARCH_PARAM_QUERY = 'q'; -interface UseSearchPageReturn { +/** Some utility functions around search queries. */ +export function useSearchPage(): { + /** + * Works hand-in-hand with `setSearchQuery`; whatever the user has inputted + * into the search box. + */ searchQuery: string; + /** + * Set a new search query. In addition to updating `searchQuery`, this handle + * also mutates the location and appends the query. + */ setSearchQuery: (newSearchQuery: string) => void; + /** + * Given a query, this handle generates the corresponding search page link, + * with base URL prepended. + */ generateSearchPageLink: (targetSearchQuery: string) => string; -} - -export default function useSearchPage(): UseSearchPageReturn { +} { const history = useHistory(); const { siteConfig: {baseUrl}, @@ -54,7 +65,9 @@ export default function useSearchPage(): UseSearchPageReturn { const generateSearchPageLink = useCallback( (targetSearchQuery: string) => // Refer to https://github.com/facebook/docusaurus/pull/2838 - `${baseUrl}search?q=${encodeURIComponent(targetSearchQuery)}`, + `${baseUrl}search?${SEARCH_PARAM_QUERY}=${encodeURIComponent( + targetSearchQuery, + )}`, [baseUrl], ); diff --git a/packages/docusaurus-theme-common/src/utils/useTOCHighlight.ts b/packages/docusaurus-theme-common/src/hooks/useTOCHighlight.ts similarity index 70% rename from packages/docusaurus-theme-common/src/utils/useTOCHighlight.ts rename to packages/docusaurus-theme-common/src/hooks/useTOCHighlight.ts index fa5d54ba4c40..afd6a12b173f 100644 --- a/packages/docusaurus-theme-common/src/utils/useTOCHighlight.ts +++ b/packages/docusaurus-theme-common/src/hooks/useTOCHighlight.ts @@ -6,14 +6,15 @@ */ import {useEffect, useRef} from 'react'; -import {useThemeConfig} from './useThemeConfig'; +import {useThemeConfig} from '../utils/useThemeConfig'; -/* -TODO make the hardcoded theme-classic classnames configurable -(or add them to ThemeClassNames?) - */ +// TODO make the hardcoded theme-classic classnames configurable (or add them +// to ThemeClassNames?) -// If the anchor has no height and is just a "marker" in the dom; we'll use the parent (normally the link text) rect boundaries instead +/** + * If the anchor has no height and is just a "marker" in the DOM; we'll use the + * parent (normally the link text) rect boundaries instead + */ function getVisibleBoundingClientRect(element: HTMLElement): DOMRect { const rect = element.getBoundingClientRect(); const hasNoHeight = rect.top === rect.bottom; @@ -23,8 +24,10 @@ function getVisibleBoundingClientRect(element: HTMLElement): DOMRect { return rect; } -// Considering we divide viewport into 2 zones of each 50vh -// This returns true if an element is in the first zone (ie, appear in viewport, near the top) +/** + * Considering we divide viewport into 2 zones of each 50vh, this returns true + * if an element is in the first zone (i.e., appear in viewport, near the top) + */ function isInViewportTopHalf(boundingRect: DOMRect) { return boundingRect.top > 0 && boundingRect.bottom < window.innerHeight / 2; } @@ -54,9 +57,10 @@ function getActiveAnchor( anchorTopOffset: number; }, ): Element | null { - // Naming is hard - // The "nextVisibleAnchor" is the first anchor that appear under the viewport top boundary - // Note: it does not mean this anchor is visible yet, but if user continues scrolling down, it will be the first to become visible + // Naming is hard: The "nextVisibleAnchor" is the first anchor that appear + // under the viewport top boundary. It does not mean this anchor is visible + // yet, but if user continues scrolling down, it will be the first to become + // visible const nextVisibleAnchor = anchors.find((anchor) => { const boundingRect = getVisibleBoundingClientRect(anchor); return boundingRect.top >= anchorTopOffset; @@ -64,23 +68,22 @@ function getActiveAnchor( if (nextVisibleAnchor) { const boundingRect = getVisibleBoundingClientRect(nextVisibleAnchor); - // If anchor is in the top half of the viewport: it is the one we consider "active" - // (unless it's too close to the top and and soon to be scrolled outside viewport) + // If anchor is in the top half of the viewport: it is the one we consider + // "active" (unless it's too close to the top and and soon to be scrolled + // outside viewport) if (isInViewportTopHalf(boundingRect)) { return nextVisibleAnchor; } - // If anchor is in the bottom half of the viewport, or under the viewport, we consider the active anchor is the previous one - // This is because the main text appearing in the user screen mostly belong to the previous anchor - else { - // Returns null for the first anchor, see https://github.com/facebook/docusaurus/issues/5318 - return anchors[anchors.indexOf(nextVisibleAnchor) - 1] ?? null; - } + // If anchor is in the bottom half of the viewport, or under the viewport, + // we consider the active anchor is the previous one. This is because the + // main text appearing in the user screen mostly belong to the previous + // anchor. Returns null for the first anchor, see + // https://github.com/facebook/docusaurus/issues/5318 + return anchors[anchors.indexOf(nextVisibleAnchor) - 1] ?? null; } // no anchor under viewport top? (ie we are at the bottom of the page) // => highlight the last anchor found - else { - return anchors[anchors.length - 1]; - } + return anchors[anchors.length - 1] ?? null; } function getLinkAnchorValue(link: HTMLAnchorElement): string { @@ -113,13 +116,24 @@ function useAnchorTopOffsetRef() { } export type TOCHighlightConfig = { + /** A class name that all TOC links share. */ linkClassName: string; + /** The class name applied to the active (highlighted) link. */ linkActiveClassName: string; + /** + * The minimum heading level that the TOC includes. Only headings that are in + * this range will be eligible as "active heading". + */ minHeadingLevel: number; + /** @see {@link TOCHighlightConfig.minHeadingLevel} */ maxHeadingLevel: number; }; -function useTOCHighlight(config: TOCHighlightConfig | undefined): void { +/** + * Side-effect that applies the active class name to the TOC heading that the + * user is currently viewing. Disabled when `config` is undefined. + */ +export function useTOCHighlight(config: TOCHighlightConfig | undefined): void { const lastActiveLinkRef = useRef(undefined); const anchorTopOffsetRef = useAnchorTopOffsetRef(); @@ -144,7 +158,7 @@ function useTOCHighlight(config: TOCHighlightConfig | undefined): void { } link.classList.add(linkActiveClassName); lastActiveLinkRef.current = link; - link.scrollIntoView({block: 'nearest'}); + // link.scrollIntoView({block: 'nearest'}); } else { link.classList.remove(linkActiveClassName); } @@ -176,5 +190,3 @@ function useTOCHighlight(config: TOCHighlightConfig | undefined): void { }; }, [config, anchorTopOffsetRef]); } - -export default useTOCHighlight; diff --git a/packages/docusaurus-theme-common/src/hooks/useWindowSize.ts b/packages/docusaurus-theme-common/src/hooks/useWindowSize.ts index 0b6e944392a1..a9583b199eb0 100644 --- a/packages/docusaurus-theme-common/src/hooks/useWindowSize.ts +++ b/packages/docusaurus-theme-common/src/hooks/useWindowSize.ts @@ -12,11 +12,6 @@ import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment'; const windowSizes = { desktop: 'desktop', mobile: 'mobile', - - // This "ssr" value is very important to handle hydration FOUC / layout shifts - // You have to handle server-rendering explicitly on the call-site - // On the server, you may need to render BOTH the mobile/desktop elements (and hide one of them with mediaquery) - // We don't return "undefined" on purpose, to make it more explicit ssr: 'ssr', } as const; @@ -33,13 +28,22 @@ function getWindowSize() { : windowSizes.mobile; } -// Simulate the SSR window size in dev, so that potential hydration FOUC/layout shift problems can be seen in dev too! const DevSimulateSSR = process.env.NODE_ENV === 'development' && true; -// This hook returns an enum value on purpose! -// We don't want it to return the actual width value, for resize perf reasons -// We only want to re-render once a breakpoint is crossed -export default function useWindowSize(): WindowSize { +/** + * Gets the current window size as an enum value. We don't want it to return the + * actual width value, so that it only re-renders once a breakpoint is crossed. + * + * It may return `"ssr"`, which is very important to handle hydration FOUC or + * layout shifts. You have to handle it explicitly upfront. On the server, you + * may need to render BOTH the mobile/desktop elements (and hide one of them + * with mediaquery). We don't return `undefined` on purpose, to make it more + * explicit. + * + * In development mode, this hook will still return `"ssr"` for one second, to + * catch potential layout shifts, similar to strict mode calling effects twice. + */ +export function useWindowSize(): WindowSize { const [windowSize, setWindowSize] = useState(() => { if (DevSimulateSSR) { return 'ssr'; diff --git a/packages/docusaurus-theme-common/src/index.ts b/packages/docusaurus-theme-common/src/index.ts index 07e6348abc7c..77afa2fdfd21 100644 --- a/packages/docusaurus-theme-common/src/index.ts +++ b/packages/docusaurus-theme-common/src/index.ts @@ -5,129 +5,157 @@ * LICENSE file in the root directory of this source tree. */ -export {useThemeConfig} from './utils/useThemeConfig'; +export { + useThemeConfig, + type ThemeConfig, + type UserThemeConfig, + type Navbar, + type NavbarItem, + type NavbarLogo, + type MultiColumnFooter, + type SimpleFooter, + type Footer, + type FooterLogo, + type FooterLinkItem, + type ColorModeConfig, +} from './utils/useThemeConfig'; export { DocSidebarItemsExpandedStateProvider, useDocSidebarItemsExpandedState, -} from './utils/docSidebarItemsExpandedState'; - -export type { - ThemeConfig, - UserThemeConfig, - Navbar, - NavbarItem, - NavbarLogo, - MultiColumnFooter, - SimpleFooter, - Footer, - FooterLinkItem, - ColorModeConfig, -} from './utils/useThemeConfig'; +} from './contexts/docSidebarItemsExpandedState'; +export {DocsVersionProvider, useDocsVersion} from './contexts/docsVersion'; +export {DocsSidebarProvider, useDocsSidebar} from './contexts/docsSidebar'; export {createStorageSlot, listStorageKeys} from './utils/storageUtils'; export {useAlternatePageUtils} from './utils/useAlternatePageUtils'; -export {useContextualSearchFilters} from './utils/useContextualSearchFilters'; - export { parseCodeBlockTitle, parseLanguage, parseLines, } from './utils/codeBlockUtils'; -export {docVersionSearchTag, DEFAULT_SEARCH_TAG} from './utils/searchUtils'; +export { + docVersionSearchTag, + DEFAULT_SEARCH_TAG, + useContextualSearchFilters, +} from './utils/searchUtils'; export { isDocsPluginEnabled, - DocsVersionProvider, - useDocsVersion, useDocById, - DocsSidebarProvider, - useDocsSidebar, findSidebarCategory, findFirstCategoryLink, useCurrentSidebarCategory, isActiveSidebarItem, + useSidebarBreadcrumbs, + useDocsVersionCandidates, + useLayoutDoc, + useLayoutDocsSidebar, } from './utils/docsUtils'; -export {isSamePath} from './utils/pathUtils'; - export {useTitleFormatter} from './utils/generalUtils'; export {usePluralForm} from './utils/usePluralForm'; export {useLocationChange} from './utils/useLocationChange'; -export {usePrevious} from './utils/usePrevious'; - export {useCollapsible, Collapsible} from './components/Collapsible'; -export type { - UseCollapsibleConfig, - UseCollapsibleReturns, -} from './components/Collapsible'; - -export {default as Details} from './components/Details'; -export type {DetailsProps} from './components/Details'; -export { - MobileSecondaryMenuProvider, - MobileSecondaryMenuFiller, - useMobileSecondaryMenuRenderer, -} from './utils/mobileSecondaryMenu'; -export type {MobileSecondaryMenuComponent} from './utils/mobileSecondaryMenu'; +export {Details, type DetailsProps} from './components/Details'; export { useDocsPreferredVersion, useDocsPreferredVersionByPluginId, -} from './utils/docsPreferredVersion/useDocsPreferredVersion'; + DocsPreferredVersionContextProvider, +} from './contexts/docsPreferredVersion'; export {duplicates, uniq} from './utils/jsUtils'; -export {DocsPreferredVersionContextProvider} from './utils/docsPreferredVersion/DocsPreferredVersionProvider'; - export {ThemeClassNames} from './utils/ThemeClassNames'; export { AnnouncementBarProvider, useAnnouncementBar, -} from './utils/announcementBarUtils'; +} from './contexts/announcementBar'; export {useLocalPathname} from './utils/useLocalPathname'; -export {translateTagsPageTitle, listTagsByLetters} from './utils/tagsUtils'; -export type {TagLetterEntry} from './utils/tagsUtils'; +export { + translateTagsPageTitle, + listTagsByLetters, + type TagLetterEntry, + type TagsListItem, +} from './utils/tagsUtils'; export {useHistoryPopHandler} from './utils/historyUtils'; -export {default as useTOCHighlight} from './utils/useTOCHighlight'; -export type {TOCHighlightConfig} from './utils/useTOCHighlight'; +export { + useTOCHighlight, + type TOCHighlightConfig, +} from './hooks/useTOCHighlight'; + +export { + useFilteredAndTreeifiedTOC, + useTreeifiedTOC, + type TOCTreeNode, +} from './utils/tocUtils'; -export {useTOCFilter} from './utils/tocUtils'; +export {isMultiColumnFooterLinks} from './utils/footerUtils'; export { ScrollControllerProvider, useScrollController, useScrollPosition, useScrollPositionBlocker, + useSmoothScrollTo, } from './utils/scrollUtils'; export { useIsomorphicLayoutEffect, useDynamicCallback, + usePrevious, + ReactContextError, } from './utils/reactUtils'; export {isRegexpStringMatch} from './utils/regexpUtils'; -export {useColorMode, ColorModeProvider} from './utils/colorModeUtils'; +export {useHomePageRoute, isSamePath} from './utils/routesUtils'; + +export { + PageMetadata, + HtmlClassNameProvider, + PluginHtmlClassNameProvider, +} from './utils/metadataUtils'; + +export { + useColorMode, + ColorModeProvider, + type ColorMode, +} from './contexts/colorMode'; + +export {splitNavbarItems, NavbarProvider} from './utils/navbarUtils'; + export { useTabGroupChoice, TabGroupChoiceProvider, -} from './utils/tabGroupChoiceUtils'; - -export {default as useHideableNavbar} from './hooks/useHideableNavbar'; -export {default as useKeyboardNavigation} from './hooks/useKeyboardNavigation'; -export {default as usePrismTheme} from './hooks/usePrismTheme'; -export {default as useLockBodyScroll} from './hooks/useLockBodyScroll'; -export {default as useWindowSize} from './hooks/useWindowSize'; -export {default as useSearchPage} from './hooks/useSearchPage'; +} from './contexts/tabGroupChoice'; + +export {useNavbarMobileSidebar} from './contexts/navbarMobileSidebar'; +export { + NavbarSecondaryMenuFiller, + type NavbarSecondaryMenuComponent, +} from './contexts/navbarSecondaryMenu/content'; +export {useNavbarSecondaryMenu} from './contexts/navbarSecondaryMenu/display'; + +export {useBackToTopButton} from './hooks/useBackToTopButton'; +export {useHideableNavbar} from './hooks/useHideableNavbar'; +export { + useKeyboardNavigation, + keyboardFocusedClassName, +} from './hooks/useKeyboardNavigation'; +export {usePrismTheme} from './hooks/usePrismTheme'; +export {useLockBodyScroll} from './hooks/useLockBodyScroll'; +export {useWindowSize} from './hooks/useWindowSize'; +export {useSearchPage} from './hooks/useSearchPage'; diff --git a/packages/docusaurus-theme-common/src/utils/ThemeClassNames.ts b/packages/docusaurus-theme-common/src/utils/ThemeClassNames.ts index b221801adae2..ec3ffc11a969 100644 --- a/packages/docusaurus-theme-common/src/utils/ThemeClassNames.ts +++ b/packages/docusaurus-theme-common/src/utils/ThemeClassNames.ts @@ -5,9 +5,13 @@ * LICENSE file in the root directory of this source tree. */ -// These class names are used to style page layouts in Docusaurus -// Those are meant to be targeted by user-provided custom CSS selectors -// /!\ Please do not modify the classnames! This is a breaking change, and annoying for users! +// Please do not modify the classnames! This is a breaking change, and annoying +// for users! + +/** + * These class names are used to style page layouts in Docusaurus, meant to be + * targeted by user-provided custom CSS selectors. + */ export const ThemeClassNames = { page: { blogListPage: 'blog-list-page', @@ -16,8 +20,8 @@ export const ThemeClassNames = { blogTagPostListPage: 'blog-tags-post-list-page', docsDocPage: 'docs-doc-page', - docsTagsListPage: 'docs-tags-list-page', // List of tags - docsTagDocListPage: 'docs-tags-doc-list-page', // Docs for a tag + docsTagsListPage: 'docs-tags-list-page', + docsTagDocListPage: 'docs-tags-doc-list-page', mdxPage: 'mdx-page', }, @@ -28,8 +32,9 @@ export const ThemeClassNames = { mdxPages: 'mdx-wrapper', }, - // /!\ Please keep the naming convention consistent! - // Something like: "theme-{blog,doc,version,page}?-" + /** + * Follows the naming convention "theme-{blog,doc,version,page}?-" + */ common: { editThisPage: 'theme-edit-this-page', lastUpdated: 'theme-last-updated', @@ -42,6 +47,7 @@ export const ThemeClassNames = { docs: { docVersionBanner: 'theme-doc-version-banner', docVersionBadge: 'theme-doc-version-badge', + docBreadcrumbs: 'theme-doc-breadcrumbs', docMarkdown: 'theme-doc-markdown', docTocMobile: 'theme-doc-toc-mobile', docTocDesktop: 'theme-doc-toc-desktop', diff --git a/packages/docusaurus-theme-common/src/utils/__tests__/__snapshots__/codeBlockUtils.test.ts.snap b/packages/docusaurus-theme-common/src/utils/__tests__/__snapshots__/codeBlockUtils.test.ts.snap new file mode 100644 index 000000000000..97a8976893e6 --- /dev/null +++ b/packages/docusaurus-theme-common/src/utils/__tests__/__snapshots__/codeBlockUtils.test.ts.snap @@ -0,0 +1,123 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`parseLines does not parse content with metastring 1`] = ` +{ + "code": "aaaaa +nnnnn", + "highlightLines": [ + 0, + ], +} +`; + +exports[`parseLines does not parse content with metastring 2`] = ` +{ + "code": "// highlight-next-line +aaaaa +bbbbb", + "highlightLines": [ + 0, + ], +} +`; + +exports[`parseLines does not parse content with metastring 3`] = ` +{ + "code": "aaaaa +bbbbb", + "highlightLines": [ + 0, + ], +} +`; + +exports[`parseLines does not parse content with no language 1`] = ` +{ + "code": "// highlight-next-line +aaaaa +bbbbb", + "highlightLines": [], +} +`; + +exports[`parseLines removes lines correctly 1`] = ` +{ + "code": "aaaaa +bbbbb", + "highlightLines": [ + 0, + ], +} +`; + +exports[`parseLines removes lines correctly 2`] = ` +{ + "code": "aaaaa +bbbbb", + "highlightLines": [ + 0, + ], +} +`; + +exports[`parseLines removes lines correctly 3`] = ` +{ + "code": "aaaaa +bbbbbbb +bbbbb", + "highlightLines": [ + 0, + 2, + 0, + 1, + ], +} +`; + +exports[`parseLines respects language 1`] = ` +{ + "code": "# highlight-next-line +aaaaa +bbbbb", + "highlightLines": [], +} +`; + +exports[`parseLines respects language 2`] = ` +{ + "code": "/* highlight-next-line */ +aaaaa +bbbbb", + "highlightLines": [], +} +`; + +exports[`parseLines respects language 3`] = ` +{ + "code": "// highlight-next-line +aaaa +/* highlight-next-line */ +bbbbb +ccccc + +dddd", + "highlightLines": [ + 4, + ], +} +`; + +exports[`parseLines respects language 4`] = ` +{ + "code": "aaaa +bbbbb +ccccc +dddd", + "highlightLines": [ + 0, + 1, + 2, + 3, + ], +} +`; diff --git a/packages/docusaurus-theme-common/src/utils/__tests__/__snapshots__/tocUtils.test.ts.snap b/packages/docusaurus-theme-common/src/utils/__tests__/__snapshots__/tocUtils.test.ts.snap new file mode 100644 index 000000000000..2ed8b17ac05b --- /dev/null +++ b/packages/docusaurus-theme-common/src/utils/__tests__/__snapshots__/tocUtils.test.ts.snap @@ -0,0 +1,40 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`useTreeifiedTOC treeifies TOC without filtering 1`] = ` +[ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [], + "id": "foxtrot", + "level": 6, + "value": "Foxtrot", + }, + ], + "id": "echo", + "level": 5, + "value": "Echo", + }, + ], + "id": "delta", + "level": 4, + "value": "Delta", + }, + ], + "id": "charlie", + "level": 3, + "value": "Charlie", + }, + ], + "id": "bravo", + "level": 2, + "value": "Bravo", + }, +] +`; diff --git a/packages/docusaurus-theme-common/src/utils/__tests__/codeBlockUtils.test.ts b/packages/docusaurus-theme-common/src/utils/__tests__/codeBlockUtils.test.ts index aa7fc3053852..bc4e77b0d1d9 100644 --- a/packages/docusaurus-theme-common/src/utils/__tests__/codeBlockUtils.test.ts +++ b/packages/docusaurus-theme-common/src/utils/__tests__/codeBlockUtils.test.ts @@ -12,71 +12,63 @@ import { } from '../codeBlockUtils'; describe('parseCodeBlockTitle', () => { - test('should parse double quote delimited title', () => { - expect(parseCodeBlockTitle(`title="index.js"`)).toEqual(`index.js`); + it('parses double quote delimited title', () => { + expect(parseCodeBlockTitle(`title="index.js"`)).toBe(`index.js`); }); - test('should parse single quote delimited title', () => { - expect(parseCodeBlockTitle(`title='index.js'`)).toEqual(`index.js`); + it('parses single quote delimited title', () => { + expect(parseCodeBlockTitle(`title='index.js'`)).toBe(`index.js`); }); - test('should not parse mismatched quote delimiters', () => { - expect(parseCodeBlockTitle(`title="index.js'`)).toEqual(``); + it('does not parse mismatched quote delimiters', () => { + expect(parseCodeBlockTitle(`title="index.js'`)).toBe(``); }); - test('should parse undefined metastring', () => { - expect(parseCodeBlockTitle(undefined)).toEqual(``); + it('parses undefined metastring', () => { + expect(parseCodeBlockTitle(undefined)).toBe(``); }); - test('should parse metastring with no title specified', () => { - expect(parseCodeBlockTitle(`{1,2-3}`)).toEqual(``); + it('parses metastring with no title specified', () => { + expect(parseCodeBlockTitle(`{1,2-3}`)).toBe(``); }); - test('should parse with multiple metadata title first', () => { - expect(parseCodeBlockTitle(`title="index.js" label="JavaScript"`)).toEqual( + it('parses with multiple metadata title first', () => { + expect(parseCodeBlockTitle(`title="index.js" label="JavaScript"`)).toBe( `index.js`, ); }); - test('should parse with multiple metadata title last', () => { - expect(parseCodeBlockTitle(`label="JavaScript" title="index.js"`)).toEqual( + it('parses with multiple metadata title last', () => { + expect(parseCodeBlockTitle(`label="JavaScript" title="index.js"`)).toBe( `index.js`, ); }); - test('should parse double quotes when delimited by single quotes', () => { - expect(parseCodeBlockTitle(`title='console.log("Hello, World!")'`)).toEqual( + it('parses double quotes when delimited by single quotes', () => { + expect(parseCodeBlockTitle(`title='console.log("Hello, World!")'`)).toBe( `console.log("Hello, World!")`, ); }); - test('should parse single quotes when delimited by double quotes', () => { - expect(parseCodeBlockTitle(`title="console.log('Hello, World!')"`)).toEqual( + it('parses single quotes when delimited by double quotes', () => { + expect(parseCodeBlockTitle(`title="console.log('Hello, World!')"`)).toBe( `console.log('Hello, World!')`, ); }); }); describe('parseLanguage', () => { - test('behaves correctly', () => { - expect(parseLanguage('language-foo xxx yyy')).toEqual('foo'); - expect(parseLanguage('xxxxx language-foo yyy')).toEqual('foo'); + it('works', () => { + expect(parseLanguage('language-foo xxx yyy')).toBe('foo'); + expect(parseLanguage('xxxxx language-foo yyy')).toBe('foo'); expect(parseLanguage('xx-language-foo yyyy')).toBeUndefined(); expect(parseLanguage('xxx yyy zzz')).toBeUndefined(); }); }); describe('parseLines', () => { - test('does not parse content with metastring', () => { - expect(parseLines('aaaaa\nbbbbb', '{1}', 'js')).toMatchInlineSnapshot(` - Object { - "code": "aaaaa - bbbbb", - "highlightLines": Array [ - 0, - ], - } - `); + it('does not parse content with metastring', () => { + expect(parseLines('aaaaa\nnnnnn', '{1}', 'js')).toMatchSnapshot(); expect( parseLines( `// highlight-next-line @@ -85,33 +77,16 @@ bbbbb`, '{1}', 'js', ), - ).toMatchInlineSnapshot(` - Object { - "code": "// highlight-next-line - aaaaa - bbbbb", - "highlightLines": Array [ - 0, - ], - } - `); + ).toMatchSnapshot(); expect( parseLines( `aaaaa bbbbb`, '{1}', ), - ).toMatchInlineSnapshot(` - Object { - "code": "aaaaa - bbbbb", - "highlightLines": Array [ - 0, - ], - } - `); + ).toMatchSnapshot(); }); - test('does not parse content with no language', () => { + it('does not parse content with no language', () => { expect( parseLines( `// highlight-next-line @@ -120,16 +95,9 @@ bbbbb`, '', undefined, ), - ).toMatchInlineSnapshot(` - Object { - "code": "// highlight-next-line - aaaaa - bbbbb", - "highlightLines": Array [], - } - `); + ).toMatchSnapshot(); }); - test('removes lines correctly', () => { + it('removes lines correctly', () => { expect( parseLines( `// highlight-next-line @@ -138,15 +106,7 @@ bbbbb`, '', 'js', ), - ).toMatchInlineSnapshot(` - Object { - "code": "aaaaa - bbbbb", - "highlightLines": Array [ - 0, - ], - } - `); + ).toMatchSnapshot(); expect( parseLines( `// highlight-start @@ -156,15 +116,7 @@ bbbbb`, '', 'js', ), - ).toMatchInlineSnapshot(` - Object { - "code": "aaaaa - bbbbb", - "highlightLines": Array [ - 0, - ], - } - `); + ).toMatchSnapshot(); expect( parseLines( `// highlight-start @@ -177,21 +129,9 @@ bbbbb`, '', 'js', ), - ).toMatchInlineSnapshot(` - Object { - "code": "aaaaa - bbbbbbb - bbbbb", - "highlightLines": Array [ - 0, - 2, - 0, - 1, - ], - } - `); + ).toMatchSnapshot(); }); - test('respects language', () => { + it('respects language', () => { expect( parseLines( `# highlight-next-line @@ -200,14 +140,7 @@ bbbbb`, '', 'js', ), - ).toMatchInlineSnapshot(` - Object { - "code": "# highlight-next-line - aaaaa - bbbbb", - "highlightLines": Array [], - } - `); + ).toMatchSnapshot(); expect( parseLines( `/* highlight-next-line */ @@ -216,14 +149,7 @@ bbbbb`, '', 'py', ), - ).toMatchInlineSnapshot(` - Object { - "code": "/* highlight-next-line */ - aaaaa - bbbbb", - "highlightLines": Array [], - } - `); + ).toMatchSnapshot(); expect( parseLines( `// highlight-next-line @@ -237,20 +163,7 @@ dddd`, '', 'py', ), - ).toMatchInlineSnapshot(` - Object { - "code": "// highlight-next-line - aaaa - /* highlight-next-line */ - bbbbb - ccccc - - dddd", - "highlightLines": Array [ - 4, - ], - } - `); + ).toMatchSnapshot(); expect( parseLines( `// highlight-next-line @@ -264,19 +177,6 @@ dddd`, '', '', ), - ).toMatchInlineSnapshot(` - Object { - "code": "aaaa - bbbbb - ccccc - dddd", - "highlightLines": Array [ - 0, - 1, - 2, - 3, - ], - } - `); + ).toMatchSnapshot(); }); }); diff --git a/packages/docusaurus-theme-common/src/utils/__tests__/docsUtils.test.tsx b/packages/docusaurus-theme-common/src/utils/__tests__/docsUtils.test.tsx index 0979ade52f36..532fb20a0acf 100644 --- a/packages/docusaurus-theme-common/src/utils/__tests__/docsUtils.test.tsx +++ b/packages/docusaurus-theme-common/src/utils/__tests__/docsUtils.test.tsx @@ -10,17 +10,20 @@ import {renderHook} from '@testing-library/react-hooks'; import { findFirstCategoryLink, isActiveSidebarItem, - DocsVersionProvider, - useDocsVersion, useDocById, - useDocsSidebar, - DocsSidebarProvider, findSidebarCategory, + useCurrentSidebarCategory, + useSidebarBreadcrumbs, } from '../docsUtils'; +import {DocsSidebarProvider} from '../../contexts/docsSidebar'; +import {DocsVersionProvider} from '../../contexts/docsVersion'; +import {StaticRouter} from 'react-router-dom'; +import {Context} from '@docusaurus/core/src/client/docusaurusContext'; import type { PropSidebar, PropSidebarItem, PropSidebarItemCategory, + PropSidebarItemLink, PropVersionMetadata, } from '@docusaurus/plugin-content-docs'; @@ -39,6 +42,15 @@ function testCategory( }; } +function testLink(data?: Partial): PropSidebarItemLink { + return { + type: 'link', + href: '/testLinkHref', + label: 'Link label', + ...data, + }; +} + function testVersion(data?: Partial): PropVersionMetadata { return { version: 'versionName', @@ -54,278 +66,417 @@ function testVersion(data?: Partial): PropVersionMetadata { }; } -describe('docsUtils', () => { - describe('useDocsVersion', () => { - test('should throw if context provider is missing', () => { - expect( - () => renderHook(() => useDocsVersion()).result.current, - ).toThrowErrorMatchingInlineSnapshot( - `"This hook requires usage of "`, - ); - }); +describe('useDocById', () => { + const version = testVersion({ + docs: { + doc1: { + id: 'doc1', + title: 'Doc 1', + description: 'desc1', + sidebar: 'sidebar1', + }, + doc2: { + id: 'doc2', + title: 'Doc 2', + description: 'desc2', + sidebar: 'sidebar2', + }, + }, + }); - test('should read value from context provider', () => { - const version = testVersion(); - const {result} = renderHook(() => useDocsVersion(), { - wrapper: ({children}) => ( - - {children} - - ), - }); - expect(result.current).toBe(version); + function mockUseDocById(docId: string | undefined) { + const {result} = renderHook(() => useDocById(docId), { + wrapper: ({children}) => ( + {children} + ), }); + return result.current; + } + + it('accepts undefined', () => { + expect(mockUseDocById(undefined)).toBeUndefined(); + }); + + it('finds doc1', () => { + expect(mockUseDocById('doc1')).toMatchObject({id: 'doc1'}); + }); + it('finds doc2', () => { + expect(mockUseDocById('doc2')).toMatchObject({id: 'doc2'}); + }); + + it('throws for doc3', () => { + expect(() => mockUseDocById('doc3')).toThrowErrorMatchingInlineSnapshot( + `"no version doc found by id=doc3"`, + ); + }); +}); + +describe('findSidebarCategory', () => { + it('is able to return undefined', () => { + expect(findSidebarCategory([], () => false)).toBeUndefined(); + expect( + findSidebarCategory([testCategory(), testCategory()], () => false), + ).toBeUndefined(); + }); + + it('returns first element matching predicate', () => { + const first = testCategory(); + const second = testCategory(); + const third = testCategory(); + const sidebar = [first, second, third]; + expect(findSidebarCategory(sidebar, () => true)).toEqual(first); + expect(findSidebarCategory(sidebar, (item) => item === first)).toEqual( + first, + ); + expect(findSidebarCategory(sidebar, (item) => item === second)).toEqual( + second, + ); + expect(findSidebarCategory(sidebar, (item) => item === third)).toEqual( + third, + ); }); - describe('useDocsSidebar', () => { - test('should throw if context provider is missing', () => { - expect( - () => renderHook(() => useDocsSidebar()).result.current, - ).toThrowErrorMatchingInlineSnapshot( - `"This hook requires usage of "`, - ); + it('is able to search in sub items', () => { + const subsub1 = testCategory(); + const subsub2 = testCategory(); + const sub1 = testCategory({ + items: [subsub1, subsub2], + }); + const sub2 = testCategory(); + const parent = testCategory({ + items: [sub1, sub2], }); + const sidebar = [parent]; + + expect(findSidebarCategory(sidebar, () => true)).toEqual(parent); + expect(findSidebarCategory(sidebar, (item) => item === sub1)).toEqual(sub1); + expect(findSidebarCategory(sidebar, (item) => item === sub2)).toEqual(sub2); + expect(findSidebarCategory(sidebar, (item) => item === subsub1)).toEqual( + subsub1, + ); + expect(findSidebarCategory(sidebar, (item) => item === subsub2)).toEqual( + subsub2, + ); + }); +}); - test('should read value from context provider', () => { - const sidebar: PropSidebar = []; - const {result} = renderHook(() => useDocsSidebar(), { - wrapper: ({children}) => ( - - {children} - - ), - }); - expect(result.current).toBe(sidebar); +describe('findFirstCategoryLink', () => { + it('works with category without link nor child', () => { + expect( + findFirstCategoryLink( + testCategory({ + href: undefined, + }), + ), + ).toBeUndefined(); + }); + + it('works with category with link', () => { + expect( + findFirstCategoryLink( + testCategory({ + href: '/itemPath', + }), + ), + ).toBe('/itemPath'); + }); + + it('works with category with deeply nested category link', () => { + expect( + findFirstCategoryLink( + testCategory({ + href: undefined, + items: [ + {type: 'html', value: '

      test1

      '}, + testCategory({ + href: undefined, + items: [ + {type: 'html', value: '

      test2

      '}, + testCategory({ + href: '/itemPath', + }), + ], + }), + ], + }), + ), + ).toBe('/itemPath'); + }); + + it('works with category with deeply nested link', () => { + expect( + findFirstCategoryLink( + testCategory({ + href: undefined, + items: [ + testCategory({ + href: undefined, + items: [{type: 'link', href: '/itemPath', label: 'Label'}], + }), + ], + }), + ), + ).toBe('/itemPath'); + }); +}); + +describe('isActiveSidebarItem', () => { + it('works with link href', () => { + const item: PropSidebarItem = { + type: 'link', + href: '/itemPath', + label: 'Label', + }; + + expect(isActiveSidebarItem(item, '/nonexistentPath')).toBe(false); + + expect(isActiveSidebarItem(item, '/itemPath')).toBe(true); + + // Ensure it's not trailing slash sensitive: + expect(isActiveSidebarItem(item, '/itemPath/')).toBe(true); + expect( + isActiveSidebarItem({...item, href: '/itemPath/'}, '/itemPath'), + ).toBe(true); + }); + + it('works with category href', () => { + const item: PropSidebarItem = testCategory({ + href: '/itemPath', }); + + expect(isActiveSidebarItem(item, '/nonexistentPath')).toBe(false); + + expect(isActiveSidebarItem(item, '/itemPath')).toBe(true); + + // Ensure it's not trailing slash sensitive: + expect(isActiveSidebarItem(item, '/itemPath/')).toBe(true); + expect( + isActiveSidebarItem({...item, href: '/itemPath/'}, '/itemPath'), + ).toBe(true); }); - describe('useDocById', () => { - const version = testVersion({ - docs: { - doc1: { - id: 'doc1', - title: 'Doc 1', - description: 'desc1', - sidebar: 'sidebar1', + it('works with category nested items', () => { + const item: PropSidebarItem = testCategory({ + href: '/category-path', + items: [ + { + type: 'link', + href: '/sub-link-path', + label: 'Label', }, - doc2: { - id: 'doc2', - title: 'Doc 2', - description: 'desc2', - sidebar: 'sidebar2', - }, - }, + testCategory({ + href: '/sub-category-path', + items: [ + { + type: 'link', + href: '/sub-sub-link-path', + label: 'Label', + }, + ], + }), + ], }); - function callHook(docId: string | undefined) { - const {result} = renderHook(() => useDocById(docId), { + expect(isActiveSidebarItem(item, '/nonexistentPath')).toBe(false); + + expect(isActiveSidebarItem(item, '/category-path')).toBe(true); + expect(isActiveSidebarItem(item, '/sub-link-path')).toBe(true); + expect(isActiveSidebarItem(item, '/sub-category-path')).toBe(true); + expect(isActiveSidebarItem(item, '/sub-sub-link-path')).toBe(true); + + // Ensure it's not trailing slash sensitive: + expect(isActiveSidebarItem(item, '/category-path/')).toBe(true); + expect(isActiveSidebarItem(item, '/sub-link-path/')).toBe(true); + expect(isActiveSidebarItem(item, '/sub-category-path/')).toBe(true); + expect(isActiveSidebarItem(item, '/sub-sub-link-path/')).toBe(true); + }); +}); + +describe('useSidebarBreadcrumbs', () => { + const createUseSidebarBreadcrumbsMock = + (sidebar: PropSidebar, breadcrumbsOption?: boolean) => (location: string) => + renderHook(() => useSidebarBreadcrumbs(), { wrapper: ({children}) => ( - - {children} - + + + + {children} + + + ), - }); - return result.current; - } + }).result.current; + it('returns empty for empty sidebar', () => { + expect(createUseSidebarBreadcrumbsMock([])('/doesNotExist')).toEqual([]); + }); - test('should accept undefined', () => { - expect(callHook(undefined)).toBeUndefined(); - }); + it('returns empty for sidebar but unknown pathname', () => { + const sidebar = [testCategory(), testLink()]; + expect(createUseSidebarBreadcrumbsMock(sidebar)('/doesNotExist')).toEqual( + [], + ); + }); - test('should find doc1', () => { - expect(callHook('doc1')).toMatchObject({id: 'doc1'}); - }); - test('should find doc2', () => { - expect(callHook('doc2')).toMatchObject({id: 'doc2'}); - }); + it('returns first level category', () => { + const pathname = '/somePathName'; + const sidebar = [testCategory({href: pathname}), testLink()]; - test('should throw for doc3', () => { - expect(() => callHook('doc3')).toThrowErrorMatchingInlineSnapshot( - `"no version doc found by id=doc3"`, - ); - }); + expect(createUseSidebarBreadcrumbsMock(sidebar)(pathname)).toEqual([ + sidebar[0], + ]); }); - describe('findSidebarCategory', () => { - test('should be able to return undefined', () => { - expect(findSidebarCategory([], () => false)).toBeUndefined(); - expect( - findSidebarCategory([testCategory(), testCategory()], () => false), - ).toBeUndefined(); - }); - - test('should return first element matching predicate', () => { - const first = testCategory(); - const second = testCategory(); - const third = testCategory(); - const sidebar = [first, second, third]; - expect(findSidebarCategory(sidebar, () => true)).toEqual(first); - expect(findSidebarCategory(sidebar, (item) => item === first)).toEqual( - first, - ); - expect(findSidebarCategory(sidebar, (item) => item === second)).toEqual( - second, - ); - expect(findSidebarCategory(sidebar, (item) => item === third)).toEqual( - third, - ); - }); + it('returns first level link', () => { + const pathname = '/somePathName'; + const sidebar = [testCategory(), testLink({href: pathname})]; - test('should be able to search in sub items', () => { - const subsub1 = testCategory(); - const subsub2 = testCategory(); - const sub1 = testCategory({ - items: [subsub1, subsub2], - }); - const sub2 = testCategory(); - const parent = testCategory({ - items: [sub1, sub2], - }); - const sidebar = [parent]; - - expect(findSidebarCategory(sidebar, () => true)).toEqual(parent); - expect(findSidebarCategory(sidebar, (item) => item === sub1)).toEqual( - sub1, - ); - expect(findSidebarCategory(sidebar, (item) => item === sub2)).toEqual( - sub2, - ); - expect(findSidebarCategory(sidebar, (item) => item === subsub1)).toEqual( - subsub1, - ); - expect(findSidebarCategory(sidebar, (item) => item === subsub2)).toEqual( - subsub2, - ); - }); + expect(createUseSidebarBreadcrumbsMock(sidebar)(pathname)).toEqual([ + sidebar[1], + ]); }); - describe('findFirstCategoryLink', () => { - test('category without link nor child', () => { - expect( - findFirstCategoryLink( - testCategory({ - href: undefined, - }), - ), - ).toEqual(undefined); - }); + it('returns nested category', () => { + const pathname = '/somePathName'; - test('category with link', () => { - expect( - findFirstCategoryLink( - testCategory({ - href: '/itemPath', - }), - ), - ).toEqual('/itemPath'); + const categoryLevel3 = testCategory({ + href: pathname, }); - test('category with deeply nested category link', () => { - expect( - findFirstCategoryLink( - testCategory({ - href: undefined, - items: [ - testCategory({ - href: undefined, - items: [ - testCategory({ - href: '/itemPath', - }), - ], - }), - ], - }), - ), - ).toEqual('/itemPath'); + const categoryLevel2 = testCategory({ + items: [ + testCategory(), + categoryLevel3, + testLink({href: pathname}), + testLink(), + ], }); - test('category with deeply nested link', () => { - expect( - findFirstCategoryLink( - testCategory({ - href: undefined, - items: [ - testCategory({ - href: undefined, - items: [{type: 'link', href: '/itemPath', label: 'Label'}], - }), - ], - }), - ), - ).toEqual('/itemPath'); + const categoryLevel1 = testCategory({ + items: [testLink(), categoryLevel2], }); + + const sidebar = [ + testLink(), + testCategory(), + categoryLevel1, + testLink(), + testCategory(), + ]; + + expect(createUseSidebarBreadcrumbsMock(sidebar)(pathname)).toEqual([ + categoryLevel1, + categoryLevel2, + categoryLevel3, + ]); }); - describe('isActiveSidebarItem', () => { - test('with link href', () => { - const item: PropSidebarItem = { - type: 'link', - href: '/itemPath', - label: 'Label', - }; + it('returns nested link', () => { + const pathname = '/somePathName'; - expect(isActiveSidebarItem(item, '/unexistingPath')).toEqual(false); + const link = testLink({href: pathname}); - expect(isActiveSidebarItem(item, '/itemPath')).toEqual(true); + const categoryLevel3 = testCategory({ + items: [testLink(), link, testLink()], + }); - // Ensure it's not trailing slash sensitive: - expect(isActiveSidebarItem(item, '/itemPath/')).toEqual(true); - expect( - isActiveSidebarItem({...item, href: '/itemPath/'}, '/itemPath'), - ).toEqual(true); + const categoryLevel2 = testCategory({ + items: [ + testCategory(), + categoryLevel3, + testLink({href: pathname}), + testLink(), + ], }); - test('with category href', () => { - const item: PropSidebarItem = testCategory({ - href: '/itemPath', - }); + const categoryLevel1 = testCategory({ + items: [testLink(), categoryLevel2], + }); - expect(isActiveSidebarItem(item, '/unexistingPath')).toEqual(false); + const sidebar = [ + testLink(), + testCategory(), + categoryLevel1, + testLink(), + testCategory(), + ]; + + expect(createUseSidebarBreadcrumbsMock(sidebar)(pathname)).toEqual([ + categoryLevel1, + categoryLevel2, + categoryLevel3, + link, + ]); + }); - expect(isActiveSidebarItem(item, '/itemPath')).toEqual(true); + it('returns null when breadcrumbs disabled', () => { + expect(createUseSidebarBreadcrumbsMock([], false)('/foo')).toBeNull(); + }); - // Ensure it's not trailing slash sensitive: - expect(isActiveSidebarItem(item, '/itemPath/')).toEqual(true); - expect( - isActiveSidebarItem({...item, href: '/itemPath/'}, '/itemPath'), - ).toEqual(true); - }); + it('returns null when there is no sidebar', () => { + expect(createUseSidebarBreadcrumbsMock(null, false)('/foo')).toBeNull(); + }); +}); - test('with category nested items', () => { - const item: PropSidebarItem = testCategory({ - href: '/category-path', - items: [ - { - type: 'link', - href: '/sub-link-path', - label: 'Label', - }, - testCategory({ - href: '/sub-category-path', - items: [ - { - type: 'link', - href: '/sub-sub-link-path', - label: 'Label', - }, - ], - }), - ], - }); - - expect(isActiveSidebarItem(item, '/unexistingPath')).toEqual(false); - - expect(isActiveSidebarItem(item, '/category-path')).toEqual(true); - expect(isActiveSidebarItem(item, '/sub-link-path')).toEqual(true); - expect(isActiveSidebarItem(item, '/sub-category-path')).toEqual(true); - expect(isActiveSidebarItem(item, '/sub-sub-link-path')).toEqual(true); - - // Ensure it's not trailing slash sensitive: - expect(isActiveSidebarItem(item, '/category-path/')).toEqual(true); - expect(isActiveSidebarItem(item, '/sub-link-path/')).toEqual(true); - expect(isActiveSidebarItem(item, '/sub-category-path/')).toEqual(true); - expect(isActiveSidebarItem(item, '/sub-sub-link-path/')).toEqual(true); - }); +describe('useCurrentSidebarCategory', () => { + const createUseCurrentSidebarCategoryMock = + (sidebar?: PropSidebar) => (location: string) => + renderHook(() => useCurrentSidebarCategory(), { + wrapper: ({children}) => ( + + {children} + + ), + }).result.current; + it('works', () => { + const category = { + type: 'category', + href: '/cat', + items: [ + {type: 'link', href: '/cat/foo', label: 'Foo'}, + {type: 'link', href: '/cat/bar', label: 'Bar'}, + {type: 'link', href: '/baz', label: 'Baz'}, + ], + }; + const mockUseCurrentSidebarCategory = createUseCurrentSidebarCategoryMock([ + {type: 'link', href: '/cat/fake', label: 'Fake'}, + category, + ]); + expect(mockUseCurrentSidebarCategory('/cat')).toEqual(category); + }); + + it('throws for non-category index page', () => { + const category = { + type: 'category', + items: [ + {type: 'link', href: '/cat/foo', label: 'Foo'}, + {type: 'link', href: '/cat/bar', label: 'Bar'}, + {type: 'link', href: '/baz', label: 'Baz'}, + ], + }; + const mockUseCurrentSidebarCategory = createUseCurrentSidebarCategoryMock([ + category, + ]); + expect(() => + mockUseCurrentSidebarCategory('/cat'), + ).toThrowErrorMatchingInlineSnapshot( + `"/cat is not associated with a category. useCurrentSidebarCategory() should only be used on category index pages."`, + ); + }); + + it('throws when sidebar is missing', () => { + const mockUseCurrentSidebarCategory = createUseCurrentSidebarCategoryMock(); + expect(() => + mockUseCurrentSidebarCategory('/cat'), + ).toThrowErrorMatchingInlineSnapshot( + `"Unexpected: cant find current sidebar in context"`, + ); }); }); diff --git a/packages/docusaurus-theme-common/src/utils/__tests__/footerUtils.test.ts b/packages/docusaurus-theme-common/src/utils/__tests__/footerUtils.test.ts new file mode 100644 index 000000000000..3c73e3dfa984 --- /dev/null +++ b/packages/docusaurus-theme-common/src/utils/__tests__/footerUtils.test.ts @@ -0,0 +1,37 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {isMultiColumnFooterLinks} from '../footerUtils'; + +describe('isMultiColumnFooterLinks', () => { + it('works', () => { + expect( + isMultiColumnFooterLinks([ + { + title: 'section', + items: [ + {href: '/foo', label: 'Foo'}, + {href: '/bar', label: 'Bar'}, + ], + }, + { + title: 'section2', + items: [ + {href: '/foo', label: 'Foo2'}, + {href: '/bar', label: 'Bar2'}, + ], + }, + ]), + ).toBe(true); + expect( + isMultiColumnFooterLinks([ + {href: '/foo', label: 'Foo'}, + {href: '/bar', label: 'Bar'}, + ]), + ).toBe(false); + }); +}); diff --git a/packages/docusaurus-theme-common/src/utils/__tests__/generalUtils.test.tsx b/packages/docusaurus-theme-common/src/utils/__tests__/generalUtils.test.tsx new file mode 100644 index 000000000000..71c49527abad --- /dev/null +++ b/packages/docusaurus-theme-common/src/utils/__tests__/generalUtils.test.tsx @@ -0,0 +1,33 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import {useTitleFormatter} from '../generalUtils'; +import {renderHook} from '@testing-library/react-hooks'; +import {Context} from '@docusaurus/core/src/client/docusaurusContext'; +import type {DocusaurusContext} from '@docusaurus/types'; + +describe('useTitleFormatter', () => { + const createUseTitleFormatterMock = + (context: DocusaurusContext) => (title?: string) => + renderHook(() => useTitleFormatter(title), { + wrapper: ({children}) => ( + {children} + ), + }).result.current; + it('works', () => { + const mockUseTitleFormatter = createUseTitleFormatterMock({ + siteConfig: { + title: 'my site', + titleDelimiter: '·', + }, + }); + expect(mockUseTitleFormatter('a page')).toBe('a page · my site'); + expect(mockUseTitleFormatter(undefined)).toBe('my site'); + expect(mockUseTitleFormatter(' ')).toBe('my site'); + }); +}); diff --git a/packages/docusaurus-theme-common/src/utils/__tests__/jsUtils.test.ts b/packages/docusaurus-theme-common/src/utils/__tests__/jsUtils.test.ts index 99ba23fedf9f..9361063910d9 100644 --- a/packages/docusaurus-theme-common/src/utils/__tests__/jsUtils.test.ts +++ b/packages/docusaurus-theme-common/src/utils/__tests__/jsUtils.test.ts @@ -8,13 +8,13 @@ import {uniq, duplicates} from '../jsUtils'; describe('duplicates', () => { - test('gets duplicate values', () => { + it('gets duplicate values', () => { expect(duplicates(['a', 'b', 'c', 'd'])).toEqual([]); expect(duplicates(['a', 'b', 'b', 'b'])).toEqual(['b', 'b']); expect(duplicates(['c', 'b', 'b', 'c'])).toEqual(['b', 'c']); expect(duplicates([{a: 1}, {a: 1}, {a: 1}])).toEqual([]); }); - test('accepts custom comparator', () => { + it('accepts custom comparator', () => { expect(duplicates([{a: 1}, {a: 1}, {a: 1}], (a, b) => a.a === b.a)).toEqual( [{a: 1}, {a: 1}], ); @@ -28,7 +28,7 @@ describe('duplicates', () => { }); describe('uniq', () => { - test('remove duplicate primitives', () => { + it('remove duplicate primitives', () => { expect(uniq(['A', 'B', 'C', 'B', 'A', 'D'])).toEqual(['A', 'B', 'C', 'D']); expect(uniq([3, 3, 5, 1, 6, 3, 5])).toEqual([3, 5, 1, 6]); expect(uniq([null, undefined, 3, null, 4, 3])).toEqual([ @@ -39,7 +39,7 @@ describe('uniq', () => { ]); }); - test('remove duplicate objects/arrays by identity', () => { + it('remove duplicate objects/arrays by identity', () => { const obj1 = {}; const obj2 = {}; const obj3 = {}; diff --git a/packages/docusaurus-theme-common/src/utils/__tests__/pathUtils.test.ts b/packages/docusaurus-theme-common/src/utils/__tests__/pathUtils.test.ts deleted file mode 100644 index 71d88b7d9a5a..000000000000 --- a/packages/docusaurus-theme-common/src/utils/__tests__/pathUtils.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import {isSamePath} from '../pathUtils'; - -describe('isSamePath', () => { - test('should be true for compared path without trailing slash', () => { - expect(isSamePath('/docs', '/docs')).toBeTruthy(); - }); - - test('should be true for compared path with trailing slash', () => { - expect(isSamePath('/docs', '/docs/')).toBeTruthy(); - }); - - test('should be false for compared path with double trailing slash', () => { - expect(isSamePath('/docs', '/docs//')).toBeFalsy(); - }); - - test('should be true for twice undefined/null', () => { - expect(isSamePath(undefined, undefined)).toBeTruthy(); - expect(isSamePath(undefined, undefined)).toBeTruthy(); - }); - - test('should be false when one undefined', () => { - expect(isSamePath('/docs', undefined)).toBeFalsy(); - expect(isSamePath(undefined, '/docs')).toBeFalsy(); - }); -}); diff --git a/packages/docusaurus-theme-common/src/utils/__tests__/reactUtils.test.ts b/packages/docusaurus-theme-common/src/utils/__tests__/reactUtils.test.ts new file mode 100644 index 000000000000..0b3e11fa1d43 --- /dev/null +++ b/packages/docusaurus-theme-common/src/utils/__tests__/reactUtils.test.ts @@ -0,0 +1,22 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {usePrevious} from '../reactUtils'; +import {renderHook} from '@testing-library/react-hooks'; + +describe('usePrevious', () => { + it('returns the previous value of a variable', () => { + const {result, rerender} = renderHook((val) => usePrevious(val), { + initialProps: 1, + }); + expect(result.current).toBeUndefined(); + rerender(2); + expect(result.current).toBe(1); + rerender(3); + expect(result.current).toBe(2); + }); +}); diff --git a/packages/docusaurus-theme-common/src/utils/__tests__/regexpUtils.test.ts b/packages/docusaurus-theme-common/src/utils/__tests__/regexpUtils.test.ts index a05f4812e106..9d65d74a7a6f 100644 --- a/packages/docusaurus-theme-common/src/utils/__tests__/regexpUtils.test.ts +++ b/packages/docusaurus-theme-common/src/utils/__tests__/regexpUtils.test.ts @@ -8,14 +8,15 @@ import {isRegexpStringMatch} from '../regexpUtils'; describe('isRegexpStringMatch', () => { - test('behaves correctly', () => { - expect(isRegexpStringMatch(undefined, 'foo')).toEqual(false); - expect(isRegexpStringMatch('bar', undefined)).toEqual(false); - expect(isRegexpStringMatch('foo', 'bar')).toEqual(false); - expect(isRegexpStringMatch('foo', 'foo')).toEqual(true); - expect(isRegexpStringMatch('fooooooooooo', 'foo')).toEqual(false); - expect(isRegexpStringMatch('foo', 'fooooooo')).toEqual(true); - expect(isRegexpStringMatch('f.*o', 'fggo')).toEqual(true); - expect(isRegexpStringMatch('FOO', 'foo')).toEqual(true); + it('works', () => { + expect(isRegexpStringMatch(undefined, 'foo')).toBe(false); + expect(isRegexpStringMatch('bar', undefined)).toBe(false); + expect(isRegexpStringMatch('foo', 'bar')).toBe(false); + expect(isRegexpStringMatch('foo', 'foo')).toBe(true); + // cSpell:ignore fooooooooooo + expect(isRegexpStringMatch('fooooooooooo', 'foo')).toBe(false); + expect(isRegexpStringMatch('foo', 'fooooooooooo')).toBe(true); + expect(isRegexpStringMatch('f.*o', 'fooooooooooo')).toBe(true); + expect(isRegexpStringMatch('FOO', 'foo')).toBe(true); }); }); diff --git a/packages/docusaurus-theme-common/src/utils/__tests__/routesUtils.test.ts b/packages/docusaurus-theme-common/src/utils/__tests__/routesUtils.test.ts new file mode 100644 index 000000000000..a226c13f1156 --- /dev/null +++ b/packages/docusaurus-theme-common/src/utils/__tests__/routesUtils.test.ts @@ -0,0 +1,140 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import type {RouteConfig} from 'react-router-config'; +import {findHomePageRoute, isSamePath} from '../routesUtils'; + +describe('isSamePath', () => { + it('returns true for compared path without trailing slash', () => { + expect(isSamePath('/docs', '/docs')).toBeTruthy(); + }); + + it('returns true for compared path with trailing slash', () => { + expect(isSamePath('/docs', '/docs/')).toBeTruthy(); + }); + + it('returns true for compared path with different case', () => { + expect(isSamePath('/doCS', '/DOcs')).toBeTruthy(); + }); + + it('returns true for compared path with different case + trailing slash', () => { + expect(isSamePath('/doCS', '/DOcs/')).toBeTruthy(); + }); + + it('returns false for compared path with double trailing slash', () => { + expect(isSamePath('/docs', '/docs//')).toBeFalsy(); + }); + + it('returns true for twice undefined/null', () => { + expect(isSamePath(undefined, undefined)).toBeTruthy(); + expect(isSamePath(undefined, undefined)).toBeTruthy(); + }); + + it('returns false when one undefined', () => { + expect(isSamePath('/docs', undefined)).toBeFalsy(); + expect(isSamePath(undefined, '/docs')).toBeFalsy(); + }); +}); + +describe('findHomePageRoute', () => { + const homePage: RouteConfig = { + path: '/', + exact: true, + }; + + it('returns undefined for no routes', () => { + expect(findHomePageRoute({baseUrl: '/', routes: []})).toBeUndefined(); + }); + + it('returns undefined for no homepage', () => { + expect( + findHomePageRoute({ + baseUrl: '/', + routes: [ + {path: '/a', exact: true}, + {path: '/b', exact: false}, + {path: '/c', exact: undefined}, + { + path: '/d', + exact: false, + routes: [ + {path: '/d/1', exact: true}, + {path: '/d/2', exact: false}, + {path: '/d/3', exact: undefined}, + ], + }, + ], + }), + ).toBeUndefined(); + }); + + it('finds top-level homepage', () => { + expect( + findHomePageRoute({ + baseUrl: '/', + routes: [ + {path: '/a', exact: true}, + {path: '/b', exact: false}, + {path: '/c', exact: undefined}, + {...homePage, exact: false}, + homePage, + {...homePage, exact: undefined}, + ], + }), + ).toEqual(homePage); + }); + + it('finds nested homepage', () => { + expect( + findHomePageRoute({ + baseUrl: '/', + routes: [ + {path: '/a', exact: true}, + { + path: '/', + exact: false, + routes: [ + {path: '/b', exact: true}, + { + path: '/', + exact: undefined, + routes: [{path: '/c', exact: true}, homePage], + }, + ], + }, + {path: '/d', exact: true}, + ], + }), + ).toEqual(homePage); + }); + + it('finds nested homepage with baseUrl', () => { + const baseUrl = '/baseUrl/'; + const baseUrlHomePage = {...homePage, path: baseUrl}; + expect( + findHomePageRoute({ + baseUrl, + routes: [ + {path: `${baseUrl}a`, exact: true}, + { + path: baseUrl, + exact: false, + routes: [ + {path: `${baseUrl}b`, exact: true}, + { + path: baseUrl, + exact: false, + routes: [{path: `${baseUrl}c`, exact: true}, baseUrlHomePage], + }, + ], + }, + {path: `${baseUrl}d`, exact: true}, + ], + }), + ).toEqual(baseUrlHomePage); + }); +}); diff --git a/packages/docusaurus-theme-common/src/utils/__tests__/searchUtils.test.ts b/packages/docusaurus-theme-common/src/utils/__tests__/searchUtils.test.ts index 1aa50b54af24..bb6ab8245e2c 100644 --- a/packages/docusaurus-theme-common/src/utils/__tests__/searchUtils.test.ts +++ b/packages/docusaurus-theme-common/src/utils/__tests__/searchUtils.test.ts @@ -8,7 +8,7 @@ import {docVersionSearchTag} from '../searchUtils'; describe('docVersionSearchTag', () => { - test('behaves correctly', () => { - expect(docVersionSearchTag('foo', 'bar')).toEqual('docs-foo-bar'); + it('works', () => { + expect(docVersionSearchTag('foo', 'bar')).toBe('docs-foo-bar'); }); }); diff --git a/packages/docusaurus-theme-common/src/utils/__tests__/tagUtils.test.ts b/packages/docusaurus-theme-common/src/utils/__tests__/tagUtils.test.ts index 028ad4f0be2a..2de931f7e047 100644 --- a/packages/docusaurus-theme-common/src/utils/__tests__/tagUtils.test.ts +++ b/packages/docusaurus-theme-common/src/utils/__tests__/tagUtils.test.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import {shuffle} from 'lodash'; +import _ from 'lodash'; import {listTagsByLetters} from '../tagsUtils'; describe('listTagsByLetters', () => { @@ -13,7 +13,7 @@ describe('listTagsByLetters', () => { type Tag = Param[number]; type Result = ReturnType; - test('Should create letters list', () => { + it('creates letters list', () => { const tag1: Tag = { name: 'tag1', permalink: '/tag1', @@ -24,7 +24,7 @@ describe('listTagsByLetters', () => { permalink: '/tag2', count: 11, }; - const tagzxy: Tag = { + const tagZxy: Tag = { name: 'zxy', permalink: '/zxy', count: 987, @@ -34,33 +34,35 @@ describe('listTagsByLetters', () => { permalink: '/abc', count: 123, }; - const tagdef: Tag = { + const tagDef: Tag = { name: 'def', permalink: '/def', count: 1, }; - const tagaaa: Tag = { + const tagAaa: Tag = { name: 'aaa', permalink: '/aaa', count: 10, }; const expectedResult: Result = [ - {letter: 'A', tags: [tagaaa, tagAbc]}, - {letter: 'D', tags: [tagdef]}, + {letter: 'A', tags: [tagAaa, tagAbc]}, + {letter: 'D', tags: [tagDef]}, {letter: 'T', tags: [tag1, tag2]}, - {letter: 'Z', tags: [tagzxy]}, + {letter: 'Z', tags: [tagZxy]}, ]; // Input order shouldn't matter, output is always consistently sorted expect( - listTagsByLetters([tag1, tag2, tagzxy, tagAbc, tagdef, tagaaa]), + listTagsByLetters([tag1, tag2, tagZxy, tagAbc, tagDef, tagAaa]), ).toEqual(expectedResult); expect( - listTagsByLetters([tagzxy, tagdef, tagaaa, tag2, tagAbc, tag1]), + listTagsByLetters([tagZxy, tagDef, tagAaa, tag2, tagAbc, tag1]), ).toEqual(expectedResult); expect( - listTagsByLetters(shuffle([tagzxy, tagdef, tagaaa, tag2, tagAbc, tag1])), + listTagsByLetters( + _.shuffle([tagZxy, tagDef, tagAaa, tag2, tagAbc, tag1]), + ), ).toEqual(expectedResult); }); }); diff --git a/packages/docusaurus-theme-common/src/utils/__tests__/tocUtils.test.ts b/packages/docusaurus-theme-common/src/utils/__tests__/tocUtils.test.ts index 4792d9a4ac61..0ae93d7024b6 100644 --- a/packages/docusaurus-theme-common/src/utils/__tests__/tocUtils.test.ts +++ b/packages/docusaurus-theme-common/src/utils/__tests__/tocUtils.test.ts @@ -6,55 +6,56 @@ */ import type {TOCItem} from '@docusaurus/types'; -import {filterTOC} from '../tocUtils'; +import {renderHook} from '@testing-library/react-hooks'; +import {useFilteredAndTreeifiedTOC, useTreeifiedTOC} from '../tocUtils'; -describe('filterTOC', () => { - test('filter a toc with all heading levels', () => { - const toc: TOCItem[] = [ - { - id: 'alpha', - level: 1, - value: 'alpha', - children: [ - { - id: 'bravo', - level: 2, - value: 'Bravo', - children: [ - { - id: 'charlie', - level: 3, - value: 'Charlie', - children: [ - { - id: 'delta', - level: 4, - value: 'Delta', - children: [ - { - id: 'echo', - level: 5, - value: 'Echo', - children: [ - { - id: 'foxtrot', - level: 6, - value: 'Foxtrot', - children: [], - }, - ], - }, - ], - }, - ], - }, - ], - }, - ], - }, - ]; +const mockTOC: TOCItem[] = [ + { + id: 'bravo', + level: 2, + value: 'Bravo', + }, + { + id: 'charlie', + level: 3, + value: 'Charlie', + }, + { + id: 'delta', + level: 4, + value: 'Delta', + }, + { + id: 'echo', + level: 5, + value: 'Echo', + }, + { + id: 'foxtrot', + level: 6, + value: 'Foxtrot', + }, +]; + +describe('useTreeifiedTOC', () => { + it('treeifies TOC without filtering', () => { + expect( + renderHook(() => useTreeifiedTOC(mockTOC)).result.current, + ).toMatchSnapshot(); + }); +}); - expect(filterTOC({toc, minHeadingLevel: 2, maxHeadingLevel: 2})).toEqual([ +describe('useFilteredAndTreeifiedTOC', () => { + it('filters a toc with all heading levels', () => { + expect( + renderHook(() => + useFilteredAndTreeifiedTOC({ + toc: mockTOC, + minHeadingLevel: 2, + maxHeadingLevel: 2, + }), + ).result.current, + ).toEqual([ { id: 'bravo', level: 2, @@ -63,7 +64,15 @@ describe('filterTOC', () => { }, ]); - expect(filterTOC({toc, minHeadingLevel: 3, maxHeadingLevel: 3})).toEqual([ + expect( + renderHook(() => + useFilteredAndTreeifiedTOC({ + toc: mockTOC, + minHeadingLevel: 3, + maxHeadingLevel: 3, + }), + ).result.current, + ).toEqual([ { id: 'charlie', level: 3, @@ -72,7 +81,15 @@ describe('filterTOC', () => { }, ]); - expect(filterTOC({toc, minHeadingLevel: 2, maxHeadingLevel: 3})).toEqual([ + expect( + renderHook(() => + useFilteredAndTreeifiedTOC({ + toc: mockTOC, + minHeadingLevel: 2, + maxHeadingLevel: 3, + }), + ).result.current, + ).toEqual([ { id: 'bravo', level: 2, @@ -88,7 +105,15 @@ describe('filterTOC', () => { }, ]); - expect(filterTOC({toc, minHeadingLevel: 2, maxHeadingLevel: 4})).toEqual([ + expect( + renderHook(() => + useFilteredAndTreeifiedTOC({ + toc: mockTOC, + minHeadingLevel: 2, + maxHeadingLevel: 4, + }), + ).result.current, + ).toEqual([ { id: 'bravo', level: 2, @@ -112,32 +137,37 @@ describe('filterTOC', () => { ]); }); - // It's not 100% clear exactly how the TOC should behave under weird heading levels provided by the user - // Adding a test so that behavior stays the same over time - test('filter invalid heading levels (but possible) TOC', () => { + // It's not 100% clear exactly how the TOC should behave under weird heading + // levels provided by the user. Adding a test so that behavior stays the same + // over time + it('filters invalid heading levels (but possible) TOC', () => { const toc: TOCItem[] = [ { id: 'charlie', level: 3, value: 'Charlie', - children: [], }, { id: 'bravo', level: 2, value: 'Bravo', - children: [ - { - id: 'delta', - level: 4, - value: 'Delta', - children: [], - }, - ], + }, + { + id: 'delta', + level: 4, + value: 'Delta', }, ]; - expect(filterTOC({toc, minHeadingLevel: 2, maxHeadingLevel: 2})).toEqual([ + expect( + renderHook(() => + useFilteredAndTreeifiedTOC({ + toc, + minHeadingLevel: 2, + maxHeadingLevel: 2, + }), + ).result.current, + ).toEqual([ { id: 'bravo', level: 2, @@ -146,7 +176,15 @@ describe('filterTOC', () => { }, ]); - expect(filterTOC({toc, minHeadingLevel: 3, maxHeadingLevel: 3})).toEqual([ + expect( + renderHook(() => + useFilteredAndTreeifiedTOC({ + toc, + minHeadingLevel: 3, + maxHeadingLevel: 3, + }), + ).result.current, + ).toEqual([ { id: 'charlie', level: 3, @@ -155,7 +193,15 @@ describe('filterTOC', () => { }, ]); - expect(filterTOC({toc, minHeadingLevel: 4, maxHeadingLevel: 4})).toEqual([ + expect( + renderHook(() => + useFilteredAndTreeifiedTOC({ + toc, + minHeadingLevel: 4, + maxHeadingLevel: 4, + }), + ).result.current, + ).toEqual([ { id: 'delta', level: 4, @@ -164,7 +210,15 @@ describe('filterTOC', () => { }, ]); - expect(filterTOC({toc, minHeadingLevel: 2, maxHeadingLevel: 3})).toEqual([ + expect( + renderHook(() => + useFilteredAndTreeifiedTOC({ + toc, + minHeadingLevel: 2, + maxHeadingLevel: 3, + }), + ).result.current, + ).toEqual([ { id: 'charlie', level: 3, @@ -179,7 +233,15 @@ describe('filterTOC', () => { }, ]); - expect(filterTOC({toc, minHeadingLevel: 3, maxHeadingLevel: 4})).toEqual([ + expect( + renderHook(() => + useFilteredAndTreeifiedTOC({ + toc, + minHeadingLevel: 3, + maxHeadingLevel: 4, + }), + ).result.current, + ).toEqual([ { id: 'charlie', level: 3, diff --git a/packages/docusaurus-theme-common/src/utils/__tests__/useAlternatePageUtils.test.tsx b/packages/docusaurus-theme-common/src/utils/__tests__/useAlternatePageUtils.test.tsx new file mode 100644 index 000000000000..79ebd6cbca7f --- /dev/null +++ b/packages/docusaurus-theme-common/src/utils/__tests__/useAlternatePageUtils.test.tsx @@ -0,0 +1,124 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import {useAlternatePageUtils} from '../useAlternatePageUtils'; +import {renderHook} from '@testing-library/react-hooks'; +import {StaticRouter} from 'react-router-dom'; +import {Context} from '@docusaurus/core/src/client/docusaurusContext'; +import type {DocusaurusContext} from '@docusaurus/types'; + +describe('useAlternatePageUtils', () => { + const createUseAlternatePageUtilsMock = + (context: DocusaurusContext) => (location: string) => + renderHook(() => useAlternatePageUtils(), { + wrapper: ({children}) => ( + + {children} + + ), + }).result.current; + it('works for baseUrl: / and currentLocale = defaultLocale', () => { + const mockUseAlternatePageUtils = createUseAlternatePageUtilsMock({ + siteConfig: {baseUrl: '/', url: 'https://example.com'}, + i18n: {defaultLocale: 'en', currentLocale: 'en'}, + }); + expect( + mockUseAlternatePageUtils('/').createUrl({ + locale: 'zh-Hans', + fullyQualified: false, + }), + ).toBe('/zh-Hans/'); + expect( + mockUseAlternatePageUtils('/foo').createUrl({ + locale: 'zh-Hans', + fullyQualified: false, + }), + ).toBe('/zh-Hans/foo'); + expect( + mockUseAlternatePageUtils('/foo').createUrl({ + locale: 'zh-Hans', + fullyQualified: true, + }), + ).toBe('https://example.com/zh-Hans/foo'); + }); + + it('works for baseUrl: / and currentLocale /= defaultLocale', () => { + const mockUseAlternatePageUtils = createUseAlternatePageUtilsMock({ + siteConfig: {baseUrl: '/zh-Hans/', url: 'https://example.com'}, + i18n: {defaultLocale: 'en', currentLocale: 'zh-Hans'}, + }); + expect( + mockUseAlternatePageUtils('/zh-Hans/').createUrl({ + locale: 'en', + fullyQualified: false, + }), + ).toBe('/'); + expect( + mockUseAlternatePageUtils('/zh-Hans/foo').createUrl({ + locale: 'en', + fullyQualified: false, + }), + ).toBe('/foo'); + expect( + mockUseAlternatePageUtils('/zh-Hans/foo').createUrl({ + locale: 'en', + fullyQualified: true, + }), + ).toBe('https://example.com/foo'); + }); + + it('works for non-root base URL and currentLocale = defaultLocale', () => { + const mockUseAlternatePageUtils = createUseAlternatePageUtilsMock({ + siteConfig: {baseUrl: '/base/', url: 'https://example.com'}, + i18n: {defaultLocale: 'en', currentLocale: 'en'}, + }); + expect( + mockUseAlternatePageUtils('/base/').createUrl({ + locale: 'zh-Hans', + fullyQualified: false, + }), + ).toBe('/base/zh-Hans/'); + expect( + mockUseAlternatePageUtils('/base/foo').createUrl({ + locale: 'zh-Hans', + fullyQualified: false, + }), + ).toBe('/base/zh-Hans/foo'); + expect( + mockUseAlternatePageUtils('/base/foo').createUrl({ + locale: 'zh-Hans', + fullyQualified: true, + }), + ).toBe('https://example.com/base/zh-Hans/foo'); + }); + + it('works for non-root base URL and currentLocale /= defaultLocale', () => { + const mockUseAlternatePageUtils = createUseAlternatePageUtilsMock({ + siteConfig: {baseUrl: '/base/zh-Hans/', url: 'https://example.com'}, + i18n: {defaultLocale: 'en', currentLocale: 'zh-Hans'}, + }); + expect( + mockUseAlternatePageUtils('/base/zh-Hans/').createUrl({ + locale: 'en', + fullyQualified: false, + }), + ).toBe('/base/'); + expect( + mockUseAlternatePageUtils('/base/zh-Hans/foo').createUrl({ + locale: 'en', + fullyQualified: false, + }), + ).toBe('/base/foo'); + expect( + mockUseAlternatePageUtils('/base/zh-Hans/foo').createUrl({ + locale: 'en', + fullyQualified: true, + }), + ).toBe('https://example.com/base/foo'); + }); +}); diff --git a/packages/docusaurus-theme-common/src/utils/__tests__/useLocalPathname.test.tsx b/packages/docusaurus-theme-common/src/utils/__tests__/useLocalPathname.test.tsx new file mode 100644 index 000000000000..163e2f69bdb7 --- /dev/null +++ b/packages/docusaurus-theme-common/src/utils/__tests__/useLocalPathname.test.tsx @@ -0,0 +1,38 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import {useLocalPathname} from '../useLocalPathname'; +import {renderHook} from '@testing-library/react-hooks'; +import {StaticRouter} from 'react-router-dom'; +import {Context} from '@docusaurus/core/src/client/docusaurusContext'; +import type {DocusaurusContext} from '@docusaurus/types'; + +describe('useLocalPathname', () => { + const createUseLocalPathnameMock = + (context: DocusaurusContext) => (location: string) => + renderHook(() => useLocalPathname(), { + wrapper: ({children}) => ( + + {children} + + ), + }).result.current; + it('works for baseUrl: /', () => { + const mockUseLocalPathname = createUseLocalPathnameMock({ + siteConfig: {baseUrl: '/'}, + }); + expect(mockUseLocalPathname('/foo')).toBe('/foo'); + }); + + it('works for non-root baseUrl', () => { + const mockUseLocalPathname = createUseLocalPathnameMock({ + siteConfig: {baseUrl: '/base/'}, + }); + expect(mockUseLocalPathname('/base/foo')).toBe('/foo'); + }); +}); diff --git a/packages/docusaurus-theme-common/src/utils/__tests__/usePluralForm.test.tsx b/packages/docusaurus-theme-common/src/utils/__tests__/usePluralForm.test.tsx new file mode 100644 index 000000000000..13777535a6e4 --- /dev/null +++ b/packages/docusaurus-theme-common/src/utils/__tests__/usePluralForm.test.tsx @@ -0,0 +1,79 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {jest} from '@jest/globals'; +import React from 'react'; +import {usePluralForm} from '../usePluralForm'; +import {renderHook} from '@testing-library/react-hooks'; +import {Context} from '@docusaurus/core/src/client/docusaurusContext'; +import type {DocusaurusContext} from '@docusaurus/types'; + +describe('usePluralForm', () => { + const createUsePluralFormMock = (context: DocusaurusContext) => () => + renderHook(() => usePluralForm(), { + wrapper: ({children}) => ( + {children} + ), + }).result.current; + it('returns the right plural', () => { + const mockUsePluralForm = createUsePluralFormMock({ + i18n: { + currentLocale: 'en', + }, + }); + expect(mockUsePluralForm().selectMessage(1, 'one|many')).toBe('one'); + expect(mockUsePluralForm().selectMessage(10, 'one|many')).toBe('many'); + }); + + it('warns against too many plurals', () => { + const mockUsePluralForm = createUsePluralFormMock({ + i18n: { + currentLocale: 'zh-Hans', + }, + }); + const consoleMock = jest + .spyOn(console, 'error') + .mockImplementation(() => {}); + expect(mockUsePluralForm().selectMessage(1, 'one|many')).toBe('one'); + expect(mockUsePluralForm().selectMessage(10, 'one|many')).toBe('one'); + expect(consoleMock.mock.calls[0][0]).toMatchInlineSnapshot( + `"For locale=zh-Hans, a maximum of 1 plural forms are expected (other), but the message contains 2: one|many"`, + ); + }); + + it('uses the last with not enough plurals', () => { + const mockUsePluralForm = createUsePluralFormMock({ + i18n: { + currentLocale: 'en', + }, + }); + expect(mockUsePluralForm().selectMessage(10, 'many')).toBe('many'); + }); + + it('falls back when Intl.PluralForms is not available', () => { + const mockUsePluralForm = createUsePluralFormMock({ + i18n: { + currentLocale: 'zh-Hans', + }, + }); + const consoleMock = jest + .spyOn(console, 'error') + .mockImplementation(() => {}); + const pluralMock = jest + .spyOn(Intl, 'PluralRules') + .mockImplementation(() => undefined); + expect(mockUsePluralForm().selectMessage(1, 'one|many')).toBe('one'); + expect(mockUsePluralForm().selectMessage(10, 'one|many')).toBe('many'); + expect(consoleMock.mock.calls[0][0]).toMatchInlineSnapshot(` + "Failed to use Intl.PluralRules for locale \\"zh-Hans\\". + Docusaurus will fallback to the default (English) implementation. + Error: pluralRules.resolvedOptions is not a function + " + `); + pluralMock.mockRestore(); + }); +}); diff --git a/packages/docusaurus-theme-common/src/utils/codeBlockUtils.ts b/packages/docusaurus-theme-common/src/utils/codeBlockUtils.ts index a279afc5fd78..cc28c5d21feb 100644 --- a/packages/docusaurus-theme-common/src/utils/codeBlockUtils.ts +++ b/packages/docusaurus-theme-common/src/utils/codeBlockUtils.ts @@ -7,50 +7,27 @@ import rangeParser from 'parse-numeric-range'; -const codeBlockTitleRegex = /title=(["'])(.*?)\1/; -const highlightLinesRangeRegex = /{([\d,-]+)}/; - -const commentTypes = ['js', 'jsBlock', 'jsx', 'python', 'html'] as const; -type CommentType = typeof commentTypes[number]; - -type CommentPattern = { - start: string; - end: string; -}; +const codeBlockTitleRegex = /title=(?["'])(?.*?)\1/; +const highlightLinesRangeRegex = /\{(?<range>[\d,-]+)\}/; // Supported types of highlight comments -const commentPatterns: Record<CommentType, CommentPattern> = { - js: { - start: '\\/\\/', - end: '', - }, - jsBlock: { - start: '\\/\\*', - end: '\\*\\/', - }, - jsx: { - start: '\\{\\s*\\/\\*', - end: '\\*\\/\\s*\\}', - }, - python: { - start: '#', - end: '', - }, - html: { - start: '<!--', - end: '-->', - }, +const commentPatterns = { + js: {start: '\\/\\/', end: ''}, + jsBlock: {start: '\\/\\*', end: '\\*\\/'}, + jsx: {start: '\\{\\s*\\/\\*', end: '\\*\\/\\s*\\}'}, + bash: {start: '#', end: ''}, + html: {start: '<!--', end: '-->'}, }; +type CommentType = keyof typeof commentPatterns; + const magicCommentDirectives = [ 'highlight-next-line', 'highlight-start', 'highlight-end', ]; -const getMagicCommentDirectiveRegex = ( - languages: readonly CommentType[] = commentTypes, -) => { +function getCommentPattern(languages: CommentType[]) { // to be more reliable, the opening and closing comment must match const commentPattern = languages .map((lang) => { @@ -60,38 +37,51 @@ const getMagicCommentDirectiveRegex = ( .join('|'); // white space is allowed, but otherwise it should be on it's own line return new RegExp(`^\\s*(?:${commentPattern})\\s*$`); -}; +} -// select comment styles based on language -const magicCommentDirectiveRegex = (lang: string) => { +/** + * Select comment styles based on language + */ +function getAllMagicCommentDirectiveStyles(lang: string) { switch (lang) { case 'js': case 'javascript': case 'ts': case 'typescript': - return getMagicCommentDirectiveRegex(['js', 'jsBlock']); + return getCommentPattern(['js', 'jsBlock']); case 'jsx': case 'tsx': - return getMagicCommentDirectiveRegex(['js', 'jsBlock', 'jsx']); + return getCommentPattern(['js', 'jsBlock', 'jsx']); case 'html': - return getMagicCommentDirectiveRegex(['js', 'jsBlock', 'html']); + return getCommentPattern(['js', 'jsBlock', 'html']); case 'python': case 'py': - return getMagicCommentDirectiveRegex(['python']); + case 'bash': + return getCommentPattern(['bash']); + + case 'markdown': + case 'md': + // Text uses HTML, front matter uses bash + return getCommentPattern(['html', 'jsx', 'bash']); default: // all comment types - return getMagicCommentDirectiveRegex(); + return getCommentPattern(Object.keys(commentPatterns) as CommentType[]); } -}; +} export function parseCodeBlockTitle(metastring?: string): string { - return metastring?.match(codeBlockTitleRegex)?.[2] ?? ''; + return metastring?.match(codeBlockTitleRegex)?.groups!.title ?? ''; } +/** + * Gets the language name from the class name (set by MDX). + * e.g. `"language-javascript"` => `"javascript"`. + * Returns undefined if there is no language class name. + */ export function parseLanguage(className: string): string | undefined { const languageClassName = className .split(' ') @@ -100,21 +90,40 @@ export function parseLanguage(className: string): string | undefined { } /** - * @param metastring The highlight range declared here starts at 1 - * @returns Note: all line numbers start at 0, not 1 + * Parses the code content, strips away any magic comments, and returns the + * clean content and the highlighted lines marked by the comments or metastring. + * + * If the metastring contains highlight range, the `content` will be returned + * as-is without any parsing. + * + * @param content The raw code with magic comments. Trailing newline will be + * trimmed upfront. + * @param metastring The full metastring, as received from MDX. Highlight range + * declared here starts at 1. + * @param language Language of the code block, used to determine which kinds of + * magic comment styles to enable. */ export function parseLines( content: string, metastring?: string, language?: string, ): { + /** + * The highlighted lines, 0-indexed. e.g. `[0, 1, 4]` means the 1st, 2nd, and + * 5th lines are highlighted. + */ highlightLines: number[]; + /** + * The clean code without any magic comments (only if highlight range isn't + * present in the metastring). + */ code: string; } { let code = content.replace(/\n$/, ''); // Highlighted lines specified in props: don't parse the content if (metastring && highlightLinesRangeRegex.test(metastring)) { - const highlightLinesRange = metastring.match(highlightLinesRangeRegex)![1]; + const highlightLinesRange = metastring.match(highlightLinesRangeRegex)! + .groups!.range!; const highlightLines = rangeParser(highlightLinesRange) .filter((n) => n > 0) .map((n) => n - 1); @@ -123,14 +132,14 @@ export function parseLines( if (language === undefined) { return {highlightLines: [], code}; } - const directiveRegex = magicCommentDirectiveRegex(language); + const directiveRegex = getAllMagicCommentDirectiveStyles(language); // go through line by line const lines = code.split('\n'); let highlightBlockStart: number; let highlightRange = ''; // loop through lines for (let lineNumber = 0; lineNumber < lines.length; ) { - const line = lines[lineNumber]; + const line = lines[lineNumber]!; const match = line.match(directiveRegex); if (match !== null) { const directive = match.slice(1).find((item) => item !== undefined); diff --git a/packages/docusaurus-theme-common/src/utils/colorModeUtils.tsx b/packages/docusaurus-theme-common/src/utils/colorModeUtils.tsx deleted file mode 100644 index ee0ebba24938..000000000000 --- a/packages/docusaurus-theme-common/src/utils/colorModeUtils.tsx +++ /dev/null @@ -1,135 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import type {ReactNode} from 'react'; -import React, { - useState, - useCallback, - useEffect, - useContext, - useMemo, -} from 'react'; - -import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment'; -import {createStorageSlot} from './storageUtils'; -import {useThemeConfig} from './useThemeConfig'; - -type ColorModeContextValue = { - readonly isDarkTheme: boolean; - readonly setLightTheme: () => void; - readonly setDarkTheme: () => void; -}; - -const ThemeStorage = createStorageSlot('theme'); - -const themes = { - light: 'light', - dark: 'dark', -} as const; - -type Themes = typeof themes[keyof typeof themes]; - -// Ensure to always return a valid theme even if input is invalid -const coerceToTheme = (theme?: string | null): Themes => - theme === themes.dark ? themes.dark : themes.light; - -const getInitialTheme = (defaultMode: Themes | undefined): Themes => { - if (!ExecutionEnvironment.canUseDOM) { - return coerceToTheme(defaultMode); - } - return coerceToTheme(document.documentElement.getAttribute('data-theme')); -}; - -const storeTheme = (newTheme: Themes) => { - createStorageSlot('theme').set(coerceToTheme(newTheme)); -}; - -function useColorModeContextValue(): ColorModeContextValue { - const { - colorMode: {defaultMode, disableSwitch, respectPrefersColorScheme}, - } = useThemeConfig(); - const [theme, setTheme] = useState(getInitialTheme(defaultMode)); - - const setLightTheme = useCallback(() => { - setTheme(themes.light); - storeTheme(themes.light); - }, []); - const setDarkTheme = useCallback(() => { - setTheme(themes.dark); - storeTheme(themes.dark); - }, []); - - useEffect(() => { - document.documentElement.setAttribute('data-theme', coerceToTheme(theme)); - }, [theme]); - - useEffect(() => { - if (disableSwitch) { - return; - } - - try { - const storedTheme = ThemeStorage.get(); - if (storedTheme !== null) { - setTheme(coerceToTheme(storedTheme)); - } - } catch (err) { - console.error(err); - } - }, [disableSwitch, setTheme]); - - useEffect(() => { - if (disableSwitch && !respectPrefersColorScheme) { - return; - } - - window - .matchMedia('(prefers-color-scheme: dark)') - .addListener(({matches}) => { - setTheme(matches ? themes.dark : themes.light); - }); - }, [disableSwitch, respectPrefersColorScheme]); - - return { - isDarkTheme: theme === themes.dark, - setLightTheme, - setDarkTheme, - }; -} - -const ColorModeContext = React.createContext<ColorModeContextValue | undefined>( - undefined, -); - -export function ColorModeProvider({ - children, -}: { - children: ReactNode; -}): JSX.Element { - const {isDarkTheme, setLightTheme, setDarkTheme} = useColorModeContextValue(); - const contextValue = useMemo( - () => ({isDarkTheme, setLightTheme, setDarkTheme}), - [isDarkTheme, setLightTheme, setDarkTheme], - ); - return ( - <ColorModeContext.Provider value={contextValue}> - {children} - </ColorModeContext.Provider> - ); -} - -export function useColorMode(): ColorModeContextValue { - const context = useContext<ColorModeContextValue | undefined>( - ColorModeContext, - ); - if (context == null) { - throw new Error( - '"useColorMode()" is used outside of "Layout" component. Please see https://docusaurus.io/docs/api/themes/configuration#use-color-mode.', - ); - } - return context; -} diff --git a/packages/docusaurus-theme-common/src/utils/docSidebarItemsExpandedState.tsx b/packages/docusaurus-theme-common/src/utils/docSidebarItemsExpandedState.tsx deleted file mode 100644 index c2435f6931ad..000000000000 --- a/packages/docusaurus-theme-common/src/utils/docSidebarItemsExpandedState.tsx +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import React, {type ReactNode, useMemo, useState, useContext} from 'react'; - -const EmptyContext: unique symbol = Symbol('EmptyContext'); -const Context = React.createContext< - DocSidebarItemsExpandedState | typeof EmptyContext ->(EmptyContext); -type DocSidebarItemsExpandedState = { - expandedItem: number | null; - setExpandedItem: (a: number | null) => void; -}; - -export function DocSidebarItemsExpandedStateProvider({ - children, -}: { - children: ReactNode; -}): JSX.Element { - const [expandedItem, setExpandedItem] = useState<number | null>(null); - const contextValue = useMemo( - () => ({expandedItem, setExpandedItem}), - [expandedItem], - ); - - return <Context.Provider value={contextValue}>{children}</Context.Provider>; -} - -export function useDocSidebarItemsExpandedState(): DocSidebarItemsExpandedState { - const contextValue = useContext(Context); - if (contextValue === EmptyContext) { - throw new Error( - 'This hook requires usage of <DocSidebarItemsExpandedStateProvider>', - ); - } - return contextValue; -} diff --git a/packages/docusaurus-theme-common/src/utils/docsPreferredVersion/DocsPreferredVersionProvider.tsx b/packages/docusaurus-theme-common/src/utils/docsPreferredVersion/DocsPreferredVersionProvider.tsx deleted file mode 100644 index d79cc59940b5..000000000000 --- a/packages/docusaurus-theme-common/src/utils/docsPreferredVersion/DocsPreferredVersionProvider.tsx +++ /dev/null @@ -1,169 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import React, { - createContext, - type ReactNode, - useContext, - useEffect, - useMemo, - useState, -} from 'react'; -import {useThemeConfig, type DocsVersionPersistence} from '../useThemeConfig'; -import {isDocsPluginEnabled} from '../docsUtils'; - -import { - useAllDocsData, - type GlobalPluginData, -} from '@docusaurus/plugin-content-docs/client'; - -import DocsPreferredVersionStorage from './DocsPreferredVersionStorage'; - -type DocsPreferredVersionName = string | null; - -// State for a single docs plugin instance -type DocsPreferredVersionPluginState = { - preferredVersionName: DocsPreferredVersionName; -}; - -// We need to store in state/storage globally -// one preferred version per docs plugin instance -// pluginId => pluginState -type DocsPreferredVersionState = Record< - string, - DocsPreferredVersionPluginState ->; - -// Initial state is always null as we can't read local storage from node SSR -function getInitialState(pluginIds: string[]): DocsPreferredVersionState { - const initialState: DocsPreferredVersionState = {}; - pluginIds.forEach((pluginId) => { - initialState[pluginId] = { - preferredVersionName: null, - }; - }); - return initialState; -} - -// Read storage for all docs plugins -// Assign to each doc plugin a preferred version (if found) -function readStorageState({ - pluginIds, - versionPersistence, - allDocsData, -}: { - pluginIds: string[]; - versionPersistence: DocsVersionPersistence; - allDocsData: Record<string, GlobalPluginData>; -}): DocsPreferredVersionState { - // The storage value we read might be stale, - // and belong to a version that does not exist in the site anymore - // In such case, we remove the storage value to avoid downstream errors - function restorePluginState( - pluginId: string, - ): DocsPreferredVersionPluginState { - const preferredVersionNameUnsafe = DocsPreferredVersionStorage.read( - pluginId, - versionPersistence, - ); - const pluginData = allDocsData[pluginId]; - const versionExists = pluginData.versions.some( - (version) => version.name === preferredVersionNameUnsafe, - ); - if (versionExists) { - return {preferredVersionName: preferredVersionNameUnsafe}; - } else { - DocsPreferredVersionStorage.clear(pluginId, versionPersistence); - return {preferredVersionName: null}; - } - } - - const initialState: DocsPreferredVersionState = {}; - pluginIds.forEach((pluginId) => { - initialState[pluginId] = restorePluginState(pluginId); - }); - return initialState; -} - -function useVersionPersistence(): DocsVersionPersistence { - return useThemeConfig().docs.versionPersistence; -} - -// Value that will be accessible through context: [state,api] -function useContextValue() { - const allDocsData = useAllDocsData(); - const versionPersistence = useVersionPersistence(); - const pluginIds = useMemo(() => Object.keys(allDocsData), [allDocsData]); - - // Initial state is empty, as we can't read browser storage in node/SSR - const [state, setState] = useState(() => getInitialState(pluginIds)); - - // On mount, we set the state read from browser storage - useEffect(() => { - setState(readStorageState({allDocsData, versionPersistence, pluginIds})); - }, [allDocsData, versionPersistence, pluginIds]); - - // The API that we expose to consumer hooks (memo for constant object) - const api = useMemo(() => { - function savePreferredVersion(pluginId: string, versionName: string) { - DocsPreferredVersionStorage.save( - pluginId, - versionPersistence, - versionName, - ); - setState((s) => ({ - ...s, - [pluginId]: {preferredVersionName: versionName}, - })); - } - - return { - savePreferredVersion, - }; - }, [versionPersistence]); - - return [state, api] as const; -} - -type DocsPreferredVersionContextValue = ReturnType<typeof useContextValue>; - -const Context = createContext<DocsPreferredVersionContextValue | null>(null); - -export function DocsPreferredVersionContextProvider({ - children, -}: { - children: JSX.Element; -}): JSX.Element { - if (isDocsPluginEnabled) { - return ( - <DocsPreferredVersionContextProviderUnsafe> - {children} - </DocsPreferredVersionContextProviderUnsafe> - ); - } else { - return children; - } -} - -function DocsPreferredVersionContextProviderUnsafe({ - children, -}: { - children: ReactNode; -}): JSX.Element { - const contextValue = useContextValue(); - return <Context.Provider value={contextValue}>{children}</Context.Provider>; -} - -export function useDocsPreferredVersionContext(): DocsPreferredVersionContextValue { - const value = useContext(Context); - if (!value) { - throw new Error( - 'Can\'t find docs preferred context, maybe you forgot to use the "DocsPreferredVersionContextProvider"?', - ); - } - return value; -} diff --git a/packages/docusaurus-theme-common/src/utils/docsPreferredVersion/DocsPreferredVersionStorage.ts b/packages/docusaurus-theme-common/src/utils/docsPreferredVersion/DocsPreferredVersionStorage.ts deleted file mode 100644 index 6e063446f330..000000000000 --- a/packages/docusaurus-theme-common/src/utils/docsPreferredVersion/DocsPreferredVersionStorage.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import {createStorageSlot} from '../storageUtils'; -import type {DocsVersionPersistence} from '../useThemeConfig'; - -const storageKey = (pluginId: string) => `docs-preferred-version-${pluginId}`; - -const DocsPreferredVersionStorage = { - save: ( - pluginId: string, - persistence: DocsVersionPersistence, - versionName: string, - ): void => { - createStorageSlot(storageKey(pluginId), {persistence}).set(versionName); - }, - - read: ( - pluginId: string, - persistence: DocsVersionPersistence, - ): string | null => - createStorageSlot(storageKey(pluginId), {persistence}).get(), - - clear: (pluginId: string, persistence: DocsVersionPersistence): void => { - createStorageSlot(storageKey(pluginId), {persistence}).del(); - }, -}; - -export default DocsPreferredVersionStorage; diff --git a/packages/docusaurus-theme-common/src/utils/docsPreferredVersion/useDocsPreferredVersion.ts b/packages/docusaurus-theme-common/src/utils/docsPreferredVersion/useDocsPreferredVersion.ts deleted file mode 100644 index d6da597d91eb..000000000000 --- a/packages/docusaurus-theme-common/src/utils/docsPreferredVersion/useDocsPreferredVersion.ts +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import {useCallback} from 'react'; -import {useDocsPreferredVersionContext} from './DocsPreferredVersionProvider'; -import { - useAllDocsData, - useDocsData, - type GlobalVersion, -} from '@docusaurus/plugin-content-docs/client'; - -import {DEFAULT_PLUGIN_ID} from '@docusaurus/constants'; - -// Note, the preferredVersion attribute will always be null before mount -export function useDocsPreferredVersion( - pluginId: string | undefined = DEFAULT_PLUGIN_ID, -): { - preferredVersion: GlobalVersion | null | undefined; - savePreferredVersionName: (versionName: string) => void; -} { - const docsData = useDocsData(pluginId); - const [state, api] = useDocsPreferredVersionContext(); - - const {preferredVersionName} = state[pluginId]; - - const preferredVersion = preferredVersionName - ? docsData.versions.find((version) => version.name === preferredVersionName) - : null; - - const savePreferredVersionName = useCallback( - (versionName: string) => { - api.savePreferredVersion(pluginId, versionName); - }, - [api, pluginId], - ); - - return {preferredVersion, savePreferredVersionName} as const; -} - -export function useDocsPreferredVersionByPluginId(): Record< - string, - GlobalVersion | null | undefined -> { - const allDocsData = useAllDocsData(); - const [state] = useDocsPreferredVersionContext(); - - function getPluginIdPreferredVersion(pluginId: string) { - const docsData = allDocsData[pluginId]; - const {preferredVersionName} = state[pluginId]; - - return preferredVersionName - ? docsData.versions.find( - (version) => version.name === preferredVersionName, - ) - : null; - } - - const pluginIds = Object.keys(allDocsData); - - const result: Record<string, GlobalVersion | null | undefined> = {}; - pluginIds.forEach((pluginId) => { - result[pluginId] = getPluginIdPreferredVersion(pluginId); - }); - - return result; -} diff --git a/packages/docusaurus-theme-common/src/utils/docsUtils.tsx b/packages/docusaurus-theme-common/src/utils/docsUtils.tsx index daf9226b79b2..64e700a48722 100644 --- a/packages/docusaurus-theme-common/src/utils/docsUtils.tsx +++ b/packages/docusaurus-theme-common/src/utils/docsUtils.tsx @@ -5,52 +5,40 @@ * LICENSE file in the root directory of this source tree. */ -import React, {createContext, type ReactNode, useContext} from 'react'; -import {useAllDocsData} from '@docusaurus/plugin-content-docs/client'; +import {useMemo} from 'react'; +import { + useAllDocsData, + useActivePlugin, + useActiveDocContext, + useLatestVersion, + type GlobalVersion, + type GlobalSidebar, + type GlobalDoc, +} from '@docusaurus/plugin-content-docs/client'; import type { PropSidebar, PropSidebarItem, PropSidebarItemCategory, PropVersionDoc, - PropVersionMetadata, + PropSidebarBreadcrumbsItem, } from '@docusaurus/plugin-content-docs'; -import {isSamePath} from './pathUtils'; +import {useDocsPreferredVersion} from '../contexts/docsPreferredVersion'; +import {useDocsVersion} from '../contexts/docsVersion'; +import {useDocsSidebar} from '../contexts/docsSidebar'; +import {uniq} from './jsUtils'; +import {isSamePath} from './routesUtils'; import {useLocation} from '@docusaurus/router'; // TODO not ideal, see also "useDocs" export const isDocsPluginEnabled: boolean = !!useAllDocsData; -// Using a Symbol because null is a valid context value (a doc can have no sidebar) -// Inspired by https://github.com/jamiebuilds/unstated-next/blob/master/src/unstated-next.tsx -const EmptyContextValue: unique symbol = Symbol('EmptyContext'); - -const DocsVersionContext = createContext< - PropVersionMetadata | typeof EmptyContextValue ->(EmptyContextValue); - -export function DocsVersionProvider({ - children, - version, -}: { - children: ReactNode; - version: PropVersionMetadata | typeof EmptyContextValue; -}): JSX.Element { - return ( - <DocsVersionContext.Provider value={version}> - {children} - </DocsVersionContext.Provider> - ); -} - -export function useDocsVersion(): PropVersionMetadata { - const version = useContext(DocsVersionContext); - if (version === EmptyContextValue) { - throw new Error('This hook requires usage of <DocsVersionProvider>'); - } - return version; -} - +/** + * A null-safe way to access a doc's data by ID in the active version. + */ export function useDocById(id: string): PropVersionDoc; +/** + * A null-safe way to access a doc's data by ID in the active version. + */ export function useDocById(id: string | undefined): PropVersionDoc | undefined; export function useDocById(id: string | undefined): PropVersionDoc | undefined { const version = useDocsVersion(); @@ -64,55 +52,31 @@ export function useDocById(id: string | undefined): PropVersionDoc | undefined { return doc; } -const DocsSidebarContext = createContext< - PropSidebar | null | typeof EmptyContextValue ->(EmptyContextValue); - -export function DocsSidebarProvider({ - children, - sidebar, -}: { - children: ReactNode; - sidebar: PropSidebar | null; -}): JSX.Element { - return ( - <DocsSidebarContext.Provider value={sidebar}> - {children} - </DocsSidebarContext.Provider> - ); -} - -export function useDocsSidebar(): PropSidebar | null { - const sidebar = useContext(DocsSidebarContext); - if (sidebar === EmptyContextValue) { - throw new Error('This hook requires usage of <DocsSidebarProvider>'); - } - return sidebar; -} - -// Use the components props and the sidebar in context -// to get back the related sidebar category that we want to render +/** + * Pure function, similar to `Array#find`, but works on the sidebar tree. + */ export function findSidebarCategory( sidebar: PropSidebar, predicate: (category: PropSidebarItemCategory) => boolean, ): PropSidebarItemCategory | undefined { - // eslint-disable-next-line no-restricted-syntax for (const item of sidebar) { if (item.type === 'category') { if (predicate(item)) { return item; - } else { - const subItem = findSidebarCategory(item.items, predicate); - if (subItem) { - return subItem; - } + } + const subItem = findSidebarCategory(item.items, predicate); + if (subItem) { + return subItem; } } } return undefined; } -// If a category card has no link => link to the first subItem having a link +/** + * Best effort to assign a link to a sidebar category. If the category doesn't + * have a link itself, we link to the first sub item with a link. + */ export function findFirstCategoryLink( item: PropSidebarItemCategory, ): string | undefined { @@ -120,16 +84,16 @@ export function findFirstCategoryLink( return item.href; } - // eslint-disable-next-line no-restricted-syntax for (const subItem of item.items) { if (subItem.type === 'link') { return subItem.href; - } - if (subItem.type === 'category') { + } else if (subItem.type === 'category') { const categoryLink = findFirstCategoryLink(subItem); if (categoryLink) { return categoryLink; } + } else if (subItem.type === 'html') { + // skip } else { throw new Error( `Unexpected category item type for ${JSON.stringify(subItem)}`, @@ -139,47 +103,174 @@ export function findFirstCategoryLink( return undefined; } +/** + * Gets the category associated with the current location. Should only be used + * on category index pages. + */ export function useCurrentSidebarCategory(): PropSidebarItemCategory { const {pathname} = useLocation(); const sidebar = useDocsSidebar(); if (!sidebar) { throw new Error('Unexpected: cant find current sidebar in context'); } - const category = findSidebarCategory(sidebar, (item) => + const category = findSidebarCategory(sidebar.items, (item) => isSamePath(item.href, pathname), ); if (!category) { throw new Error( - `Unexpected: sidebar category could not be found for pathname='${pathname}'. -Hook useCurrentSidebarCategory() should only be used on Category pages`, + `${pathname} is not associated with a category. useCurrentSidebarCategory() should only be used on category index pages.`, ); } return category; } -function containsActiveSidebarItem( +const isActive = (testedPath: string | undefined, activePath: string) => + typeof testedPath !== 'undefined' && isSamePath(testedPath, activePath); +const containsActiveSidebarItem = ( items: PropSidebarItem[], activePath: string, -): boolean { - return items.some((subItem) => isActiveSidebarItem(subItem, activePath)); -} +) => items.some((subItem) => isActiveSidebarItem(subItem, activePath)); +/** + * Checks if a sidebar item should be active, based on the active path. + */ export function isActiveSidebarItem( item: PropSidebarItem, activePath: string, ): boolean { - const isActive = (testedPath: string | undefined) => - typeof testedPath !== 'undefined' && isSamePath(testedPath, activePath); - if (item.type === 'link') { - return isActive(item.href); + return isActive(item.href, activePath); } if (item.type === 'category') { return ( - isActive(item.href) || containsActiveSidebarItem(item.items, activePath) + isActive(item.href, activePath) || + containsActiveSidebarItem(item.items, activePath) ); } return false; } + +/** + * Gets the breadcrumbs of the current doc page, based on its sidebar location. + * Returns `null` if there's no sidebar or breadcrumbs are disabled. + */ +export function useSidebarBreadcrumbs(): PropSidebarBreadcrumbsItem[] | null { + const sidebar = useDocsSidebar(); + const {pathname} = useLocation(); + const breadcrumbsOption = useActivePlugin()?.pluginData.breadcrumbs; + + if (breadcrumbsOption === false || !sidebar) { + return null; + } + + const breadcrumbs: PropSidebarBreadcrumbsItem[] = []; + + function extract(items: PropSidebar) { + for (const item of items) { + if ( + (item.type === 'category' && + (isSamePath(item.href, pathname) || extract(item.items))) || + (item.type === 'link' && isSamePath(item.href, pathname)) + ) { + breadcrumbs.push(item); + return true; + } + } + + return false; + } + + extract(sidebar.items); + + return breadcrumbs.reverse(); +} + +/** + * "Version candidates" are mostly useful for the layout components, which must + * be able to work on all pages. For example, if a user has `{ type: "doc", + * docId: "intro" }` as a navbar item, which version does that refer to? We + * believe that it could refer to at most three version candidates: + * + * 1. The **active version**, the one that the user is currently browsing. See + * {@link useActiveDocContext}. + * 2. The **preferred version**, the one that the user last visited. See + * {@link useDocsPreferredVersion}. + * 3. The **latest version**, the "default". See {@link useLatestVersion}. + * + * @param docsPluginId The plugin ID to get versions from. + * @returns An array of 1~3 versions with priorities defined above, guaranteed + * to be unique and non-sparse. Will be memoized, hence stable for deps array. + */ +export function useDocsVersionCandidates( + docsPluginId?: string, +): [GlobalVersion, ...GlobalVersion[]] { + const {activeVersion} = useActiveDocContext(docsPluginId); + const {preferredVersion} = useDocsPreferredVersion(docsPluginId); + const latestVersion = useLatestVersion(docsPluginId); + return useMemo( + () => + uniq( + [activeVersion, preferredVersion, latestVersion].filter(Boolean), + ) as [GlobalVersion, ...GlobalVersion[]], + [activeVersion, preferredVersion, latestVersion], + ); +} + +/** + * The layout components, like navbar items, must be able to work on all pages, + * even on non-doc ones. This hook would always return a sidebar to be linked + * to. See also {@link useDocsVersionCandidates} for how this selection is done. + * + * @throws This hook throws if a sidebar with said ID is not found. + */ +export function useLayoutDocsSidebar( + sidebarId: string, + docsPluginId?: string, +): GlobalSidebar { + const versions = useDocsVersionCandidates(docsPluginId); + return useMemo(() => { + const allSidebars = versions.flatMap((version) => + version.sidebars ? Object.entries(version.sidebars) : [], + ); + const sidebarEntry = allSidebars.find( + (sidebar) => sidebar[0] === sidebarId, + ); + if (!sidebarEntry) { + throw new Error( + `Can't find any sidebar with id "${sidebarId}" in version${ + versions.length > 1 ? 's' : '' + } ${versions.map((version) => version.name).join(', ')}". + Available sidebar ids are: + - ${Object.keys(allSidebars).join('\n- ')}`, + ); + } + return sidebarEntry[1]; + }, [sidebarId, versions]); +} + +/** + * The layout components, like navbar items, must be able to work on all pages, + * even on non-doc ones. This hook would always return a doc to be linked + * to. See also {@link useDocsVersionCandidates} for how this selection is done. + * + * @throws This hook throws if a doc with said ID is not found. + */ +export function useLayoutDoc(docId: string, docsPluginId?: string): GlobalDoc { + const versions = useDocsVersionCandidates(docsPluginId); + return useMemo(() => { + const allDocs = versions.flatMap((version) => version.docs); + const doc = allDocs.find((versionDoc) => versionDoc.id === docId); + if (!doc) { + throw new Error( + `DocNavbarItem: couldn't find any doc with id "${docId}" in version${ + versions.length > 1 ? 's' : '' + } ${versions.map((version) => version.name).join(', ')}". +Available doc ids are: +- ${uniq(allDocs.map((versionDoc) => versionDoc.id)).join('\n- ')}`, + ); + } + return doc; + }, [docId, versions]); +} diff --git a/packages/docusaurus-theme-common/src/utils/footerUtils.ts b/packages/docusaurus-theme-common/src/utils/footerUtils.ts new file mode 100644 index 000000000000..28add85dceee --- /dev/null +++ b/packages/docusaurus-theme-common/src/utils/footerUtils.ts @@ -0,0 +1,18 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import type {MultiColumnFooter, SimpleFooter} from './useThemeConfig'; + +/** + * A rough duck-typing about whether the `footer.links` is intended to be multi- + * column. + */ +export function isMultiColumnFooterLinks( + links: MultiColumnFooter['links'] | SimpleFooter['links'], +): links is MultiColumnFooter['links'] { + return 'title' in links[0]!; +} diff --git a/packages/docusaurus-theme-common/src/utils/generalUtils.ts b/packages/docusaurus-theme-common/src/utils/generalUtils.ts index 055c81517086..c6732b82ccf6 100644 --- a/packages/docusaurus-theme-common/src/utils/generalUtils.ts +++ b/packages/docusaurus-theme-common/src/utils/generalUtils.ts @@ -7,10 +7,13 @@ import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; -export const useTitleFormatter = (title?: string | undefined): string => { +/** + * Formats the page's title based on relevant site config and other contexts. + */ +export function useTitleFormatter(title?: string | undefined): string { const {siteConfig} = useDocusaurusContext(); const {title: siteTitle, titleDelimiter} = siteConfig; - return title && title.trim().length + return title?.trim().length ? `${title.trim()} ${titleDelimiter} ${siteTitle}` : siteTitle; -}; +} diff --git a/packages/docusaurus-theme-common/src/utils/historyUtils.ts b/packages/docusaurus-theme-common/src/utils/historyUtils.ts index 8243fd419961..129396b44e7d 100644 --- a/packages/docusaurus-theme-common/src/utils/historyUtils.ts +++ b/packages/docusaurus-theme-common/src/utils/historyUtils.ts @@ -5,43 +5,38 @@ * LICENSE file in the root directory of this source tree. */ -import {useEffect, useRef} from 'react'; +import {useEffect} from 'react'; import {useHistory} from '@docusaurus/router'; -import type {Location, Action} from '@docusaurus/history'; +import {useDynamicCallback} from './reactUtils'; +import type {Location, Action} from 'history'; type HistoryBlockHandler = (location: Location, action: Action) => void | false; -/* -Permits to register a handler that will be called on history actions (pop,push,replace) -If the handler returns false, the navigation transition will be blocked/cancelled +/** + * Permits to register a handler that will be called on history actions (pop, + * push, replace). If the handler returns `false`, the navigation transition + * will be blocked/cancelled. */ -export function useHistoryActionHandler(handler: HistoryBlockHandler): void { +function useHistoryActionHandler(handler: HistoryBlockHandler): void { const {block} = useHistory(); - - // Avoid stale closure issues without triggering useless re-renders - const lastHandlerRef = useRef(handler); - useEffect(() => { - lastHandlerRef.current = handler; - }, [handler]); - + const stableHandler = useDynamicCallback(handler); useEffect( - () => - // See https://github.com/remix-run/history/blob/main/docs/blocking-transitions.md - block((location, action) => lastHandlerRef.current(location, action)), - [block, lastHandlerRef], + // See https://github.com/remix-run/history/blob/main/docs/blocking-transitions.md + () => block((location, action) => stableHandler(location, action)), + [block, stableHandler], ); } -/* -Permits to register a handler that will be called on history pop navigation (backward/forward) -If the handler returns false, the backward/forward transition will be blocked - -Unfortunately there's no good way to detect the "direction" (backward/forward) of the POP event. +/** + * Permits to register a handler that will be called on history pop navigation + * (backward/forward). If the handler returns `false`, the backward/forward + * transition will be blocked. Unfortunately there's no good way to detect the + * "direction" (backward/forward) of the POP event. */ export function useHistoryPopHandler(handler: HistoryBlockHandler): void { useHistoryActionHandler((location, action) => { if (action === 'POP') { - // Eventually block navigation if handler returns false + // Maybe block navigation if handler returns false return handler(location, action); } // Don't block other navigation actions diff --git a/packages/docusaurus-theme-common/src/utils/jsUtils.ts b/packages/docusaurus-theme-common/src/utils/jsUtils.ts index bcd41b3dc45f..740a5a8b61df 100644 --- a/packages/docusaurus-theme-common/src/utils/jsUtils.ts +++ b/packages/docusaurus-theme-common/src/utils/jsUtils.ts @@ -10,8 +10,11 @@ /** * Gets the duplicate values in an array. * @param arr The array. - * @param comparator Compares two values and returns `true` if they are equal (duplicated). - * @returns Value of the elements `v` that have a preceding element `u` where `comparator(u, v) === true`. Values within the returned array are not guaranteed to be unique. + * @param comparator Compares two values and returns `true` if they are equal + * (duplicated). + * @returns Value of the elements `v` that have a preceding element `u` where + * `comparator(u, v) === true`. Values within the returned array are not + * guaranteed to be unique. */ export function duplicates<T>( arr: readonly T[], @@ -23,7 +26,7 @@ export function duplicates<T>( } /** - * Remove duplicate array items (similar to _.uniq) + * Remove duplicate array items (similar to `_.uniq`) * @param arr The array. * @returns An array with duplicate elements removed by reference comparison. */ diff --git a/packages/docusaurus-theme-common/src/utils/metadataUtils.tsx b/packages/docusaurus-theme-common/src/utils/metadataUtils.tsx new file mode 100644 index 000000000000..d7b85f0d04b3 --- /dev/null +++ b/packages/docusaurus-theme-common/src/utils/metadataUtils.tsx @@ -0,0 +1,115 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, {type ReactNode} from 'react'; +import Head from '@docusaurus/Head'; +import clsx from 'clsx'; +import useRouteContext from '@docusaurus/useRouteContext'; +import {useBaseUrlUtils} from '@docusaurus/useBaseUrl'; +import {useTitleFormatter} from './generalUtils'; + +interface PageMetadataProps { + readonly title?: string; + readonly description?: string; + readonly keywords?: readonly string[] | string; + readonly image?: string; + readonly children?: ReactNode; +} + +/** + * Helper component to manipulate page metadata and override site defaults. + * Works in the same way as Helmet. + */ +export function PageMetadata({ + title, + description, + keywords, + image, + children, +}: PageMetadataProps): JSX.Element { + const pageTitle = useTitleFormatter(title); + const {withBaseUrl} = useBaseUrlUtils(); + const pageImage = image ? withBaseUrl(image, {absolute: true}) : undefined; + + return ( + <Head> + {title && <title>{pageTitle}} + {title && } + + {description && } + {description && } + + {keywords && ( + + )} + + {pageImage && } + {pageImage && } + + {children} + + ); +} + +const HtmlClassNameContext = React.createContext(undefined); + +/** + * Every layer of this provider will append a class name to the HTML element. + * There's no consumer for this hook: it's side-effect-only. This wrapper is + * necessary because Helmet does not "merge" classes. + * @see https://github.com/staylor/react-helmet-async/issues/161 + */ +export function HtmlClassNameProvider({ + className: classNameProp, + children, +}: { + className: string; + children: ReactNode; +}): JSX.Element { + const classNameContext = React.useContext(HtmlClassNameContext); + const className = clsx(classNameContext, classNameProp); + return ( + + + + + {children} + + ); +} + +function pluginNameToClassName(pluginName: string) { + return `plugin-${pluginName.replace( + /docusaurus-(?:plugin|theme)-(?:content-)?/gi, + '', + )}`; +} + +/** + * A very thin wrapper around `HtmlClassNameProvider` that adds the plugin ID + + * name to the HTML class name. + */ +export function PluginHtmlClassNameProvider({ + children, +}: { + children: ReactNode; +}): JSX.Element { + const routeContext = useRouteContext(); + const nameClass = pluginNameToClassName(routeContext.plugin.name); + const idClass = `plugin-id-${routeContext.plugin.id}`; + return ( + + {children} + + ); +} diff --git a/packages/docusaurus-theme-common/src/utils/mobileSecondaryMenu.tsx b/packages/docusaurus-theme-common/src/utils/mobileSecondaryMenu.tsx deleted file mode 100644 index cae6047260ad..000000000000 --- a/packages/docusaurus-theme-common/src/utils/mobileSecondaryMenu.tsx +++ /dev/null @@ -1,114 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import React, { - useState, - type ReactNode, - useContext, - createContext, - useEffect, - type ComponentType, - useMemo, -} from 'react'; - -/* -The idea behind all this is that a specific component must be able to fill a placeholder in the generic layout -The doc page should be able to fill the secondary menu of the main mobile navbar. -This permits to reduce coupling between the main layout and the specific page. - -This kind of feature is often called portal/teleport/gateway... various unmaintained React libs exist -Most up-to-date one: https://github.com/gregberge/react-teleporter -Not sure any of those is safe regarding concurrent mode. - */ - -type ExtraProps = { - toggleSidebar: () => void; -}; - -export type MobileSecondaryMenuComponent = ComponentType< - Props & ExtraProps ->; - -type State = { - component: MobileSecondaryMenuComponent; - props: unknown; -} | null; - -function useContextValue() { - return useState(null); -} - -type ContextValue = ReturnType; - -const Context = createContext(null); - -export function MobileSecondaryMenuProvider({ - children, -}: { - children: ReactNode; -}): JSX.Element { - return ( - {children} - ); -} - -function useMobileSecondaryMenuContext(): ContextValue { - const value = useContext(Context); - if (value === null) { - throw new Error( - 'MobileSecondaryMenuProvider was not used correctly, context value is null', - ); - } - return value; -} - -export function useMobileSecondaryMenuRenderer(): ( - extraProps: ExtraProps, -) => ReactNode | undefined { - const [state] = useMobileSecondaryMenuContext(); - if (state) { - const Comp = state.component; - return function render(extraProps) { - return ; - }; - } - return () => undefined; -} - -function useShallowMemoizedObject>(obj: O) { - return useMemo( - () => obj, - // Is this safe? - // eslint-disable-next-line react-hooks/exhaustive-deps - [...Object.keys(obj), ...Object.values(obj)], - ); -} - -// Fill the secondary menu placeholder with some real content -export function MobileSecondaryMenuFiller< - Props extends Record, ->({ - component, - props, -}: { - component: MobileSecondaryMenuComponent; - props: Props; -}): JSX.Element | null { - const [, setState] = useMobileSecondaryMenuContext(); - - // To avoid useless context re-renders, props are memoized shallowly - const memoizedProps = useShallowMemoizedObject(props); - - useEffect(() => { - // @ts-expect-error: context is not 100% type-safe but it's ok - setState({component, props: memoizedProps}); - }, [setState, component, memoizedProps]); - - useEffect(() => () => setState(null), [setState]); - - return null; -} diff --git a/packages/docusaurus-theme-common/src/utils/navbarUtils.tsx b/packages/docusaurus-theme-common/src/utils/navbarUtils.tsx new file mode 100644 index 000000000000..bd64eac04880 --- /dev/null +++ b/packages/docusaurus-theme-common/src/utils/navbarUtils.tsx @@ -0,0 +1,45 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, {type ReactNode} from 'react'; +import {NavbarMobileSidebarProvider} from '../contexts/navbarMobileSidebar'; +import {NavbarSecondaryMenuContentProvider} from '../contexts/navbarSecondaryMenu/content'; +import {NavbarSecondaryMenuDisplayProvider} from '../contexts/navbarSecondaryMenu/display'; + +const DefaultNavItemPosition = 'right'; + +/** + * Split links by left/right. If position is unspecified, fallback to right. + */ +export function splitNavbarItems( + items: T[], +): [leftItems: T[], rightItems: T[]] { + function isLeft(item: T): boolean { + return (item.position ?? DefaultNavItemPosition) === 'left'; + } + + const leftItems = items.filter(isLeft); + const rightItems = items.filter((item) => !isLeft(item)); + + return [leftItems, rightItems]; +} + +/** + * Composes multiple navbar state providers that are mutually dependent and + * hence can't be re-ordered. + */ +export function NavbarProvider({children}: {children: ReactNode}): JSX.Element { + return ( + + + + {children} + + + + ); +} diff --git a/packages/docusaurus-theme-common/src/utils/pathUtils.ts b/packages/docusaurus-theme-common/src/utils/pathUtils.ts deleted file mode 100644 index cde5ae1a0672..000000000000 --- a/packages/docusaurus-theme-common/src/utils/pathUtils.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -// Compare the 2 paths, ignoring trailing / -export const isSamePath = ( - path1: string | undefined, - path2: string | undefined, -): boolean => { - const normalize = (pathname: string | undefined) => - !pathname || pathname?.endsWith('/') ? pathname : `${pathname}/`; - return normalize(path1) === normalize(path2); -}; diff --git a/packages/docusaurus-theme-common/src/utils/reactUtils.tsx b/packages/docusaurus-theme-common/src/utils/reactUtils.tsx index 5a3ce9ec7064..09cb1b7b52e7 100644 --- a/packages/docusaurus-theme-common/src/utils/reactUtils.tsx +++ b/packages/docusaurus-theme-common/src/utils/reactUtils.tsx @@ -6,20 +6,32 @@ */ import {useCallback, useEffect, useLayoutEffect, useRef} from 'react'; +import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment'; -// This hook is like useLayoutEffect, but without the SSR warning -// It seems hacky but it's used in many React libs (Redux, Formik...) -// Also mentioned here: https://github.com/facebook/react/issues/16956 -// It is useful when you need to update a ref as soon as possible after a React render (before useEffect) -export const useIsomorphicLayoutEffect = - typeof window !== 'undefined' ? useLayoutEffect : useEffect; - -// Permits to transform an unstable callback (like an arrow function provided as props) -// to a "stable" callback that is safe to use in a useEffect dependency array -// Useful to avoid React stale closure problems + avoid useless effect re-executions -// -// Workaround until the React team recommends a good solution, see https://github.com/facebook/react/issues/16956 -// This generally works has some potential drawbacks, such as https://github.com/facebook/react/issues/16956#issuecomment-536636418 +/** + * This hook is like `useLayoutEffect`, but without the SSR warning. + * It seems hacky but it's used in many React libs (Redux, Formik...). + * Also mentioned here: https://github.com/facebook/react/issues/16956 + * + * It is useful when you need to update a ref as soon as possible after a React + * render (before `useEffect`). + */ +export const useIsomorphicLayoutEffect = ExecutionEnvironment.canUseDOM + ? useLayoutEffect + : useEffect; + +/** + * Permits to transform an unstable callback (like an arrow function provided as + * props) to a "stable" callback that is safe to use in a `useEffect` dependency + * array. Useful to avoid React stale closure problems + avoid useless effect + * re-executions. + * + * Workaround until the React team recommends a good solution, see + * https://github.com/facebook/react/issues/16956 + * + * This generally works but has some potential drawbacks, such as + * https://github.com/facebook/react/issues/16956#issuecomment-536636418 + */ export function useDynamicCallback unknown>( callback: T, ): T { @@ -29,6 +41,36 @@ export function useDynamicCallback unknown>( ref.current = callback; }, [callback]); - // @ts-expect-error: TODO, not sure how to fix this TS error + // @ts-expect-error: TS is right that this callback may be a supertype of T, + // but good enough for our use return useCallback((...args) => ref.current(...args), []); } + +/** + * Gets `value` from the last render. + */ +export function usePrevious(value: T): T | undefined { + const ref = useRef(); + + useIsomorphicLayoutEffect(() => { + ref.current = value; + }); + + return ref.current; +} + +/** + * This error is thrown when a context is consumed outside its provider. Allows + * reusing a generic error message format and reduces bundle size. The hook's + * name will be extracted from its stack, so only the provider's name is needed. + */ +export class ReactContextError extends Error { + constructor(providerName: string, additionalInfo?: string) { + super(); + this.name = 'ReactContextError'; + this.message = `Hook ${ + this.stack?.split('\n')[1]?.match(/at (?:\w+\.)?(?\w+)/)?.groups! + .name + } is called outside the <${providerName}>. ${additionalInfo || ''}`; + } +} diff --git a/packages/docusaurus-theme-common/src/utils/regexpUtils.ts b/packages/docusaurus-theme-common/src/utils/regexpUtils.ts index bcb7bb4c8832..4e87fe68c2ef 100644 --- a/packages/docusaurus-theme-common/src/utils/regexpUtils.ts +++ b/packages/docusaurus-theme-common/src/utils/regexpUtils.ts @@ -6,7 +6,8 @@ */ /** - * Utility to convert an optional string into a Regex case insensitive and global + * Matches a string regex (as provided from the config) against a target in a + * null-safe fashion, case insensitive and global. */ export function isRegexpStringMatch( regexAsString?: string, diff --git a/packages/docusaurus-theme-common/src/utils/routesUtils.ts b/packages/docusaurus-theme-common/src/utils/routesUtils.ts new file mode 100644 index 000000000000..4faa24b675a0 --- /dev/null +++ b/packages/docusaurus-theme-common/src/utils/routesUtils.ts @@ -0,0 +1,75 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {useMemo} from 'react'; +import generatedRoutes from '@generated/routes'; +import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; +import type {RouteConfig} from 'react-router-config'; + +/** + * Compare the 2 paths, case insensitive and ignoring trailing slash + */ +export function isSamePath( + path1: string | undefined, + path2: string | undefined, +): boolean { + const normalize = (pathname: string | undefined) => + (!pathname || pathname?.endsWith('/') + ? pathname + : `${pathname}/` + )?.toLowerCase(); + return normalize(path1) === normalize(path2); +} + +/** + * Note that sites don't always have a homepage in practice, so we can't assume + * that linking to '/' is always safe. + * @see https://github.com/facebook/docusaurus/pull/6517#issuecomment-1048709116 + */ +export function findHomePageRoute({ + baseUrl, + routes: initialRoutes, +}: { + routes: RouteConfig[]; + baseUrl: string; +}): RouteConfig | undefined { + function isHomePageRoute(route: RouteConfig): boolean { + return route.path === baseUrl && route.exact === true; + } + + function isHomeParentRoute(route: RouteConfig): boolean { + return route.path === baseUrl && !route.exact; + } + + function doFindHomePageRoute(routes: RouteConfig[]): RouteConfig | undefined { + if (routes.length === 0) { + return undefined; + } + const homePage = routes.find(isHomePageRoute); + if (homePage) { + return homePage; + } + const indexSubRoutes = routes + .filter(isHomeParentRoute) + .flatMap((route) => route.routes ?? []); + return doFindHomePageRoute(indexSubRoutes); + } + + return doFindHomePageRoute(initialRoutes); +} + +/** + * Fetches the route that points to "/". Use this instead of the naive "/", + * because the homepage may not exist. + */ +export function useHomePageRoute(): RouteConfig | undefined { + const {baseUrl} = useDocusaurusContext().siteConfig; + return useMemo( + () => findHomePageRoute({routes: generatedRoutes, baseUrl}), + [baseUrl], + ); +} diff --git a/packages/docusaurus-theme-common/src/utils/scrollUtils.tsx b/packages/docusaurus-theme-common/src/utils/scrollUtils.tsx index 68e8cfc58108..79799c37fc24 100644 --- a/packages/docusaurus-theme-common/src/utils/scrollUtils.tsx +++ b/packages/docusaurus-theme-common/src/utils/scrollUtils.tsx @@ -6,37 +6,24 @@ */ import React, { - createContext, - type ReactNode, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, + type ReactNode, } from 'react'; -import {useDynamicCallback} from './reactUtils'; +import {useDynamicCallback, ReactContextError} from './reactUtils'; import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment'; +import useIsBrowser from '@docusaurus/useIsBrowser'; -/** - * We need a way to update the scroll position while ignoring scroll events - * without affecting Navbar/BackToTop visibility - * - * This API permits to temporarily disable/ignore scroll events - * Motivated by https://github.com/facebook/docusaurus/pull/5618 - */ type ScrollController = { - /** - * A boolean ref tracking whether scroll events are enabled - */ + /** A boolean ref tracking whether scroll events are enabled. */ scrollEventsEnabledRef: React.MutableRefObject; - /** - * Enables scroll events in `useScrollPosition` - */ + /** Enable scroll events in `useScrollPosition`. */ enableScrollEvents: () => void; - /** - * Disables scroll events in `useScrollPosition` - */ + /** Disable scroll events in `useScrollPosition`. */ disableScrollEvents: () => void; }; @@ -57,7 +44,7 @@ function useScrollControllerContextValue(): ScrollController { ); } -const ScrollMonitorContext = createContext( +const ScrollMonitorContext = React.createContext( undefined, ); @@ -66,23 +53,31 @@ export function ScrollControllerProvider({ }: { children: ReactNode; }): JSX.Element { + const value = useScrollControllerContextValue(); return ( - + {children} ); } +/** + * We need a way to update the scroll position while ignoring scroll events + * so as not to toggle Navbar/BackToTop visibility. + * + * This API permits to temporarily disable/ignore scroll events. Motivated by + * https://github.com/facebook/docusaurus/pull/5618 + */ export function useScrollController(): ScrollController { const context = useContext(ScrollMonitorContext); if (context == null) { - throw new Error( - '"useScrollController" is used but no context provider was found in the React tree.', - ); + throw new ReactContextError('ScrollControllerProvider'); } return context; } +type ScrollPosition = {scrollX: number; scrollY: number}; + const getScrollPosition = (): ScrollPosition | null => ExecutionEnvironment.canUseDOM ? { @@ -91,8 +86,14 @@ const getScrollPosition = (): ScrollPosition | null => } : null; -type ScrollPosition = {scrollX: number; scrollY: number}; - +/** + * This hook fires an effect when the scroll position changes. The effect will + * be provided with the before/after scroll positions. Note that the effect may + * not be always run: if scrolling is disabled through `useScrollController`, it + * will be a no-op. + * + * @see {@link useScrollController} + */ export function useScrollPosition( effect: ( position: ScrollPosition, @@ -127,22 +128,16 @@ export function useScrollPosition( window.addEventListener('scroll', handleScroll, opts); return () => window.removeEventListener('scroll', handleScroll, opts); - }, [ - dynamicEffect, - scrollEventsEnabledRef, // eslint-disable-next-line react-hooks/exhaustive-deps - ...deps, - ]); + }, [dynamicEffect, scrollEventsEnabledRef, ...deps]); } type UseScrollPositionSaver = { - /** - * Measure the top of an element, and store the details - */ + /** Measure the top of an element, and store the details. */ save: (elem: HTMLElement) => void; /** * Restore the page position to keep the stored element's position from - * the top of the viewport, and remove the stored details + * the top of the viewport, and remove the stored details. */ restore: () => {restored: boolean}; }; @@ -180,21 +175,24 @@ function useScrollPositionSaver(): UseScrollPositionSaver { return useMemo(() => ({save, restore}), [restore, save]); } -type UseScrollPositionBlockerReturn = { - blockElementScrollPositionUntilNextRender: (el: HTMLElement) => void; -}; - /** - * This hook permits to "block" the scroll position of a dom element + * This hook permits to "block" the scroll position of a DOM element. * The idea is that we should be able to update DOM content above this element - * but the screen position of this element should not change + * but the screen position of this element should not change. + * + * Feature motivated by the Tabs groups: clicking on a tab may affect tabs of + * the same group upper in the tree, yet to avoid a bad UX, the clicked tab must + * remain under the user mouse. * - * Feature motivated by the Tabs groups: - * clicking on a tab may affect tabs of the same group upper in the tree - * Yet to avoid a bad UX, the clicked tab must remain under the user mouse! - * See GIF here: https://github.com/facebook/docusaurus/pull/5618 + * @see https://github.com/facebook/docusaurus/pull/5618 */ -export function useScrollPositionBlocker(): UseScrollPositionBlockerReturn { +export function useScrollPositionBlocker(): { + /** + * Takes an element, and keeps its screen position no matter what's getting + * rendered above it, until the next render. + */ + blockElementScrollPositionUntilNextRender: (el: HTMLElement) => void; +} { const scrollController = useScrollController(); const scrollPositionSaver = useScrollPositionSaver(); @@ -210,9 +208,9 @@ export function useScrollPositionBlocker(): UseScrollPositionBlockerReturn { const {restored} = scrollPositionSaver.restore(); nextLayoutEffectCallbackRef.current = undefined; - // Restoring the former scroll position will trigger a scroll event - // We need to wait for next scroll event to happen - // before enabling again the scrollController events + // Restoring the former scroll position will trigger a scroll event. We + // need to wait for next scroll event to happen before enabling the + // scrollController events again. if (restored) { const handleScrollRestoreEvent = () => { scrollController.enableScrollEvents(); @@ -235,3 +233,76 @@ export function useScrollPositionBlocker(): UseScrollPositionBlockerReturn { blockElementScrollPositionUntilNextRender, }; } + +type CancelScrollTop = () => void; + +function smoothScrollNative(top: number): CancelScrollTop { + window.scrollTo({top, behavior: 'smooth'}); + return () => { + // Nothing to cancel, it's natively cancelled if user tries to scroll down + }; +} + +function smoothScrollPolyfill(top: number): CancelScrollTop { + let raf: number | null = null; + const isUpScroll = document.documentElement.scrollTop > top; + function rafRecursion() { + const currentScroll = document.documentElement.scrollTop; + if ( + (isUpScroll && currentScroll > top) || + (!isUpScroll && currentScroll < top) + ) { + raf = requestAnimationFrame(rafRecursion); + window.scrollTo(0, Math.floor((currentScroll - top) * 0.85) + top); + } + } + rafRecursion(); + + // Break the recursion. Prevents the user from "fighting" against that + // recursion producing a weird UX + return () => raf && cancelAnimationFrame(raf); +} + +/** + * A "smart polyfill" of `window.scrollTo({ top, behavior: "smooth" })`. + * This currently always uses a polyfilled implementation unless + * `scroll-behavior: smooth` has been set in CSS, because native support + * detection for scroll behavior seems unreliable. + * + * This hook does not do anything by itself: it returns a start and a stop + * handle. You can execute either handle at any time. + */ +export function useSmoothScrollTo(): { + /** + * Start the scroll. + * + * @param top The final scroll top position. + */ + startScroll: (top: number) => void; + /** + * A cancel function, because the non-native smooth scroll-top + * implementation must be interrupted if user scrolls down. If there's no + * existing animation or the scroll is using native behavior, this is a no-op. + */ + cancelScroll: CancelScrollTop; +} { + const cancelRef = useRef(null); + const isBrowser = useIsBrowser(); + // Not all have support for smooth scrolling (particularly Safari mobile iOS) + // TODO proper detection is currently unreliable! + // see https://github.com/wessberg/scroll-behavior-polyfill/issues/16 + // For now, we only use native scroll behavior if smooth is already set, + // because otherwise the polyfill produces a weird UX when both CSS and JS try + // to scroll a page, and they cancel each other. + const supportsNativeSmoothScrolling = + isBrowser && + getComputedStyle(document.documentElement).scrollBehavior === 'smooth'; + return { + startScroll: (top: number) => { + cancelRef.current = supportsNativeSmoothScrolling + ? smoothScrollNative(top) + : smoothScrollPolyfill(top); + }, + cancelScroll: () => cancelRef.current?.(), + }; +} diff --git a/packages/docusaurus-theme-common/src/utils/searchUtils.ts b/packages/docusaurus-theme-common/src/utils/searchUtils.ts index daa93c9c4db1..463c69a90fa6 100644 --- a/packages/docusaurus-theme-common/src/utils/searchUtils.ts +++ b/packages/docusaurus-theme-common/src/utils/searchUtils.ts @@ -5,11 +5,62 @@ * LICENSE file in the root directory of this source tree. */ +import { + useAllDocsData, + useActivePluginAndVersion, +} from '@docusaurus/plugin-content-docs/client'; +import {useDocsPreferredVersionByPluginId} from '../contexts/docsPreferredVersion'; +import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; + export const DEFAULT_SEARCH_TAG = 'default'; +/** The search tag to append as each doc's metadata. */ export function docVersionSearchTag( pluginId: string, versionName: string, ): string { return `docs-${pluginId}-${versionName}`; } + +/** + * Gets the relevant context information for contextual search. + * + * The value is generic and not coupled to Algolia/DocSearch, since we may want + * to support multiple search engines, or allowing users to use their own search + * engine solution. + */ +export function useContextualSearchFilters(): {locale: string; tags: string[]} { + const {i18n} = useDocusaurusContext(); + const allDocsData = useAllDocsData(); + const activePluginAndVersion = useActivePluginAndVersion(); + const docsPreferredVersionByPluginId = useDocsPreferredVersionByPluginId(); + + // This can't use more specialized hooks because we are mapping over all + // plugin instances. + function getDocPluginTags(pluginId: string) { + const activeVersion = + activePluginAndVersion?.activePlugin?.pluginId === pluginId + ? activePluginAndVersion.activeVersion + : undefined; + + const preferredVersion = docsPreferredVersionByPluginId[pluginId]; + + const latestVersion = allDocsData[pluginId]!.versions.find( + (v) => v.isLast, + )!; + + const version = activeVersion ?? preferredVersion ?? latestVersion; + + return docVersionSearchTag(pluginId, version.name); + } + + const tags = [ + DEFAULT_SEARCH_TAG, + ...Object.keys(allDocsData).map(getDocPluginTags), + ]; + + return { + locale: i18n.currentLocale, + tags, + }; +} diff --git a/packages/docusaurus-theme-common/src/utils/storageUtils.ts b/packages/docusaurus-theme-common/src/utils/storageUtils.ts index 76e4c38fb086..d5dcbdc8c3f5 100644 --- a/packages/docusaurus-theme-common/src/utils/storageUtils.ts +++ b/packages/docusaurus-theme-common/src/utils/storageUtils.ts @@ -11,8 +11,12 @@ export type StorageType = typeof StorageTypes[number]; const DefaultStorageType: StorageType = 'localStorage'; -// Will return null browser storage is unavailable (like running Docusaurus in iframe) -// See https://github.com/facebook/docusaurus/pull/4501 +/** + * Will return `null` if browser storage is unavailable (like running Docusaurus + * in an iframe). This should NOT be called in SSR. + * + * @see https://github.com/facebook/docusaurus/pull/4501 + */ function getBrowserStorage( storageType: StorageType = DefaultStorageType, ): Storage | null { @@ -23,21 +27,21 @@ function getBrowserStorage( } if (storageType === 'none') { return null; - } else { - try { - return window[storageType]; - } catch (e) { - logOnceBrowserStorageNotAvailableWarning(e as Error); - return null; - } + } + try { + return window[storageType]; + } catch (err) { + logOnceBrowserStorageNotAvailableWarning(err as Error); + return null; } } +let hasLoggedBrowserStorageNotAvailableWarning = false; /** - * Poor man's memoization to avoid logging multiple times the same warning - * Sometimes, localStorage/sessionStorage is unavailable due to browser policies + * Poor man's memoization to avoid logging multiple times the same warning. + * Sometimes, `localStorage`/`sessionStorage` is unavailable due to browser + * policies. */ -let hasLoggedBrowserStorageNotAvailableWarning = false; function logOnceBrowserStorageNotAvailableWarning(error: Error) { if (!hasLoggedBrowserStorageNotAvailableWarning) { console.warn( @@ -62,7 +66,7 @@ const NoopStorageSlot: StorageSlot = { del: () => {}, }; -// Fail-fast, as storage APIs should not be used during the SSR process +// Fail-fast, as storage APIs should not be used during the SSR process function createServerStorageSlot(key: string): StorageSlot { function throwError(): never { throw new Error(`Illegal storage API usage for storage key "${key}". @@ -78,16 +82,19 @@ Please only call storage APIs in effects and event handlers.`); } /** - * Creates an object for accessing a particular key in localStorage. - * The API is fail-safe, and usage of browser storage should be considered unreliable - * Local storage might simply be unavailable (iframe + browser security) or operations might fail individually - * Please assume that using this API can be a NO-OP - * See also https://github.com/facebook/docusaurus/issues/6036 + * Creates an interface to work on a particular key in the storage model. + * Note that this function only initializes the interface, but doesn't allocate + * anything by itself (i.e. no side-effects). + * + * The API is fail-safe, since usage of browser storage should be considered + * unreliable. Local storage might simply be unavailable (iframe + browser + * security) or operations might fail individually. Please assume that using + * this API can be a no-op. See also https://github.com/facebook/docusaurus/issues/6036 */ -export const createStorageSlot = ( +export function createStorageSlot( key: string, options?: {persistence?: StorageType}, -): StorageSlot => { +): StorageSlot { if (typeof window === 'undefined') { return createServerStorageSlot(key); } @@ -99,30 +106,33 @@ export const createStorageSlot = ( get: () => { try { return browserStorage.getItem(key); - } catch (e) { - console.error(`Docusaurus storage error, can't get key=${key}`, e); + } catch (err) { + console.error(`Docusaurus storage error, can't get key=${key}`, err); return null; } }, set: (value) => { try { browserStorage.setItem(key, value); - } catch (e) { - console.error(`Docusaurus storage error, can't set ${key}=${value}`, e); + } catch (err) { + console.error( + `Docusaurus storage error, can't set ${key}=${value}`, + err, + ); } }, del: () => { try { browserStorage.removeItem(key); - } catch (e) { - console.error(`Docusaurus storage error, can't delete key=${key}`, e); + } catch (err) { + console.error(`Docusaurus storage error, can't delete key=${key}`, err); } }, }; -}; +} /** - * Returns a list of all the keys currently stored in browser storage + * Returns a list of all the keys currently stored in browser storage, * or an empty list if browser storage can't be accessed. */ export function listStorageKeys( diff --git a/packages/docusaurus-theme-common/src/utils/tagsUtils.ts b/packages/docusaurus-theme-common/src/utils/tagsUtils.ts index edd29f2c8f30..99dfdc9d379f 100644 --- a/packages/docusaurus-theme-common/src/utils/tagsUtils.ts +++ b/packages/docusaurus-theme-common/src/utils/tagsUtils.ts @@ -14,23 +14,30 @@ export const translateTagsPageTitle = (): string => description: 'The title of the tag list page', }); -type TagsListItem = Readonly<{name: string; permalink: string; count: number}>; // TODO remove duplicated type :s +export type TagsListItem = Readonly<{ + name: string; + permalink: string; + count: number; +}>; export type TagLetterEntry = Readonly<{letter: string; tags: TagsListItem[]}>; function getTagLetter(tag: string): string { - return tag[0].toUpperCase(); + return tag[0]!.toUpperCase(); } +/** + * Takes a list of tags (as provided by the content plugins), and groups them by + * their initials. + */ export function listTagsByLetters( tags: readonly TagsListItem[], ): TagLetterEntry[] { - // Group by letters - const groups: Record = {}; + const groups: {[initial: string]: TagsListItem[]} = {}; Object.values(tags).forEach((tag) => { - const letter = getTagLetter(tag.name); - groups[letter] = groups[letter] ?? []; - groups[letter].push(tag); + const initial = getTagLetter(tag.name); + groups[initial] ??= []; + groups[initial]!.push(tag); }); return ( diff --git a/packages/docusaurus-theme-common/src/utils/tocUtils.ts b/packages/docusaurus-theme-common/src/utils/tocUtils.ts index 3797cded4b48..c5a7fc9c6659 100644 --- a/packages/docusaurus-theme-common/src/utils/tocUtils.ts +++ b/packages/docusaurus-theme-common/src/utils/tocUtils.ts @@ -8,18 +8,68 @@ import {useMemo} from 'react'; import type {TOCItem} from '@docusaurus/types'; -type FilterTOCParam = { - toc: readonly TOCItem[]; - minHeadingLevel: number; - maxHeadingLevel: number; +export type TOCTreeNode = { + readonly value: string; + readonly id: string; + readonly level: number; + readonly children: readonly TOCTreeNode[]; }; -export function filterTOC({ +function treeifyTOC(flatTOC: readonly TOCItem[]): TOCTreeNode[] { + const headings = flatTOC.map((heading) => ({ + ...heading, + parentIndex: -1, + children: [] as TOCTreeNode[], + })); + + // Keep track of which previous index would be the current heading's direct + // parent. Each entry is the last index of the `headings` array at heading + // level . We will modify these indices as we iterate through all headings. + // e.g. if an ### H3 was last seen at index 2, then prevIndexForLevel[3] === 2 + // indices 0 and 1 will remain unused. + const prevIndexForLevel = Array(7).fill(-1); + + headings.forEach((curr, currIndex) => { + // take the last seen index for each ancestor level. the highest + // index will be the direct ancestor of the current heading. + const ancestorLevelIndexes = prevIndexForLevel.slice(2, curr.level); + curr.parentIndex = Math.max(...ancestorLevelIndexes); + // mark that curr.level was last seen at the current index + prevIndexForLevel[curr.level] = currIndex; + }); + + const rootNodes: TOCTreeNode[] = []; + + // For a given parentIndex, add each Node into that parent's `children` array + headings.forEach((heading) => { + const {parentIndex, ...rest} = heading; + if (parentIndex >= 0) { + headings[parentIndex]!.children.push(rest); + } else { + rootNodes.push(rest); + } + }); + return rootNodes; +} + +/** + * Takes a flat TOC list (from the MDX loader) and treeifies it into what the + * TOC components expect. Memoized for performance. + */ +export function useTreeifiedTOC(toc: TOCItem[]): readonly TOCTreeNode[] { + return useMemo(() => treeifyTOC(toc), [toc]); +} + +function filterTOC({ toc, minHeadingLevel, maxHeadingLevel, -}: FilterTOCParam): TOCItem[] { - function isValid(item: TOCItem) { +}: { + toc: readonly TOCTreeNode[]; + minHeadingLevel: number; + maxHeadingLevel: number; +}): TOCTreeNode[] { + function isValid(item: TOCTreeNode) { return item.level >= minHeadingLevel && item.level <= maxHeadingLevel; } @@ -36,20 +86,34 @@ export function filterTOC({ children: filteredChildren, }, ]; - } else { - return filteredChildren; } + return filteredChildren; }); } -// Memoize potentially expensive filtering logic -export function useTOCFilter({ +/** + * Takes a flat TOC list (from the MDX loader) and treeifies it into what the + * TOC components expect, applying the `minHeadingLevel` and `maxHeadingLevel`. + * Memoized for performance. + * + * **Important**: this is not the same as `useTreeifiedTOC(toc.filter(...))`, + * because we have to filter the TOC after it has been treeified. This is mostly + * to ensure that weird TOC structures preserve their semantics. For example, an + * h3-h2-h4 sequence should not be treeified as an "h3 > h4" hierarchy with + * min=3, max=4, but should rather be "[h3, h4]" (since the h2 heading has split + * the two headings and they are not parents) + */ +export function useFilteredAndTreeifiedTOC({ toc, minHeadingLevel, maxHeadingLevel, -}: FilterTOCParam): readonly TOCItem[] { +}: { + toc: readonly TOCItem[]; + minHeadingLevel: number; + maxHeadingLevel: number; +}): readonly TOCTreeNode[] { return useMemo( - () => filterTOC({toc, minHeadingLevel, maxHeadingLevel}), + () => filterTOC({toc: treeifyTOC(toc), minHeadingLevel, maxHeadingLevel}), [toc, minHeadingLevel, maxHeadingLevel], ); } diff --git a/packages/docusaurus-theme-common/src/utils/useAlternatePageUtils.ts b/packages/docusaurus-theme-common/src/utils/useAlternatePageUtils.ts index bff300a634c7..ae0e55dcddff 100644 --- a/packages/docusaurus-theme-common/src/utils/useAlternatePageUtils.ts +++ b/packages/docusaurus-theme-common/src/utils/useAlternatePageUtils.ts @@ -8,12 +8,26 @@ import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import {useLocation} from '@docusaurus/router'; -// Permits to obtain the url of the current page in another locale -// Useful to generate hreflang meta headers etc... -// See https://developers.google.com/search/docs/advanced/crawling/localized-versions +/** + * Permits to obtain the url of the current page in another locale, useful to + * generate hreflang meta headers etc... + * + * @see https://developers.google.com/search/docs/advanced/crawling/localized-versions + */ export function useAlternatePageUtils(): { + /** + * Everything (pathname, base URL, etc.) is read from the context. Just tell + * it which locale to link to and it will give you the alternate link for the + * current page. + */ createUrl: ({ + /** The locale name to link to. */ locale, + /** + * For hreflang SEO headers, we need it to be fully qualified (full + * protocol/domain/path...); but for locale dropdowns, using a pathname is + * good enough. + */ fullyQualified, }: { locale: string; @@ -39,14 +53,13 @@ export function useAlternatePageUtils(): { : `${baseUrlUnlocalized}${locale}/`; } - // TODO support correct alternate url when localized site is deployed on another domain + // TODO support correct alternate url when localized site is deployed on + // another domain function createUrl({ locale, fullyQualified, }: { locale: string; - // For hreflang SEO headers, we need it to be fully qualified (full protocol/domain/path...) - // For locale dropdown, using a path is good enough fullyQualified: boolean; }) { return `${fullyQualified ? url : ''}${getLocalizedBaseUrl( diff --git a/packages/docusaurus-theme-common/src/utils/useContextualSearchFilters.ts b/packages/docusaurus-theme-common/src/utils/useContextualSearchFilters.ts deleted file mode 100644 index 6df9198a3508..000000000000 --- a/packages/docusaurus-theme-common/src/utils/useContextualSearchFilters.ts +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import { - useAllDocsData, - useActivePluginAndVersion, -} from '@docusaurus/plugin-content-docs/client'; -import {useDocsPreferredVersionByPluginId} from './docsPreferredVersion/useDocsPreferredVersion'; -import {docVersionSearchTag, DEFAULT_SEARCH_TAG} from './searchUtils'; -import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; - -export type useContextualSearchFiltersReturns = { - locale: string; - tags: string[]; -}; - -// We may want to support multiple search engines, don't couple that to Algolia/DocSearch -// Maybe users will want to use its own search engine solution -export function useContextualSearchFilters(): useContextualSearchFiltersReturns { - const {i18n} = useDocusaurusContext(); - const allDocsData = useAllDocsData(); - const activePluginAndVersion = useActivePluginAndVersion(); - const docsPreferredVersionByPluginId = useDocsPreferredVersionByPluginId(); - - function getDocPluginTags(pluginId: string) { - const activeVersion = - activePluginAndVersion?.activePlugin?.pluginId === pluginId - ? activePluginAndVersion.activeVersion - : undefined; - - const preferredVersion = docsPreferredVersionByPluginId[pluginId]; - - const latestVersion = allDocsData[pluginId].versions.find((v) => v.isLast)!; - - const version = activeVersion ?? preferredVersion ?? latestVersion; - - return docVersionSearchTag(pluginId, version.name); - } - - const tags = [ - DEFAULT_SEARCH_TAG, - ...Object.keys(allDocsData).map(getDocPluginTags), - ]; - - return { - locale: i18n.currentLocale, - tags, - }; -} diff --git a/packages/docusaurus-theme-common/src/utils/useLocalPathname.ts b/packages/docusaurus-theme-common/src/utils/useLocalPathname.ts index 8e1615824216..07a1e987136b 100644 --- a/packages/docusaurus-theme-common/src/utils/useLocalPathname.ts +++ b/packages/docusaurus-theme-common/src/utils/useLocalPathname.ts @@ -5,12 +5,14 @@ * LICENSE file in the root directory of this source tree. */ -import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import {useLocation} from '@docusaurus/router'; +import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; -// Get the pathname of current route, without the optional site baseUrl -// - /docs/myDoc => /docs/myDoc -// - /baseUrl/docs/myDoc => /docs/myDoc +/** + * Get the pathname of current route, without the optional site baseUrl. + * - `/docs/myDoc` => `/docs/myDoc` + * - `/baseUrl/docs/myDoc` => `/docs/myDoc` + */ export function useLocalPathname(): string { const { siteConfig: {baseUrl}, diff --git a/packages/docusaurus-theme-common/src/utils/useLocationChange.ts b/packages/docusaurus-theme-common/src/utils/useLocationChange.ts index a584f2757e24..dd8ec1d0249c 100644 --- a/packages/docusaurus-theme-common/src/utils/useLocationChange.ts +++ b/packages/docusaurus-theme-common/src/utils/useLocationChange.ts @@ -7,24 +7,30 @@ import {useEffect} from 'react'; import {useLocation} from '@docusaurus/router'; -import type {Location} from '@docusaurus/history'; -import {usePrevious} from './usePrevious'; -import {useDynamicCallback} from './reactUtils'; +import type {Location} from 'history'; +import {useDynamicCallback, usePrevious} from './reactUtils'; -type LocationChangeEvent = { - location: Location; - previousLocation: Location | undefined; -}; - -type OnLocationChange = (locationChangeEvent: LocationChangeEvent) => void; - -export function useLocationChange(onLocationChange: OnLocationChange): void { +/** + * Fires an effect when the location changes (which includes hash, query, etc.). + * Importantly, doesn't fire when there's no previous location: see + * https://github.com/facebook/docusaurus/pull/6696 + */ +export function useLocationChange( + onLocationChange: (locationChangeEvent: { + location: Location; + previousLocation: Location | undefined; + }) => void, +): void { const location = useLocation(); const previousLocation = usePrevious(location); const onLocationChangeDynamic = useDynamicCallback(onLocationChange); useEffect(() => { + if (!previousLocation) { + return; + } + if (location !== previousLocation) { onLocationChangeDynamic({ location, diff --git a/packages/docusaurus-theme-common/src/utils/usePluralForm.ts b/packages/docusaurus-theme-common/src/utils/usePluralForm.ts index 77a87d88aea2..cc5ce45317bf 100644 --- a/packages/docusaurus-theme-common/src/utils/usePluralForm.ts +++ b/packages/docusaurus-theme-common/src/utils/usePluralForm.ts @@ -49,36 +49,34 @@ function createLocalePluralForms(locale: string): LocalePluralForms { }; } -// Poor man's PluralSelector implementation, using an english fallback. -// We want a lightweight, future-proof and good-enough solution. -// We don't want a perfect and heavy solution. -// -// Docusaurus classic theme has only 2 deeply nested labels requiring complex plural rules -// We don't want to use Intl + PluralRules polyfills + full ICU syntax (react-intl) just for that. -// -// Notes: -// - 2021: 92+% Browsers support Intl.PluralRules, and support will increase in the future -// - NodeJS >= 13 has full ICU support by default -// - In case of "mismatch" between SSR and Browser ICU support, React keeps working! +/** + * Poor man's `PluralSelector` implementation, using an English fallback. We + * want a lightweight, future-proof and good-enough solution. We don't want a + * perfect and heavy solution. + * + * Docusaurus classic theme has only 2 deeply nested labels requiring complex + * plural rules. We don't want to use `Intl` + `PluralRules` polyfills + full + * ICU syntax (react-intl) just for that. + * + * Notes: + * - 2021: 92+% Browsers support `Intl.PluralRules`, and support will increase + * in the future + * - NodeJS >= 13 has full ICU support by default + * - In case of "mismatch" between SSR and Browser ICU support, React keeps + * working! + */ function useLocalePluralForms(): LocalePluralForms { const { i18n: {currentLocale}, } = useDocusaurusContext(); return useMemo(() => { - // @ts-expect-error checking Intl.PluralRules in case browser doesn't have it (e.g Safari 12-) - if (Intl.PluralRules) { - try { - return createLocalePluralForms(currentLocale); - } catch (e) { - console.error(`Failed to use Intl.PluralRules for locale "${currentLocale}". -Docusaurus will fallback to a default/fallback (English) Intl.PluralRules implementation. + try { + return createLocalePluralForms(currentLocale); + } catch (err) { + console.error(`Failed to use Intl.PluralRules for locale "${currentLocale}". +Docusaurus will fallback to the default (English) implementation. +Error: ${(err as Error).message} `); - return EnglishPluralForms; - } - } else { - console.error(`Intl.PluralRules not available! -Docusaurus will fallback to a default/fallback (English) Intl.PluralRules implementation. - `); return EnglishPluralForms; } }, [currentLocale]); @@ -93,21 +91,32 @@ function selectPluralMessage( const parts = pluralMessages.split(separator); if (parts.length === 1) { - return parts[0]; - } else { - if (parts.length > localePluralForms.pluralForms.length) { - console.error( - `For locale=${localePluralForms.locale}, a maximum of ${localePluralForms.pluralForms.length} plural forms are expected (${localePluralForms.pluralForms}), but the message contains ${parts.length} plural forms: ${pluralMessages} `, - ); - } - const pluralForm = localePluralForms.select(count); - const pluralFormIndex = localePluralForms.pluralForms.indexOf(pluralForm); - // In case of not enough plural form messages, we take the last one (other) instead of returning undefined - return parts[Math.min(pluralFormIndex, parts.length - 1)]; + return parts[0]!; + } + if (parts.length > localePluralForms.pluralForms.length) { + console.error( + `For locale=${localePluralForms.locale}, a maximum of ${localePluralForms.pluralForms.length} plural forms are expected (${localePluralForms.pluralForms}), but the message contains ${parts.length}: ${pluralMessages}`, + ); } + const pluralForm = localePluralForms.select(count); + const pluralFormIndex = localePluralForms.pluralForms.indexOf(pluralForm); + // In case of not enough plural form messages, we take the last one (other) + // instead of returning undefined + return parts[Math.min(pluralFormIndex, parts.length - 1)]!; } +/** + * Reads the current locale and returns an interface very similar to + * `Intl.PluralRules`. + */ export function usePluralForm(): { + /** + * Give it a `count` and it will select the relevant message from + * `pluralMessages`. `pluralMessages` should be separated by `|`, and in the + * order of "zero", "one", "two", "few", "many", "other". The actual selection + * is done by `Intl.PluralRules`, which tells us all plurals the locale has + * and which plural we should use for `count`. + */ selectMessage: (count: number, pluralMessages: string) => string; } { const localePluralForm = useLocalePluralForms(); diff --git a/packages/docusaurus-theme-common/src/utils/usePrevious.ts b/packages/docusaurus-theme-common/src/utils/usePrevious.ts deleted file mode 100644 index 22cb744e05bd..000000000000 --- a/packages/docusaurus-theme-common/src/utils/usePrevious.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import {useRef} from 'react'; -import {useIsomorphicLayoutEffect} from './reactUtils'; - -export function usePrevious(value: T): T | undefined { - const ref = useRef(); - - useIsomorphicLayoutEffect(() => { - ref.current = value; - }); - - return ref.current; -} diff --git a/packages/docusaurus-theme-common/src/utils/useThemeConfig.ts b/packages/docusaurus-theme-common/src/utils/useThemeConfig.ts index 26975d2563ce..684b573b96cf 100644 --- a/packages/docusaurus-theme-common/src/utils/useThemeConfig.ts +++ b/packages/docusaurus-theme-common/src/utils/useThemeConfig.ts @@ -7,7 +7,6 @@ import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import type {PrismTheme} from 'prism-react-renderer'; -import type {CSSProperties} from 'react'; import type {DeepPartial} from 'utility-types'; export type DocsVersionPersistence = 'localStorage' | 'none'; @@ -18,7 +17,7 @@ export type NavbarItem = { items?: NavbarItem[]; label?: string; position?: 'left' | 'right'; -} & Record; +} & {[key: string]: unknown}; export type NavbarLogo = { src: string; @@ -43,12 +42,6 @@ export type ColorModeConfig = { defaultMode: 'light' | 'dark'; disableSwitch: boolean; respectPrefersColorScheme: boolean; - switchConfig: { - darkIcon: string; - darkIconStyle: CSSProperties; - lightIcon: string; - lightIconStyle: CSSProperties; - }; }; export type AnnouncementBarConfig = { @@ -63,7 +56,7 @@ export type PrismConfig = { theme?: PrismTheme; darkTheme?: PrismTheme; defaultLanguage?: string; - additionalLanguages?: string[]; + additionalLanguages: string[]; }; export type FooterLinkItem = { @@ -72,18 +65,20 @@ export type FooterLinkItem = { href?: string; html?: string; prependBaseUrlToHref?: string; +} & {[key: string]: unknown}; + +export type FooterLogo = { + alt?: string; + src: string; + srcDark?: string; + width?: string | number; + height?: string | number; + href?: string; }; export type FooterBase = { style: 'light' | 'dark'; - logo?: { - alt?: string; - src?: string; - srcDark?: string; - width?: string | number; - height?: string | number; - href?: string; - }; + logo?: FooterLogo; copyright?: string; }; @@ -124,7 +119,7 @@ export type ThemeConfig = { hideableSidebar: boolean; autoCollapseSidebarCategories: boolean; image?: string; - metadata: Array>; + metadata: Array<{[key: string]: string}>; sidebarCollapsible: boolean; tableOfContents: TableOfContents; }; @@ -132,6 +127,9 @@ export type ThemeConfig = { // User-provided theme config, unnormalized export type UserThemeConfig = DeepPartial; +/** + * A convenient/more semantic way to get theme config from context. + */ export function useThemeConfig(): ThemeConfig { return useDocusaurusContext().siteConfig.themeConfig as ThemeConfig; } diff --git a/packages/docusaurus-theme-live-codeblock/package.json b/packages/docusaurus-theme-live-codeblock/package.json index 0ffe0b5b1472..ffdcbb6ca6b0 100644 --- a/packages/docusaurus-theme-live-codeblock/package.json +++ b/packages/docusaurus-theme-live-codeblock/package.json @@ -1,14 +1,18 @@ { "name": "@docusaurus/theme-live-codeblock", - "version": "2.0.0-beta.14", + "version": "2.0.0-beta.18", "description": "Docusaurus live code block component.", "main": "lib/index.js", + "types": "src/theme-live-codeblock.d.ts", "publishConfig": { "access": "public" }, "scripts": { - "build": "tsc && node copyUntypedFiles.mjs", - "watch": "node copyUntypedFiles.mjs && tsc --watch" + "build": "yarn build:server && yarn build:client && yarn build:copy && yarn build:format", + "build:server": "tsc --project tsconfig.server.json", + "build:client": "tsc --project tsconfig.client.json", + "build:copy": "node copyUntypedFiles.mjs", + "build:format": "prettier --config ../../.prettierrc --write \"lib/**/*.js\"" }, "repository": { "type": "git", @@ -17,20 +21,18 @@ }, "license": "MIT", "dependencies": { - "@docusaurus/core": "2.0.0-beta.14", - "@docusaurus/theme-common": "2.0.0-beta.14", - "@docusaurus/theme-translations": "2.0.0-beta.14", - "@docusaurus/utils-validation": "2.0.0-beta.14", + "@docusaurus/core": "2.0.0-beta.18", + "@docusaurus/theme-common": "2.0.0-beta.18", + "@docusaurus/theme-translations": "2.0.0-beta.18", + "@docusaurus/utils-validation": "2.0.0-beta.18", "@philpl/buble": "^0.19.7", "clsx": "^1.1.1", - "fs-extra": "^10.0.0", - "parse-numeric-range": "^1.3.0", - "prism-react-renderer": "^1.2.1", + "fs-extra": "^10.0.1", "react-live": "2.2.3", "tslib": "^2.3.1" }, "devDependencies": { - "@docusaurus/types": "2.0.0-beta.14", + "@docusaurus/types": "2.0.0-beta.18", "@types/buble": "^0.20.1" }, "peerDependencies": { diff --git a/packages/docusaurus-theme-live-codeblock/src/__tests__/validateThemeConfig.test.ts b/packages/docusaurus-theme-live-codeblock/src/__tests__/validateThemeConfig.test.ts index 0feddebd0f97..33fe017f0fd9 100644 --- a/packages/docusaurus-theme-live-codeblock/src/__tests__/validateThemeConfig.test.ts +++ b/packages/docusaurus-theme-live-codeblock/src/__tests__/validateThemeConfig.test.ts @@ -22,7 +22,7 @@ function testValidateThemeConfig(themeConfig) { } describe('validateThemeConfig', () => { - test('undefined config', () => { + it('undefined config', () => { const liveCodeBlock = undefined; expect(testValidateThemeConfig({liveCodeBlock})).toEqual({ liveCodeBlock: { @@ -31,7 +31,7 @@ describe('validateThemeConfig', () => { }); }); - test('unexist config', () => { + it('nonexistent config', () => { expect(testValidateThemeConfig({})).toEqual({ liveCodeBlock: { ...DEFAULT_CONFIG, @@ -39,7 +39,7 @@ describe('validateThemeConfig', () => { }); }); - test('empty config', () => { + it('empty config', () => { const liveCodeBlock = {}; expect(testValidateThemeConfig({liveCodeBlock})).toEqual({ liveCodeBlock: { @@ -48,7 +48,7 @@ describe('validateThemeConfig', () => { }); }); - test('playgroundPosition top', () => { + it('playgroundPosition top', () => { const liveCodeBlock = { playgroundPosition: 'top', }; @@ -60,7 +60,7 @@ describe('validateThemeConfig', () => { }); }); - test('playgroundPosition bottom', () => { + it('playgroundPosition bottom', () => { const liveCodeBlock = { playgroundPosition: 'bottom', }; @@ -72,7 +72,7 @@ describe('validateThemeConfig', () => { }); }); - test('playgroundPosition invalid string', () => { + it('playgroundPosition invalid string', () => { const liveCodeBlock = {playgroundPosition: 'invalid'}; expect(() => testValidateThemeConfig({liveCodeBlock}), @@ -80,7 +80,7 @@ describe('validateThemeConfig', () => { `"\\"liveCodeBlock.playgroundPosition\\" must be one of [top, bottom]"`, ); }); - test('playgroundPosition invalid boolean', () => { + it('playgroundPosition invalid boolean', () => { const liveCodeBlock = {playgroundPosition: true}; expect(() => testValidateThemeConfig({liveCodeBlock}), diff --git a/packages/docusaurus-theme-live-codeblock/src/custom-buble.ts b/packages/docusaurus-theme-live-codeblock/src/custom-buble.ts index f99f5bffe8d8..d2c2f8c24638 100644 --- a/packages/docusaurus-theme-live-codeblock/src/custom-buble.ts +++ b/packages/docusaurus-theme-live-codeblock/src/custom-buble.ts @@ -10,7 +10,6 @@ // https://github.com/FormidableLabs/react-live#what-bundle-size-can-i-expect import { transform as bubleTransform, - features as bubleFeatures, type TransformOptions, type TransformOutput, } from '@philpl/buble'; @@ -18,7 +17,7 @@ import { // This file is designed to mimic what's written in // https://github.com/kitten/buble/blob/mini/src/index.js, with custom transforms options, // so that webpack can consume it correctly. -export {bubleFeatures as features}; +export {features} from '@philpl/buble'; export function transform( source: string, diff --git a/packages/docusaurus-theme-live-codeblock/src/deps.d.ts b/packages/docusaurus-theme-live-codeblock/src/deps.d.ts index af1b85719cc6..57918444adca 100644 --- a/packages/docusaurus-theme-live-codeblock/src/deps.d.ts +++ b/packages/docusaurus-theme-live-codeblock/src/deps.d.ts @@ -7,7 +7,7 @@ declare module '@philpl/buble' { import type {TransformOptions as OriginalTransformOptions} from 'buble'; - // eslint-disable-next-line import/no-extraneous-dependencies + // eslint-disable-next-line import/no-extraneous-dependencies, no-restricted-syntax export * from 'buble'; export const features: string[]; export interface TransformOptions extends OriginalTransformOptions { diff --git a/packages/docusaurus-theme-live-codeblock/src/index.ts b/packages/docusaurus-theme-live-codeblock/src/index.ts index be944c429007..0f02878c50c0 100644 --- a/packages/docusaurus-theme-live-codeblock/src/index.ts +++ b/packages/docusaurus-theme-live-codeblock/src/index.ts @@ -5,11 +5,10 @@ * LICENSE file in the root directory of this source tree. */ -import path from 'path'; import {readDefaultCodeTranslationMessages} from '@docusaurus/theme-translations'; -import type {DocusaurusContext, Plugin} from '@docusaurus/types'; +import type {LoadContext, Plugin} from '@docusaurus/types'; -export default function theme(context: DocusaurusContext): Plugin { +export default function themeLiveCodeblock(context: LoadContext): Plugin { const { i18n: {currentLocale}, } = context; @@ -18,7 +17,10 @@ export default function theme(context: DocusaurusContext): Plugin { name: 'docusaurus-theme-live-codeblock', getThemePath() { - return path.resolve(__dirname, './theme'); + return '../lib/theme'; + }, + getTypeScriptThemePath() { + return '../src/theme'; }, getDefaultCodeTranslationMessages() { @@ -32,7 +34,7 @@ export default function theme(context: DocusaurusContext): Plugin { return { resolve: { alias: { - buble: path.resolve(__dirname, './custom-buble.js'), + buble: require.resolve('./custom-buble.js'), }, }, }; diff --git a/packages/docusaurus-theme-live-codeblock/src/theme-live-codeblock.d.ts b/packages/docusaurus-theme-live-codeblock/src/theme-live-codeblock.d.ts new file mode 100644 index 000000000000..b377abadb66a --- /dev/null +++ b/packages/docusaurus-theme-live-codeblock/src/theme-live-codeblock.d.ts @@ -0,0 +1,32 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +declare module '@docusaurus/theme-live-codeblock' { + export type ThemeConfig = { + liveCodeBlock: { + playgroundPosition: 'top' | 'bottom'; + }; + }; +} + +declare module '@theme/Playground' { + import type {LiveProviderProps} from 'react-live'; + + export interface Props extends LiveProviderProps { + children: string; + } + export default function Playground(props: LiveProviderProps): JSX.Element; +} + +declare module '@theme/ReactLiveScope' { + interface Scope { + [key: string]: unknown; + } + + const ReactLiveScope: Scope; + export default ReactLiveScope; +} diff --git a/packages/docusaurus-theme-live-codeblock/src/theme/CodeBlock/index.js b/packages/docusaurus-theme-live-codeblock/src/theme/CodeBlock/index.tsx similarity index 68% rename from packages/docusaurus-theme-live-codeblock/src/theme/CodeBlock/index.js rename to packages/docusaurus-theme-live-codeblock/src/theme/CodeBlock/index.tsx index 425e35bf7feb..8babca566018 100644 --- a/packages/docusaurus-theme-live-codeblock/src/theme/CodeBlock/index.js +++ b/packages/docusaurus-theme-live-codeblock/src/theme/CodeBlock/index.tsx @@ -8,11 +8,12 @@ import React from 'react'; import Playground from '@theme/Playground'; import ReactLiveScope from '@theme/ReactLiveScope'; -import CodeBlock from '@theme-init/CodeBlock'; +import CodeBlock, {type Props} from '@theme-init/CodeBlock'; -const withLiveEditor = (Component) => { - function WrappedComponent(props) { +const withLiveEditor = (Component: typeof CodeBlock) => { + function WrappedComponent(props: Props) { if (props.live) { + // @ts-expect-error: we have deliberately widened the type of language prop return ; } diff --git a/packages/docusaurus-theme-live-codeblock/src/theme/Playground/index.js b/packages/docusaurus-theme-live-codeblock/src/theme/Playground/index.tsx similarity index 83% rename from packages/docusaurus-theme-live-codeblock/src/theme/Playground/index.js rename to packages/docusaurus-theme-live-codeblock/src/theme/Playground/index.tsx index 04e8bcdecdb9..62a11ab6ae2c 100644 --- a/packages/docusaurus-theme-live-codeblock/src/theme/Playground/index.js +++ b/packages/docusaurus-theme-live-codeblock/src/theme/Playground/index.tsx @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import * as React from 'react'; +import React from 'react'; import {LiveProvider, LiveEditor, LiveError, LivePreview} from 'react-live'; import clsx from 'clsx'; import Translate from '@docusaurus/Translate'; @@ -14,8 +14,10 @@ import BrowserOnly from '@docusaurus/BrowserOnly'; import {usePrismTheme} from '@docusaurus/theme-common'; import styles from './styles.module.css'; import useIsBrowser from '@docusaurus/useIsBrowser'; +import type {Props} from '@theme/Playground'; +import type {ThemeConfig} from '@docusaurus/theme-live-codeblock'; -function Header({children}) { +function Header({children}: {children: React.ReactNode}) { return
      {children}
      ; } @@ -55,7 +57,7 @@ function ThemedLiveEditor() { ); @@ -76,18 +78,22 @@ function EditorWithHeader() { ); } -export default function Playground({children, transformCode, ...props}) { +export default function Playground({ + children, + transformCode, + ...props +}: Props): JSX.Element { const { - siteConfig: { - themeConfig: { - liveCodeBlock: {playgroundPosition}, - }, - }, + siteConfig: {themeConfig}, } = useDocusaurusContext(); + const { + liveCodeBlock: {playgroundPosition}, + } = themeConfig as ThemeConfig; const prismTheme = usePrismTheme(); return (
      + {/* @ts-expect-error: type incompatibility with refs */} `${code};`)} diff --git a/packages/docusaurus-theme-live-codeblock/src/theme/ReactLiveScope/index.js b/packages/docusaurus-theme-live-codeblock/src/theme/ReactLiveScope/index.tsx similarity index 100% rename from packages/docusaurus-theme-live-codeblock/src/theme/ReactLiveScope/index.js rename to packages/docusaurus-theme-live-codeblock/src/theme/ReactLiveScope/index.tsx diff --git a/packages/docusaurus-theme-live-codeblock/src/types.d.ts b/packages/docusaurus-theme-live-codeblock/src/types.d.ts new file mode 100644 index 000000000000..9ae9bb21cef3 --- /dev/null +++ b/packages/docusaurus-theme-live-codeblock/src/types.d.ts @@ -0,0 +1,19 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/// +/// + +declare module '@theme-init/CodeBlock' { + import type CodeBlock, {Props as BaseProps} from '@theme/CodeBlock'; + + export interface Props extends BaseProps { + live?: boolean; + } + const CodeBlockComp: typeof CodeBlock; + export default CodeBlockComp; +} diff --git a/packages/docusaurus-theme-live-codeblock/src/validateThemeConfig.ts b/packages/docusaurus-theme-live-codeblock/src/validateThemeConfig.ts index 7a23e907b90f..4b9fb583be03 100644 --- a/packages/docusaurus-theme-live-codeblock/src/validateThemeConfig.ts +++ b/packages/docusaurus-theme-live-codeblock/src/validateThemeConfig.ts @@ -6,13 +6,16 @@ */ import {Joi} from '@docusaurus/utils-validation'; -import type {ThemeConfig, Validate, ValidationResult} from '@docusaurus/types'; +import type { + ThemeConfig, + ThemeConfigValidationContext, +} from '@docusaurus/types'; -const DEFAULT_CONFIG = { +export const DEFAULT_CONFIG = { playgroundPosition: 'bottom', }; -const Schema = Joi.object({ +export const Schema = Joi.object({ liveCodeBlock: Joi.object({ playgroundPosition: Joi.string() .equal('top', 'bottom') @@ -22,14 +25,9 @@ const Schema = Joi.object({ .default(DEFAULT_CONFIG), }); -function validateThemeConfig({ +export function validateThemeConfig({ validate, themeConfig, -}: { - validate: Validate; - themeConfig: ThemeConfig; -}): ValidationResult { +}: ThemeConfigValidationContext): ThemeConfig { return validate(Schema, themeConfig); } - -export {DEFAULT_CONFIG, Schema, validateThemeConfig}; diff --git a/packages/docusaurus-theme-live-codeblock/tsconfig.client.json b/packages/docusaurus-theme-live-codeblock/tsconfig.client.json new file mode 100644 index 000000000000..462a6376e3be --- /dev/null +++ b/packages/docusaurus-theme-live-codeblock/tsconfig.client.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "esnext", + "jsx": "react-native" + }, + "include": ["src/theme/", "src/*.d.ts", "src/custom-buble.ts"] +} diff --git a/packages/docusaurus-theme-live-codeblock/tsconfig.server.json b/packages/docusaurus-theme-live-codeblock/tsconfig.server.json new file mode 100644 index 000000000000..2159e2ef8e20 --- /dev/null +++ b/packages/docusaurus-theme-live-codeblock/tsconfig.server.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.json", + "include": ["src/*.ts"], + "exclude": ["src/custom-buble.ts"] +} diff --git a/packages/docusaurus-theme-search-algolia/package.json b/packages/docusaurus-theme-search-algolia/package.json index ab5bb1065e09..e2cd35f6bdf6 100644 --- a/packages/docusaurus-theme-search-algolia/package.json +++ b/packages/docusaurus-theme-search-algolia/package.json @@ -1,6 +1,6 @@ { "name": "@docusaurus/theme-search-algolia", - "version": "2.0.0-beta.14", + "version": "2.0.0-beta.18", "description": "Algolia search component for Docusaurus.", "main": "lib/index.js", "exports": { @@ -18,30 +18,32 @@ }, "license": "MIT", "scripts": { - "build": "yarn build:server && yarn build:browser && yarn build:copy", + "build": "yarn build:server && yarn build:client && yarn build:copy && yarn build:format", "build:server": "tsc --project tsconfig.server.json", - "build:browser": "tsc --project tsconfig.browser.json", - "build:copy": "node copyUntypedFiles.mjs" + "build:client": "tsc --project tsconfig.client.json", + "build:copy": "node copyUntypedFiles.mjs", + "build:format": "prettier --config ../../.prettierrc --write \"lib/**/*.js\"" }, "dependencies": { - "@docsearch/react": "^3.0.0-alpha.39", - "@docusaurus/core": "2.0.0-beta.14", - "@docusaurus/logger": "2.0.0-beta.14", - "@docusaurus/theme-common": "2.0.0-beta.14", - "@docusaurus/theme-translations": "2.0.0-beta.14", - "@docusaurus/utils": "2.0.0-beta.14", - "@docusaurus/utils-validation": "2.0.0-beta.14", - "algoliasearch": "^4.10.5", - "algoliasearch-helper": "^3.5.5", + "@docsearch/react": "^3.0.0", + "@docusaurus/core": "2.0.0-beta.18", + "@docusaurus/logger": "2.0.0-beta.18", + "@docusaurus/plugin-content-docs": "2.0.0-beta.18", + "@docusaurus/theme-common": "2.0.0-beta.18", + "@docusaurus/theme-translations": "2.0.0-beta.18", + "@docusaurus/utils": "2.0.0-beta.18", + "@docusaurus/utils-validation": "2.0.0-beta.18", + "algoliasearch": "^4.13.0", + "algoliasearch-helper": "^3.7.4", "clsx": "^1.1.1", "eta": "^1.12.3", - "lodash": "^4.17.20", + "fs-extra": "^10.0.1", + "lodash": "^4.17.21", "tslib": "^2.3.1", "utility-types": "^3.10.0" }, "devDependencies": { - "@docusaurus/module-type-aliases": "2.0.0-beta.14", - "fs-extra": "^10.0.0" + "@docusaurus/module-type-aliases": "2.0.0-beta.18" }, "peerDependencies": { "react": "^16.8.4 || ^17.0.0", diff --git a/packages/docusaurus-theme-search-algolia/src/__tests__/validateThemeConfig.test.ts b/packages/docusaurus-theme-search-algolia/src/__tests__/validateThemeConfig.test.ts index 8aef03a32553..6d0cb4cfca1c 100644 --- a/packages/docusaurus-theme-search-algolia/src/__tests__/validateThemeConfig.test.ts +++ b/packages/docusaurus-theme-search-algolia/src/__tests__/validateThemeConfig.test.ts @@ -8,8 +8,8 @@ import type {Joi} from '@docusaurus/utils-validation'; import {validateThemeConfig, DEFAULT_CONFIG} from '../validateThemeConfig'; -function testValidateThemeConfig(themeConfig: Record) { - function validate(schema: Joi.Schema, cfg: Record) { +function testValidateThemeConfig(themeConfig: {[key: string]: unknown}) { + function validate(schema: Joi.Schema, cfg: {[key: string]: unknown}) { const {value, error} = schema.validate(cfg, { convert: false, }); @@ -23,10 +23,11 @@ function testValidateThemeConfig(themeConfig: Record) { } describe('validateThemeConfig', () => { - test('minimal config', () => { + it('minimal config', () => { const algolia = { indexName: 'index', apiKey: 'apiKey', + appId: 'BH4D9OD16A', }; expect(testValidateThemeConfig({algolia})).toEqual({ algolia: { @@ -36,11 +37,12 @@ describe('validateThemeConfig', () => { }); }); - test('unknown attributes', () => { + it('unknown attributes', () => { const algolia = { indexName: 'index', apiKey: 'apiKey', unknownKey: 'unknownKey', + appId: 'BH4D9OD16A', }; expect(testValidateThemeConfig({algolia})).toEqual({ algolia: { @@ -50,7 +52,7 @@ describe('validateThemeConfig', () => { }); }); - test('undefined config', () => { + it('undefined config', () => { const algolia = undefined; expect(() => testValidateThemeConfig({algolia}), @@ -59,7 +61,7 @@ describe('validateThemeConfig', () => { ); }); - test('undefined config 2', () => { + it('undefined config 2', () => { expect(() => testValidateThemeConfig({}), ).toThrowErrorMatchingInlineSnapshot( @@ -67,15 +69,8 @@ describe('validateThemeConfig', () => { ); }); - test('empty config', () => { - const algolia = {}; - expect(() => - testValidateThemeConfig({algolia}), - ).toThrowErrorMatchingInlineSnapshot(`"\\"algolia.apiKey\\" is required"`); - }); - - test('missing indexName config', () => { - const algolia = {apiKey: 'apiKey'}; + it('missing indexName config', () => { + const algolia = {apiKey: 'apiKey', appId: 'BH4D9OD16A'}; expect(() => testValidateThemeConfig({algolia}), ).toThrowErrorMatchingInlineSnapshot( @@ -83,15 +78,25 @@ describe('validateThemeConfig', () => { ); }); - test('missing apiKey config', () => { - const algolia = {indexName: 'indexName'}; + it('missing apiKey config', () => { + const algolia = {indexName: 'indexName', appId: 'BH4D9OD16A'}; expect(() => testValidateThemeConfig({algolia}), ).toThrowErrorMatchingInlineSnapshot(`"\\"algolia.apiKey\\" is required"`); }); - test('contextualSearch config', () => { + it('missing appId config', () => { + const algolia = {indexName: 'indexName', apiKey: 'apiKey'}; + expect(() => + testValidateThemeConfig({algolia}), + ).toThrowErrorMatchingInlineSnapshot( + `"\\"algolia.appId\\" is required. If you haven't migrated to the new DocSearch infra, please refer to the blog post for instructions: https://docusaurus.io/blog/2021/11/21/algolia-docsearch-migration"`, + ); + }); + + it('contextualSearch config', () => { const algolia = { + appId: 'BH4D9OD16A', indexName: 'index', apiKey: 'apiKey', contextualSearch: true, @@ -104,8 +109,9 @@ describe('validateThemeConfig', () => { }); }); - test('externalUrlRegex config', () => { + it('externalUrlRegex config', () => { const algolia = { + appId: 'BH4D9OD16A', indexName: 'index', apiKey: 'apiKey', externalUrlRegex: 'http://external-domain.com', @@ -118,8 +124,9 @@ describe('validateThemeConfig', () => { }); }); - test('searchParameters.facetFilters search config', () => { + it('searchParameters.facetFilters search config', () => { const algolia = { + appId: 'BH4D9OD16A', indexName: 'index', apiKey: 'apiKey', searchParameters: { diff --git a/packages/docusaurus-theme-search-algolia/src/deps.d.ts b/packages/docusaurus-theme-search-algolia/src/deps.d.ts new file mode 100644 index 000000000000..1b69f16b7850 --- /dev/null +++ b/packages/docusaurus-theme-search-algolia/src/deps.d.ts @@ -0,0 +1,10 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +declare module '@docsearch/react/modal'; + +declare module '@docsearch/react/style'; diff --git a/packages/docusaurus-theme-search-algolia/src/index.ts b/packages/docusaurus-theme-search-algolia/src/index.ts index 31f0e02f29cb..46f80161652c 100644 --- a/packages/docusaurus-theme-search-algolia/src/index.ts +++ b/packages/docusaurus-theme-search-algolia/src/index.ts @@ -6,24 +6,26 @@ */ import path from 'path'; -import fs from 'fs'; +import fs from 'fs-extra'; import {defaultConfig, compile} from 'eta'; import {normalizeUrl} from '@docusaurus/utils'; import {readDefaultCodeTranslationMessages} from '@docusaurus/theme-translations'; import logger from '@docusaurus/logger'; import openSearchTemplate from './templates/opensearch'; -import {memoize} from 'lodash'; +import _ from 'lodash'; import type {LoadContext, Plugin} from '@docusaurus/types'; +import type {ThemeConfig} from '@docusaurus/theme-search-algolia'; -const getCompiledOpenSearchTemplate = memoize(() => +const getCompiledOpenSearchTemplate = _.memoize(() => compile(openSearchTemplate.trim()), ); function renderOpenSearchTemplate(data: { title: string; - url: string; - favicon: string | null; + siteUrl: string; + searchUrl: string; + faviconUrl: string | null; }) { const compiled = getCompiledOpenSearchTemplate(); return compiled(data, defaultConfig); @@ -34,19 +36,21 @@ const OPEN_SEARCH_FILENAME = 'opensearch.xml'; export default function themeSearchAlgolia(context: LoadContext): Plugin { const { baseUrl, - siteConfig: {title, url, favicon}, + siteConfig: {title, url, favicon, themeConfig}, i18n: {currentLocale}, } = context; + const { + algolia: {searchPagePath}, + } = themeConfig as ThemeConfig; return { name: 'docusaurus-theme-search-algolia', getThemePath() { - return path.resolve(__dirname, './theme'); + return '../lib/theme'; }, - getTypeScriptThemePath() { - return path.resolve(__dirname, '..', 'src', 'theme'); + return '../src/theme'; }, getDefaultCodeTranslationMessages() { @@ -57,30 +61,41 @@ export default function themeSearchAlgolia(context: LoadContext): Plugin { }, async contentLoaded({actions: {addRoute}}) { - addRoute({ - path: normalizeUrl([baseUrl, 'search']), - component: '@theme/SearchPage', - exact: true, - }); + if (searchPagePath) { + addRoute({ + path: normalizeUrl([baseUrl, searchPagePath]), + component: '@theme/SearchPage', + exact: true, + }); + } }, async postBuild({outDir}) { - try { - fs.writeFileSync( - path.join(outDir, OPEN_SEARCH_FILENAME), - renderOpenSearchTemplate({ - title, - url: url + baseUrl, - favicon: favicon ? normalizeUrl([url, baseUrl, favicon]) : null, - }), - ); - } catch (e) { - logger.error('Generating OpenSearch file failed.'); - throw e; + if (searchPagePath) { + const siteUrl = normalizeUrl([url, baseUrl]); + + try { + await fs.writeFile( + path.join(outDir, OPEN_SEARCH_FILENAME), + renderOpenSearchTemplate({ + title, + siteUrl, + searchUrl: normalizeUrl([siteUrl, searchPagePath]), + faviconUrl: favicon ? normalizeUrl([siteUrl, favicon]) : null, + }), + ); + } catch (err) { + logger.error('Generating OpenSearch file failed.'); + throw err; + } } }, injectHtmlTags() { + if (!searchPagePath) { + return {}; + } + return { headTags: [ { diff --git a/packages/docusaurus-theme-search-algolia/src/templates/opensearch.ts b/packages/docusaurus-theme-search-algolia/src/templates/opensearch.ts index 04295bdde55e..b202b66b9fba 100644 --- a/packages/docusaurus-theme-search-algolia/src/templates/opensearch.ts +++ b/packages/docusaurus-theme-search-algolia/src/templates/opensearch.ts @@ -12,11 +12,11 @@ export default ` <%= it.title %> Search <%= it.title %> UTF-8 - <% if (it.favicon) { _%> - <%= it.favicon %> + <% if (it.faviconUrl) { _%> + <%= it.faviconUrl %> <% } _%> - - - <%= it.url %> + + + <%= it.siteUrl %> `; diff --git a/packages/docusaurus-theme-search-algolia/src/theme-search-algolia.d.ts b/packages/docusaurus-theme-search-algolia/src/theme-search-algolia.d.ts index 77ee9c74c047..1aea75deabb3 100644 --- a/packages/docusaurus-theme-search-algolia/src/theme-search-algolia.d.ts +++ b/packages/docusaurus-theme-search-algolia/src/theme-search-algolia.d.ts @@ -15,7 +15,8 @@ declare module '@docusaurus/theme-search-algolia' { appId: string; apiKey: string; indexName: string; - searchParameters: Record; + searchParameters: {[key: string]: unknown}; + searchPagePath: string | false | null; }; }; export type UserThemeConfig = DeepPartial; @@ -26,11 +27,9 @@ declare module '@docusaurus/theme-search-algolia/client' { } declare module '@theme/SearchPage' { - const SearchPage: () => JSX.Element; - export default SearchPage; + export default function SearchPage(): JSX.Element; } declare module '@theme/SearchBar' { - const SearchBar: () => JSX.Element; - export default SearchBar; + export default function SearchBar(): JSX.Element; } diff --git a/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx b/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx index 5d0023cc38da..0078f932950d 100644 --- a/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx +++ b/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx @@ -4,7 +4,6 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ -/* eslint-disable @typescript-eslint/ban-ts-comment */ import React, {useState, useRef, useCallback, useMemo} from 'react'; import {createPortal} from 'react-dom'; @@ -16,7 +15,7 @@ import Head from '@docusaurus/Head'; import {isRegexpStringMatch, useSearchPage} from '@docusaurus/theme-common'; import {DocSearchButton, useDocSearchKeyboardEvents} from '@docsearch/react'; import {useAlgoliaContextualFacetFilters} from '@docusaurus/theme-search-algolia/client'; -import {translate} from '@docusaurus/Translate'; +import Translate, {translate} from '@docusaurus/Translate'; import styles from './styles.module.css'; import type { @@ -35,6 +34,7 @@ type DocSearchProps = Omit< > & { contextualSearch?: string; externalUrlRegex?: string; + searchPagePath: boolean | string; }; let DocSearchModal: typeof DocSearchModalType | null = null; @@ -59,7 +59,11 @@ function ResultsFooter({state, onClose}: ResultsFooterProps) { return ( - See all {state.context.nbHits} results + + {'See all {count} results'} + ); } @@ -71,8 +75,8 @@ type FacetFilters = Required< function mergeFacetFilters(f1: FacetFilters, f2: FacetFilters): FacetFilters { const normalize = ( f: FacetFilters, - ): readonly string[] | ReadonlyArray => - f instanceof Array ? f : [f]; + ): readonly string[] | ReadonlyArray => + typeof f === 'string' ? [f] : f; return [...normalize(f1), ...normalize(f2)] as FacetFilters; } @@ -116,9 +120,7 @@ function DocSearch({ } return Promise.all([ - // @ts-ignore import('@docsearch/react/modal'), - // @ts-ignore import('@docsearch/react/style'), import('./styles.css'), ]).then(([{DocSearchModal: Modal}]) => { @@ -167,7 +169,8 @@ function DocSearch({ const transformItems = useRef( (items) => items.map((item) => { - // If Algolia contains a external domain, we should navigate without relative URL + // If Algolia contains a external domain, we should navigate without + // relative URL if (isRegexpStringMatch(externalUrlRegex, item.url)) { return item; } @@ -254,8 +257,10 @@ function DocSearch({ navigator={navigator} transformItems={transformItems} hitComponent={Hit} - resultsFooterComponent={resultsFooterComponent} transformSearchClient={transformSearchClient} + {...(props.searchPagePath && { + resultsFooterComponent, + })} {...props} searchParameters={searchParameters} />, @@ -265,10 +270,7 @@ function DocSearch({ ); } -function SearchBar(): JSX.Element { +export default function SearchBar(): JSX.Element { const {siteConfig} = useDocusaurusContext(); - // @ts-ignore - return ; + return ; } - -export default SearchBar; diff --git a/packages/docusaurus-theme-search-algolia/src/theme/SearchMetadata/index.tsx b/packages/docusaurus-theme-search-algolia/src/theme/SearchMetadata/index.tsx deleted file mode 100644 index de0b99a2f42a..000000000000 --- a/packages/docusaurus-theme-search-algolia/src/theme/SearchMetadata/index.tsx +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import React from 'react'; - -import Head from '@docusaurus/Head'; -import type {Props} from '@theme/SearchMetadata'; - -// Override default/agnostic SearchMetadata to use Algolia-specific metadata -function SearchMetadata({locale, version, tag}: Props): JSX.Element { - // Seems safe to consider here the locale is the language, - // as the existing docsearch:language filter is afaik a regular string-based filter - const language = locale; - - return ( - - {language && } - {version && } - {tag && } - - ); -} - -export default SearchMetadata; diff --git a/packages/docusaurus-theme-search-algolia/src/theme/SearchPage/index.tsx b/packages/docusaurus-theme-search-algolia/src/theme/SearchPage/index.tsx index 50be44a8b21d..5a79ce31050f 100644 --- a/packages/docusaurus-theme-search-algolia/src/theme/SearchPage/index.tsx +++ b/packages/docusaurus-theme-search-algolia/src/theme/SearchPage/index.tsx @@ -6,7 +6,6 @@ */ /* eslint-disable jsx-a11y/no-autofocus */ -/* eslint-disable @typescript-eslint/ban-ts-comment */ import React, {useEffect, useState, useReducer, useRef} from 'react'; @@ -18,6 +17,7 @@ import Head from '@docusaurus/Head'; import Link from '@docusaurus/Link'; import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment'; import { + HtmlClassNameProvider, useTitleFormatter, usePluralForm, isRegexpStringMatch, @@ -29,6 +29,7 @@ import {useAllDocsData} from '@docusaurus/plugin-content-docs/client'; import Layout from '@theme/Layout'; import Translate, {translate} from '@docusaurus/Translate'; import styles from './styles.module.css'; +import type {ThemeConfig} from '@docusaurus/theme-search-algolia'; // Very simple pluralization: probably good enough for now function useDocumentsFoundPlural() { @@ -53,15 +54,16 @@ function useDocsSearchVersionsHelpers() { // State of the version select menus / algolia facet filters // docsPluginId -> versionName map - const [searchVersions, setSearchVersions] = useState>( - () => - Object.entries(allDocsData).reduce( - (acc, [pluginId, pluginData]) => ({ - ...acc, - [pluginId]: pluginData.versions[0].name, - }), - {}, - ), + const [searchVersions, setSearchVersions] = useState<{ + [pluginId: string]: string; + }>(() => + Object.entries(allDocsData).reduce( + (acc, [pluginId, pluginData]) => ({ + ...acc, + [pluginId]: pluginData.versions[0]!.name, + }), + {}, + ), ); // Set the value of a single select menu @@ -149,16 +151,14 @@ type ResultDispatcher = | {type: 'update'; value: ResultDispatcherState} | {type: 'advance'; value?: undefined}; -function SearchPage(): JSX.Element { +function SearchPageContent(): JSX.Element { const { - siteConfig: { - themeConfig: { - // @ts-ignore - algolia: {appId, apiKey, indexName, externalUrlRegex}, - }, - }, + siteConfig: {themeConfig}, i18n: {currentLocale}, } = useDocusaurusContext(); + const { + algolia: {appId, apiKey, indexName, externalUrlRegex}, + } = themeConfig as ThemeConfig; const documentsFoundPlural = useDocumentsFoundPlural(); const docsSearchVersionsHelpers = useDocsSearchVersionsHelpers(); @@ -220,7 +220,7 @@ function SearchPage(): JSX.Element { algoliaHelper.on( 'result', ({results: {query, hits, page, nbHits, nbPages}}) => { - if (query === '' || !(hits instanceof Array)) { + if (query === '' || !Array.isArray(hits)) { searchResultStateDispatcher({type: 'reset'}); return; } @@ -279,7 +279,7 @@ function SearchPage(): JSX.Element { const { isIntersecting, boundingClientRect: {y: currentY}, - } = entries[0]; + } = entries[0]!; if (isIntersecting && prevY.current > currentY) { searchResultStateDispatcher({type: 'advance'}); @@ -358,7 +358,7 @@ function SearchPage(): JSX.Element { }, [makeSearch, searchResultState.lastPage]); return ( - + {useTitleFormatter(getTitle())} {/* @@ -519,4 +519,10 @@ function SearchPage(): JSX.Element { ); } -export default SearchPage; +export default function SearchPage(): JSX.Element { + return ( + + + + ); +} diff --git a/packages/docusaurus-theme-search-algolia/src/types.d.ts b/packages/docusaurus-theme-search-algolia/src/types.d.ts index 945c78410add..04ccef3c4442 100644 --- a/packages/docusaurus-theme-search-algolia/src/types.d.ts +++ b/packages/docusaurus-theme-search-algolia/src/types.d.ts @@ -8,7 +8,3 @@ /// /// /// - -export type FacetFilters = Required< - Required['searchParameters'] ->['facetFilters']; diff --git a/packages/docusaurus-theme-search-algolia/src/validateThemeConfig.ts b/packages/docusaurus-theme-search-algolia/src/validateThemeConfig.ts index 11fe86fbcb95..95ff9e9f7cb4 100644 --- a/packages/docusaurus-theme-search-algolia/src/validateThemeConfig.ts +++ b/packages/docusaurus-theme-search-algolia/src/validateThemeConfig.ts @@ -6,18 +6,18 @@ */ import {Joi} from '@docusaurus/utils-validation'; -import type {ThemeConfig, Validate, ValidationResult} from '@docusaurus/types'; +import type { + ThemeConfig, + ThemeConfigValidationContext, +} from '@docusaurus/types'; export const DEFAULT_CONFIG = { // enabled by default, as it makes sense in most cases // see also https://github.com/facebook/docusaurus/issues/5880 contextualSearch: true, - // By default, all Docusaurus sites are using the same AppId - // This has been designed on purpose with Algolia. - appId: 'BH4D9OD16A', - searchParameters: {}, + searchPagePath: 'search', }; export const Schema = Joi.object({ @@ -26,12 +26,19 @@ export const Schema = Joi.object({ contextualSearch: Joi.boolean().default(DEFAULT_CONFIG.contextualSearch), externalUrlRegex: Joi.string().optional(), // Algolia attributes - appId: Joi.string().default(DEFAULT_CONFIG.appId), + appId: Joi.string().required().messages({ + 'any.required': + '"algolia.appId" is required. If you haven\'t migrated to the new DocSearch infra, please refer to the blog post for instructions: https://docusaurus.io/blog/2021/11/21/algolia-docsearch-migration', + }), apiKey: Joi.string().required(), indexName: Joi.string().required(), searchParameters: Joi.object() .default(DEFAULT_CONFIG.searchParameters) .unknown(), + searchPagePath: Joi.alternatives() + .try(Joi.boolean().invalid(true), Joi.string()) + .allow(null) + .default(DEFAULT_CONFIG.searchPagePath), }) .label('themeConfig.algolia') .required() @@ -41,9 +48,6 @@ export const Schema = Joi.object({ export function validateThemeConfig({ validate, themeConfig, -}: { - validate: Validate; - themeConfig: ThemeConfig; -}): ValidationResult { +}: ThemeConfigValidationContext): ThemeConfig { return validate(Schema, themeConfig); } diff --git a/packages/docusaurus-theme-search-algolia/tsconfig.browser.json b/packages/docusaurus-theme-search-algolia/tsconfig.client.json similarity index 100% rename from packages/docusaurus-theme-search-algolia/tsconfig.browser.json rename to packages/docusaurus-theme-search-algolia/tsconfig.client.json diff --git a/packages/docusaurus-theme-translations/__tests__/update.test.ts b/packages/docusaurus-theme-translations/__tests__/update.test.ts deleted file mode 100644 index 21a2573e82f8..000000000000 --- a/packages/docusaurus-theme-translations/__tests__/update.test.ts +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import {extractThemeCodeMessages} from '../update'; -import path from 'path'; -import fs from 'fs-extra'; -import {mapValues} from 'lodash'; - -// Seems the 5s default timeout fails sometimes -jest.setTimeout(15000); - -describe('theme-translations package', () => { - test(`to have base messages files contain EXACTLY all the translations extracted from the theme. Please run "yarn workspace @docusaurus/theme-translations update" to keep base messages files up-to-date.`, async () => { - const baseMessagesDirPath = path.join(__dirname, '../locales/base'); - const baseMessages = Object.fromEntries( - ( - await Promise.all( - ( - await fs.readdir(baseMessagesDirPath) - ).map(async (baseMessagesFile) => - Object.entries( - JSON.parse( - ( - await fs.readFile( - path.join(baseMessagesDirPath, baseMessagesFile), - ) - ).toString(), - ) as Record, - ), - ), - ) - ) - .flat() - .filter(([key]) => !key.endsWith('___DESCRIPTION')), - ); - const codeMessages = mapValues( - await extractThemeCodeMessages(), - (translation) => translation.message, - ); - - expect(codeMessages).toEqual(baseMessages); - }); -}); diff --git a/packages/docusaurus-theme-translations/locales/__tests__/locales.test.ts b/packages/docusaurus-theme-translations/locales/__tests__/locales.test.ts new file mode 100644 index 000000000000..0dc8db25977f --- /dev/null +++ b/packages/docusaurus-theme-translations/locales/__tests__/locales.test.ts @@ -0,0 +1,43 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {jest} from '@jest/globals'; +import {extractThemeCodeMessages} from '../../src/utils'; +import path from 'path'; +import fs from 'fs-extra'; +import _ from 'lodash'; + +// Seems the 5s default timeout fails sometimes +jest.setTimeout(15000); + +describe('theme translations', () => { + it('has base messages files contain EXACTLY all the translations extracted from the theme. Please run "yarn workspace @docusaurus/theme-translations update" to keep base messages files up-to-date', async () => { + const baseMessagesDirPath = path.join(__dirname, '../base'); + const baseMessages = Object.fromEntries( + await Promise.all( + ( + await fs.readdir(baseMessagesDirPath) + ).map(async (baseMessagesFile) => + Object.entries( + (await fs.readJSON( + path.join(baseMessagesDirPath, baseMessagesFile), + 'utf-8', + )) as {[key: string]: string}, + ), + ), + ).then((translations) => + translations.flat().filter(([key]) => !key.endsWith('___DESCRIPTION')), + ), + ); + const codeMessages = _.mapValues( + await extractThemeCodeMessages(), + (translation) => translation.message, + ); + + expect(codeMessages).toEqual(baseMessages); + }); +}); diff --git a/packages/docusaurus-theme-translations/locales/ar/theme-common.json b/packages/docusaurus-theme-translations/locales/ar/theme-common.json index b5ecd3d1e01a..24e99fddfe40 100644 --- a/packages/docusaurus-theme-translations/locales/ar/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/ar/theme-common.json @@ -21,12 +21,17 @@ "theme.blog.post.paginator.olderPost": "مقالات أقدم", "theme.blog.post.plurals": "مقاله واحده|{count} مقالات", "theme.blog.post.readMore": "اقرأ المزيد", + "theme.blog.post.readMoreLabel": "Read more about {title}", "theme.blog.post.readingTime.plurals": "{readingTime} دقائق قراءة|{readingTime} دقائق قراءة", "theme.blog.sidebar.navAriaLabel": "أحدث مشاركات المدونة", "theme.blog.tagTitle": "{nPosts} موسومة ب \"{tagName}\"", + "theme.colorToggle.ariaLabel": "Switch between dark and light mode (currently {mode})", + "theme.colorToggle.ariaLabel.mode.dark": "dark mode", + "theme.colorToggle.ariaLabel.mode.light": "light mode", "theme.common.editThisPage": "تعديل هذه الصفحة", "theme.common.headingLinkTitle": "ارتباط مباشر بالعنوان", "theme.common.skipToMainContent": "انتقل إلى المحتوى الرئيسي", + "theme.docs.DocCard.categoryDescription": "{count} items", "theme.docs.paginator.navAriaLabel": "التنقل بين صفحات الددات", "theme.docs.paginator.next": "التالى", "theme.docs.paginator.previous": "السابق", @@ -36,6 +41,7 @@ "theme.docs.sidebar.expandButtonTitle": "توسيع الشريط الجانبي", "theme.docs.tagDocListPageTitle": "{nDocsTagged} مستند موسوم بـ \"{tagName}\"", "theme.docs.tagDocListPageTitle.nDocsTagged": "مستند موسوم واحد|{count} مستندات موسومة", + "theme.docs.versionBadge.label": "Version: {versionLabel}", "theme.docs.versions.latestVersionLinkLabel": "احدث اصدار", "theme.docs.versions.latestVersionSuggestionLabel": "للحصول على أحدث الوثائق، راجع {latestVersionLink} ({versionLabel}).", "theme.docs.versions.unmaintainedVersionLabel": "هذه هي وثائق {siteTitle} {versionLabel}، التي لم تعد تتم صيانتها بشكل نشط.", @@ -43,6 +49,7 @@ "theme.lastUpdated.atDate": " في {date}", "theme.lastUpdated.byUser": " بواسطة {user}", "theme.lastUpdated.lastUpdatedAtBy": "آخر تحديث{atDate}{byUser}", + "theme.navbar.mobileLanguageDropdown.label": "Languages", "theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": "→ العودة إلى القائمة الرئيسية", "theme.navbar.mobileVersionsDropdown.label": "إصدارات", "theme.tags.tagsListLabel": "الوسوم:", diff --git a/packages/docusaurus-theme-translations/locales/ar/theme-search-algolia.json b/packages/docusaurus-theme-translations/locales/ar/theme-search-algolia.json index a7a675e7e83c..3163cef69570 100644 --- a/packages/docusaurus-theme-translations/locales/ar/theme-search-algolia.json +++ b/packages/docusaurus-theme-translations/locales/ar/theme-search-algolia.json @@ -1,5 +1,6 @@ { "theme.SearchBar.label": "بحث", + "theme.SearchBar.seeAll": "See all {count} results", "theme.SearchPage.algoliaLabel": "البحث بواسطه Algolia", "theme.SearchPage.documentsFound.plurals": "تم العثور على مستند واحد|تم العثور على {count} مستندات", "theme.SearchPage.emptyResultsTitle": "ابحث في الوثائق", diff --git a/packages/docusaurus-theme-translations/locales/base/theme-common.json b/packages/docusaurus-theme-translations/locales/base/theme-common.json index 09cd6357ceb4..fb5f46320e19 100644 --- a/packages/docusaurus-theme-translations/locales/base/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/base/theme-common.json @@ -43,18 +43,28 @@ "theme.blog.post.plurals___DESCRIPTION": "Pluralized label for \"{count} posts\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)", "theme.blog.post.readMore": "Read More", "theme.blog.post.readMore___DESCRIPTION": "The label used in blog post item excerpts to link to full blog posts", + "theme.blog.post.readMoreLabel": "Read more about {title}", + "theme.blog.post.readMoreLabel___DESCRIPTION": "The ARIA label for the link to full blog posts from excerpts", "theme.blog.post.readingTime.plurals": "One min read|{readingTime} min read", "theme.blog.post.readingTime.plurals___DESCRIPTION": "Pluralized label for \"{readingTime} min read\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)", "theme.blog.sidebar.navAriaLabel": "Blog recent posts navigation", "theme.blog.sidebar.navAriaLabel___DESCRIPTION": "The ARIA label for recent posts in the blog sidebar", "theme.blog.tagTitle": "{nPosts} tagged with \"{tagName}\"", "theme.blog.tagTitle___DESCRIPTION": "The title of the page for a blog tag", + "theme.colorToggle.ariaLabel": "Switch between dark and light mode (currently {mode})", + "theme.colorToggle.ariaLabel___DESCRIPTION": "The ARIA label for the navbar color mode toggle", + "theme.colorToggle.ariaLabel.mode.dark": "dark mode", + "theme.colorToggle.ariaLabel.mode.dark___DESCRIPTION": "The name for the dark color mode", + "theme.colorToggle.ariaLabel.mode.light": "light mode", + "theme.colorToggle.ariaLabel.mode.light___DESCRIPTION": "The name for the light color mode", "theme.common.editThisPage": "Edit this page", "theme.common.editThisPage___DESCRIPTION": "The link label to edit the current page", "theme.common.headingLinkTitle": "Direct link to heading", "theme.common.headingLinkTitle___DESCRIPTION": "Title for link to heading", "theme.common.skipToMainContent": "Skip to main content", "theme.common.skipToMainContent___DESCRIPTION": "The skip to content label used for accessibility, allowing to rapidly navigate to main content with keyboard tab/enter navigation", + "theme.docs.DocCard.categoryDescription": "{count} items", + "theme.docs.DocCard.categoryDescription___DESCRIPTION": "The default description for a category card in the generated index about how many items this category includes", "theme.docs.paginator.navAriaLabel": "Docs pages navigation", "theme.docs.paginator.navAriaLabel___DESCRIPTION": "The ARIA label for the docs pagination", "theme.docs.paginator.next": "Next", @@ -73,6 +83,7 @@ "theme.docs.tagDocListPageTitle___DESCRIPTION": "The title of the page for a docs tag", "theme.docs.tagDocListPageTitle.nDocsTagged": "One doc tagged|{count} docs tagged", "theme.docs.tagDocListPageTitle.nDocsTagged___DESCRIPTION": "Pluralized label for \"{count} docs tagged\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)", + "theme.docs.versionBadge.label": "Version: {versionLabel}", "theme.docs.versions.latestVersionLinkLabel": "latest version", "theme.docs.versions.latestVersionLinkLabel___DESCRIPTION": "The label used for the latest version suggestion link label", "theme.docs.versions.latestVersionSuggestionLabel": "For up-to-date documentation, see the {latestVersionLink} ({versionLabel}).", @@ -87,6 +98,8 @@ "theme.lastUpdated.byUser___DESCRIPTION": "The words used to describe by who the page has been last updated", "theme.lastUpdated.lastUpdatedAtBy": "Last updated{atDate}{byUser}", "theme.lastUpdated.lastUpdatedAtBy___DESCRIPTION": "The sentence used to display when a page has been last updated, and by who", + "theme.navbar.mobileLanguageDropdown.label": "Languages", + "theme.navbar.mobileLanguageDropdown.label___DESCRIPTION": "The label for the mobile language switcher dropdown", "theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": "← Back to main menu", "theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel___DESCRIPTION": "The label of the back button to return to main menu, inside the mobile navbar sidebar secondary menu (notably used to display the docs sidebar)", "theme.navbar.mobileVersionsDropdown.label": "Versions", diff --git a/packages/docusaurus-theme-translations/locales/base/theme-search-algolia.json b/packages/docusaurus-theme-translations/locales/base/theme-search-algolia.json index ec8d06b4638e..053c35c0a385 100644 --- a/packages/docusaurus-theme-translations/locales/base/theme-search-algolia.json +++ b/packages/docusaurus-theme-translations/locales/base/theme-search-algolia.json @@ -1,6 +1,7 @@ { "theme.SearchBar.label": "Search", "theme.SearchBar.label___DESCRIPTION": "The ARIA label and placeholder for search button", + "theme.SearchBar.seeAll": "See all {count} results", "theme.SearchPage.algoliaLabel": "Search by Algolia", "theme.SearchPage.algoliaLabel___DESCRIPTION": "The ARIA label for Algolia mention", "theme.SearchPage.documentsFound.plurals": "One document found|{count} documents found", diff --git a/packages/docusaurus-theme-translations/locales/bn/theme-common.json b/packages/docusaurus-theme-translations/locales/bn/theme-common.json index 4be790fdbbd0..a05c817a21c4 100644 --- a/packages/docusaurus-theme-translations/locales/bn/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/bn/theme-common.json @@ -21,12 +21,17 @@ "theme.blog.post.paginator.olderPost": "পুরানো পোস্ট", "theme.blog.post.plurals": "একটি পোস্ট|{count} পোস্টস", "theme.blog.post.readMore": "আরও পড়ুন", + "theme.blog.post.readMoreLabel": "Read more about {title}", "theme.blog.post.readingTime.plurals": "এক মিনিট পড়া|{readingTime} মিনিট পড়া", "theme.blog.sidebar.navAriaLabel": "সাম্প্রতিক ব্লগ পোস্ট নেভিগেশন", "theme.blog.tagTitle": "{nPosts} সঙ্গে ট্যাগ্গেড \"{tagName}\" ", + "theme.colorToggle.ariaLabel": "Switch between dark and light mode (currently {mode})", + "theme.colorToggle.ariaLabel.mode.dark": "dark mode", + "theme.colorToggle.ariaLabel.mode.light": "light mode", "theme.common.editThisPage": "এই পেজটি এডিট করুন", "theme.common.headingLinkTitle": "হেডিং এর সঙ্গে সরাসরি লিংকড", "theme.common.skipToMainContent": "স্কিপ করে মূল কন্টেন্ট এ যান", + "theme.docs.DocCard.categoryDescription": "{count} items", "theme.docs.paginator.navAriaLabel": "ডক্স পৃষ্টাগুলির নেভিগেশন", "theme.docs.paginator.next": "পরবর্তী", "theme.docs.paginator.previous": "পূর্ববর্তী", @@ -36,6 +41,7 @@ "theme.docs.sidebar.expandButtonTitle": "সাইডবারটি প্রসারিত করুন", "theme.docs.tagDocListPageTitle": "{nDocsTagged} with \"{tagName}\"", "theme.docs.tagDocListPageTitle.nDocsTagged": "One doc tagged|{count} docs tagged", + "theme.docs.versionBadge.label": "Version: {versionLabel}", "theme.docs.versions.latestVersionLinkLabel": "লেটেস্ট ভার্সন", "theme.docs.versions.latestVersionSuggestionLabel": "আপ-টু-ডেট ডকুমেন্টেশনের জন্য, {latestVersionLink} ({versionLabel}) দেখুন।", "theme.docs.versions.unmaintainedVersionLabel": "এটি {siteTitle} {versionLabel} এর জন্যে ডকুমেন্টেশন, যা আর সক্রিয়ভাবে রক্ষণাবেক্ষণ করা হয় না।", @@ -43,6 +49,7 @@ "theme.lastUpdated.atDate": " {date} তারিখে", "theme.lastUpdated.byUser": "{user} দ্বারা", "theme.lastUpdated.lastUpdatedAtBy": "সর্বশেষ সংষ্করণ{atDate}{byUser}", + "theme.navbar.mobileLanguageDropdown.label": "Languages", "theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": "← মেন মেনুতে যান", "theme.navbar.mobileVersionsDropdown.label": "Versions", "theme.tags.tagsListLabel": "ট্যাগ্স:", diff --git a/packages/docusaurus-theme-translations/locales/bn/theme-search-algolia.json b/packages/docusaurus-theme-translations/locales/bn/theme-search-algolia.json index 94375d80d3ef..5866b43939ba 100644 --- a/packages/docusaurus-theme-translations/locales/bn/theme-search-algolia.json +++ b/packages/docusaurus-theme-translations/locales/bn/theme-search-algolia.json @@ -1,5 +1,6 @@ { "theme.SearchBar.label": "সার্চ", + "theme.SearchBar.seeAll": "See all {count} results", "theme.SearchPage.algoliaLabel": "আলগোলিয়া দ্বারা অনুসন্ধান করুন", "theme.SearchPage.documentsFound.plurals": "একটি ডকুমেন্ট পাওয়া গেছে|{count} ডকুমেন্টস পাওয়া গেছে", "theme.SearchPage.emptyResultsTitle": "ডকুমেন্টেশন অনুসন্ধান করুন", diff --git a/packages/docusaurus-theme-translations/locales/cs/theme-common.json b/packages/docusaurus-theme-translations/locales/cs/theme-common.json index fe062d63ada4..e0245b23ad23 100644 --- a/packages/docusaurus-theme-translations/locales/cs/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/cs/theme-common.json @@ -21,12 +21,17 @@ "theme.blog.post.paginator.olderPost": "Starší článek", "theme.blog.post.plurals": "Jeden článek|{count} články|{count} článků|{count} článků", "theme.blog.post.readMore": "Celý článek", + "theme.blog.post.readMoreLabel": "Read more about {title}", "theme.blog.post.readingTime.plurals": "Jedna minuta čtení|{readingTime} minuty čtení|{readingTime} minut čtení|{readingTime} minut čtení", "theme.blog.sidebar.navAriaLabel": "Navigace s aktuálními články na blogu", "theme.blog.tagTitle": "{nPosts} s tagem \"{tagName}\"", + "theme.colorToggle.ariaLabel": "Switch between dark and light mode (currently {mode})", + "theme.colorToggle.ariaLabel.mode.dark": "dark mode", + "theme.colorToggle.ariaLabel.mode.light": "light mode", "theme.common.editThisPage": "Upravit tuto stránku", "theme.common.headingLinkTitle": "Přímý odkaz na nadpis", "theme.common.skipToMainContent": "Přeskočit na hlavní obsah", + "theme.docs.DocCard.categoryDescription": "{count} items", "theme.docs.paginator.navAriaLabel": "Stránkování dokumentace", "theme.docs.paginator.next": "Další", "theme.docs.paginator.previous": "Předchozí", @@ -36,6 +41,7 @@ "theme.docs.sidebar.expandButtonTitle": "Otevřít postranní lištu", "theme.docs.tagDocListPageTitle": "{nDocsTagged} with \"{tagName}\"", "theme.docs.tagDocListPageTitle.nDocsTagged": "One doc tagged|{count} docs tagged", + "theme.docs.versionBadge.label": "Version: {versionLabel}", "theme.docs.versions.latestVersionLinkLabel": "Nejnovější verze", "theme.docs.versions.latestVersionSuggestionLabel": "Aktuální dokumentace viz {latestVersionLink} ({versionLabel}).", "theme.docs.versions.unmaintainedVersionLabel": "Tato dokumentace je pro {siteTitle} {versionLabel}, která už není aktivně udržována.", @@ -43,6 +49,7 @@ "theme.lastUpdated.atDate": " {date}", "theme.lastUpdated.byUser": " od {user}", "theme.lastUpdated.lastUpdatedAtBy": "Naposledy aktualizováno{atDate}{byUser}", + "theme.navbar.mobileLanguageDropdown.label": "Languages", "theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": "← Zpět na hlavní menu", "theme.navbar.mobileVersionsDropdown.label": "Versions", "theme.tags.tagsListLabel": "Tagy:", diff --git a/packages/docusaurus-theme-translations/locales/cs/theme-search-algolia.json b/packages/docusaurus-theme-translations/locales/cs/theme-search-algolia.json index 90dd8695625b..95f0f82409c8 100644 --- a/packages/docusaurus-theme-translations/locales/cs/theme-search-algolia.json +++ b/packages/docusaurus-theme-translations/locales/cs/theme-search-algolia.json @@ -1,5 +1,6 @@ { "theme.SearchBar.label": "Hledat", + "theme.SearchBar.seeAll": "See all {count} results", "theme.SearchPage.algoliaLabel": "Vyhledávání od Algolia", "theme.SearchPage.documentsFound.plurals": "Jeden dokument nalezen|{count} dokumenty nalezeny||{count} dokumentů nalezeno||{count} dokumentů nalezeno", "theme.SearchPage.emptyResultsTitle": "Prohledat dokumentaci", diff --git a/packages/docusaurus-theme-translations/locales/da/theme-common.json b/packages/docusaurus-theme-translations/locales/da/theme-common.json index af72b9b75336..0591720ce6c9 100644 --- a/packages/docusaurus-theme-translations/locales/da/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/da/theme-common.json @@ -21,12 +21,17 @@ "theme.blog.post.paginator.olderPost": "Tidligere indslag", "theme.blog.post.plurals": "Et indslag|{count} indslag", "theme.blog.post.readMore": "Læs mere", + "theme.blog.post.readMoreLabel": "Read more about {title}", "theme.blog.post.readingTime.plurals": "Et minuts læsetid|{readingTime} minutters læsetid", "theme.blog.sidebar.navAriaLabel": "Blog recent posts navigation", "theme.blog.tagTitle": "{nPosts} med følgende tag \"{tagName}\"", + "theme.colorToggle.ariaLabel": "Switch between dark and light mode (currently {mode})", + "theme.colorToggle.ariaLabel.mode.dark": "dark mode", + "theme.colorToggle.ariaLabel.mode.light": "light mode", "theme.common.editThisPage": "Rediger denne side", "theme.common.headingLinkTitle": "Direkte link til overskrift", "theme.common.skipToMainContent": "Hop til hovedindhold", + "theme.docs.DocCard.categoryDescription": "{count} items", "theme.docs.paginator.navAriaLabel": "Dokumentside navigation", "theme.docs.paginator.next": "Næste", "theme.docs.paginator.previous": "Tidligere", @@ -36,6 +41,7 @@ "theme.docs.sidebar.expandButtonTitle": "Udvid sidenavigation", "theme.docs.tagDocListPageTitle": "{nDocsTagged} with \"{tagName}\"", "theme.docs.tagDocListPageTitle.nDocsTagged": "One doc tagged|{count} docs tagged", + "theme.docs.versionBadge.label": "Version: {versionLabel}", "theme.docs.versions.latestVersionLinkLabel": "seneste version", "theme.docs.versions.latestVersionSuggestionLabel": "For seneste dokumentation, se {latestVersionLink} ({versionLabel}).", "theme.docs.versions.unmaintainedVersionLabel": "Dette er dokumentationen for {siteTitle} {versionLabel}, som ikke længere bliver aktivt vedligeholdt.", @@ -43,6 +49,7 @@ "theme.lastUpdated.atDate": " den {date}", "theme.lastUpdated.byUser": " af {user}", "theme.lastUpdated.lastUpdatedAtBy": "Senest opdateret{atDate}{byUser}", + "theme.navbar.mobileLanguageDropdown.label": "Languages", "theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": "← Back to main menu", "theme.navbar.mobileVersionsDropdown.label": "Versions", "theme.tags.tagsListLabel": "Tags:", diff --git a/packages/docusaurus-theme-translations/locales/da/theme-search-algolia.json b/packages/docusaurus-theme-translations/locales/da/theme-search-algolia.json index a67cec19d011..7d5a5364d146 100644 --- a/packages/docusaurus-theme-translations/locales/da/theme-search-algolia.json +++ b/packages/docusaurus-theme-translations/locales/da/theme-search-algolia.json @@ -1,5 +1,6 @@ { "theme.SearchBar.label": "Søg", + "theme.SearchBar.seeAll": "See all {count} results", "theme.SearchPage.algoliaLabel": "Søg med Algolia", "theme.SearchPage.documentsFound.plurals": "Et dokument fundet|{count} dokumenter fundet", "theme.SearchPage.emptyResultsTitle": "Søg i dokumentationen", diff --git a/packages/docusaurus-theme-translations/locales/de/plugin-ideal-image.json b/packages/docusaurus-theme-translations/locales/de/plugin-ideal-image.json index 3576b692c08d..eda06f750f11 100644 --- a/packages/docusaurus-theme-translations/locales/de/plugin-ideal-image.json +++ b/packages/docusaurus-theme-translations/locales/de/plugin-ideal-image.json @@ -1,7 +1,7 @@ { - "theme.IdealImageMessage.404error": "404. Image not found", - "theme.IdealImageMessage.error": "Error. Click to reload", - "theme.IdealImageMessage.load": "Click to load{sizeMessage}", - "theme.IdealImageMessage.loading": "Loading...", - "theme.IdealImageMessage.offline": "Your browser is offline. Image not loaded" + "theme.IdealImageMessage.404error": "404. Bild nicht gefunden.", + "theme.IdealImageMessage.error": "Fehler. Zum neu laden klicken", + "theme.IdealImageMessage.load": "Zum Laden klicken {sizeMessage}", + "theme.IdealImageMessage.loading": "Wird geladen...", + "theme.IdealImageMessage.offline": "Browser ist offline. Bild nicht geladen" } diff --git a/packages/docusaurus-theme-translations/locales/de/theme-common.json b/packages/docusaurus-theme-translations/locales/de/theme-common.json index 95e7017f08e8..54c567dedeec 100644 --- a/packages/docusaurus-theme-translations/locales/de/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/de/theme-common.json @@ -4,15 +4,15 @@ "theme.CodeBlock.copied": "Kopiert", "theme.CodeBlock.copy": "Kopieren", "theme.CodeBlock.copyButtonAriaLabel": "In die Zwischenablage kopieren", - "theme.DocSidebarItem.toggleCollapsedCategoryAriaLabel": "Toggle the collapsible sidebar category '{label}'", - "theme.ErrorPageContent.title": "This page crashed.", - "theme.ErrorPageContent.tryAgain": "Try again", + "theme.DocSidebarItem.toggleCollapsedCategoryAriaLabel": "Umschalten der Seitenleiste mit einklappbarer Kategorie '{label}'", + "theme.ErrorPageContent.title": "Die Seite ist abgestürzt.", + "theme.ErrorPageContent.tryAgain": "Nochmal versuchen", "theme.NotFound.p1": "Wir konnten nicht finden, wonach Sie gesucht haben.", "theme.NotFound.p2": "Bitte kontaktieren Sie den Besitzer der Seite, die Sie mit der ursprünglichen URL verlinkt hat, und teilen Sie ihm mit, dass der Link nicht mehr funktioniert.", "theme.NotFound.title": "Seite nicht gefunden", "theme.TOCCollapsible.toggleButtonLabel": "Auf dieser Seite", - "theme.blog.archive.description": "Archive", - "theme.blog.archive.title": "Archive", + "theme.blog.archive.description": "Archiv", + "theme.blog.archive.title": "Archiv", "theme.blog.paginator.navAriaLabel": "Navigation der Blog-Listenseite", "theme.blog.paginator.newerEntries": "Neuere Einträge", "theme.blog.paginator.olderEntries": "Ältere Einträge", @@ -21,12 +21,17 @@ "theme.blog.post.paginator.olderPost": "Älterer Post", "theme.blog.post.plurals": "Ein Post|{count} Posts", "theme.blog.post.readMore": "Mehr lesen", + "theme.blog.post.readMoreLabel": "Mehr lesen über {title}", "theme.blog.post.readingTime.plurals": "Eine Minute Lesezeit|{readingTime} Minuten Lesezeit", - "theme.blog.sidebar.navAriaLabel": "Blog recent posts navigation", + "theme.blog.sidebar.navAriaLabel": "Navigation der letzten Beiträge im Blog", "theme.blog.tagTitle": "{nPosts} getaggt mit \"{tagName}\"", + "theme.colorToggle.ariaLabel": "Umschalten zwischen dunkler und heller Ansicht (momentan {mode})", + "theme.colorToggle.ariaLabel.mode.dark": "dunkler Modus", + "theme.colorToggle.ariaLabel.mode.light": "heller Modus", "theme.common.editThisPage": "Diese Seite bearbeiten", "theme.common.headingLinkTitle": "Direkter Link zur Überschrift", "theme.common.skipToMainContent": "Zum Hauptinhalt springen", + "theme.docs.DocCard.categoryDescription": "{count} Einträge", "theme.docs.paginator.navAriaLabel": "Dokumentation Seiten Navigation", "theme.docs.paginator.next": "Weiter", "theme.docs.paginator.previous": "Zurück", @@ -34,8 +39,9 @@ "theme.docs.sidebar.collapseButtonTitle": "Seitenleiste einklappen", "theme.docs.sidebar.expandButtonAriaLabel": "Seitenleiste ausklappen", "theme.docs.sidebar.expandButtonTitle": "Seitenleiste ausklappen", - "theme.docs.tagDocListPageTitle": "{nDocsTagged} with \"{tagName}\"", - "theme.docs.tagDocListPageTitle.nDocsTagged": "One doc tagged|{count} docs tagged", + "theme.docs.tagDocListPageTitle": "{nDocsTagged} mit \"{tagName}\"", + "theme.docs.tagDocListPageTitle.nDocsTagged": "Ein doc getaggt|{count} docs getaggt", + "theme.docs.versionBadge.label": "Version: {versionLabel}", "theme.docs.versions.latestVersionLinkLabel": "letzte Version", "theme.docs.versions.latestVersionSuggestionLabel": "Für die aktuellste Dokumentation bitte auf {latestVersionLink} ({versionLabel}) gehen.", "theme.docs.versions.unmaintainedVersionLabel": "Das ist die Dokumentation für {siteTitle} {versionLabel} und wird nicht weiter gewartet.", @@ -43,8 +49,9 @@ "theme.lastUpdated.atDate": " am {date}", "theme.lastUpdated.byUser": " von {user}", "theme.lastUpdated.lastUpdatedAtBy": "Letztes Update{atDate}{byUser}", + "theme.navbar.mobileLanguageDropdown.label": "Sprachen", "theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": "← Zurück zum Hauptmenü", - "theme.navbar.mobileVersionsDropdown.label": "Versions", + "theme.navbar.mobileVersionsDropdown.label": "Versionen", "theme.tags.tagsListLabel": "Tags:", "theme.tags.tagsPageLink": "Alle Tags anzeigen", "theme.tags.tagsPageTitle": "Tags" diff --git a/packages/docusaurus-theme-translations/locales/de/theme-search-algolia.json b/packages/docusaurus-theme-translations/locales/de/theme-search-algolia.json index 9f4322be35ab..e0816abc0a81 100644 --- a/packages/docusaurus-theme-translations/locales/de/theme-search-algolia.json +++ b/packages/docusaurus-theme-translations/locales/de/theme-search-algolia.json @@ -1,5 +1,6 @@ { "theme.SearchBar.label": "Suche", + "theme.SearchBar.seeAll": "Alle {count} Ergebnisse anzeigen", "theme.SearchPage.algoliaLabel": "Suche von Algolia", "theme.SearchPage.documentsFound.plurals": "Ein Dokument gefunden|{count} Dokumente gefunden", "theme.SearchPage.emptyResultsTitle": "Suche in der Dokumentation", diff --git a/packages/docusaurus-theme-translations/locales/es/theme-common.json b/packages/docusaurus-theme-translations/locales/es/theme-common.json index 76c433601df0..fbf476146716 100644 --- a/packages/docusaurus-theme-translations/locales/es/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/es/theme-common.json @@ -21,12 +21,17 @@ "theme.blog.post.paginator.olderPost": "Publicación más antigua", "theme.blog.post.plurals": "Una publicación|{count} publicaciones", "theme.blog.post.readMore": "Leer Más", + "theme.blog.post.readMoreLabel": "Read more about {title}", "theme.blog.post.readingTime.plurals": "Lectura de un minuto|{readingTime} min de lectura", "theme.blog.sidebar.navAriaLabel": "Navegación de publicaciones recientes", "theme.blog.tagTitle": "{nPosts} etiquetados con \"{tagName}\"", + "theme.colorToggle.ariaLabel": "Switch between dark and light mode (currently {mode})", + "theme.colorToggle.ariaLabel.mode.dark": "dark mode", + "theme.colorToggle.ariaLabel.mode.light": "light mode", "theme.common.editThisPage": "Editar esta página", "theme.common.headingLinkTitle": "Enlace directo al encabezado", "theme.common.skipToMainContent": "Saltar al contenido principal", + "theme.docs.DocCard.categoryDescription": "{count} items", "theme.docs.paginator.navAriaLabel": "Navegación de páginas de documentos", "theme.docs.paginator.next": "Siguiente", "theme.docs.paginator.previous": "Anterior", @@ -36,6 +41,7 @@ "theme.docs.sidebar.expandButtonTitle": "Expandir barra lateral", "theme.docs.tagDocListPageTitle": "{nDocsTagged} con \"{tagName}\"", "theme.docs.tagDocListPageTitle.nDocsTagged": "Un documento etiquetado|{count} documentos etiquetados", + "theme.docs.versionBadge.label": "Version: {versionLabel}", "theme.docs.versions.latestVersionLinkLabel": "última versión", "theme.docs.versions.latestVersionSuggestionLabel": "Para la documentación actualizada, vea {latestVersionLink} ({versionLabel}).", "theme.docs.versions.unmaintainedVersionLabel": "Esta es documentación para {siteTitle} {versionLabel}, que ya no se mantiene activamente.", @@ -43,6 +49,7 @@ "theme.lastUpdated.atDate": " en {date}", "theme.lastUpdated.byUser": " por {user}", "theme.lastUpdated.lastUpdatedAtBy": "Última actualización{atDate}{byUser}", + "theme.navbar.mobileLanguageDropdown.label": "Languages", "theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": "← Volver al menú principal", "theme.navbar.mobileVersionsDropdown.label": "Versiones", "theme.tags.tagsListLabel": "Etiquetas:", diff --git a/packages/docusaurus-theme-translations/locales/es/theme-search-algolia.json b/packages/docusaurus-theme-translations/locales/es/theme-search-algolia.json index 7a005d971dd7..2641c1e1d5b1 100644 --- a/packages/docusaurus-theme-translations/locales/es/theme-search-algolia.json +++ b/packages/docusaurus-theme-translations/locales/es/theme-search-algolia.json @@ -1,5 +1,6 @@ { "theme.SearchBar.label": "Buscar", + "theme.SearchBar.seeAll": "See all {count} results", "theme.SearchPage.algoliaLabel": "Búsqueda por Algolia", "theme.SearchPage.documentsFound.plurals": "Un documento encontrado|{count} documentos encontrados", "theme.SearchPage.emptyResultsTitle": "Búsqueda en la documentación", diff --git a/packages/docusaurus-theme-translations/locales/fa/theme-common.json b/packages/docusaurus-theme-translations/locales/fa/theme-common.json index abb3da5930a2..1ba33a3417ca 100644 --- a/packages/docusaurus-theme-translations/locales/fa/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/fa/theme-common.json @@ -21,12 +21,17 @@ "theme.blog.post.paginator.olderPost": "پست قدیمی تر", "theme.blog.post.plurals": "یک پست|{count} پست", "theme.blog.post.readMore": "ادامه مطلب", + "theme.blog.post.readMoreLabel": "Read more about {title}", "theme.blog.post.readingTime.plurals": "خواندن ۱ دقیقه|خواندن {readingTime} دقیقه", "theme.blog.sidebar.navAriaLabel": "کنترل پست های اخیر وبلاگ", "theme.blog.tagTitle": "{nPosts} با برچسب \"{tagName}\"", + "theme.colorToggle.ariaLabel": "Switch between dark and light mode (currently {mode})", + "theme.colorToggle.ariaLabel.mode.dark": "dark mode", + "theme.colorToggle.ariaLabel.mode.light": "light mode", "theme.common.editThisPage": "ویرایش مطالب این صفحه", "theme.common.headingLinkTitle": "لینک مستقیم به عنوان", "theme.common.skipToMainContent": "پرش به مطلب اصلی", + "theme.docs.DocCard.categoryDescription": "{count} items", "theme.docs.paginator.navAriaLabel": "کنترل صفحات مطالب", "theme.docs.paginator.next": "بعدی", "theme.docs.paginator.previous": "قبلی", @@ -36,6 +41,7 @@ "theme.docs.sidebar.expandButtonTitle": "باز کردن نوار کناری", "theme.docs.tagDocListPageTitle": "{nDocsTagged} با \"{tagName}\"", "theme.docs.tagDocListPageTitle.nDocsTagged": "یک مطلب برچسب شده|{count} مطلب برچسب شده", + "theme.docs.versionBadge.label": "Version: {versionLabel}", "theme.docs.versions.latestVersionLinkLabel": "آخرین نسخه", "theme.docs.versions.latestVersionSuggestionLabel": "برای دیدن آخرین نسخه این متن، نسخه {latestVersionLink} ({versionLabel}) را ببینید.", "theme.docs.versions.unmaintainedVersionLabel": "نسخه {siteTitle} {versionLabel} دیگر به روزرسانی نمی شود.", @@ -43,6 +49,7 @@ "theme.lastUpdated.atDate": " در تاریخ {date}", "theme.lastUpdated.byUser": " توسط {user}", "theme.lastUpdated.lastUpdatedAtBy": "آخرین بروزرسانی{atDate}{byUser}", + "theme.navbar.mobileLanguageDropdown.label": "Languages", "theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": "→ بازگشت به منو اصلی", "theme.navbar.mobileVersionsDropdown.label": "نسخه ها", "theme.tags.tagsListLabel": "برچسب ها:", diff --git a/packages/docusaurus-theme-translations/locales/fa/theme-search-algolia.json b/packages/docusaurus-theme-translations/locales/fa/theme-search-algolia.json index 94540a252e4a..d456a81151f0 100644 --- a/packages/docusaurus-theme-translations/locales/fa/theme-search-algolia.json +++ b/packages/docusaurus-theme-translations/locales/fa/theme-search-algolia.json @@ -1,5 +1,6 @@ { "theme.SearchBar.label": "جستجو", + "theme.SearchBar.seeAll": "See all {count} results", "theme.SearchPage.algoliaLabel": "جستجو با Algolia", "theme.SearchPage.documentsFound.plurals": "یک مورد پیدا شد|{count} مورد پیدا شد", "theme.SearchPage.emptyResultsTitle": "جستجو در متن", diff --git a/packages/docusaurus-theme-translations/locales/fil/theme-common.json b/packages/docusaurus-theme-translations/locales/fil/theme-common.json index 866ab7b8cd3a..af8d60182d8b 100644 --- a/packages/docusaurus-theme-translations/locales/fil/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/fil/theme-common.json @@ -21,12 +21,17 @@ "theme.blog.post.paginator.olderPost": "Mas Lumang Post", "theme.blog.post.plurals": "Isang post|{count} na mga post", "theme.blog.post.readMore": "Basahin Pa", + "theme.blog.post.readMoreLabel": "Read more about {title}", "theme.blog.post.readingTime.plurals": "Isang minutong basahin|{readingTime} minutong basahin", "theme.blog.sidebar.navAriaLabel": "Blog recent posts navigation", "theme.blog.tagTitle": "{nPosts} na may tag na \"{tagName}\"", + "theme.colorToggle.ariaLabel": "Switch between dark and light mode (currently {mode})", + "theme.colorToggle.ariaLabel.mode.dark": "dark mode", + "theme.colorToggle.ariaLabel.mode.light": "light mode", "theme.common.editThisPage": "I-edit ang page", "theme.common.headingLinkTitle": "Direktang link patungo sa heading", "theme.common.skipToMainContent": "Lumaktaw patungo sa pangunahing content", + "theme.docs.DocCard.categoryDescription": "{count} items", "theme.docs.paginator.navAriaLabel": "Nabegasyón para sa mga pahinang docs.", "theme.docs.paginator.next": "Sumunod", "theme.docs.paginator.previous": "Naraaan", @@ -36,6 +41,7 @@ "theme.docs.sidebar.expandButtonTitle": "Palakihin ang sidebar", "theme.docs.tagDocListPageTitle": "{nDocsTagged} with \"{tagName}\"", "theme.docs.tagDocListPageTitle.nDocsTagged": "One doc tagged|{count} docs tagged", + "theme.docs.versionBadge.label": "Version: {versionLabel}", "theme.docs.versions.latestVersionLinkLabel": "pinakahuling bersiyón", "theme.docs.versions.latestVersionSuggestionLabel": "Para sa up-to-date na dokumentasyón, tingnan ang {latestVersionLink} ({versionLabel}).", "theme.docs.versions.unmaintainedVersionLabel": "Ito ay dokumentasyón para sa {siteTitle} {versionLabel} na hindi na aktibong mine-maintain.", @@ -43,6 +49,7 @@ "theme.lastUpdated.atDate": " noong {date}", "theme.lastUpdated.byUser": " ni {user}", "theme.lastUpdated.lastUpdatedAtBy": "Huling inapdeyt{atDate}{byUser}", + "theme.navbar.mobileLanguageDropdown.label": "Languages", "theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": "← Back to main menu", "theme.navbar.mobileVersionsDropdown.label": "Versions", "theme.tags.tagsListLabel": "Mga Tag:", diff --git a/packages/docusaurus-theme-translations/locales/fil/theme-search-algolia.json b/packages/docusaurus-theme-translations/locales/fil/theme-search-algolia.json index 8edf8eff1c82..a232ab50ae85 100644 --- a/packages/docusaurus-theme-translations/locales/fil/theme-search-algolia.json +++ b/packages/docusaurus-theme-translations/locales/fil/theme-search-algolia.json @@ -1,5 +1,6 @@ { "theme.SearchBar.label": "Maghanap", + "theme.SearchBar.seeAll": "See all {count} results", "theme.SearchPage.algoliaLabel": "Paghahanap hatid ng Algolia", "theme.SearchPage.documentsFound.plurals": "Isang dokumento ang nahanap|{count} na mga dokumento ang nahanap", "theme.SearchPage.emptyResultsTitle": "Maghanap sa dokumentasyón", diff --git a/packages/docusaurus-theme-translations/locales/fr/theme-common.json b/packages/docusaurus-theme-translations/locales/fr/theme-common.json index e0ef5d744898..efaa07c61103 100644 --- a/packages/docusaurus-theme-translations/locales/fr/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/fr/theme-common.json @@ -21,12 +21,17 @@ "theme.blog.post.paginator.olderPost": "Article plus ancien", "theme.blog.post.plurals": "Un article|{count} articles", "theme.blog.post.readMore": "Lire plus", + "theme.blog.post.readMoreLabel": "Read more about {title}", "theme.blog.post.readingTime.plurals": "Une minute de lecture|{readingTime} minutes de lecture", "theme.blog.sidebar.navAriaLabel": "Navigation article de blog récent", "theme.blog.tagTitle": "{nPosts} tagués avec « {tagName} »", + "theme.colorToggle.ariaLabel": "Switch between dark and light mode (currently {mode})", + "theme.colorToggle.ariaLabel.mode.dark": "dark mode", + "theme.colorToggle.ariaLabel.mode.light": "light mode", "theme.common.editThisPage": "Éditer cette page", "theme.common.headingLinkTitle": "Lien direct vers le titre", "theme.common.skipToMainContent": "Aller au contenu principal", + "theme.docs.DocCard.categoryDescription": "{count} items", "theme.docs.paginator.navAriaLabel": "Pagination des documents", "theme.docs.paginator.next": "Suivant", "theme.docs.paginator.previous": "Précédent", @@ -36,6 +41,7 @@ "theme.docs.sidebar.expandButtonTitle": "Déplier le menu latéral", "theme.docs.tagDocListPageTitle": "{nDocsTagged} avec \"{tagName}\"", "theme.docs.tagDocListPageTitle.nDocsTagged": "Un document tagué|{count} documents tagués", + "theme.docs.versionBadge.label": "Version: {versionLabel}", "theme.docs.versions.latestVersionLinkLabel": "dernière version", "theme.docs.versions.latestVersionSuggestionLabel": "Pour une documentation à jour, consultez la {latestVersionLink} ({versionLabel}).", "theme.docs.versions.unmaintainedVersionLabel": "Ceci est la documentation de {siteTitle} {versionLabel}, qui n'est plus activement maintenue.", @@ -43,6 +49,7 @@ "theme.lastUpdated.atDate": " le {date}", "theme.lastUpdated.byUser": " par {user}", "theme.lastUpdated.lastUpdatedAtBy": "Dernière mise à jour{atDate}{byUser}", + "theme.navbar.mobileLanguageDropdown.label": "Languages", "theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": "← Retour au menu principal", "theme.navbar.mobileVersionsDropdown.label": "Versions", "theme.tags.tagsListLabel": "Tags :", diff --git a/packages/docusaurus-theme-translations/locales/fr/theme-search-algolia.json b/packages/docusaurus-theme-translations/locales/fr/theme-search-algolia.json index e56e24b95c28..03e6cefd330b 100644 --- a/packages/docusaurus-theme-translations/locales/fr/theme-search-algolia.json +++ b/packages/docusaurus-theme-translations/locales/fr/theme-search-algolia.json @@ -1,5 +1,6 @@ { "theme.SearchBar.label": "Chercher", + "theme.SearchBar.seeAll": "See all {count} results", "theme.SearchPage.algoliaLabel": "Recherche par Algolia", "theme.SearchPage.documentsFound.plurals": "Un document trouvé|{count} documents trouvés", "theme.SearchPage.emptyResultsTitle": "Rechercher dans la documentation", diff --git a/packages/docusaurus-theme-translations/locales/he/theme-common.json b/packages/docusaurus-theme-translations/locales/he/theme-common.json index eeb3635d252d..fab29a6c24bf 100644 --- a/packages/docusaurus-theme-translations/locales/he/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/he/theme-common.json @@ -21,12 +21,17 @@ "theme.blog.post.paginator.olderPost": "ישן יותר", "theme.blog.post.plurals": "רשומה אחת|{count} רשומות|{count} רשומות|{count} רשומות", "theme.blog.post.readMore": "קרא עוד...", + "theme.blog.post.readMoreLabel": "Read more about {title}", "theme.blog.post.readingTime.plurals": "דקת קריאה|{readingTime} דקות קריאה|{readingTime} דקות קריאה|{readingTime} דקות קריאה", "theme.blog.sidebar.navAriaLabel": "מעבר לרשומות אחרונות בבלוג", "theme.blog.tagTitle": "{nPosts} עם התגית \"{tagName}\"", + "theme.colorToggle.ariaLabel": "Switch between dark and light mode (currently {mode})", + "theme.colorToggle.ariaLabel.mode.dark": "dark mode", + "theme.colorToggle.ariaLabel.mode.light": "light mode", "theme.common.editThisPage": "ערוך דף זה", "theme.common.headingLinkTitle": "קישור ישיר לכותרת", "theme.common.skipToMainContent": "דלג לתוכן הראשי", + "theme.docs.DocCard.categoryDescription": "{count} items", "theme.docs.paginator.navAriaLabel": "רשימת דוקומנטאציה", "theme.docs.paginator.next": "הבא", "theme.docs.paginator.previous": "הקודם", @@ -36,6 +41,7 @@ "theme.docs.sidebar.expandButtonTitle": "פתח", "theme.docs.tagDocListPageTitle": "{nDocsTagged} with \"{tagName}\"", "theme.docs.tagDocListPageTitle.nDocsTagged": "One doc tagged|{count} docs tagged", + "theme.docs.versionBadge.label": "Version: {versionLabel}", "theme.docs.versions.latestVersionLinkLabel": "גרסא אחרונה", "theme.docs.versions.latestVersionSuggestionLabel": "לדוקומנטאציה עדכנית, ראה {latestVersionLink} ({versionLabel}).", "theme.docs.versions.unmaintainedVersionLabel": "דוקומנטאציה זו {siteTitle} {versionLabel}, כבר לא נתמכת.", @@ -43,6 +49,7 @@ "theme.lastUpdated.atDate": " בתאריך {date}", "theme.lastUpdated.byUser": " על ידי {user}", "theme.lastUpdated.lastUpdatedAtBy": "עודכן{atDate}{byUser}", + "theme.navbar.mobileLanguageDropdown.label": "Languages", "theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": "← חזרה לתפריט הראשי", "theme.navbar.mobileVersionsDropdown.label": "Versions", "theme.tags.tagsListLabel": "תגיות:", diff --git a/packages/docusaurus-theme-translations/locales/he/theme-search-algolia.json b/packages/docusaurus-theme-translations/locales/he/theme-search-algolia.json index 5a0b5516235e..b970ec4bc7ac 100644 --- a/packages/docusaurus-theme-translations/locales/he/theme-search-algolia.json +++ b/packages/docusaurus-theme-translations/locales/he/theme-search-algolia.json @@ -1,5 +1,6 @@ { "theme.SearchBar.label": "חיפוש", + "theme.SearchBar.seeAll": "See all {count} results", "theme.SearchPage.algoliaLabel": "חיפוש by Algolia", "theme.SearchPage.documentsFound.plurals": "נמצא מסמך אחד|{count} מסמכים נמצאו|{count} מסמכים נמצאו|{count} מסמכים נמצאו", "theme.SearchPage.emptyResultsTitle": "חפש בדוקומנטאציה", diff --git a/packages/docusaurus-theme-translations/locales/hi/theme-common.json b/packages/docusaurus-theme-translations/locales/hi/theme-common.json index 8e829c51fd7f..1850be82282f 100644 --- a/packages/docusaurus-theme-translations/locales/hi/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/hi/theme-common.json @@ -21,12 +21,17 @@ "theme.blog.post.paginator.olderPost": "पुराने पोस्ट", "theme.blog.post.plurals": "एक पोस्ट|{count} पोस्ट", "theme.blog.post.readMore": "और पढ़ें", + "theme.blog.post.readMoreLabel": "Read more about {title}", "theme.blog.post.readingTime.plurals": "एक मिनट में पढ़ें|{readingTime} मिनट में पढ़ें", "theme.blog.sidebar.navAriaLabel": "नया ब्लॉग पोस्ट नेविगेशन", "theme.blog.tagTitle": "{nPosts} पोस्ट \"{tagName}\" टैग के साथ", + "theme.colorToggle.ariaLabel": "Switch between dark and light mode (currently {mode})", + "theme.colorToggle.ariaLabel.mode.dark": "dark mode", + "theme.colorToggle.ariaLabel.mode.light": "light mode", "theme.common.editThisPage": "इस पेज को बदलें", "theme.common.headingLinkTitle": "शीर्षक का सीधा लिंक", "theme.common.skipToMainContent": "मुख्य कंटेंट तक स्किप करें", + "theme.docs.DocCard.categoryDescription": "{count} items", "theme.docs.paginator.navAriaLabel": "डॉक्स पेज नेविगेशन", "theme.docs.paginator.next": "अगला", "theme.docs.paginator.previous": "पिछ्ला", @@ -36,6 +41,7 @@ "theme.docs.sidebar.expandButtonTitle": "साइडबार खोलें", "theme.docs.tagDocListPageTitle": "{nDocsTagged} with \"{tagName}\"", "theme.docs.tagDocListPageTitle.nDocsTagged": "One doc tagged|{count} docs tagged", + "theme.docs.versionBadge.label": "Version: {versionLabel}", "theme.docs.versions.latestVersionLinkLabel": "सबसे नया वर्जन", "theme.docs.versions.latestVersionSuggestionLabel": "अप-टू-डेट डॉक्यूमेंटेशन के लिए {latestVersionLink} ({versionLabel}) देखें।", "theme.docs.versions.unmaintainedVersionLabel": "यह {siteTitle} {versionLabel} के लिए डॉक्यूमेंटेशन है, जिसे अब सक्रिय रूप से नहीं बनाए रखा गया है।", @@ -43,6 +49,7 @@ "theme.lastUpdated.atDate": " {date} पर", "theme.lastUpdated.byUser": " {user} द्वारा", "theme.lastUpdated.lastUpdatedAtBy": "आखरी अपडेट{atDate}{byUser}", + "theme.navbar.mobileLanguageDropdown.label": "Languages", "theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": "← मुख्य मेनू में वापस जाएं", "theme.navbar.mobileVersionsDropdown.label": "Versions", "theme.tags.tagsListLabel": "टैग:", diff --git a/packages/docusaurus-theme-translations/locales/hi/theme-search-algolia.json b/packages/docusaurus-theme-translations/locales/hi/theme-search-algolia.json index e974c8389921..04f8cc5ba05a 100644 --- a/packages/docusaurus-theme-translations/locales/hi/theme-search-algolia.json +++ b/packages/docusaurus-theme-translations/locales/hi/theme-search-algolia.json @@ -1,5 +1,6 @@ { "theme.SearchBar.label": "खोज करें", + "theme.SearchBar.seeAll": "See all {count} results", "theme.SearchPage.algoliaLabel": "अल्गोलिया द्वारा खोजें", "theme.SearchPage.documentsFound.plurals": "एक डॉक्यूमेंट मिला|{count} डॉक्यूमेंट मिलें", "theme.SearchPage.emptyResultsTitle": "डॉक्यूमेंटेशन में खोजें", diff --git a/packages/docusaurus-theme-translations/locales/it/theme-common.json b/packages/docusaurus-theme-translations/locales/it/theme-common.json index 0f4012ee0359..feed8869eb80 100644 --- a/packages/docusaurus-theme-translations/locales/it/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/it/theme-common.json @@ -21,12 +21,17 @@ "theme.blog.post.paginator.olderPost": "Post più vecchio", "theme.blog.post.plurals": "Un post|{count} post", "theme.blog.post.readMore": "Leggi di più", + "theme.blog.post.readMoreLabel": "Read more about {title}", "theme.blog.post.readingTime.plurals": "Lettura di 1 minuto|{readingTime} minuti di lettura", "theme.blog.sidebar.navAriaLabel": "Navigazione dei post recenti del blog", "theme.blog.tagTitle": "{nPosts} etichettati con \"{tagName}\"", + "theme.colorToggle.ariaLabel": "Switch between dark and light mode (currently {mode})", + "theme.colorToggle.ariaLabel.mode.dark": "dark mode", + "theme.colorToggle.ariaLabel.mode.light": "light mode", "theme.common.editThisPage": "Modifica questa pagina", "theme.common.headingLinkTitle": "Link diretto all'intestazione", "theme.common.skipToMainContent": "Passa al contenuto principale", + "theme.docs.DocCard.categoryDescription": "{count} items", "theme.docs.paginator.navAriaLabel": "Navigazione delle pagine dei documenti", "theme.docs.paginator.next": "Successivo", "theme.docs.paginator.previous": "Precedente", @@ -36,6 +41,7 @@ "theme.docs.sidebar.expandButtonTitle": "Espandi la barra laterale", "theme.docs.tagDocListPageTitle": "{nDocsTagged} con \"{tagName}\"", "theme.docs.tagDocListPageTitle.nDocsTagged": "Un documento etichettato|{count} documenti etichettati", + "theme.docs.versionBadge.label": "Version: {versionLabel}", "theme.docs.versions.latestVersionLinkLabel": "ultima versione", "theme.docs.versions.latestVersionSuggestionLabel": "Per la documentazione aggiornata, guarda la {latestVersionLink} ({versionLabel}).", "theme.docs.versions.unmaintainedVersionLabel": "Questa è la documentazione per {siteTitle} {versionLabel}, che non è più attivamente mantenuta.", @@ -43,6 +49,7 @@ "theme.lastUpdated.atDate": " il {date}", "theme.lastUpdated.byUser": " da {user}", "theme.lastUpdated.lastUpdatedAtBy": "Ultimo aggiornamento{atDate}{byUser}", + "theme.navbar.mobileLanguageDropdown.label": "Languages", "theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": "← Indietro al menu principale", "theme.navbar.mobileVersionsDropdown.label": "Versioni", "theme.tags.tagsListLabel": "Etichette:", diff --git a/packages/docusaurus-theme-translations/locales/it/theme-search-algolia.json b/packages/docusaurus-theme-translations/locales/it/theme-search-algolia.json index be4a5f585392..36efa98738d6 100644 --- a/packages/docusaurus-theme-translations/locales/it/theme-search-algolia.json +++ b/packages/docusaurus-theme-translations/locales/it/theme-search-algolia.json @@ -1,5 +1,6 @@ { "theme.SearchBar.label": "Cerca", + "theme.SearchBar.seeAll": "See all {count} results", "theme.SearchPage.algoliaLabel": "Ricerca tramite Algolia", "theme.SearchPage.documentsFound.plurals": "Un documento trovato|{count} documenti trovati", "theme.SearchPage.emptyResultsTitle": "Cerca nella documentazione", diff --git a/packages/docusaurus-theme-translations/locales/ja/theme-common.json b/packages/docusaurus-theme-translations/locales/ja/theme-common.json index 5c1d471ce89a..926428492064 100644 --- a/packages/docusaurus-theme-translations/locales/ja/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/ja/theme-common.json @@ -21,12 +21,17 @@ "theme.blog.post.paginator.olderPost": "過去の記事", "theme.blog.post.plurals": "{count}件", "theme.blog.post.readMore": "もっと見る", + "theme.blog.post.readMoreLabel": "Read more about {title}", "theme.blog.post.readingTime.plurals": "約{readingTime}分", "theme.blog.sidebar.navAriaLabel": "Blog recent posts navigation", "theme.blog.tagTitle": "「{tagName}」タグの記事が{nPosts}あります", + "theme.colorToggle.ariaLabel": "Switch between dark and light mode (currently {mode})", + "theme.colorToggle.ariaLabel.mode.dark": "dark mode", + "theme.colorToggle.ariaLabel.mode.light": "light mode", "theme.common.editThisPage": "このページを編集", "theme.common.headingLinkTitle": "見出しへの直接リンク", "theme.common.skipToMainContent": "メインコンテンツまでスキップ", + "theme.docs.DocCard.categoryDescription": "{count} items", "theme.docs.paginator.navAriaLabel": "ドキュメントのナビゲーション", "theme.docs.paginator.next": "次へ", "theme.docs.paginator.previous": "前へ", @@ -36,6 +41,7 @@ "theme.docs.sidebar.expandButtonTitle": "サイドバーを開く", "theme.docs.tagDocListPageTitle": "{nDocsTagged} with \"{tagName}\"", "theme.docs.tagDocListPageTitle.nDocsTagged": "One doc tagged|{count} docs tagged", + "theme.docs.versionBadge.label": "Version: {versionLabel}", "theme.docs.versions.latestVersionLinkLabel": "最新バージョン", "theme.docs.versions.latestVersionSuggestionLabel": "最新のドキュメントは{latestVersionLink} ({versionLabel}) を見てください。", "theme.docs.versions.unmaintainedVersionLabel": "これは{siteTitle} {versionLabel}のドキュメントで現在はアクティブにメンテナンスされていません。", @@ -43,6 +49,7 @@ "theme.lastUpdated.atDate": "{date}に", "theme.lastUpdated.byUser": "{user}が", "theme.lastUpdated.lastUpdatedAtBy": "{atDate}{byUser}最終更新", + "theme.navbar.mobileLanguageDropdown.label": "Languages", "theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": "← Back to main menu", "theme.navbar.mobileVersionsDropdown.label": "Versions", "theme.tags.tagsListLabel": "タグ:", diff --git a/packages/docusaurus-theme-translations/locales/ja/theme-search-algolia.json b/packages/docusaurus-theme-translations/locales/ja/theme-search-algolia.json index 5f6ef56479a1..21c13e5dce20 100644 --- a/packages/docusaurus-theme-translations/locales/ja/theme-search-algolia.json +++ b/packages/docusaurus-theme-translations/locales/ja/theme-search-algolia.json @@ -1,5 +1,6 @@ { "theme.SearchBar.label": "検索", + "theme.SearchBar.seeAll": "See all {count} results", "theme.SearchPage.algoliaLabel": "Algoliaで検索", "theme.SearchPage.documentsFound.plurals": "{count}件のドキュメントが見つかりました", "theme.SearchPage.emptyResultsTitle": "ドキュメントを検索", diff --git a/packages/docusaurus-theme-translations/locales/ko/theme-common.json b/packages/docusaurus-theme-translations/locales/ko/theme-common.json index 666455346160..688c695499d3 100644 --- a/packages/docusaurus-theme-translations/locales/ko/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/ko/theme-common.json @@ -5,7 +5,7 @@ "theme.CodeBlock.copy": "복사", "theme.CodeBlock.copyButtonAriaLabel": "클립보드에 코드 복사", "theme.DocSidebarItem.toggleCollapsedCategoryAriaLabel": "접을 수 있는 사이드바 분류 '{label}' 접기(펼치기)", - "theme.ErrorPageContent.title": "이 문서가 깨졌습니다.", + "theme.ErrorPageContent.title": "페이지에 오류가 발생하였습니다.", "theme.ErrorPageContent.tryAgain": "다시 시도해 보세요", "theme.NotFound.p1": "원하는 페이지를 찾을 수 없습니다.", "theme.NotFound.p2": "사이트 관리자에게 링크가 깨진 것을 알려주세요.", @@ -21,12 +21,17 @@ "theme.blog.post.paginator.olderPost": "다음 게시물", "theme.blog.post.plurals": "{count}개 게시물", "theme.blog.post.readMore": "자세히 보기", + "theme.blog.post.readMoreLabel": "{title} 에 대해 더 읽어보기", "theme.blog.post.readingTime.plurals": "약 {readingTime}분", "theme.blog.sidebar.navAriaLabel": "최근 블로그 문서 둘러보기", "theme.blog.tagTitle": "\"{tagName}\" 태그로 연결된 {nPosts}개의 게시물이 있습니다.", + "theme.colorToggle.ariaLabel": "어두운 모드와 밝은 모드 전환하기 (현재 {mode})", + "theme.colorToggle.ariaLabel.mode.dark": "어두운 모드", + "theme.colorToggle.ariaLabel.mode.light": "밝은 모드", "theme.common.editThisPage": "페이지 편집", "theme.common.headingLinkTitle": "제목으로 바로 가기", "theme.common.skipToMainContent": "본문으로 건너뛰기", + "theme.docs.DocCard.categoryDescription": "{count} 항목", "theme.docs.paginator.navAriaLabel": "문서 탐색", "theme.docs.paginator.next": "다음", "theme.docs.paginator.previous": "이전", @@ -36,13 +41,15 @@ "theme.docs.sidebar.expandButtonTitle": "사이드바 열기", "theme.docs.tagDocListPageTitle": "{nDocsTagged} \"{tagName}\" 태그에 분류되었습니다", "theme.docs.tagDocListPageTitle.nDocsTagged": "{count} 개 문서가", + "theme.docs.versionBadge.label": "Version: {versionLabel}", "theme.docs.versions.latestVersionLinkLabel": "최신 버전", "theme.docs.versions.latestVersionSuggestionLabel": "최신 문서는 {latestVersionLink} ({versionLabel})을 확인하세요.", "theme.docs.versions.unmaintainedVersionLabel": "{siteTitle} {versionLabel} 문서는 더 이상 업데이트되지 않습니다.", "theme.docs.versions.unreleasedVersionLabel": "{siteTitle} {versionLabel} 문서는 아직 정식 공개되지 않았습니다.", "theme.lastUpdated.atDate": " {date}에", "theme.lastUpdated.byUser": " {user}가", - "theme.lastUpdated.lastUpdatedAtBy": "{atDate}{byUser} 마지막으로 업데이트했습니다.", + "theme.lastUpdated.lastUpdatedAtBy": "최종 수정 : {atDate}{byUser}", + "theme.navbar.mobileLanguageDropdown.label": "Languages", "theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": "← 메인 메뉴로 돌아가기", "theme.navbar.mobileVersionsDropdown.label": "버전", "theme.tags.tagsListLabel": "태그:", diff --git a/packages/docusaurus-theme-translations/locales/ko/theme-search-algolia.json b/packages/docusaurus-theme-translations/locales/ko/theme-search-algolia.json index 19b0b21e00c8..149d93d623cc 100644 --- a/packages/docusaurus-theme-translations/locales/ko/theme-search-algolia.json +++ b/packages/docusaurus-theme-translations/locales/ko/theme-search-algolia.json @@ -1,5 +1,6 @@ { "theme.SearchBar.label": "검색", + "theme.SearchBar.seeAll": "See all {count} results", "theme.SearchPage.algoliaLabel": "Search by Algolia", "theme.SearchPage.documentsFound.plurals": "{count}개 문서를 찾았습니다.", "theme.SearchPage.emptyResultsTitle": "문서를 검색합니다.", diff --git a/packages/docusaurus-theme-translations/locales/pl/theme-common.json b/packages/docusaurus-theme-translations/locales/pl/theme-common.json index fa4060029c28..a4e7bf78fa1c 100644 --- a/packages/docusaurus-theme-translations/locales/pl/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/pl/theme-common.json @@ -21,12 +21,17 @@ "theme.blog.post.paginator.olderPost": "Starszy posty", "theme.blog.post.plurals": "Jeden post|{count} posty|{count} postów", "theme.blog.post.readMore": "Czytaj więcej", + "theme.blog.post.readMoreLabel": "Read more about {title}", "theme.blog.post.readingTime.plurals": "{readingTime} min aby przeczytać|{readingTime} min aby przeczytać|{readingTime} min aby przeczytać", "theme.blog.sidebar.navAriaLabel": "Blog recent posts navigation", "theme.blog.tagTitle": "{nPosts} z tagiem \"{tagName}\"", + "theme.colorToggle.ariaLabel": "Switch between dark and light mode (currently {mode})", + "theme.colorToggle.ariaLabel.mode.dark": "dark mode", + "theme.colorToggle.ariaLabel.mode.light": "light mode", "theme.common.editThisPage": "Edytuj tą stronę", "theme.common.headingLinkTitle": "Bezpośredni link do nagłówka", "theme.common.skipToMainContent": "Przejdź do głównej zawartości", + "theme.docs.DocCard.categoryDescription": "{count} items", "theme.docs.paginator.navAriaLabel": "Nawigacja na stronie dokumentacji", "theme.docs.paginator.next": "Następna strona", "theme.docs.paginator.previous": "Poprzednia strona", @@ -36,6 +41,7 @@ "theme.docs.sidebar.expandButtonTitle": "Rozszerz boczny panel", "theme.docs.tagDocListPageTitle": "{nDocsTagged} with \"{tagName}\"", "theme.docs.tagDocListPageTitle.nDocsTagged": "One doc tagged|{count} docs tagged", + "theme.docs.versionBadge.label": "Version: {versionLabel}", "theme.docs.versions.latestVersionLinkLabel": "bieżącej wersji", "theme.docs.versions.latestVersionSuggestionLabel": "Aby zobaczyć bieżącą dokumentację, przejdź do wersji {latestVersionLink} ({versionLabel}).", "theme.docs.versions.unmaintainedVersionLabel": "Ta dokumentacja dotyczy {siteTitle} w wersji {versionLabel} i nie jest już aktywnie aktualizowana.", @@ -43,6 +49,7 @@ "theme.lastUpdated.atDate": " dnia {date}", "theme.lastUpdated.byUser": " przez {user}", "theme.lastUpdated.lastUpdatedAtBy": "Ostatnia aktualizacja{atDate}{byUser}", + "theme.navbar.mobileLanguageDropdown.label": "Languages", "theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": "← Back to main menu", "theme.navbar.mobileVersionsDropdown.label": "Versions", "theme.tags.tagsListLabel": "Tagi:", diff --git a/packages/docusaurus-theme-translations/locales/pl/theme-search-algolia.json b/packages/docusaurus-theme-translations/locales/pl/theme-search-algolia.json index ba039366ec83..03b178ca9e2e 100644 --- a/packages/docusaurus-theme-translations/locales/pl/theme-search-algolia.json +++ b/packages/docusaurus-theme-translations/locales/pl/theme-search-algolia.json @@ -1,5 +1,6 @@ { "theme.SearchBar.label": "Szukaj", + "theme.SearchBar.seeAll": "See all {count} results", "theme.SearchPage.algoliaLabel": "Dostawca rozwiązania Algolia", "theme.SearchPage.documentsFound.plurals": "One document found|{count} documents found", "theme.SearchPage.emptyResultsTitle": "Wyszukaj w dokumentacji", diff --git a/packages/docusaurus-theme-translations/locales/pt-BR/theme-common.json b/packages/docusaurus-theme-translations/locales/pt-BR/theme-common.json index 0dde14a48ff1..31a708844d37 100644 --- a/packages/docusaurus-theme-translations/locales/pt-BR/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/pt-BR/theme-common.json @@ -21,12 +21,17 @@ "theme.blog.post.paginator.olderPost": "Postagem mais antiga", "theme.blog.post.plurals": "Uma postagem|{count} postagens", "theme.blog.post.readMore": "Leia Mais", + "theme.blog.post.readMoreLabel": "Read more about {title}", "theme.blog.post.readingTime.plurals": "Leitura de um minuto|Leitura de {readingTime} minutos", "theme.blog.sidebar.navAriaLabel": "Blog recent posts navigation", "theme.blog.tagTitle": "{nPosts} marcadas com \"{tagName}\"", + "theme.colorToggle.ariaLabel": "Switch between dark and light mode (currently {mode})", + "theme.colorToggle.ariaLabel.mode.dark": "dark mode", + "theme.colorToggle.ariaLabel.mode.light": "light mode", "theme.common.editThisPage": "Editar essa página", "theme.common.headingLinkTitle": "Link direto para o título", "theme.common.skipToMainContent": "Pular para o conteúdo principal", + "theme.docs.DocCard.categoryDescription": "{count} items", "theme.docs.paginator.navAriaLabel": "Navigação das páginas de documentação", "theme.docs.paginator.next": "Próxima", "theme.docs.paginator.previous": "Anterior", @@ -36,6 +41,7 @@ "theme.docs.sidebar.expandButtonTitle": "Expandir painel lateral", "theme.docs.tagDocListPageTitle": "{nDocsTagged} com \"{tagName}\"", "theme.docs.tagDocListPageTitle.nDocsTagged": "Um documento selecionado|{count} documentos selecionados", + "theme.docs.versionBadge.label": "Version: {versionLabel}", "theme.docs.versions.latestVersionLinkLabel": "última versão", "theme.docs.versions.latestVersionSuggestionLabel": "Para a documentação atualizada, veja: {latestVersionLink} ({versionLabel}).", "theme.docs.versions.unmaintainedVersionLabel": "Esta é a documentação para {siteTitle} {versionLabel}, que não é mais mantida ativamente.", @@ -43,6 +49,7 @@ "theme.lastUpdated.atDate": " em {date}", "theme.lastUpdated.byUser": " por {user}", "theme.lastUpdated.lastUpdatedAtBy": "Última atualização {atDate}{byUser}", + "theme.navbar.mobileLanguageDropdown.label": "Languages", "theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": "← Voltar para o menu principal", "theme.navbar.mobileVersionsDropdown.label": "Versions", "theme.tags.tagsListLabel": "Marcadores:", diff --git a/packages/docusaurus-theme-translations/locales/pt-BR/theme-search-algolia.json b/packages/docusaurus-theme-translations/locales/pt-BR/theme-search-algolia.json index de2228134314..8ba752bb1363 100644 --- a/packages/docusaurus-theme-translations/locales/pt-BR/theme-search-algolia.json +++ b/packages/docusaurus-theme-translations/locales/pt-BR/theme-search-algolia.json @@ -1,5 +1,6 @@ { "theme.SearchBar.label": "Buscar", + "theme.SearchBar.seeAll": "See all {count} results", "theme.SearchPage.algoliaLabel": "Busca feita por Algolia", "theme.SearchPage.documentsFound.plurals": "Um documento encontrado|{count} documentos encontrados", "theme.SearchPage.emptyResultsTitle": "Busca da documentação", diff --git a/packages/docusaurus-theme-translations/locales/pt-PT/theme-common.json b/packages/docusaurus-theme-translations/locales/pt-PT/theme-common.json index 755fd1a8a0f1..349ce35e090b 100644 --- a/packages/docusaurus-theme-translations/locales/pt-PT/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/pt-PT/theme-common.json @@ -21,12 +21,17 @@ "theme.blog.post.paginator.olderPost": "Publicação mais antiga", "theme.blog.post.plurals": "Uma publicação|{count} publicações", "theme.blog.post.readMore": "Ler mais", + "theme.blog.post.readMoreLabel": "Read more about {title}", "theme.blog.post.readingTime.plurals": "Leitura de um minuto|Leitura de {readingTime} minutos", "theme.blog.sidebar.navAriaLabel": "Blog recent posts navigation", "theme.blog.tagTitle": "{nPosts} marcadas com \"{tagName}\"", + "theme.colorToggle.ariaLabel": "Switch between dark and light mode (currently {mode})", + "theme.colorToggle.ariaLabel.mode.dark": "dark mode", + "theme.colorToggle.ariaLabel.mode.light": "light mode", "theme.common.editThisPage": "Editar esta página", "theme.common.headingLinkTitle": "Link direto para o título", "theme.common.skipToMainContent": "Saltar para o conteúdo principal", + "theme.docs.DocCard.categoryDescription": "{count} items", "theme.docs.paginator.navAriaLabel": "Navigação das páginas de documentação", "theme.docs.paginator.next": "Próxima", "theme.docs.paginator.previous": "Anterior", @@ -36,6 +41,7 @@ "theme.docs.sidebar.expandButtonTitle": "Expandir barra lateral", "theme.docs.tagDocListPageTitle": "{nDocsTagged} with \"{tagName}\"", "theme.docs.tagDocListPageTitle.nDocsTagged": "One doc tagged|{count} docs tagged", + "theme.docs.versionBadge.label": "Version: {versionLabel}", "theme.docs.versions.latestVersionLinkLabel": "última versão", "theme.docs.versions.latestVersionSuggestionLabel": "Para a documentação atualizada, veja: {latestVersionLink} ({versionLabel}).", "theme.docs.versions.unmaintainedVersionLabel": "Esta é a documentação para {siteTitle} {versionLabel}, que já não é mantida ativamente.", @@ -43,6 +49,7 @@ "theme.lastUpdated.atDate": " a {date}", "theme.lastUpdated.byUser": " por {user}", "theme.lastUpdated.lastUpdatedAtBy": "Última atualização{atDate}{byUser}", + "theme.navbar.mobileLanguageDropdown.label": "Languages", "theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": "← Back to main menu", "theme.navbar.mobileVersionsDropdown.label": "Versions", "theme.tags.tagsListLabel": "Tags:", diff --git a/packages/docusaurus-theme-translations/locales/pt-PT/theme-search-algolia.json b/packages/docusaurus-theme-translations/locales/pt-PT/theme-search-algolia.json index 34130eb435c1..27ab1b3c58a5 100644 --- a/packages/docusaurus-theme-translations/locales/pt-PT/theme-search-algolia.json +++ b/packages/docusaurus-theme-translations/locales/pt-PT/theme-search-algolia.json @@ -1,5 +1,6 @@ { "theme.SearchBar.label": "Pesquisar", + "theme.SearchBar.seeAll": "See all {count} results", "theme.SearchPage.algoliaLabel": "Pesquisa por Algolia", "theme.SearchPage.documentsFound.plurals": "Um documento encontrado|{count} documentos encontrados", "theme.SearchPage.emptyResultsTitle": "Pesquisar pela documentação", diff --git a/packages/docusaurus-theme-translations/locales/ru/plugin-ideal-image.json b/packages/docusaurus-theme-translations/locales/ru/plugin-ideal-image.json index 3576b692c08d..8790da025cee 100644 --- a/packages/docusaurus-theme-translations/locales/ru/plugin-ideal-image.json +++ b/packages/docusaurus-theme-translations/locales/ru/plugin-ideal-image.json @@ -1,7 +1,7 @@ { - "theme.IdealImageMessage.404error": "404. Image not found", - "theme.IdealImageMessage.error": "Error. Click to reload", - "theme.IdealImageMessage.load": "Click to load{sizeMessage}", - "theme.IdealImageMessage.loading": "Loading...", - "theme.IdealImageMessage.offline": "Your browser is offline. Image not loaded" + "theme.IdealImageMessage.404error": "404. Изображение не найдено", + "theme.IdealImageMessage.error": "Ошибка. Нажмите для перезагрузки", + "theme.IdealImageMessage.load": "Нажмите для загрузки {sizeMessage}", + "theme.IdealImageMessage.loading": "Загрузка...", + "theme.IdealImageMessage.offline": "Ваш браузер находится в автономном режиме. Изображение не загружено" } diff --git a/packages/docusaurus-theme-translations/locales/ru/theme-common.json b/packages/docusaurus-theme-translations/locales/ru/theme-common.json index 6a12eb4ff9d8..36cc2692ebe4 100644 --- a/packages/docusaurus-theme-translations/locales/ru/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/ru/theme-common.json @@ -1,18 +1,18 @@ { "theme.AnnouncementBar.closeButtonAriaLabel": "Закрыть", - "theme.BackToTopButton.buttonAriaLabel": "Scroll back to top", + "theme.BackToTopButton.buttonAriaLabel": "Прокрутка к началу", "theme.CodeBlock.copied": "Скопировано", "theme.CodeBlock.copy": "Скопировать", "theme.CodeBlock.copyButtonAriaLabel": "Скопировать в буфер обмена", - "theme.DocSidebarItem.toggleCollapsedCategoryAriaLabel": "Toggle the collapsible sidebar category '{label}'", - "theme.ErrorPageContent.title": "This page crashed.", - "theme.ErrorPageContent.tryAgain": "Try again", + "theme.DocSidebarItem.toggleCollapsedCategoryAriaLabel": "Свернуть/развернуть категорию '{label}'", + "theme.ErrorPageContent.title": "На странице произошёл сбой.", + "theme.ErrorPageContent.tryAgain": "Попробуйте ещё раз", "theme.NotFound.p1": "К сожалению, мы не смогли найти запрашиваемую вами страницу.", "theme.NotFound.p2": "Пожалуйста, обратитесь к владельцу сайта, с которого вы перешли на эту ссылку, чтобы сообщить ему, что ссылка не работает.", "theme.NotFound.title": "Страница не найдена", "theme.TOCCollapsible.toggleButtonLabel": "Содержание этой страницы", - "theme.blog.archive.description": "Archive", - "theme.blog.archive.title": "Archive", + "theme.blog.archive.description": "Архив", + "theme.blog.archive.title": "Архив", "theme.blog.paginator.navAriaLabel": "Навигация по странице списка блогов", "theme.blog.paginator.newerEntries": "Следующие записи", "theme.blog.paginator.olderEntries": "Предыдущие записи", @@ -21,12 +21,17 @@ "theme.blog.post.paginator.olderPost": "Предыдущий пост", "theme.blog.post.plurals": "{count} запись|{count} записи|{count} записей", "theme.blog.post.readMore": "Читать дальше", + "theme.blog.post.readMoreLabel": "Подробнее о {title}", "theme.blog.post.readingTime.plurals": "{readingTime} мин. чтения", "theme.blog.sidebar.navAriaLabel": "Навигация по последним постам в блоге", "theme.blog.tagTitle": "{nPosts} с тегом \"{tagName}\"", + "theme.colorToggle.ariaLabel": "Переключение между темным и светлым режимом (сейчас используется {mode})", + "theme.colorToggle.ariaLabel.mode.dark": "Тёмный режим", + "theme.colorToggle.ariaLabel.mode.light": "Светлый режим", "theme.common.editThisPage": "Отредактировать эту страницу", "theme.common.headingLinkTitle": "Прямая ссылка на этот заголовок", "theme.common.skipToMainContent": "Перейти к основному содержимому", + "theme.docs.DocCard.categoryDescription": "{count} элемент|{count} элемента|{count} элементов", "theme.docs.paginator.navAriaLabel": "Навигация по странице документации", "theme.docs.paginator.next": "Следующая страница", "theme.docs.paginator.previous": "Предыдущая страница", @@ -34,8 +39,9 @@ "theme.docs.sidebar.collapseButtonTitle": "Свернуть сайдбар", "theme.docs.sidebar.expandButtonAriaLabel": "Развернуть сайдбар", "theme.docs.sidebar.expandButtonTitle": "Развернуть сайдбар", - "theme.docs.tagDocListPageTitle": "{nDocsTagged} with \"{tagName}\"", - "theme.docs.tagDocListPageTitle.nDocsTagged": "One doc tagged|{count} docs tagged", + "theme.docs.tagDocListPageTitle": "{nDocsTagged} с тегом \"{tagName}\"", + "theme.docs.tagDocListPageTitle.nDocsTagged": "Одна страница|{count} страницы|{count} страниц", + "theme.docs.versionBadge.label": "Версия: {versionLabel}", "theme.docs.versions.latestVersionLinkLabel": "последней версии", "theme.docs.versions.latestVersionSuggestionLabel": "Актуальная документация находится на странице {latestVersionLink} ({versionLabel}).", "theme.docs.versions.unmaintainedVersionLabel": "Это документация {siteTitle} для версии {versionLabel}, которая уже не поддерживается.", @@ -43,8 +49,9 @@ "theme.lastUpdated.atDate": " {date}", "theme.lastUpdated.byUser": " от {user}", "theme.lastUpdated.lastUpdatedAtBy": "Последнее обновление{atDate}{byUser}", + "theme.navbar.mobileLanguageDropdown.label": "Языки", "theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": "← Перейти к главному меню", - "theme.navbar.mobileVersionsDropdown.label": "Versions", + "theme.navbar.mobileVersionsDropdown.label": "Версии", "theme.tags.tagsListLabel": "Теги:", "theme.tags.tagsPageLink": "Посмотреть все теги", "theme.tags.tagsPageTitle": "Теги" diff --git a/packages/docusaurus-theme-translations/locales/ru/theme-search-algolia.json b/packages/docusaurus-theme-translations/locales/ru/theme-search-algolia.json index f165f3c5d2cd..2142bc727590 100644 --- a/packages/docusaurus-theme-translations/locales/ru/theme-search-algolia.json +++ b/packages/docusaurus-theme-translations/locales/ru/theme-search-algolia.json @@ -1,5 +1,6 @@ { "theme.SearchBar.label": "Поиск", + "theme.SearchBar.seeAll": "Посмотреть все результаты ({count})", "theme.SearchPage.algoliaLabel": "Поиск от Algolia", "theme.SearchPage.documentsFound.plurals": "{count} документ|{count} документа|{count} документов", "theme.SearchPage.emptyResultsTitle": "Поиск по сайту", diff --git a/packages/docusaurus-theme-translations/locales/sr/theme-common.json b/packages/docusaurus-theme-translations/locales/sr/theme-common.json index f4ea8185609c..294ab67609a2 100644 --- a/packages/docusaurus-theme-translations/locales/sr/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/sr/theme-common.json @@ -21,12 +21,17 @@ "theme.blog.post.paginator.olderPost": "Стари пост", "theme.blog.post.plurals": "Један пост|{count} постова", "theme.blog.post.readMore": "Прочитајте више", + "theme.blog.post.readMoreLabel": "Read more about {title}", "theme.blog.post.readingTime.plurals": "Једноминутно читање|{readingTime} минута читања", "theme.blog.sidebar.navAriaLabel": "Недавни постови на блогу", "theme.blog.tagTitle": "{nPosts} означени са \"{tagName}\"", + "theme.colorToggle.ariaLabel": "Switch between dark and light mode (currently {mode})", + "theme.colorToggle.ariaLabel.mode.dark": "dark mode", + "theme.colorToggle.ariaLabel.mode.light": "light mode", "theme.common.editThisPage": "Уреди ову страницу", "theme.common.headingLinkTitle": "Веза до наслова", "theme.common.skipToMainContent": "Пређи на главни садржај", + "theme.docs.DocCard.categoryDescription": "{count} items", "theme.docs.paginator.navAriaLabel": "Навигација по документима", "theme.docs.paginator.next": "Даље", "theme.docs.paginator.previous": "Назад", @@ -36,6 +41,7 @@ "theme.docs.sidebar.expandButtonTitle": "Прошири бочну листу", "theme.docs.tagDocListPageTitle": "{nDocsTagged} означени са \"{tagName}\"", "theme.docs.tagDocListPageTitle.nDocsTagged": "Један документ означен|{count} означених докумената", + "theme.docs.versionBadge.label": "Version: {versionLabel}", "theme.docs.versions.latestVersionLinkLabel": "Најновија верзија", "theme.docs.versions.latestVersionSuggestionLabel": "За најновију верзију документације погледајте {latestVersionLink} ({versionLabel}).", "theme.docs.versions.unmaintainedVersionLabel": "Ово је документација за {siteTitle} {versionLabel}, која се више не одржава", @@ -43,6 +49,7 @@ "theme.lastUpdated.atDate": " на {date}", "theme.lastUpdated.byUser": " од {user}", "theme.lastUpdated.lastUpdatedAtBy": "Последња измена {atDate}{byUser}", + "theme.navbar.mobileLanguageDropdown.label": "Languages", "theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": "← Назад на главни мени", "theme.navbar.mobileVersionsDropdown.label": "Верзије", "theme.tags.tagsListLabel": "Ознаке:", diff --git a/packages/docusaurus-theme-translations/locales/sr/theme-search-algolia.json b/packages/docusaurus-theme-translations/locales/sr/theme-search-algolia.json index 5dc155e4fd8e..cb43c520b88d 100644 --- a/packages/docusaurus-theme-translations/locales/sr/theme-search-algolia.json +++ b/packages/docusaurus-theme-translations/locales/sr/theme-search-algolia.json @@ -1,5 +1,6 @@ { "theme.SearchBar.label": "Тражи", + "theme.SearchBar.seeAll": "See all {count} results", "theme.SearchPage.algoliaLabel": "Претрага из Algolia", "theme.SearchPage.documentsFound.plurals": "Један пронађен документ|{count} пронађених докумената", "theme.SearchPage.emptyResultsTitle": "Тражи документацију", diff --git a/packages/docusaurus-theme-translations/locales/tr/theme-common.json b/packages/docusaurus-theme-translations/locales/tr/theme-common.json index 173b29e2b4d4..b27a78a202af 100644 --- a/packages/docusaurus-theme-translations/locales/tr/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/tr/theme-common.json @@ -21,12 +21,17 @@ "theme.blog.post.paginator.olderPost": "Daha Eski Gönderi", "theme.blog.post.plurals": "Bir gönderi|{count} gönderi", "theme.blog.post.readMore": "Daha Fazla", + "theme.blog.post.readMoreLabel": "Read more about {title}", "theme.blog.post.readingTime.plurals": "{readingTime} dakikalık okuma|{readingTime} dakikalık okuma", "theme.blog.sidebar.navAriaLabel": "Blog son gönderiler navigasyonu", "theme.blog.tagTitle": "\"{tagName}\" ile etiketlenmiş {nPosts}", + "theme.colorToggle.ariaLabel": "Switch between dark and light mode (currently {mode})", + "theme.colorToggle.ariaLabel.mode.dark": "dark mode", + "theme.colorToggle.ariaLabel.mode.light": "light mode", "theme.common.editThisPage": "Bu sayfayı düzenle", "theme.common.headingLinkTitle": "Başlığa doğrudan bağlantı", "theme.common.skipToMainContent": "Ana içeriğe geç", + "theme.docs.DocCard.categoryDescription": "{count} items", "theme.docs.paginator.navAriaLabel": "Dokümanlar sayfası navigasyonu", "theme.docs.paginator.next": "Sonraki", "theme.docs.paginator.previous": "Önceki", @@ -36,6 +41,7 @@ "theme.docs.sidebar.expandButtonTitle": "Kenar çubuğunu genişlet", "theme.docs.tagDocListPageTitle": "\"{tagName}\" ile etiketlenmiş {nDocsTagged}", "theme.docs.tagDocListPageTitle.nDocsTagged": "Bir doküman etiketlendi|{count} doküman etiketlendi", + "theme.docs.versionBadge.label": "Version: {versionLabel}", "theme.docs.versions.latestVersionLinkLabel": "en son sürüm", "theme.docs.versions.latestVersionSuggestionLabel": "Güncel belgeler için bkz. {latestVersionLink} ({versionLabel}).", "theme.docs.versions.unmaintainedVersionLabel": "Bu, {siteTitle} {versionLabel} dokümantasyonudur ve bakımı sonlanmıştır.", @@ -43,6 +49,7 @@ "theme.lastUpdated.atDate": " {date} tarihinde", "theme.lastUpdated.byUser": " {user} tarafından", "theme.lastUpdated.lastUpdatedAtBy": "En son{atDate}{byUser} güncellendi", + "theme.navbar.mobileLanguageDropdown.label": "Languages", "theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": "← Ana menüye dön", "theme.navbar.mobileVersionsDropdown.label": "Versiyonlar", "theme.tags.tagsListLabel": "Etiketler:", diff --git a/packages/docusaurus-theme-translations/locales/tr/theme-search-algolia.json b/packages/docusaurus-theme-translations/locales/tr/theme-search-algolia.json index c258a0ae3452..0b0965cf5027 100644 --- a/packages/docusaurus-theme-translations/locales/tr/theme-search-algolia.json +++ b/packages/docusaurus-theme-translations/locales/tr/theme-search-algolia.json @@ -1,5 +1,6 @@ { "theme.SearchBar.label": "Ara", + "theme.SearchBar.seeAll": "See all {count} results", "theme.SearchPage.algoliaLabel": "Algolia ile Ara", "theme.SearchPage.documentsFound.plurals": "Bir döküman bulundu|{count} döküman bulundu", "theme.SearchPage.emptyResultsTitle": "Dokümanlarda ara", diff --git a/packages/docusaurus-theme-translations/locales/vi/plugin-ideal-image.json b/packages/docusaurus-theme-translations/locales/vi/plugin-ideal-image.json index 3576b692c08d..cd485c0ede20 100644 --- a/packages/docusaurus-theme-translations/locales/vi/plugin-ideal-image.json +++ b/packages/docusaurus-theme-translations/locales/vi/plugin-ideal-image.json @@ -1,7 +1,7 @@ { - "theme.IdealImageMessage.404error": "404. Image not found", - "theme.IdealImageMessage.error": "Error. Click to reload", - "theme.IdealImageMessage.load": "Click to load{sizeMessage}", - "theme.IdealImageMessage.loading": "Loading...", - "theme.IdealImageMessage.offline": "Your browser is offline. Image not loaded" + "theme.IdealImageMessage.404error": "404. Không tìm thấy ảnh", + "theme.IdealImageMessage.error": "Có lỗi. Nhấn để tải lại", + "theme.IdealImageMessage.load": "Nhấn để tải {sizeMessage}", + "theme.IdealImageMessage.loading": "Đang tái...", + "theme.IdealImageMessage.offline": "Hiện bạn đang ngoại tuyến. Không thể tải ảnh" } diff --git a/packages/docusaurus-theme-translations/locales/vi/theme-common.json b/packages/docusaurus-theme-translations/locales/vi/theme-common.json index 87fa89a4ddd4..f56dc9927cd4 100644 --- a/packages/docusaurus-theme-translations/locales/vi/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/vi/theme-common.json @@ -4,9 +4,9 @@ "theme.CodeBlock.copied": "Đã sao chép", "theme.CodeBlock.copy": "Sao chép", "theme.CodeBlock.copyButtonAriaLabel": "Sao chép code vào bộ nhớ tạm", - "theme.DocSidebarItem.toggleCollapsedCategoryAriaLabel": "Toggle the collapsible sidebar category '{label}'", - "theme.ErrorPageContent.title": "This page crashed.", - "theme.ErrorPageContent.tryAgain": "Try again", + "theme.DocSidebarItem.toggleCollapsedCategoryAriaLabel": "Chuyển đổi danh mục thanh bên có thể thu gọn '{label}'", + "theme.ErrorPageContent.title": "Trang này đã bị lỗi.", + "theme.ErrorPageContent.tryAgain": "Thử lại", "theme.NotFound.p1": "Chúng tôi không thể tìm thấy những gì bạn đang tìm kiếm.", "theme.NotFound.p2": "Vui lòng liên hệ với trang web đã dẫn bạn tới đây và thông báo cho họ biết rằng đường dẫn này bị hỏng.", "theme.NotFound.title": "Không tìm thấy trang", @@ -21,12 +21,17 @@ "theme.blog.post.paginator.olderPost": "Bài cũ hơn", "theme.blog.post.plurals": "Một bài viết|{count} bài viết", "theme.blog.post.readMore": "Đọc Tiếp", + "theme.blog.post.readMoreLabel": "Đọc thêm về {title}", "theme.blog.post.readingTime.plurals": "Một phút để đọc|{readingTime} phút để đọc", "theme.blog.sidebar.navAriaLabel": "Điều hướng các bài viết gần đây trên blog", "theme.blog.tagTitle": "{nPosts} được gắn thẻ \"{tagName}\"", + "theme.colorToggle.ariaLabel": "Chuyển đổi chế độ sáng và tối (hiện tại {mode})", + "theme.colorToggle.ariaLabel.mode.dark": "chế độ tối", + "theme.colorToggle.ariaLabel.mode.light": "chế độ sáng", "theme.common.editThisPage": "Sửa trang này", "theme.common.headingLinkTitle": "Đường dẫn trực tiếp tới đề mục này", "theme.common.skipToMainContent": "Nhảy tới nội dung", + "theme.docs.DocCard.categoryDescription": "{count} mục", "theme.docs.paginator.navAriaLabel": "Thanh điều hướng của trang tài liệu", "theme.docs.paginator.next": "Kế tiếp", "theme.docs.paginator.previous": "Trước", @@ -36,6 +41,7 @@ "theme.docs.sidebar.expandButtonTitle": "Mở rộng thanh bên", "theme.docs.tagDocListPageTitle": "{nDocsTagged} với \"{tagName}\"", "theme.docs.tagDocListPageTitle.nDocsTagged": "Một tài liệu đã gắn thẻ|{count} tài liệu đã gắn thẻ", + "theme.docs.versionBadge.label": "Version: {versionLabel}", "theme.docs.versions.latestVersionLinkLabel": "phiên bản mới nhất", "theme.docs.versions.latestVersionSuggestionLabel": "Để xem các cập nhật mới nhất, vui lòng xem phiên bản {latestVersionLink} ({versionLabel}).", "theme.docs.versions.unmaintainedVersionLabel": "Đây là tài liệu của {siteTitle} {versionLabel}, hiện không còn được bảo trì.", @@ -43,8 +49,9 @@ "theme.lastUpdated.atDate": " vào {date}", "theme.lastUpdated.byUser": " bởi {user}", "theme.lastUpdated.lastUpdatedAtBy": "Cập nhật lần cuối {atDate} bởi {byUser}", + "theme.navbar.mobileLanguageDropdown.label": "Ngôn ngữ", "theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": "← Trở lại menu chính", - "theme.navbar.mobileVersionsDropdown.label": "Versions", + "theme.navbar.mobileVersionsDropdown.label": "Phiên bản", "theme.tags.tagsListLabel": "Thẻ:", "theme.tags.tagsPageLink": "Xem tất cả Thẻ", "theme.tags.tagsPageTitle": "Thẻ" diff --git a/packages/docusaurus-theme-translations/locales/vi/theme-search-algolia.json b/packages/docusaurus-theme-translations/locales/vi/theme-search-algolia.json index 82f27ccca78c..d1e1fea0f1de 100644 --- a/packages/docusaurus-theme-translations/locales/vi/theme-search-algolia.json +++ b/packages/docusaurus-theme-translations/locales/vi/theme-search-algolia.json @@ -1,5 +1,6 @@ { "theme.SearchBar.label": "Tìm kiếm", + "theme.SearchBar.seeAll": "Xem {count} kết quả", "theme.SearchPage.algoliaLabel": "Tìm kiếm với Algolia", "theme.SearchPage.documentsFound.plurals": "Tìm thấy một kết quả|Tìm thấy {count} kết quả", "theme.SearchPage.emptyResultsTitle": "Tìm kiếm", diff --git a/packages/docusaurus-theme-translations/locales/zh-Hans/theme-common.json b/packages/docusaurus-theme-translations/locales/zh-Hans/theme-common.json index 37b8f836ce4b..d2579b9efeaa 100644 --- a/packages/docusaurus-theme-translations/locales/zh-Hans/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/zh-Hans/theme-common.json @@ -21,12 +21,17 @@ "theme.blog.post.paginator.olderPost": "较旧一篇", "theme.blog.post.plurals": "{count} 篇博文", "theme.blog.post.readMore": "阅读更多", + "theme.blog.post.readMoreLabel": "阅读 {title} 的全文", "theme.blog.post.readingTime.plurals": "{readingTime} 分钟阅读", "theme.blog.sidebar.navAriaLabel": "最近博文导航", "theme.blog.tagTitle": "{nPosts} 含有标签「{tagName}」", + "theme.colorToggle.ariaLabel": "切换浅色/暗黑模式(当前为{mode})", + "theme.colorToggle.ariaLabel.mode.dark": "暗黑模式", + "theme.colorToggle.ariaLabel.mode.light": "浅色模式", "theme.common.editThisPage": "编辑此页", "theme.common.headingLinkTitle": "标题的直接链接", "theme.common.skipToMainContent": "跳到主要内容", + "theme.docs.DocCard.categoryDescription": "{count} 个项目", "theme.docs.paginator.navAriaLabel": "文档分页导航", "theme.docs.paginator.next": "下一页", "theme.docs.paginator.previous": "上一页", @@ -34,8 +39,9 @@ "theme.docs.sidebar.collapseButtonTitle": "收起侧边栏", "theme.docs.sidebar.expandButtonAriaLabel": "展开侧边栏", "theme.docs.sidebar.expandButtonTitle": "展开侧边栏", - "theme.docs.tagDocListPageTitle": "{nDocsTagged} 篇带有标签「{tagName}」", + "theme.docs.tagDocListPageTitle": "{nDocsTagged}「{tagName}」", "theme.docs.tagDocListPageTitle.nDocsTagged": "{count} 篇文档带有标签", + "theme.docs.versionBadge.label": "版本:{versionLabel}", "theme.docs.versions.latestVersionLinkLabel": "最新版本", "theme.docs.versions.latestVersionSuggestionLabel": "最新的文档请参阅 {latestVersionLink} ({versionLabel})。", "theme.docs.versions.unmaintainedVersionLabel": "此为 {siteTitle} {versionLabel} 版的文档,现已不再积极维护。", @@ -43,6 +49,7 @@ "theme.lastUpdated.atDate": "于 {date} ", "theme.lastUpdated.byUser": "由 {user} ", "theme.lastUpdated.lastUpdatedAtBy": "最后{byUser}{atDate}更新", + "theme.navbar.mobileLanguageDropdown.label": "选择语言", "theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": "← 回到主菜单", "theme.navbar.mobileVersionsDropdown.label": "选择版本", "theme.tags.tagsListLabel": "标签:", diff --git a/packages/docusaurus-theme-translations/locales/zh-Hans/theme-search-algolia.json b/packages/docusaurus-theme-translations/locales/zh-Hans/theme-search-algolia.json index 32a67458e57d..7cc4c33e56da 100644 --- a/packages/docusaurus-theme-translations/locales/zh-Hans/theme-search-algolia.json +++ b/packages/docusaurus-theme-translations/locales/zh-Hans/theme-search-algolia.json @@ -1,5 +1,6 @@ { "theme.SearchBar.label": "搜索", + "theme.SearchBar.seeAll": "查看全部 {count} 个结果", "theme.SearchPage.algoliaLabel": "通过 Algolia 搜索", "theme.SearchPage.documentsFound.plurals": "找到 {count} 份文件", "theme.SearchPage.emptyResultsTitle": "在文档中搜索", diff --git a/packages/docusaurus-theme-translations/locales/zh-Hant/plugin-ideal-image.json b/packages/docusaurus-theme-translations/locales/zh-Hant/plugin-ideal-image.json index c32c46453289..173c62f02768 100644 --- a/packages/docusaurus-theme-translations/locales/zh-Hant/plugin-ideal-image.json +++ b/packages/docusaurus-theme-translations/locales/zh-Hant/plugin-ideal-image.json @@ -1,7 +1,7 @@ { "theme.IdealImageMessage.404error": "未找到圖片", - "theme.IdealImageMessage.error": "出現錯誤,點擊重試", - "theme.IdealImageMessage.load": "點擊以加載{sizeMessage}", - "theme.IdealImageMessage.loading": "加載中...", - "theme.IdealImageMessage.offline": "你的瀏覽器處於離線狀態。圖片未加載" + "theme.IdealImageMessage.error": "出現錯誤,按一下來重試", + "theme.IdealImageMessage.load": "按一下以載入{sizeMessage}", + "theme.IdealImageMessage.loading": "載入中...", + "theme.IdealImageMessage.offline": "你的瀏覽器處於離線狀態。圖片未載入" } diff --git a/packages/docusaurus-theme-translations/locales/zh-Hant/plugin-pwa.json b/packages/docusaurus-theme-translations/locales/zh-Hant/plugin-pwa.json index 67362bf03a89..e604689ffece 100644 --- a/packages/docusaurus-theme-translations/locales/zh-Hant/plugin-pwa.json +++ b/packages/docusaurus-theme-translations/locales/zh-Hant/plugin-pwa.json @@ -1,5 +1,5 @@ { "theme.PwaReloadPopup.closeButtonAriaLabel": "關閉", "theme.PwaReloadPopup.info": "有可用的新版本", - "theme.PwaReloadPopup.refreshButtonText": "刷新" + "theme.PwaReloadPopup.refreshButtonText": "重新整理" } diff --git a/packages/docusaurus-theme-translations/locales/zh-Hant/theme-common.json b/packages/docusaurus-theme-translations/locales/zh-Hant/theme-common.json index a4929b3352c5..81a5a7d36971 100644 --- a/packages/docusaurus-theme-translations/locales/zh-Hant/theme-common.json +++ b/packages/docusaurus-theme-translations/locales/zh-Hant/theme-common.json @@ -4,15 +4,15 @@ "theme.CodeBlock.copied": "複製成功", "theme.CodeBlock.copy": "複製", "theme.CodeBlock.copyButtonAriaLabel": "複製代碼至剪貼簿", - "theme.DocSidebarItem.toggleCollapsedCategoryAriaLabel": "打開/收起側邊欄菜單「{label}」", - "theme.ErrorPageContent.title": "頁面已崩潰。", + "theme.DocSidebarItem.toggleCollapsedCategoryAriaLabel": "打開/收起側邊欄選單「{label}」", + "theme.ErrorPageContent.title": "此頁已當機。", "theme.ErrorPageContent.tryAgain": "重試", - "theme.NotFound.p1": "我們找不到您要找的頁面。", + "theme.NotFound.p1": "我們沒有您要找的頁面。", "theme.NotFound.p2": "請聯絡原始連結來源網站的所有者,並通知他們連結已毀損。", "theme.NotFound.title": "找不到頁面", "theme.TOCCollapsible.toggleButtonLabel": "本頁導覽", - "theme.blog.archive.description": "歷史博文", - "theme.blog.archive.title": "歷史博文", + "theme.blog.archive.description": "歷史文章", + "theme.blog.archive.title": "歷史文章", "theme.blog.paginator.navAriaLabel": "部落格文章列表分頁導覽", "theme.blog.paginator.newerEntries": "較新的文章", "theme.blog.paginator.olderEntries": "較舊的文章", @@ -21,12 +21,17 @@ "theme.blog.post.paginator.olderPost": "較舊一篇", "theme.blog.post.plurals": "{count} 篇文章", "theme.blog.post.readMore": "閱讀更多", - "theme.blog.post.readingTime.plurals": "{readingTime} 分鐘閱讀", + "theme.blog.post.readMoreLabel": "閱讀 {title} 全文", + "theme.blog.post.readingTime.plurals": "閱讀時間約 {readingTime} 分鐘", "theme.blog.sidebar.navAriaLabel": "最近部落格文章導覽", "theme.blog.tagTitle": "{nPosts} 含有標籤「{tagName}」", + "theme.colorToggle.ariaLabel": "切換淺色/暗黑模式(當前為{mode})", + "theme.colorToggle.ariaLabel.mode.dark": "暗黑模式", + "theme.colorToggle.ariaLabel.mode.light": "淺色模式", "theme.common.editThisPage": "編輯此頁", "theme.common.headingLinkTitle": "標題的直接連結", "theme.common.skipToMainContent": "跳至主要内容", + "theme.docs.DocCard.categoryDescription": "{count} 個項目", "theme.docs.paginator.navAriaLabel": "文件分頁導覽", "theme.docs.paginator.next": "下一頁", "theme.docs.paginator.previous": "上一頁", @@ -34,8 +39,9 @@ "theme.docs.sidebar.collapseButtonTitle": "收起側邊欄", "theme.docs.sidebar.expandButtonAriaLabel": "展開側邊欄", "theme.docs.sidebar.expandButtonTitle": "展開側邊欄", - "theme.docs.tagDocListPageTitle": "{nDocsTagged} 篇帶有標籤「{tagName}」", + "theme.docs.tagDocListPageTitle": "{nDocsTagged}「{tagName}」", "theme.docs.tagDocListPageTitle.nDocsTagged": "{count} 篇文件帶有標籤", + "theme.docs.versionBadge.label": "版本:{versionLabel}", "theme.docs.versions.latestVersionLinkLabel": "最新版本", "theme.docs.versions.latestVersionSuggestionLabel": "最新的文件請參閱 {latestVersionLink} ({versionLabel})。", "theme.docs.versions.unmaintainedVersionLabel": "此為 {siteTitle} {versionLabel} 版的文件,現已不再積極維護。", @@ -43,9 +49,10 @@ "theme.lastUpdated.atDate": "於 {date} ", "theme.lastUpdated.byUser": "由 {user} ", "theme.lastUpdated.lastUpdatedAtBy": "最後{byUser}{atDate}更新", - "theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": "← 回到主菜單", + "theme.navbar.mobileLanguageDropdown.label": "選擇語言", + "theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": "← 回到主選單", "theme.navbar.mobileVersionsDropdown.label": "選擇版本", "theme.tags.tagsListLabel": "標籤:", - "theme.tags.tagsPageLink": "查看所有標籤", + "theme.tags.tagsPageLink": "檢視所有標籤", "theme.tags.tagsPageTitle": "標籤" } diff --git a/packages/docusaurus-theme-translations/locales/zh-Hant/theme-search-algolia.json b/packages/docusaurus-theme-translations/locales/zh-Hant/theme-search-algolia.json index 6fdf316a47b4..69f908f89198 100644 --- a/packages/docusaurus-theme-translations/locales/zh-Hant/theme-search-algolia.json +++ b/packages/docusaurus-theme-translations/locales/zh-Hant/theme-search-algolia.json @@ -1,10 +1,11 @@ { "theme.SearchBar.label": "搜尋", + "theme.SearchBar.seeAll": "查看全部 {count} 個結果", "theme.SearchPage.algoliaLabel": "透過 Algolia 搜尋", "theme.SearchPage.documentsFound.plurals": "找到 {count} 份文件", "theme.SearchPage.emptyResultsTitle": "在文件中搜尋", "theme.SearchPage.existingResultsTitle": "「{query}」的搜尋結果", - "theme.SearchPage.fetchingNewResults": "正在獲取新的搜尋結果...", + "theme.SearchPage.fetchingNewResults": "正在取得新的搜尋結果...", "theme.SearchPage.inputLabel": "搜尋", "theme.SearchPage.inputPlaceholder": "在此輸入搜尋字詞", "theme.SearchPage.noResultsText": "未找到任何結果" diff --git a/packages/docusaurus-theme-translations/package.json b/packages/docusaurus-theme-translations/package.json index 0bd360b6629d..64b10648c7a5 100644 --- a/packages/docusaurus-theme-translations/package.json +++ b/packages/docusaurus-theme-translations/package.json @@ -1,6 +1,6 @@ { "name": "@docusaurus/theme-translations", - "version": "2.0.0-beta.14", + "version": "2.0.0-beta.18", "description": "Docusaurus theme translations.", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -14,18 +14,18 @@ }, "license": "MIT", "scripts": { - "build": "tsc", - "watch": "tsc --watch", - "update": "node ./update.js" + "build": "tsc -p tsconfig.build.json", + "watch": "tsc -p tsconfig.build.json --watch", + "update": "node ./update.mjs" }, "dependencies": { - "fs-extra": "^10.0.0", + "fs-extra": "^10.0.1", "tslib": "^2.3.1" }, "devDependencies": { - "@docusaurus/core": "2.0.0-beta.14", - "@docusaurus/logger": "2.0.0-beta.14", - "lodash": "^4.17.20" + "@docusaurus/core": "2.0.0-beta.18", + "@docusaurus/logger": "2.0.0-beta.18", + "lodash": "^4.17.21" }, "engines": { "node": ">=14" diff --git a/packages/docusaurus-theme-translations/src/__tests__/__fixtures__/theme/index.js b/packages/docusaurus-theme-translations/src/__tests__/__fixtures__/theme/index.js new file mode 100644 index 000000000000..80b33a00c39d --- /dev/null +++ b/packages/docusaurus-theme-translations/src/__tests__/__fixtures__/theme/index.js @@ -0,0 +1,5 @@ +import Translate from '@docusaurus/Translate'; + +export default function Foo() { + return {index}; +} diff --git a/packages/docusaurus-theme-translations/src/__tests__/index.test.ts b/packages/docusaurus-theme-translations/src/__tests__/index.test.ts index b6e82aeb4937..980fa83d87ee 100644 --- a/packages/docusaurus-theme-translations/src/__tests__/index.test.ts +++ b/packages/docusaurus-theme-translations/src/__tests__/index.test.ts @@ -13,15 +13,15 @@ import { } from '../index'; describe('codeTranslationLocalesToTry', () => { - test('should return appropriate locale lists', () => { + it('returns appropriate locale lists', () => { expect(codeTranslationLocalesToTry('fr')).toEqual([ 'fr', 'fr-FR', 'fr-Latn', ]); expect(codeTranslationLocalesToTry('fr-FR')).toEqual(['fr-FR', 'fr']); - // Note: "pt" is expanded into "pt-BR", not "pt-PT", as "pt-BR" is more widely used! - // See https://github.com/facebook/docusaurus/pull/4536#issuecomment-810088783 + // Note: "pt" is expanded into "pt-BR", not "pt-PT", as "pt-BR" is more + // widely used! See https://github.com/facebook/docusaurus/pull/4536#issuecomment-810088783 expect(codeTranslationLocalesToTry('pt')).toEqual([ 'pt', 'pt-BR', @@ -56,7 +56,7 @@ describe('readDefaultCodeTranslationMessages', () => { ); } - test('for empty locale', async () => { + it('for empty locale', async () => { await expect( readDefaultCodeTranslationMessages({ locale: '', @@ -67,7 +67,7 @@ describe('readDefaultCodeTranslationMessages', () => { ); }); - test('for unexisting locale', async () => { + it('for nonexistent locale', async () => { await expect( readDefaultCodeTranslationMessages({ locale: 'es', @@ -77,7 +77,7 @@ describe('readDefaultCodeTranslationMessages', () => { ).resolves.toEqual({}); }); - test('for fr but bad folder', async () => { + it('for fr but bad folder', async () => { await expect( readDefaultCodeTranslationMessages({ locale: 'fr', @@ -87,7 +87,7 @@ describe('readDefaultCodeTranslationMessages', () => { ).resolves.toEqual({}); }); - test('for fr', async () => { + it('for fr', async () => { await expect( readDefaultCodeTranslationMessages({ locale: 'fr', @@ -97,7 +97,7 @@ describe('readDefaultCodeTranslationMessages', () => { ).resolves.toEqual(await readAsJSON('fr')); }); - test('for fr-FR', async () => { + it('for fr-FR', async () => { await expect( readDefaultCodeTranslationMessages({ locale: 'fr-FR', @@ -107,7 +107,7 @@ describe('readDefaultCodeTranslationMessages', () => { ).resolves.toEqual(await readAsJSON('fr-FR')); }); - test('for en', async () => { + it('for en', async () => { await expect( readDefaultCodeTranslationMessages({ locale: 'en', @@ -117,7 +117,7 @@ describe('readDefaultCodeTranslationMessages', () => { ).resolves.toEqual(await readAsJSON('en')); }); - test('for en-US', async () => { + it('for en-US', async () => { await expect( readDefaultCodeTranslationMessages({ locale: 'en-US', @@ -127,7 +127,7 @@ describe('readDefaultCodeTranslationMessages', () => { ).resolves.toEqual(await readAsJSON('en')); }); - test('for en-WHATEVER', async () => { + it('for en-WHATEVER', async () => { await expect( readDefaultCodeTranslationMessages({ locale: 'en-WHATEVER', @@ -137,7 +137,7 @@ describe('readDefaultCodeTranslationMessages', () => { ).resolves.toEqual(await readAsJSON('en')); }); - test('default locale', async () => { + it('default locale', async () => { await expect( readDefaultCodeTranslationMessages({ locale: 'zh', diff --git a/packages/docusaurus-theme-translations/src/__tests__/utils.test.ts b/packages/docusaurus-theme-translations/src/__tests__/utils.test.ts new file mode 100644 index 000000000000..72f5cfd83994 --- /dev/null +++ b/packages/docusaurus-theme-translations/src/__tests__/utils.test.ts @@ -0,0 +1,26 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import path from 'path'; +import {extractThemeCodeMessages} from '../utils'; + +describe('extractThemeCodeMessages', () => { + it('throws with invalid syntax', async () => { + await expect(() => + extractThemeCodeMessages([path.join(__dirname, '__fixtures__/theme')]), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + " + Please make sure all theme translations are static! + Some warnings were found! + + Translate content could not be extracted. It has to be a static string and use optional but static props, like text. + File: packages/docusaurus-theme-translations/src/__tests__/__fixtures__/theme/index.js at line 4 + Full code: {index} + " + `); + }); +}); diff --git a/packages/docusaurus-theme-translations/src/index.ts b/packages/docusaurus-theme-translations/src/index.ts index 24c7559c7540..743ac3a6087f 100644 --- a/packages/docusaurus-theme-translations/src/index.ts +++ b/packages/docusaurus-theme-translations/src/index.ts @@ -7,6 +7,7 @@ import path from 'path'; import fs from 'fs-extra'; +import type {CodeTranslations} from '@docusaurus/types'; function getDefaultLocalesDirPath(): string { return path.join(__dirname, '../locales'); @@ -15,8 +16,8 @@ function getDefaultLocalesDirPath(): string { // Return an ordered list of locales we should try export function codeTranslationLocalesToTry(locale: string): string[] { const intlLocale = new Intl.Locale(locale); - // if locale is just a simple language like "pt", we want to fallback to pt-BR (not pt-PT!) - // see https://github.com/facebook/docusaurus/pull/4536#issuecomment-810088783 + // if locale is just a simple language like "pt", we want to fallback to pt-BR + // (not pt-PT!) See https://github.com/facebook/docusaurus/pull/4536#issuecomment-810088783 if (intlLocale.language === locale) { const maximizedLocale = intlLocale.maximize(); // pt-Latn-BR` // ["pt","pt-BR"]; ["zh", "zh-Hans"] @@ -27,9 +28,7 @@ export function codeTranslationLocalesToTry(locale: string): string[] { ]; } // if locale is like "pt-BR", we want to fallback to "pt" - else { - return [locale, intlLocale.language!]; - } + return [locale, intlLocale.language!]; } // Useful to implement getDefaultCodeTranslationMessages() in themes @@ -41,12 +40,11 @@ export async function readDefaultCodeTranslationMessages({ dirPath?: string; locale: string; name: string; -}): Promise> { +}): Promise { const localesToTry = codeTranslationLocalesToTry(locale); // Return the content of the first file that match // fr_FR.json => fr.json => nothing - // eslint-disable-next-line no-restricted-syntax for (const localeToTry of localesToTry) { const filePath = path.resolve(dirPath, localeToTry, `${name}.json`); diff --git a/packages/docusaurus-theme-translations/src/utils.ts b/packages/docusaurus-theme-translations/src/utils.ts new file mode 100644 index 000000000000..beb1370817e1 --- /dev/null +++ b/packages/docusaurus-theme-translations/src/utils.ts @@ -0,0 +1,99 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +// This file isn't used by index.ts. It's used by update.mjs and tests. It's +// only here so that (a) we get a partially typed infrastructure (although the +// update script has ts-check anyways) (b) the test coverage isn't destroyed by +// the untested update.mjs file (c) we can ergonomically import the util +// functions in the Jest test without using `await import` + +import path from 'path'; +import fs from 'fs-extra'; +// Unsafe import, should we create a package for the translationsExtractor ?; +import { + globSourceCodeFilePaths, + extractAllSourceCodeFileTranslations, +} from '@docusaurus/core/lib/server/translations/translationsExtractor'; +import type {TranslationFileContent} from '@docusaurus/types'; + +async function getPackageCodePath(packageName: string) { + const packagePath = path.join(__dirname, '../..', packageName); + const packageJsonPath = path.join(packagePath, 'package.json'); + const {main} = await fs.readJSON(packageJsonPath); + const packageSrcPath = path.join(packagePath, path.dirname(main)); + const packageLibNextPath = packageSrcPath.replace('lib', 'lib-next'); + return (await fs.pathExists(packageLibNextPath)) + ? packageLibNextPath + : packageSrcPath; +} + +export async function getThemes(): Promise<{name: string; src: string[]}[]> { + return [ + { + name: 'theme-common', + src: [ + await getPackageCodePath('docusaurus-theme-classic'), + await getPackageCodePath('docusaurus-theme-common'), + ], + }, + { + name: 'theme-search-algolia', + src: [await getPackageCodePath('docusaurus-theme-search-algolia')], + }, + { + name: 'theme-live-codeblock', + src: [await getPackageCodePath('docusaurus-theme-live-codeblock')], + }, + { + name: 'plugin-pwa', + src: [await getPackageCodePath('docusaurus-plugin-pwa')], + }, + { + name: 'plugin-ideal-image', + src: [await getPackageCodePath('docusaurus-plugin-ideal-image')], + }, + ]; +} + +export async function extractThemeCodeMessages( + targetDirs?: string[], +): Promise { + // eslint-disable-next-line no-param-reassign + targetDirs ??= (await getThemes()).flatMap((theme) => theme.src); + + const filePaths = (await globSourceCodeFilePaths(targetDirs)).filter( + (filePath) => ['.js', '.jsx'].includes(path.extname(filePath)), + ); + + const filesExtractedTranslations = await extractAllSourceCodeFileTranslations( + filePaths, + { + presets: [require.resolve('@docusaurus/core/lib/babel/preset')], + }, + ); + + filesExtractedTranslations.forEach((fileExtractedTranslations) => { + if (fileExtractedTranslations.warnings.length > 0) { + throw new Error(` +Please make sure all theme translations are static! +Some warnings were found! + +${fileExtractedTranslations.warnings.join('\n\n')} +`); + } + }); + + const translations = filesExtractedTranslations.reduce( + (acc, extractedTranslations) => ({ + ...acc, + ...extractedTranslations.translations, + }), + {}, + ); + + return translations; +} diff --git a/packages/docusaurus-theme-translations/tsconfig.build.json b/packages/docusaurus-theme-translations/tsconfig.build.json new file mode 100644 index 000000000000..aee99fc0f38e --- /dev/null +++ b/packages/docusaurus-theme-translations/tsconfig.build.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "incremental": true, + "tsBuildInfoFile": "./lib/.tsbuildinfo", + "sourceMap": true, + "declarationMap": true, + "rootDir": "src", + "outDir": "lib" + } +} diff --git a/packages/docusaurus-theme-translations/tsconfig.json b/packages/docusaurus-theme-translations/tsconfig.json index aee99fc0f38e..59be626a2754 100644 --- a/packages/docusaurus-theme-translations/tsconfig.json +++ b/packages/docusaurus-theme-translations/tsconfig.json @@ -1,11 +1,10 @@ { "extends": "../../tsconfig.json", "compilerOptions": { - "incremental": true, - "tsBuildInfoFile": "./lib/.tsbuildinfo", - "sourceMap": true, - "declarationMap": true, - "rootDir": "src", - "outDir": "lib" - } + "module": "esnext", + "noEmit": true, + "checkJs": true, + "allowJs": true + }, + "include": ["update.mjs", "src"] } diff --git a/packages/docusaurus-theme-translations/update.js b/packages/docusaurus-theme-translations/update.js deleted file mode 100644 index 7f0239354c38..000000000000 --- a/packages/docusaurus-theme-translations/update.js +++ /dev/null @@ -1,355 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -// @ts-check -/* eslint-disable import/no-extraneous-dependencies */ - -const logger = require('@docusaurus/logger').default; -const path = require('path'); -const fs = require('fs-extra'); -const {mapValues, pickBy, difference, orderBy} = require('lodash'); - -const LocalesDirPath = path.join(__dirname, 'locales'); -const Themes = [ - { - name: 'theme-common', - src: [ - getPackageCodePath('docusaurus-theme-classic'), - getPackageCodePath('docusaurus-theme-common'), - ], - }, - { - name: 'theme-search-algolia', - src: [getPackageCodePath('docusaurus-theme-search-algolia')], - }, - { - name: 'theme-live-codeblock', - src: [getPackageCodePath('docusaurus-theme-live-codeblock')], - }, - { - name: 'plugin-pwa', - src: [getPackageCodePath('docusaurus-plugin-pwa')], - }, - { - name: 'plugin-ideal-image', - src: [getPackageCodePath('docusaurus-plugin-ideal-image')], - }, -]; -const AllThemesSrcDirs = Themes.flatMap((theme) => theme.src); - -logger.info`Will scan folders for code translations:path=${AllThemesSrcDirs}`; - -/** - * @param {string} packageName - */ -function getPackageCodePath(packageName) { - const packagePath = path.join(__dirname, '..', packageName); - const packageJsonPath = path.join(packagePath, 'package.json'); - const {main} = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); - const packageSrcPath = path.join(packagePath, path.dirname(main)); - const packageLibNextPath = packageSrcPath.replace('lib', 'lib-next'); - return fs.existsSync(packageLibNextPath) - ? packageLibNextPath - : packageSrcPath; -} - -/** - * @param {string} locale - * @param {string} themeName - */ -function getThemeLocalePath(locale, themeName) { - return path.join(LocalesDirPath, locale, `${themeName}.json`); -} - -/** - * @param {string} key - */ -function removeDescriptionSuffix(key) { - if (key.replace('___DESCRIPTION', '')) { - return key.replace('___DESCRIPTION', ''); - } - return key; -} - -/** - * @param {Record} obj - */ -function sortObjectKeys(obj) { - let keys = Object.keys(obj); - keys = orderBy(keys, [(k) => removeDescriptionSuffix(k)]); - return keys.reduce((acc, key) => { - acc[key] = obj[key]; - return acc; - }, {}); -} - -/** - * @param {string[]} targetDirs - * @returns {Promise} - */ -async function extractThemeCodeMessages(targetDirs = AllThemesSrcDirs) { - // Unsafe import, should we create a package for the translationsExtractor ? - const { - globSourceCodeFilePaths, - extractAllSourceCodeFileTranslations, - // eslint-disable-next-line global-require - } = require('@docusaurus/core/lib/server/translations/translationsExtractor'); - - const filePaths = (await globSourceCodeFilePaths(targetDirs)).filter( - (filePath) => ['.js', '.jsx'].includes(path.extname(filePath)), - ); - - const filesExtractedTranslations = await extractAllSourceCodeFileTranslations( - filePaths, - { - presets: [require.resolve('@docusaurus/core/lib/babel/preset')], - }, - ); - - filesExtractedTranslations.forEach((fileExtractedTranslations) => { - fileExtractedTranslations.warnings.forEach((warning) => { - throw new Error(` -Please make sure all theme translations are static! -Some warnings were found! - -${warning} - `); - }); - }); - - const translations = filesExtractedTranslations.reduce( - (acc, extractedTranslations) => ({ - ...acc, - ...extractedTranslations.translations, - }), - {}, - ); - - return translations; -} - -/** - * @param {string} filePath - * @returns {Promise>} - */ -async function readMessagesFile(filePath) { - if (!(await fs.pathExists(filePath))) { - logger.info`File path=${filePath} not found. Creating new translation base file.`; - await fs.writeFile(filePath, '{}\n'); - } - return JSON.parse((await fs.readFile(filePath)).toString()); -} - -/** - * @param {string} filePath - * @param {Record} messages - */ -async function writeMessagesFile(filePath, messages) { - const sortedMessages = sortObjectKeys(messages); - - const content = `${JSON.stringify(sortedMessages, null, 2)}\n`; // \n makes prettier happy - await fs.outputFile(filePath, content); - logger.info`path=${path.basename( - filePath, - )} updated subdue=${logger.interpolate`(number=${ - Object.keys(sortedMessages).length - } messages)`}\n`; -} - -/** - * @param {string} themeName - */ -async function getCodeTranslationFiles(themeName) { - const baseFile = getThemeLocalePath('base', themeName); - const localesFiles = (await fs.readdir(LocalesDirPath)) - .filter((dirName) => dirName !== 'base') - .map((locale) => getThemeLocalePath(locale, themeName)); - return {baseFile, localesFiles}; -} - -const DescriptionSuffix = '___DESCRIPTION'; - -/** - * @param {string} baseFile - * @param {string[]} targetDirs - */ -async function updateBaseFile(baseFile, targetDirs) { - const baseMessagesWithDescriptions = await readMessagesFile(baseFile); - const baseMessages = pickBy( - baseMessagesWithDescriptions, - (_, key) => !key.endsWith(DescriptionSuffix), - ); - - const codeExtractedTranslations = await extractThemeCodeMessages(targetDirs); - const codeMessages = mapValues( - codeExtractedTranslations, - (translation) => translation.message, - ); - - const unknownMessages = difference( - Object.keys(baseMessages), - Object.keys(codeMessages), - ); - - if (unknownMessages.length) { - logger.error`Some messages exist in base locale but were not found by the code extractor! -They won't be removed automatically, so do the cleanup manually if necessary! code=${unknownMessages}`; - } - - const newBaseMessages = { - ...baseMessages, // Ensure we don't automatically remove unknown messages - ...codeMessages, - }; - - /** @type {Record} */ - const newBaseMessagesDescriptions = Object.entries(newBaseMessages).reduce( - (acc, [key]) => { - const codeTranslation = codeExtractedTranslations[key]; - return { - ...acc, - [`${key}${DescriptionSuffix}`]: codeTranslation - ? codeTranslation.description - : undefined, - }; - }, - {}, - ); - - const newBaseMessagesWitDescription = { - ...newBaseMessages, - ...newBaseMessagesDescriptions, - }; - - await writeMessagesFile(baseFile, newBaseMessagesWitDescription); - - return newBaseMessages; -} - -/** - * @param {string} localeFile - * @param {Record} baseFileMessages - */ -async function updateLocaleCodeTranslations(localeFile, baseFileMessages) { - const localeFileMessages = await readMessagesFile(localeFile); - - const unknownMessages = difference( - Object.keys(localeFileMessages), - Object.keys(baseFileMessages), - ); - - if (unknownMessages.length) { - logger.error`Some localized messages do not exist in base.json! -You may want to delete these! code=${unknownMessages}`; - } - - const newLocaleFileMessages = { - ...baseFileMessages, - ...localeFileMessages, - }; - - const untranslatedKeys = Object.entries(newLocaleFileMessages) - .filter(([key, value]) => value === baseFileMessages[key]) - .map(([key]) => key); - - if (untranslatedKeys.length) { - logger.warn`Some messages do not seem to be translated! code=${untranslatedKeys}`; - } - - await writeMessagesFile(localeFile, newLocaleFileMessages); - return {untranslated: untranslatedKeys.length}; -} - -async function updateCodeTranslations() { - /** @type {Record} */ - const stats = {}; - let messageCount = 0; - const {2: newLocale} = process.argv; - // Order is important. The log messages must be in the same order as execution - // eslint-disable-next-line no-restricted-syntax - for (const theme of Themes) { - const {baseFile, localesFiles} = await getCodeTranslationFiles(theme.name); - logger.info`Will update base file for name=${theme.name}\n`; - const baseFileMessages = await updateBaseFile(baseFile, theme.src); - - if (newLocale) { - const newLocalePath = getThemeLocalePath(newLocale, theme.name); - - if (!fs.existsSync(newLocalePath)) { - await writeMessagesFile(newLocalePath, baseFileMessages); - logger.success`Locale file path=${path.basename( - newLocalePath, - )} have been created.`; - } else { - logger.warn`Locale file path=${path.basename( - newLocalePath, - )} was already created!`; - } - } else { - // eslint-disable-next-line no-restricted-syntax - for (const localeFile of localesFiles) { - const localeName = path.basename(path.dirname(localeFile)); - const pluginName = path.basename(localeFile, path.extname(localeFile)); - logger.info`Will update name=${localeName} locale in name=${pluginName}`; - const stat = await updateLocaleCodeTranslations( - localeFile, - baseFileMessages, - ); - - stats[localeName] ??= {untranslated: 0}; - stats[localeName].untranslated += stat.untranslated; - } - messageCount += Object.keys(baseFileMessages).length; - } - } - if (newLocale) { - return null; - } - return {stats, messageCount}; -} - -if (require.main === module) { - updateCodeTranslations().then( - (result) => { - logger.success('updateCodeTranslations end\n'); - if (result) { - const {stats, messageCount} = result; - const locales = Object.entries(stats).sort( - (a, b) => a[1].untranslated - b[1].untranslated, - ); - const messages = locales.map(([name, stat]) => { - const percentage = (messageCount - stat.untranslated) / messageCount; - const filled = Math.floor(percentage * 30); - const color = - // eslint-disable-next-line no-nested-ternary - percentage > 0.99 - ? logger.green - : percentage > 0.7 - ? logger.yellow - : logger.red; - const progress = color( - `[${''.padStart(filled, '=')}${''.padStart(30 - filled, ' ')}]`, - ); - return logger.interpolate`name=${name.padStart(8)} ${progress} ${( - percentage * 100 - ).toFixed(1)} subdue=${`(${ - messageCount - stat.untranslated - }/${messageCount})`}`; - }); - logger.info`Translation coverage: -${messages.join('\n')}`; - } - }, - (e) => { - logger.error( - `\nupdateCodeTranslations failure: ${e.message}\n${e.stack}\n`, - ); - process.exit(1); - }, - ); -} - -exports.extractThemeCodeMessages = extractThemeCodeMessages; diff --git a/packages/docusaurus-theme-translations/update.mjs b/packages/docusaurus-theme-translations/update.mjs new file mode 100644 index 000000000000..de7a0960ad7f --- /dev/null +++ b/packages/docusaurus-theme-translations/update.mjs @@ -0,0 +1,246 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +// @ts-check + +import logger from '@docusaurus/logger'; +import path from 'path'; +import {fileURLToPath} from 'url'; +import fs from 'fs-extra'; +import _ from 'lodash'; +import {getThemes, extractThemeCodeMessages} from './lib/utils.js'; + +const LocalesDirPath = fileURLToPath(new URL('locales', import.meta.url)); +const Themes = await getThemes(); +const AllThemesSrcDirs = Themes.flatMap((theme) => theme.src); + +logger.info`Will scan folders for code translations:path=${AllThemesSrcDirs}`; + +/** + * @param {string} locale + * @param {string} themeName + */ +function getThemeLocalePath(locale, themeName) { + return path.join(LocalesDirPath, locale, `${themeName}.json`); +} + +/** + * @param {string} key + */ +function removeDescriptionSuffix(key) { + if (key.replace('___DESCRIPTION', '')) { + return key.replace('___DESCRIPTION', ''); + } + return key; +} + +/** + * @param {Record} obj + */ +function sortObjectKeys(obj) { + const keys = _.orderBy(Object.keys(obj), (k) => removeDescriptionSuffix(k)); + return Object.fromEntries(keys.map((k) => [k, obj[k]])); +} + +/** + * @param {string} filePath + * @returns {Promise>} + */ +async function readMessagesFile(filePath) { + if (!(await fs.pathExists(filePath))) { + logger.info`File path=${filePath} not found. Creating new translation base file.`; + await fs.outputFile(filePath, '{}\n'); + } + return JSON.parse((await fs.readFile(filePath)).toString()); +} + +/** + * @param {string} filePath + * @param {Record} messages + */ +async function writeMessagesFile(filePath, messages) { + const sortedMessages = sortObjectKeys(messages); + + const content = `${JSON.stringify(sortedMessages, null, 2)}\n`; // \n makes prettier happy + await fs.outputFile(filePath, content); + logger.info`path=${path.basename( + filePath, + )} updated subdue=${logger.interpolate`(number=${ + Object.keys(sortedMessages).length + } messages)`}\n`; +} + +/** + * @param {string} themeName + */ +async function getCodeTranslationFiles(themeName) { + const baseFile = getThemeLocalePath('base', themeName); + const localesFiles = (await fs.readdir(LocalesDirPath)) + .filter((dirName) => dirName !== 'base' && !dirName.startsWith('__')) + .map((locale) => getThemeLocalePath(locale, themeName)); + return {baseFile, localesFiles}; +} + +const DescriptionSuffix = '___DESCRIPTION'; + +/** + * @param {string} baseFile + * @param {string[]} targetDirs + */ +async function updateBaseFile(baseFile, targetDirs) { + const baseMessagesWithDescriptions = await readMessagesFile(baseFile); + const baseMessages = _.pickBy( + baseMessagesWithDescriptions, + (v, key) => !key.endsWith(DescriptionSuffix), + ); + + const codeExtractedTranslations = await extractThemeCodeMessages(targetDirs); + const codeMessages = _.mapValues( + codeExtractedTranslations, + (translation) => translation.message, + ); + + const unknownMessages = _.difference( + Object.keys(baseMessages), + Object.keys(codeMessages), + ); + + if (unknownMessages.length) { + logger.error`Some messages exist in base locale but were not found by the code extractor! +They won't be removed automatically, so do the cleanup manually if necessary! code=${unknownMessages}`; + } + + const newBaseMessages = { + ...baseMessages, // Ensure we don't automatically remove unknown messages + ...codeMessages, + }; + + /** @type {Record} */ + const newBaseMessagesDescriptions = Object.entries(newBaseMessages).reduce( + (acc, [key]) => { + const codeTranslation = codeExtractedTranslations[key]; + return { + ...acc, + [`${key}${DescriptionSuffix}`]: codeTranslation + ? codeTranslation.description + : undefined, + }; + }, + {}, + ); + + const newBaseMessagesWitDescription = { + ...newBaseMessages, + ...newBaseMessagesDescriptions, + }; + + await writeMessagesFile(baseFile, newBaseMessagesWitDescription); + + return newBaseMessages; +} + +/** + * @param {string} localeFile + * @param {Record} baseFileMessages + */ +async function updateLocaleCodeTranslations(localeFile, baseFileMessages) { + const localeFileMessages = await readMessagesFile(localeFile); + + const unknownMessages = _.difference( + Object.keys(localeFileMessages), + Object.keys(baseFileMessages), + ); + + if (unknownMessages.length) { + logger.error`Some localized messages do not exist in base.json! +You may want to delete these! code=${unknownMessages}`; + } + + const newLocaleFileMessages = { + ...baseFileMessages, + ...localeFileMessages, + }; + + const untranslatedKeys = Object.entries(newLocaleFileMessages) + .filter(([key, value]) => value === baseFileMessages[key]) + .map(([key]) => key); + + if (untranslatedKeys.length) { + logger.warn`Some messages do not seem to be translated! code=${untranslatedKeys}`; + } + + await writeMessagesFile(localeFile, newLocaleFileMessages); + return {untranslated: untranslatedKeys.length}; +} + +/** @type {Record} */ +const stats = {}; +let messageCount = 0; +const {2: newLocale} = process.argv; +for (const theme of Themes) { + const {baseFile, localesFiles} = await getCodeTranslationFiles(theme.name); + logger.info`Will update base file for name=${theme.name}\n`; + const baseFileMessages = await updateBaseFile(baseFile, theme.src); + + if (newLocale) { + const newLocalePath = getThemeLocalePath(newLocale, theme.name); + + if (!(await fs.pathExists(newLocalePath))) { + await writeMessagesFile(newLocalePath, baseFileMessages); + logger.success`Locale file path=${path.basename( + newLocalePath, + )} have been created.`; + } else { + logger.warn`Locale file path=${path.basename( + newLocalePath, + )} was already created!`; + } + } else { + for (const localeFile of localesFiles) { + const localeName = path.basename(path.dirname(localeFile)); + const pluginName = path.basename(localeFile, path.extname(localeFile)); + logger.info`Will update name=${localeName} locale in name=${pluginName}`; + const stat = await updateLocaleCodeTranslations( + localeFile, + baseFileMessages, + ); + + (stats[localeName] ??= {untranslated: 0}).untranslated += + stat.untranslated; + } + messageCount += Object.keys(baseFileMessages).length; + } +} + +logger.success('updateCodeTranslations end\n'); +if (newLocale) { + process.exit(); +} +const locales = Object.entries(stats).sort( + (a, b) => a[1].untranslated - b[1].untranslated, +); +const messages = locales.map(([name, stat]) => { + const percentage = (messageCount - stat.untranslated) / messageCount; + const filled = Math.floor(percentage * 30); + const color = + // eslint-disable-next-line no-nested-ternary + percentage > 0.99 + ? logger.green + : percentage > 0.7 + ? logger.yellow + : logger.red; + const progress = color( + `[${''.padStart(filled, '=')}${''.padStart(30 - filled, ' ')}]`, + ); + return logger.interpolate`name=${name.padStart(8)} ${progress} ${( + percentage * 100 + ).toFixed(1)} subdue=${`(${ + messageCount - stat.untranslated + }/${messageCount})`}`; +}); +logger.info`Translation coverage: +${messages.join('\n')}`; diff --git a/packages/docusaurus-types/package.json b/packages/docusaurus-types/package.json index accb99caeae7..df32340b1745 100644 --- a/packages/docusaurus-types/package.json +++ b/packages/docusaurus-types/package.json @@ -1,6 +1,6 @@ { "name": "@docusaurus/types", - "version": "2.0.0-beta.14", + "version": "2.0.0-beta.18", "description": "Common types for Docusaurus packages.", "types": "./src/index.d.ts", "publishConfig": { @@ -16,11 +16,13 @@ "test": "tsc -p ." }, "dependencies": { + "@docusaurus/react-loadable": "5.5.2", + "react-loadable": "npm:@docusaurus/react-loadable@5.5.2", "commander": "^5.1.0", - "joi": "^17.4.2", - "querystring": "0.2.1", + "history": "^4.9.0", + "joi": "^17.6.0", "utility-types": "^3.10.0", - "webpack": "^5.61.0", + "webpack": "^5.70.0", "webpack-merge": "^5.8.0" } } diff --git a/packages/docusaurus-types/src/index.d.ts b/packages/docusaurus-types/src/index.d.ts index bb5d564a121d..58969aa81136 100644 --- a/packages/docusaurus-types/src/index.d.ts +++ b/packages/docusaurus-types/src/index.d.ts @@ -5,32 +5,65 @@ * LICENSE file in the root directory of this source tree. */ -import type {RuleSetRule, Configuration} from 'webpack'; -import type {Command} from 'commander'; +import type {RuleSetRule, Configuration as WebpackConfiguration} from 'webpack'; +import type {CustomizeRuleString} from 'webpack-merge/dist/types'; +import type {CommanderStatic} from 'commander'; import type {ParsedUrlQueryInput} from 'querystring'; import type Joi from 'joi'; -import type {Overwrite, DeepPartial} from 'utility-types'; +import type { + DeepRequired, + Required as RequireKeys, + DeepPartial, +} from 'utility-types'; +import type {Location} from 'history'; -// Convert webpack-merge webpack-merge enum to union type -// For type retro-compatible webpack-merge upgrade: we used string literals before) -// see https://github.com/survivejs/webpack-merge/issues/179 -type MergeStrategy = 'match' | 'merge' | 'append' | 'prepend' | 'replace'; +// === Configuration === export type ReportingSeverity = 'ignore' | 'log' | 'warn' | 'error' | 'throw'; +export type PluginOptions = {id?: string} & {[key: string]: unknown}; + +export type PluginConfig = + | string + | [string, PluginOptions] + | [PluginModule, PluginOptions] + | PluginModule; + +export type PresetConfig = string | [string, {[key: string]: unknown}]; + export type ThemeConfig = { [key: string]: unknown; }; -// Docusaurus config, after validation/normalization -export interface DocusaurusConfig { +export type I18nLocaleConfig = { + label: string; + htmlLang: string; + direction: string; +}; + +export type I18nConfig = { + defaultLocale: string; + locales: [string, ...string[]]; + localeConfigs: {[locale: string]: Partial}; +}; + +/** + * Docusaurus config, after validation/normalization. + */ +export type DocusaurusConfig = { + /** + * Always has both leading and trailing slash (`/base/`). May be localized. + */ baseUrl: string; baseUrlIssueBanner: boolean; favicon?: string; - tagline?: string; + tagline: string; title: string; url: string; - // trailingSlash undefined = legacy retrocompatible behavior => /file => /file/index.html + /** + * `undefined` = legacy retrocompatible behavior. Usually it means `/file` => + * `/file/index.html`. + */ trailingSlash: boolean | undefined; i18n: I18nConfig; onBrokenLinks: ReportingSeverity; @@ -42,24 +75,24 @@ export interface DocusaurusConfig { deploymentBranch?: string; githubHost?: string; githubPort?: string; - plugins?: PluginConfig[]; - themes?: PluginConfig[]; - presets?: PresetConfig[]; + plugins: PluginConfig[]; + themes: PluginConfig[]; + presets: PresetConfig[]; themeConfig: ThemeConfig; customFields?: { [key: string]: unknown; }; - scripts?: ( + scripts: ( | string | { src: string; [key: string]: unknown; } )[]; - clientModules?: string[]; + clientModules: string[]; ssrTemplate?: string; staticDirectories: string[]; - stylesheets?: ( + stylesheets: ( | string | { href: string; @@ -70,28 +103,28 @@ export interface DocusaurusConfig { webpack?: { jsLoader: 'babel' | ((isServer: boolean) => RuleSetRule); }; -} - -// Docusaurus config, as provided by the user (partial/unnormalized) -// This type is used to provide type-safety / IDE auto-complete on the config file -// See https://docusaurus.io/docs/typescript-support -export type Config = Overwrite< - Partial, - { - title: Required; - url: Required; - baseUrl: Required; - i18n?: DeepPartial; - } +}; + +/** + * Docusaurus config, as provided by the user (partial/unnormalized). This type + * is used to provide type-safety / IDE auto-complete on the config file. + * @see https://docusaurus.io/docs/typescript-support + */ +export type Config = RequireKeys< + DeepPartial, + 'title' | 'url' | 'baseUrl' >; +// === Data loading === + /** * - `type: 'package'`, plugin is in a different package. * - `type: 'project'`, plugin is in the same docusaurus project. - * - `type: 'local'`, none of plugin's ancestor directory contains any package.json. + * - `type: 'local'`, none of the plugin's ancestor directories contains a + * package.json. * - `type: 'synthetic'`, docusaurus generated internal plugin. */ -export type DocusaurusPluginVersionInformation = +export type PluginVersionInformation = | { readonly type: 'package'; readonly name?: string; @@ -101,72 +134,55 @@ export type DocusaurusPluginVersionInformation = | {readonly type: 'local'} | {readonly type: 'synthetic'}; -export interface DocusaurusSiteMetadata { +export type SiteMetadata = { readonly docusaurusVersion: string; readonly siteVersion?: string; - readonly pluginVersions: Record; -} - -// Inspired by Chrome JSON, because it's a widely supported i18n format -// https://developer.chrome.com/apps/i18n-messages -// https://support.crowdin.com/file-formats/chrome-json/ -// https://www.applanga.com/docs/formats/chrome_i18n_json -// https://docs.transifex.com/formats/chrome-json -// https://help.phrase.com/help/chrome-json-messages -export type TranslationMessage = {message: string; description?: string}; -export type TranslationFileContent = Record; -export type TranslationFile = {path: string; content: TranslationFileContent}; -export type TranslationFiles = TranslationFile[]; - -export type I18nLocaleConfig = { - label: string; - htmlLang: string; - direction: string; + readonly pluginVersions: {[pluginName: string]: PluginVersionInformation}; }; -export type I18nConfig = { - defaultLocale: string; - locales: [string, ...string[]]; - localeConfigs: Record>; +/** + * Inspired by Chrome JSON, because it's a widely supported i18n format + * @see https://developer.chrome.com/apps/i18n-messages + * @see https://support.crowdin.com/file-formats/chrome-json/ + * @see https://www.applanga.com/docs/formats/chrome_i18n_json + * @see https://docs.transifex.com/formats/chrome-json + * @see https://help.phrase.com/help/chrome-json-messages + */ +export type TranslationMessage = {message: string; description?: string}; +export type TranslationFileContent = {[msgId: string]: TranslationMessage}; +/** + * An abstract representation of how a translation file exists on disk. The core + * would handle the file reading/writing; plugins just need to deal with + * translations in-memory. + */ +export type TranslationFile = { + /** + * Relative to the directory where it's expected to be found. For plugin + * files, it's relative to `i18n///`. Should NOT + * have any extension. + */ + path: string; + content: TranslationFileContent; }; -export type I18n = { - defaultLocale: string; - locales: [string, ...string[]]; - currentLocale: string; - localeConfigs: Record; -}; +export type I18n = DeepRequired & {currentLocale: string}; + +export type GlobalData = {[pluginName: string]: {[pluginId: string]: unknown}}; -export interface DocusaurusContext { +export type CodeTranslations = {[msgId: string]: string}; + +export type DocusaurusContext = { siteConfig: DocusaurusConfig; - siteMetadata: DocusaurusSiteMetadata; - globalData: Record; + siteMetadata: SiteMetadata; + globalData: GlobalData; i18n: I18n; - codeTranslations: Record; + codeTranslations: CodeTranslations; // Don't put mutable values here, to avoid triggering re-renders // We could reconsider that choice if context selectors are implemented // isBrowser: boolean; // Not here on purpose! -} - -export interface Preset { - plugins?: PluginConfig[]; - themes?: PluginConfig[]; -} - -export type PresetModule = { - (context: LoadContext, presetOptions: T): Preset; -}; - -export type ImportedPresetModule = PresetModule & { - default?: PresetModule; }; -export type PresetConfig = - | [string, Record] - | [string] - | string; - export type HostPortCLIOptions = { host?: string; port?: string; @@ -190,90 +206,133 @@ export type ServeCLIOptions = HostPortCLIOptions & build: boolean; }; -export type BuildOptions = ConfigOptions & { +export type BuildCLIOptions = ConfigOptions & { bundleAnalyzer: boolean; outDir: string; minify: boolean; skipBuild: boolean; -}; - -export type BuildCLIOptions = BuildOptions & { locale?: string; }; -export interface LoadContext { +export type LoadContext = { siteDir: string; generatedFilesDir: string; siteConfig: DocusaurusConfig; siteConfigPath: string; outDir: string; - baseUrl: string; // TODO to remove: useless, there's already siteConfig.baseUrl! + /** + * Duplicated from `siteConfig.baseUrl`, but probably worth keeping. We mutate + * `siteConfig` to make `baseUrl` there localized as well, but that's mostly + * for client-side. `context.baseUrl` is still more convenient for plugins. + */ + baseUrl: string; i18n: I18n; ssrTemplate: string; - codeTranslations: Record; -} - -export interface InjectedHtmlTags { - headTags: string; - preBodyTags: string; - postBodyTags: string; -} - -export type HtmlTags = string | HtmlTagObject | (string | HtmlTagObject)[]; + codeTranslations: CodeTranslations; +}; -export interface Props extends LoadContext, InjectedHtmlTags { - readonly siteMetadata: DocusaurusSiteMetadata; +export type Props = LoadContext & { + readonly headTags: string; + readonly preBodyTags: string; + readonly postBodyTags: string; + readonly siteMetadata: SiteMetadata; readonly routes: RouteConfig[]; readonly routesPaths: string[]; readonly plugins: LoadedPlugin[]; -} +}; -export interface PluginContentLoadedActions { +// === Plugin === + +export type PluginContentLoadedActions = { addRoute: (config: RouteConfig) => void; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - createData: (name: string, data: any) => Promise; - setGlobalData: (data: T) => void; -} - -export type AllContent = Record< - string, // plugin name - Record< - string, // plugin id - unknown // plugin data - > ->; + createData: (name: string, data: string) => Promise; + setGlobalData: (data: unknown) => void; +}; + +export type ConfigureWebpackUtils = { + getStyleLoaders: ( + isServer: boolean, + cssOptions: { + [key: string]: unknown; + }, + ) => RuleSetRule[]; + getJSLoader: (options: { + isServer: boolean; + babelOptions?: {[key: string]: unknown}; + }) => RuleSetRule; +}; + +export type AllContent = { + [pluginName: string]: { + [pluginID: string]: unknown; + }; +}; // TODO improve type (not exposed by postcss-loader) -export type PostCssOptions = Record & {plugins: unknown[]}; +export type PostCssOptions = {[key: string]: unknown} & {plugins: unknown[]}; + +type HtmlTagObject = { + /** + * Attributes of the html tag. + * E.g. `{ disabled: true, value: "demo", rel: "preconnect" }` + */ + attributes?: Partial<{[key: string]: string | boolean}>; + /** The tag name, e.g. `div`, `script`, `link`, `meta` */ + tagName: string; + /** The inner HTML */ + innerHTML?: string; +}; + +export type HtmlTags = string | HtmlTagObject | (string | HtmlTagObject)[]; -export interface Plugin { +export type ValidationSchema = Joi.ObjectSchema; + +export type Validate = ( + validationSchema: ValidationSchema, + options: T, +) => U; + +export type OptionValidationContext = { + validate: Validate; + options: T; +}; + +export type ThemeConfigValidationContext = { + validate: Validate; + themeConfig: Partial; +}; + +export type Plugin = { name: string; loadContent?: () => Promise; - contentLoaded?: ({ - content, - actions, - }: { - content: Content; // the content loaded by this plugin instance - allContent: AllContent; // content loaded by ALL the plugins + contentLoaded?: (args: { + /** the content loaded by this plugin instance */ + content: Content; // + /** content loaded by ALL the plugins */ + allContent: AllContent; actions: PluginContentLoadedActions; }) => Promise; routesLoaded?: (routes: RouteConfig[]) => void; // TODO remove soon, deprecated (alpha-60) postBuild?: (props: Props & {content: Content}) => Promise; - postStart?: (props: Props) => void; - // TODO refactor the configureWebpack API surface: use an object instead of multiple params (requires breaking change) + // TODO refactor the configureWebpack API surface: use an object instead of + // multiple params (requires breaking change) configureWebpack?: ( - config: Configuration, + config: WebpackConfiguration, isServer: boolean, utils: ConfigureWebpackUtils, content: Content, - ) => Configuration & {mergeStrategy?: ConfigureWebpackFnMergeStrategy}; + ) => WebpackConfiguration & { + mergeStrategy?: { + [key: string]: CustomizeRuleString; + }; + }; configurePostCss?: (options: PostCssOptions) => PostCssOptions; getThemePath?: () => string; getTypeScriptThemePath?: () => string; getPathsToWatch?: () => string[]; getClientModules?: () => string[]; - extendCli?: (cli: Command) => void; - injectHtmlTags?: ({content}: {content: Content}) => { + extendCli?: (cli: CommanderStatic) => void; + injectHtmlTags?: (args: {content: Content}) => { headTags?: HtmlTags; preBodyTags?: HtmlTags; postBodyTags?: HtmlTags; @@ -281,169 +340,250 @@ export interface Plugin { // TODO before/afterDevServer implementation // translations - getTranslationFiles?: ({ - content, - }: { + getTranslationFiles?: (args: { + content: Content; + }) => Promise; + getDefaultCodeTranslationMessages?: () => Promise<{[id: string]: string}>; + translateContent?: (args: { + /** The content loaded by this plugin instance. */ content: Content; - }) => Promise; - getDefaultCodeTranslationMessages?: () => Promise< - Record< - string, // id - string // message - > - >; - translateContent?: ({ - content, - translationFiles, - }: { - content: Content; // the content loaded by this plugin instance - translationFiles: TranslationFiles; + translationFiles: TranslationFile[]; }) => Content; - translateThemeConfig?: ({ - themeConfig, - translationFiles, - }: { + translateThemeConfig?: (args: { themeConfig: ThemeConfig; - translationFiles: TranslationFiles; + translationFiles: TranslationFile[]; }) => ThemeConfig; -} +}; + +export type NormalizedPluginConfig = { + /** + * The default export of the plugin module, or alternatively, what's provided + * in the config file as inline plugins. Note that if a file is like: + * + * ```ts + * export default plugin() {...} + * export validateOptions() {...} + * ``` + * + * Then the static methods may not exist here. `pluginModule.module` will + * always take priority. + */ + plugin: PluginModule; + /** Options as they are provided in the config, not validated yet. */ + options: PluginOptions; + /** Only available when a string is provided in config. */ + pluginModule?: { + /** + * Raw module name as provided in the config. Shorthands have been resolved, + * so at least it's directly `require.resolve`able. + */ + path: string; + /** Whatever gets imported with `require`. */ + module: ImportedPluginModule; + }; + /** + * Different from `pluginModule.path`, this one is always an absolute path, + * used to resolve relative paths returned from lifecycles. If it's an inline + * plugin, it will be path to the config file. + */ + entryPath: string; +}; -export type InitializedPlugin = Plugin & { - readonly options: PluginOptions; - readonly version: DocusaurusPluginVersionInformation; +export type InitializedPlugin = Plugin & { + readonly options: Required; + readonly version: PluginVersionInformation; + /** The absolute path to the folder containing the entry point file. */ + readonly path: string; }; -export type LoadedPlugin = InitializedPlugin & { - readonly content: Content; +export type LoadedPlugin = InitializedPlugin & { + readonly content: unknown; +}; + +export type SwizzleAction = 'eject' | 'wrap'; +export type SwizzleActionStatus = 'safe' | 'unsafe' | 'forbidden'; + +export type SwizzleComponentConfig = { + actions: {[action in SwizzleAction]: SwizzleActionStatus}; + description?: string; +}; + +export type SwizzleConfig = { + components: {[componentName: string]: SwizzleComponentConfig}; + // Other settings could be added here, + // For example: the ability to declare the config as exhaustive + // so that we can emit errors }; export type PluginModule = { - (context: LoadContext, options: Options): - | Plugin - | Promise>; - validateOptions?: (data: OptionValidationContext) => T; + (context: LoadContext, options: unknown): Plugin | Promise; + validateOptions?: (data: OptionValidationContext) => U; validateThemeConfig?: (data: ThemeConfigValidationContext) => T; - getSwizzleComponentList?: () => string[]; + + getSwizzleComponentList?: () => string[] | undefined; // TODO deprecate this one later + getSwizzleConfig?: () => SwizzleConfig | undefined; }; export type ImportedPluginModule = PluginModule & { default?: PluginModule; }; -export type ConfigureWebpackFn = Plugin['configureWebpack']; -export type ConfigureWebpackFnMergeStrategy = Record; -export type ConfigurePostCssFn = Plugin['configurePostCss']; +export type Preset = { + plugins?: PluginConfig[]; + themes?: PluginConfig[]; +}; -export type PluginOptions = {id?: string} & Record; +export type PresetModule = { + (context: LoadContext, presetOptions: T): Preset; +}; -export type PluginConfig = - | [string, PluginOptions] - | [string] - | string - | [PluginModule, PluginOptions] - | PluginModule; +export type ImportedPresetModule = PresetModule & { + default?: PresetModule; +}; -export interface ChunkRegistry { - loader: string; - modulePath: string; -} +// === Route registry === +/** + * A "module" represents a unit of serialized data emitted from the plugin. It + * will be imported on client-side and passed as props, context, etc. + * + * If it's a string, it's a file path that Webpack can `require`; if it's + * an object, it can also contain `query` or other metadata. + */ export type Module = | { - path: string; + /** + * A marker that tells the route generator this is an import and not a + * nested object to recurse. + */ __import?: boolean; + path: string; query?: ParsedUrlQueryInput; } | string; -export interface RouteModule { - [module: string]: Module | RouteModule | RouteModule[]; -} - -export interface ChunkNames { - [name: string]: string | null | ChunkNames | ChunkNames[]; -} +/** + * Represents the data attached to each route. Since the routes.js is a + * monolithic data file, any data (like props) should be serialized separately + * and registered here as file paths (a {@link Module}), so that we could + * code-split. + */ +export type RouteModules = { + [propName: string]: Module | RouteModules | RouteModules[]; +}; -export interface RouteConfig { +/** + * Represents a "slice" of the final route structure returned from the plugin + * `addRoute` action. + */ +export type RouteConfig = { + /** With leading slash. Trailing slash will be normalized by config. */ path: string; + /** Component used to render this route, a path that Webpack can `require`. */ component: string; - modules?: RouteModule; + /** + * Props. Each entry should be `[propName]: pathToPropModule` (created with + * `createData`) + */ + modules?: RouteModules; + /** Nested routes config. */ routes?: RouteConfig[]; + /** React router config option: `exact` routes would not match subroutes. */ exact?: boolean; + /** Used to sort routes. Higher-priority routes will be placed first. */ priority?: number; + /** Extra props; will be copied to routes.js. */ [propName: string]: unknown; -} - -// Aliases used for Webpack resolution (when using docusaurus swizzle) -export interface ThemeAliases { - [alias: string]: string; -} - -export interface ConfigureWebpackUtils { - getStyleLoaders: ( - isServer: boolean, - cssOptions: { - [key: string]: unknown; - }, - ) => RuleSetRule[]; - getJSLoader: (options: { - isServer: boolean; - babelOptions?: Record; - }) => RuleSetRule; - - // TODO deprecated: remove before end of 2021? - getCacheLoader: ( - isServer: boolean, - cacheOptions?: Record, - ) => RuleSetRule | null; - - // TODO deprecated: remove before end of 2021? - getBabelLoader: ( - isServer: boolean, - options?: Record, - ) => RuleSetRule; -} +}; -interface HtmlTagObject { - /** - * Attributes of the html tag - * E.g. `{'disabled': true, 'value': 'demo', 'rel': 'preconnect'}` - */ - attributes?: Partial>; +export type RouteContext = { /** - * The tag name e.g. `div`, `script`, `link`, `meta` + * Plugin-specific context data. */ - tagName: string; - /** - * The inner HTML - */ - innerHTML?: string; -} + data?: object | undefined; +}; -export type ValidationResult = T; +/** + * Top-level plugin routes automatically add some context data to the route. + * This permits us to know which plugin is handling the current route. + */ +export type PluginRouteContext = RouteContext & { + plugin: { + id: string; + name: string; + }; +}; -export type ValidationSchema = Joi.ObjectSchema; +/** + * The shape would be isomorphic to {@link RouteModules}: + * {@link Module} -> `string`, `RouteModules[]` -> `ChunkNames[]`. + * + * Each `string` chunk name will correlate with one key in the {@link Registry}. + */ +export type ChunkNames = { + [propName: string]: string | ChunkNames | ChunkNames[]; +}; -export type Validate = ( - validationSchema: ValidationSchema, - options: Partial, -) => ValidationResult; +/** + * A map from route paths (with a hash) to the chunk names of each module, which + * the bundler will collect. + * + * Chunk keys are routes with a hash, because 2 routes can conflict with each + * other if they have the same path, e.g.: parent=/docs, child=/docs + * + * @see https://github.com/facebook/docusaurus/issues/2917 + */ +export type RouteChunkNames = { + [routePathHashed: string]: ChunkNames; +}; -export interface OptionValidationContext { - validate: Validate; - options: Partial; -} +/** + * Each key is the chunk name, which you can get from `routeChunkNames` (see + * {@link RouteChunkNames}). The values are the opts data that react-loadable + * needs. For example: + * + * ```js + * const options = { + * optsLoader: { + * component: () => import('./Pages.js'), + * content.foo: () => import('./doc1.md'), + * }, + * optsModules: ['./Pages.js', './doc1.md'], + * optsWebpack: [ + * require.resolveWeak('./Pages.js'), + * require.resolveWeak('./doc1.md'), + * ], + * } + * ``` + * + * @see https://github.com/jamiebuilds/react-loadable#declaring-which-modules-are-being-loaded + */ +export type Registry = { + readonly [chunkName: string]: [ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + Loader: () => Promise, + ModuleName: string, + ResolvedModuleName: string, + ]; +}; -export interface ThemeConfigValidationContext { - validate: Validate; - themeConfig: Partial; -} +/** + * Aliases used for Webpack resolution (useful for implementing swizzling) + */ +export type ThemeAliases = { + [alias: string]: string; +}; -export interface TOCItem { +export type TOCItem = { readonly value: string; readonly id: string; - readonly children: TOCItem[]; readonly level: number; -} +}; -export type RouteChunksTree = {[x: string | number]: string | RouteChunksTree}; +export type ClientModule = { + onRouteUpdate?: (args: { + previousLocation: Location | null; + location: Location; + }) => void; + onRouteUpdateDelayed?: (args: {location: Location}) => void; +}; diff --git a/packages/docusaurus-utils-common/package.json b/packages/docusaurus-utils-common/package.json index 004dbdd93a45..ee7156d5b08b 100644 --- a/packages/docusaurus-utils-common/package.json +++ b/packages/docusaurus-utils-common/package.json @@ -1,6 +1,6 @@ { "name": "@docusaurus/utils-common", - "version": "2.0.0-beta.14", + "version": "2.0.0-beta.18", "description": "Common (Node/Browser) utility functions for Docusaurus packages.", "main": "./lib/index.js", "types": "./lib/index.d.ts", @@ -21,7 +21,7 @@ "tslib": "^2.3.1" }, "devDependencies": { - "@docusaurus/types": "2.0.0-beta.14" + "@docusaurus/types": "2.0.0-beta.18" }, "engines": { "node": ">=14" diff --git a/packages/docusaurus-utils-common/src/__tests__/applyTrailingSlash.test.ts b/packages/docusaurus-utils-common/src/__tests__/applyTrailingSlash.test.ts index 810a700277b8..4096b8273fca 100644 --- a/packages/docusaurus-utils-common/src/__tests__/applyTrailingSlash.test.ts +++ b/packages/docusaurus-utils-common/src/__tests__/applyTrailingSlash.test.ts @@ -17,162 +17,162 @@ function params( } describe('applyTrailingSlash', () => { - test('should apply to empty', () => { - expect(applyTrailingSlash('', params(true))).toEqual('/'); - expect(applyTrailingSlash('', params(false))).toEqual(''); - expect(applyTrailingSlash('', params(undefined))).toEqual(''); + it('applies to empty', () => { + expect(applyTrailingSlash('', params(true))).toBe('/'); + expect(applyTrailingSlash('', params(false))).toBe(''); + expect(applyTrailingSlash('', params(undefined))).toBe(''); }); - test('should not apply to /', () => { - expect(applyTrailingSlash('/', params(true))).toEqual('/'); - expect(applyTrailingSlash('/', params(false))).toEqual('/'); - expect(applyTrailingSlash('/', params(undefined))).toEqual('/'); + it('does not apply to /', () => { + expect(applyTrailingSlash('/', params(true))).toBe('/'); + expect(applyTrailingSlash('/', params(false))).toBe('/'); + expect(applyTrailingSlash('/', params(undefined))).toBe('/'); - expect(applyTrailingSlash('/?query#anchor', params(true))).toEqual( + expect(applyTrailingSlash('/?query#anchor', params(true))).toBe( '/?query#anchor', ); - expect(applyTrailingSlash('/?query#anchor', params(false))).toEqual( + expect(applyTrailingSlash('/?query#anchor', params(false))).toBe( '/?query#anchor', ); - expect(applyTrailingSlash('/?query#anchor', params(undefined))).toEqual( + expect(applyTrailingSlash('/?query#anchor', params(undefined))).toBe( '/?query#anchor', ); }); - test('should not apply to /baseUrl/', () => { + it('does not apply to /baseUrl/', () => { const baseUrl = '/baseUrl/'; - expect(applyTrailingSlash('/baseUrl/', params(true, baseUrl))).toEqual( + expect(applyTrailingSlash('/baseUrl/', params(true, baseUrl))).toBe( '/baseUrl/', ); - expect(applyTrailingSlash('/baseUrl/', params(false, baseUrl))).toEqual( + expect(applyTrailingSlash('/baseUrl/', params(false, baseUrl))).toBe( '/baseUrl/', ); - expect(applyTrailingSlash('/baseUrl/', params(undefined, baseUrl))).toEqual( + expect(applyTrailingSlash('/baseUrl/', params(undefined, baseUrl))).toBe( '/baseUrl/', ); expect( applyTrailingSlash('/baseUrl/?query#anchor', params(true, baseUrl)), - ).toEqual('/baseUrl/?query#anchor'); + ).toBe('/baseUrl/?query#anchor'); expect( applyTrailingSlash('/baseUrl/?query#anchor', params(false, baseUrl)), - ).toEqual('/baseUrl/?query#anchor'); + ).toBe('/baseUrl/?query#anchor'); expect( applyTrailingSlash('/baseUrl/?query#anchor', params(undefined, baseUrl)), - ).toEqual('/baseUrl/?query#anchor'); + ).toBe('/baseUrl/?query#anchor'); }); - test('should not apply to #anchor links', () => { - expect(applyTrailingSlash('#', params(true))).toEqual('#'); - expect(applyTrailingSlash('#', params(false))).toEqual('#'); - expect(applyTrailingSlash('#', params(undefined))).toEqual('#'); - expect(applyTrailingSlash('#anchor', params(true))).toEqual('#anchor'); - expect(applyTrailingSlash('#anchor', params(false))).toEqual('#anchor'); - expect(applyTrailingSlash('#anchor', params(undefined))).toEqual('#anchor'); + it('does not apply to #anchor links', () => { + expect(applyTrailingSlash('#', params(true))).toBe('#'); + expect(applyTrailingSlash('#', params(false))).toBe('#'); + expect(applyTrailingSlash('#', params(undefined))).toBe('#'); + expect(applyTrailingSlash('#anchor', params(true))).toBe('#anchor'); + expect(applyTrailingSlash('#anchor', params(false))).toBe('#anchor'); + expect(applyTrailingSlash('#anchor', params(undefined))).toBe('#anchor'); }); - test('should apply to simple paths', () => { - expect(applyTrailingSlash('abc', params(true))).toEqual('abc/'); - expect(applyTrailingSlash('abc', params(false))).toEqual('abc'); - expect(applyTrailingSlash('abc', params(undefined))).toEqual('abc'); - expect(applyTrailingSlash('abc/', params(true))).toEqual('abc/'); - expect(applyTrailingSlash('abc/', params(false))).toEqual('abc'); - expect(applyTrailingSlash('abc/', params(undefined))).toEqual('abc/'); - expect(applyTrailingSlash('/abc', params(true))).toEqual('/abc/'); - expect(applyTrailingSlash('/abc', params(false))).toEqual('/abc'); - expect(applyTrailingSlash('/abc', params(undefined))).toEqual('/abc'); - expect(applyTrailingSlash('/abc/', params(true))).toEqual('/abc/'); - expect(applyTrailingSlash('/abc/', params(false))).toEqual('/abc'); - expect(applyTrailingSlash('/abc/', params(undefined))).toEqual('/abc/'); + it('applies to simple paths', () => { + expect(applyTrailingSlash('abc', params(true))).toBe('abc/'); + expect(applyTrailingSlash('abc', params(false))).toBe('abc'); + expect(applyTrailingSlash('abc', params(undefined))).toBe('abc'); + expect(applyTrailingSlash('abc/', params(true))).toBe('abc/'); + expect(applyTrailingSlash('abc/', params(false))).toBe('abc'); + expect(applyTrailingSlash('abc/', params(undefined))).toBe('abc/'); + expect(applyTrailingSlash('/abc', params(true))).toBe('/abc/'); + expect(applyTrailingSlash('/abc', params(false))).toBe('/abc'); + expect(applyTrailingSlash('/abc', params(undefined))).toBe('/abc'); + expect(applyTrailingSlash('/abc/', params(true))).toBe('/abc/'); + expect(applyTrailingSlash('/abc/', params(false))).toBe('/abc'); + expect(applyTrailingSlash('/abc/', params(undefined))).toBe('/abc/'); }); - test('should apply to path with #anchor', () => { - expect(applyTrailingSlash('/abc#anchor', params(true))).toEqual( + it('applies to path with #anchor', () => { + expect(applyTrailingSlash('/abc#anchor', params(true))).toBe( '/abc/#anchor', ); - expect(applyTrailingSlash('/abc#anchor', params(false))).toEqual( + expect(applyTrailingSlash('/abc#anchor', params(false))).toBe( '/abc#anchor', ); - expect(applyTrailingSlash('/abc#anchor', params(undefined))).toEqual( + expect(applyTrailingSlash('/abc#anchor', params(undefined))).toBe( '/abc#anchor', ); - expect(applyTrailingSlash('/abc/#anchor', params(true))).toEqual( + expect(applyTrailingSlash('/abc/#anchor', params(true))).toBe( '/abc/#anchor', ); - expect(applyTrailingSlash('/abc/#anchor', params(false))).toEqual( + expect(applyTrailingSlash('/abc/#anchor', params(false))).toBe( '/abc#anchor', ); - expect(applyTrailingSlash('/abc/#anchor', params(undefined))).toEqual( + expect(applyTrailingSlash('/abc/#anchor', params(undefined))).toBe( '/abc/#anchor', ); }); - test('should apply to path with ?search', () => { - expect(applyTrailingSlash('/abc?search', params(true))).toEqual( + it('applies to path with ?search', () => { + expect(applyTrailingSlash('/abc?search', params(true))).toBe( '/abc/?search', ); - expect(applyTrailingSlash('/abc?search', params(false))).toEqual( + expect(applyTrailingSlash('/abc?search', params(false))).toBe( '/abc?search', ); - expect(applyTrailingSlash('/abc?search', params(undefined))).toEqual( + expect(applyTrailingSlash('/abc?search', params(undefined))).toBe( '/abc?search', ); - expect(applyTrailingSlash('/abc/?search', params(true))).toEqual( + expect(applyTrailingSlash('/abc/?search', params(true))).toBe( '/abc/?search', ); - expect(applyTrailingSlash('/abc/?search', params(false))).toEqual( + expect(applyTrailingSlash('/abc/?search', params(false))).toBe( '/abc?search', ); - expect(applyTrailingSlash('/abc/?search', params(undefined))).toEqual( + expect(applyTrailingSlash('/abc/?search', params(undefined))).toBe( '/abc/?search', ); }); - test('should apply to path with ?search#anchor', () => { - expect(applyTrailingSlash('/abc?search#anchor', params(true))).toEqual( + it('applies to path with ?search#anchor', () => { + expect(applyTrailingSlash('/abc?search#anchor', params(true))).toBe( '/abc/?search#anchor', ); - expect(applyTrailingSlash('/abc?search#anchor', params(false))).toEqual( + expect(applyTrailingSlash('/abc?search#anchor', params(false))).toBe( '/abc?search#anchor', ); - expect(applyTrailingSlash('/abc?search#anchor', params(undefined))).toEqual( + expect(applyTrailingSlash('/abc?search#anchor', params(undefined))).toBe( '/abc?search#anchor', ); - expect(applyTrailingSlash('/abc/?search#anchor', params(true))).toEqual( + expect(applyTrailingSlash('/abc/?search#anchor', params(true))).toBe( '/abc/?search#anchor', ); - expect(applyTrailingSlash('/abc/?search#anchor', params(false))).toEqual( + expect(applyTrailingSlash('/abc/?search#anchor', params(false))).toBe( '/abc?search#anchor', ); - expect( - applyTrailingSlash('/abc/?search#anchor', params(undefined)), - ).toEqual('/abc/?search#anchor'); + expect(applyTrailingSlash('/abc/?search#anchor', params(undefined))).toBe( + '/abc/?search#anchor', + ); }); - test('should apply to fully qualified urls', () => { + it('applies to fully qualified urls', () => { expect( applyTrailingSlash('https://xyz.com/abc?search#anchor', params(true)), - ).toEqual('https://xyz.com/abc/?search#anchor'); + ).toBe('https://xyz.com/abc/?search#anchor'); expect( applyTrailingSlash('https://xyz.com/abc?search#anchor', params(false)), - ).toEqual('https://xyz.com/abc?search#anchor'); + ).toBe('https://xyz.com/abc?search#anchor'); expect( applyTrailingSlash( 'https://xyz.com/abc?search#anchor', params(undefined), ), - ).toEqual('https://xyz.com/abc?search#anchor'); + ).toBe('https://xyz.com/abc?search#anchor'); expect( applyTrailingSlash('https://xyz.com/abc/?search#anchor', params(true)), - ).toEqual('https://xyz.com/abc/?search#anchor'); + ).toBe('https://xyz.com/abc/?search#anchor'); expect( applyTrailingSlash('https://xyz.com/abc/?search#anchor', params(false)), - ).toEqual('https://xyz.com/abc?search#anchor'); + ).toBe('https://xyz.com/abc?search#anchor'); expect( applyTrailingSlash( 'https://xyz.com/abc/?search#anchor', params(undefined), ), - ).toEqual('https://xyz.com/abc/?search#anchor'); + ).toBe('https://xyz.com/abc/?search#anchor'); }); }); diff --git a/packages/docusaurus-utils-common/src/applyTrailingSlash.ts b/packages/docusaurus-utils-common/src/applyTrailingSlash.ts index 50bf598a738c..d8faa439cc5b 100644 --- a/packages/docusaurus-utils-common/src/applyTrailingSlash.ts +++ b/packages/docusaurus-utils-common/src/applyTrailingSlash.ts @@ -41,12 +41,12 @@ export default function applyTrailingSlash( } // The trailing slash should be handled before the ?search#hash ! - const [pathname] = path.split(/[#?]/); + const [pathname] = path.split(/[#?]/) as [string, ...string[]]; // Never transform '/' to '' // Never remove the baseUrl trailing slash! - // If baseUrl = /myBase/, we want to emit /myBase/index.html and not /myBase.html ! - // See https://github.com/facebook/docusaurus/issues/5077 + // If baseUrl = /myBase/, we want to emit /myBase/index.html and not + // /myBase.html! See https://github.com/facebook/docusaurus/issues/5077 const shouldNotApply = pathname === '/' || pathname === baseUrl; const newPathname = shouldNotApply diff --git a/packages/docusaurus-utils-common/src/index.ts b/packages/docusaurus-utils-common/src/index.ts index 6d7fb91b4f0d..11b925a28302 100644 --- a/packages/docusaurus-utils-common/src/index.ts +++ b/packages/docusaurus-utils-common/src/index.ts @@ -6,5 +6,7 @@ */ export const blogPostContainerID = 'post-content'; -export {default as applyTrailingSlash} from './applyTrailingSlash'; -export type {ApplyTrailingSlashParams} from './applyTrailingSlash'; +export { + default as applyTrailingSlash, + type ApplyTrailingSlashParams, +} from './applyTrailingSlash'; diff --git a/packages/docusaurus-utils-validation/package.json b/packages/docusaurus-utils-validation/package.json index bc6b3bbe5e46..ea845a2df40d 100644 --- a/packages/docusaurus-utils-validation/package.json +++ b/packages/docusaurus-utils-validation/package.json @@ -1,6 +1,6 @@ { "name": "@docusaurus/utils-validation", - "version": "2.0.0-beta.14", + "version": "2.0.0-beta.18", "description": "Node validation utility functions for Docusaurus packages.", "main": "./lib/index.js", "types": "./lib/index.d.ts", @@ -18,15 +18,12 @@ }, "license": "MIT", "dependencies": { - "@docusaurus/logger": "2.0.0-beta.14", - "@docusaurus/utils": "2.0.0-beta.14", - "joi": "^17.4.2", + "@docusaurus/logger": "2.0.0-beta.18", + "@docusaurus/utils": "2.0.0-beta.18", + "joi": "^17.6.0", + "js-yaml": "^4.1.0", "tslib": "^2.3.1" }, - "peerDependencies": { - "react": "*", - "react-dom": "*" - }, "engines": { "node": ">=14" } diff --git a/packages/docusaurus-utils-validation/src/JoiFrontMatter.ts b/packages/docusaurus-utils-validation/src/JoiFrontMatter.ts index 6fa679b62b9e..011bccd8796c 100644 --- a/packages/docusaurus-utils-validation/src/JoiFrontMatter.ts +++ b/packages/docusaurus-utils-validation/src/JoiFrontMatter.ts @@ -7,11 +7,6 @@ import Joi from './Joi'; -// Enhance the default Joi.string() type so that it can convert number to strings -// If user use front matter "tag: 2021", we shouldn't need to ask the user to write "tag: '2021'" -// Also yaml tries to convert patterns like "2019-01-01" to dates automatically -// see https://github.com/facebook/docusaurus/issues/4642 -// see https://github.com/sideway/joi/issues/1442#issuecomment-823997884 const JoiFrontMatterString: Joi.Extension = { type: 'string', base: Joi.string(), @@ -23,4 +18,14 @@ const JoiFrontMatterString: Joi.Extension = { return {value}; }, }; + +/** + * Enhance the default `Joi.string()` type so that it can convert number to + * strings. If user use front matter "tag: 2021", we shouldn't need to ask her + * to write "tag: '2021'". Also yaml tries to convert patterns like "2019-01-01" + * to dates automatically. + * + * @see https://github.com/facebook/docusaurus/issues/4642 + * @see https://github.com/sideway/joi/issues/1442#issuecomment-823997884 + */ export const JoiFrontMatter: typeof Joi = Joi.extend(JoiFrontMatterString); diff --git a/packages/docusaurus-utils-validation/src/__tests__/__snapshots__/validationSchemas.test.ts.snap b/packages/docusaurus-utils-validation/src/__tests__/__snapshots__/validationSchemas.test.ts.snap index b7e3d9b45515..dc6695380d21 100644 --- a/packages/docusaurus-utils-validation/src/__tests__/__snapshots__/validationSchemas.test.ts.snap +++ b/packages/docusaurus-utils-validation/src/__tests__/__snapshots__/validationSchemas.test.ts.snap @@ -1,67 +1,103 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`validation schemas AdmonitionsSchema: for value=[] 1`] = `"\\"value\\" must be of type object"`; +exports[`validation schemas admonitionsSchema: for value=[] 1`] = `"\\"value\\" must be of type object"`; -exports[`validation schemas AdmonitionsSchema: for value=3 1`] = `"\\"value\\" must be of type object"`; +exports[`validation schemas admonitionsSchema: for value=3 1`] = `"\\"value\\" must be of type object"`; -exports[`validation schemas AdmonitionsSchema: for value=null 1`] = `"\\"value\\" must be of type object"`; +exports[`validation schemas admonitionsSchema: for value=null 1`] = `"\\"value\\" must be of type object"`; -exports[`validation schemas AdmonitionsSchema: for value=true 1`] = `"\\"value\\" must be of type object"`; +exports[`validation schemas admonitionsSchema: for value=true 1`] = `"\\"value\\" must be of type object"`; -exports[`validation schemas PathnameSchema: for value="foo" 1`] = `"\\"value\\" is not a valid pathname. Pathname should start with slash and not contain any domain or query string."`; +exports[`validation schemas pathnameSchema: for value="foo" 1`] = `"\\"value\\" is not a valid pathname. Pathname should start with slash and not contain any domain or query string."`; -exports[`validation schemas PathnameSchema: for value="https://github.com/foo" 1`] = `"\\"value\\" is not a valid pathname. Pathname should start with slash and not contain any domain or query string."`; +exports[`validation schemas pathnameSchema: for value="https://github.com/foo" 1`] = `"\\"value\\" is not a valid pathname. Pathname should start with slash and not contain any domain or query string."`; -exports[`validation schemas PluginIdSchema: for value="/docs" 1`] = `"\\"value\\" with value \\"/docs\\" fails to match the required pattern: /^[a-zA-Z_-]+$/"`; +exports[`validation schemas pluginIdSchema: for value="/docs" 1`] = `"Illegal plugin ID value \\"/docs\\": it should only contain alphanumerics, underscores, and dashes."`; -exports[`validation schemas PluginIdSchema: for value="do cs" 1`] = `"\\"value\\" with value \\"do cs\\" fails to match the required pattern: /^[a-zA-Z_-]+$/"`; +exports[`validation schemas pluginIdSchema: for value="do cs" 1`] = `"Illegal plugin ID value \\"do cs\\": it should only contain alphanumerics, underscores, and dashes."`; -exports[`validation schemas PluginIdSchema: for value="do/cs" 1`] = `"\\"value\\" with value \\"do/cs\\" fails to match the required pattern: /^[a-zA-Z_-]+$/"`; +exports[`validation schemas pluginIdSchema: for value="do/cs" 1`] = `"Illegal plugin ID value \\"do/cs\\": it should only contain alphanumerics, underscores, and dashes."`; -exports[`validation schemas PluginIdSchema: for value="docs/" 1`] = `"\\"value\\" with value \\"docs/\\" fails to match the required pattern: /^[a-zA-Z_-]+$/"`; +exports[`validation schemas pluginIdSchema: for value="docs/" 1`] = `"Illegal plugin ID value \\"docs/\\": it should only contain alphanumerics, underscores, and dashes."`; -exports[`validation schemas PluginIdSchema: for value=[] 1`] = `"\\"value\\" must be a string"`; +exports[`validation schemas pluginIdSchema: for value=[] 1`] = `"\\"value\\" must be a string"`; -exports[`validation schemas PluginIdSchema: for value=3 1`] = `"\\"value\\" must be a string"`; +exports[`validation schemas pluginIdSchema: for value=3 1`] = `"\\"value\\" must be a string"`; -exports[`validation schemas PluginIdSchema: for value=null 1`] = `"\\"value\\" must be a string"`; +exports[`validation schemas pluginIdSchema: for value=null 1`] = `"\\"value\\" must be a string"`; -exports[`validation schemas PluginIdSchema: for value=true 1`] = `"\\"value\\" must be a string"`; +exports[`validation schemas pluginIdSchema: for value=true 1`] = `"\\"value\\" must be a string"`; -exports[`validation schemas RehypePluginsSchema: for value=[[]] 1`] = `"\\"[0]\\" does not match any of the allowed types"`; +exports[`validation schemas rehypePluginsSchema: for value=[[]] 1`] = ` +"\\"[0]\\" does not look like a valid MDX plugin config. A plugin config entry should be one of: +- A tuple, like \`[require(\\"rehype-katex\\"), { strict: false }]\`, or +- A simple module, like \`require(\\"remark-math\\")\`" +`; -exports[`validation schemas RehypePluginsSchema: for value=[[null,null]] 1`] = `"\\"[0]\\" does not match any of the allowed types"`; +exports[`validation schemas rehypePluginsSchema: for value=[[null,null]] 1`] = ` +"\\"[0]\\" does not look like a valid MDX plugin config. A plugin config entry should be one of: +- A tuple, like \`[require(\\"rehype-katex\\"), { strict: false }]\`, or +- A simple module, like \`require(\\"remark-math\\")\`" +`; -exports[`validation schemas RehypePluginsSchema: for value=[[null,true]] 1`] = `"\\"[0]\\" does not match any of the allowed types"`; +exports[`validation schemas rehypePluginsSchema: for value=[3] 1`] = ` +"\\"[0]\\" does not look like a valid MDX plugin config. A plugin config entry should be one of: +- A tuple, like \`[require(\\"rehype-katex\\"), { strict: false }]\`, or +- A simple module, like \`require(\\"remark-math\\")\`" +`; -exports[`validation schemas RehypePluginsSchema: for value=[3] 1`] = `"\\"[0]\\" does not match any of the allowed types"`; +exports[`validation schemas rehypePluginsSchema: for value=[false] 1`] = ` +"\\"[0]\\" does not look like a valid MDX plugin config. A plugin config entry should be one of: +- A tuple, like \`[require(\\"rehype-katex\\"), { strict: false }]\`, or +- A simple module, like \`require(\\"remark-math\\")\`" +`; -exports[`validation schemas RehypePluginsSchema: for value=[false] 1`] = `"\\"[0]\\" does not match any of the allowed types"`; +exports[`validation schemas rehypePluginsSchema: for value=[null] 1`] = ` +"\\"[0]\\" does not look like a valid MDX plugin config. A plugin config entry should be one of: +- A tuple, like \`[require(\\"rehype-katex\\"), { strict: false }]\`, or +- A simple module, like \`require(\\"remark-math\\")\`" +`; -exports[`validation schemas RehypePluginsSchema: for value=[null] 1`] = `"\\"[0]\\" does not match any of the allowed types"`; +exports[`validation schemas rehypePluginsSchema: for value=3 1`] = `"\\"value\\" must be an array"`; -exports[`validation schemas RehypePluginsSchema: for value=3 1`] = `"\\"value\\" must be an array"`; +exports[`validation schemas rehypePluginsSchema: for value=false 1`] = `"\\"value\\" must be an array"`; -exports[`validation schemas RehypePluginsSchema: for value=false 1`] = `"\\"value\\" must be an array"`; +exports[`validation schemas rehypePluginsSchema: for value=null 1`] = `"\\"value\\" must be an array"`; -exports[`validation schemas RehypePluginsSchema: for value=null 1`] = `"\\"value\\" must be an array"`; +exports[`validation schemas remarkPluginsSchema: for value=[[]] 1`] = ` +"\\"[0]\\" does not look like a valid MDX plugin config. A plugin config entry should be one of: +- A tuple, like \`[require(\\"rehype-katex\\"), { strict: false }]\`, or +- A simple module, like \`require(\\"remark-math\\")\`" +`; -exports[`validation schemas RemarkPluginsSchema: for value=[[]] 1`] = `"\\"[0]\\" does not match any of the allowed types"`; +exports[`validation schemas remarkPluginsSchema: for value=[[null,null]] 1`] = ` +"\\"[0]\\" does not look like a valid MDX plugin config. A plugin config entry should be one of: +- A tuple, like \`[require(\\"rehype-katex\\"), { strict: false }]\`, or +- A simple module, like \`require(\\"remark-math\\")\`" +`; -exports[`validation schemas RemarkPluginsSchema: for value=[[null,null]] 1`] = `"\\"[0]\\" does not match any of the allowed types"`; +exports[`validation schemas remarkPluginsSchema: for value=[3] 1`] = ` +"\\"[0]\\" does not look like a valid MDX plugin config. A plugin config entry should be one of: +- A tuple, like \`[require(\\"rehype-katex\\"), { strict: false }]\`, or +- A simple module, like \`require(\\"remark-math\\")\`" +`; -exports[`validation schemas RemarkPluginsSchema: for value=[[null,true]] 1`] = `"\\"[0]\\" does not match any of the allowed types"`; +exports[`validation schemas remarkPluginsSchema: for value=[false] 1`] = ` +"\\"[0]\\" does not look like a valid MDX plugin config. A plugin config entry should be one of: +- A tuple, like \`[require(\\"rehype-katex\\"), { strict: false }]\`, or +- A simple module, like \`require(\\"remark-math\\")\`" +`; -exports[`validation schemas RemarkPluginsSchema: for value=[3] 1`] = `"\\"[0]\\" does not match any of the allowed types"`; +exports[`validation schemas remarkPluginsSchema: for value=[null] 1`] = ` +"\\"[0]\\" does not look like a valid MDX plugin config. A plugin config entry should be one of: +- A tuple, like \`[require(\\"rehype-katex\\"), { strict: false }]\`, or +- A simple module, like \`require(\\"remark-math\\")\`" +`; -exports[`validation schemas RemarkPluginsSchema: for value=[false] 1`] = `"\\"[0]\\" does not match any of the allowed types"`; +exports[`validation schemas remarkPluginsSchema: for value=3 1`] = `"\\"value\\" must be an array"`; -exports[`validation schemas RemarkPluginsSchema: for value=[null] 1`] = `"\\"[0]\\" does not match any of the allowed types"`; +exports[`validation schemas remarkPluginsSchema: for value=false 1`] = `"\\"value\\" must be an array"`; -exports[`validation schemas RemarkPluginsSchema: for value=3 1`] = `"\\"value\\" must be an array"`; +exports[`validation schemas remarkPluginsSchema: for value=null 1`] = `"\\"value\\" must be an array"`; -exports[`validation schemas RemarkPluginsSchema: for value=false 1`] = `"\\"value\\" must be an array"`; - -exports[`validation schemas RemarkPluginsSchema: for value=null 1`] = `"\\"value\\" must be an array"`; - -exports[`validation schemas URISchema: for value="spaces are invalid in a URL" 1`] = `"\\"value\\" does not look like a valid url (value='')"`; +exports[`validation schemas uRISchema: for value="spaces are invalid in a URL" 1`] = `"\\"value\\" does not look like a valid url (value='')"`; diff --git a/packages/docusaurus-utils-validation/src/__tests__/validationSchemas.test.ts b/packages/docusaurus-utils-validation/src/__tests__/validationSchemas.test.ts index 7987f5b94607..534ac218fd94 100644 --- a/packages/docusaurus-utils-validation/src/__tests__/validationSchemas.test.ts +++ b/packages/docusaurus-utils-validation/src/__tests__/validationSchemas.test.ts @@ -43,13 +43,13 @@ function testMarkdownPluginSchemas(schema: Joi.Schema) { }); testOK(undefined); - testOK([function () {}]); - testOK([[function () {}, {attr: 'val'}]]); - testOK([ - [function () {}, {attr: 'val'}], - function () {}, - [function () {}, {attr: 'val'}], - ]); + testOK([() => {}]); + testOK([[() => {}, {attr: 'val'}]]); + testOK([[() => {}, {attr: 'val'}], () => {}, [() => {}, {attr: 'val'}]]); + // cSpell:ignore remarkjs + // official `remarkjs/remark-frontmatter` plugin accepts string options + testOK([[() => {}, 'string-option']]); + testOK([[() => {}, true]]); testFail(null); testFail(false); @@ -58,12 +58,11 @@ function testMarkdownPluginSchemas(schema: Joi.Schema) { testFail([false]); testFail([3]); testFail([[]]); - testFail([[function () {}, undefined]]); - testFail([[function () {}, true]]); + testFail([[() => {}, undefined]]); } describe('validation schemas', () => { - test('PluginIdSchema', () => { + it('pluginIdSchema', () => { const {testOK, testFail} = createTestHelpers({ schema: PluginIdSchema, defaultValue: 'default', @@ -73,6 +72,7 @@ describe('validation schemas', () => { testOK('docs'); testOK('default'); testOK('plugin-id_with-simple-special-chars'); + testOK('doc1'); testFail('/docs'); testFail('docs/'); @@ -84,7 +84,7 @@ describe('validation schemas', () => { testFail([]); }); - test('AdmonitionsSchema', () => { + it('admonitionsSchema', () => { const {testOK, testFail} = createTestHelpers({ schema: AdmonitionsSchema, defaultValue: {}, @@ -100,15 +100,15 @@ describe('validation schemas', () => { testFail([]); }); - test('RemarkPluginsSchema', () => { + it('remarkPluginsSchema', () => { testMarkdownPluginSchemas(RemarkPluginsSchema); }); - test('RehypePluginsSchema', () => { + it('rehypePluginsSchema', () => { testMarkdownPluginSchemas(RehypePluginsSchema); }); - test('URISchema', () => { + it('uRISchema', () => { const {testFail, testOK} = createTestHelpers({schema: URISchema}); const validURL = 'https://docusaurus.io'; @@ -130,7 +130,7 @@ describe('validation schemas', () => { testOK(protocolRelativeUrl2); }); - test('PathnameSchema', () => { + it('pathnameSchema', () => { const {testFail, testOK} = createTestHelpers({schema: PathnameSchema}); testOK('/foo'); diff --git a/packages/docusaurus-utils-validation/src/__tests__/validationUtils.test.ts b/packages/docusaurus-utils-validation/src/__tests__/validationUtils.test.ts index 4d06e1014f5c..25ed7b2f2793 100644 --- a/packages/docusaurus-utils-validation/src/__tests__/validationUtils.test.ts +++ b/packages/docusaurus-utils-validation/src/__tests__/validationUtils.test.ts @@ -5,12 +5,116 @@ * LICENSE file in the root directory of this source tree. */ +import {jest} from '@jest/globals'; import Joi from '../Joi'; import {JoiFrontMatter} from '../JoiFrontMatter'; -import {validateFrontMatter} from '../validationUtils'; +import { + normalizePluginOptions, + normalizeThemeConfig, + validateFrontMatter, +} from '../validationUtils'; + +describe('normalizePluginOptions', () => { + it('always adds an "id" field', () => { + const options = {id: 'a'}; + expect( + normalizePluginOptions( + // "Malicious" schema that tries to forbid "id" + Joi.object({id: Joi.any().forbidden()}), + options, + ), + ).toEqual(options); + expect( + normalizePluginOptions(Joi.object({foo: Joi.string()}), undefined), + ).toEqual({id: 'default'}); + }); + + it('normalizes plugin options', () => { + const options = {}; + expect( + normalizePluginOptions( + Joi.object({foo: Joi.string().default('a')}), + options, + ), + ).toEqual({foo: 'a', id: 'default'}); + }); + + it('throws for invalid options', () => { + const options = {foo: 1}; + expect(() => + normalizePluginOptions(Joi.object({foo: Joi.string()}), options), + ).toThrowErrorMatchingInlineSnapshot(`"\\"foo\\" must be a string"`); + }); + + it('warns', () => { + const options = {foo: 'a'}; + const consoleMock = jest + .spyOn(console, 'warn') + .mockImplementation(() => {}); + expect( + normalizePluginOptions( + Joi.object({foo: Joi.string().warning('deprecated', {})}).messages({ + deprecated: '{#label} deprecated', + }), + options, + ), + ).toEqual({foo: 'a', id: 'default'}); + expect(consoleMock).toBeCalledWith( + expect.stringMatching(/"foo" deprecated/), + ); + }); +}); + +describe('normalizeThemeConfig', () => { + it('always allows unknown attributes', () => { + const themeConfig = {foo: 'a', bar: 1}; + expect( + normalizeThemeConfig( + // "Malicious" schema that tries to forbid extra properties + Joi.object({foo: Joi.string()}).unknown(false), + themeConfig, + ), + ).toEqual(themeConfig); + }); + + it('normalizes theme config', () => { + const themeConfig = {bar: 1}; + expect( + normalizeThemeConfig( + Joi.object({foo: Joi.string().default('a')}), + themeConfig, + ), + ).toEqual({bar: 1, foo: 'a'}); + }); + + it('throws for invalid options', () => { + const themeConfig = {foo: 1, bar: 1}; + expect(() => + normalizeThemeConfig(Joi.object({foo: Joi.string()}), themeConfig), + ).toThrowErrorMatchingInlineSnapshot(`"\\"foo\\" must be a string"`); + }); + + it('warns', () => { + const themeConfig = {foo: 'a', bar: 1}; + const consoleMock = jest + .spyOn(console, 'warn') + .mockImplementation(() => {}); + expect( + normalizeThemeConfig( + Joi.object({foo: Joi.string().warning('deprecated', {})}).messages({ + deprecated: '{#label} deprecated', + }), + themeConfig, + ), + ).toEqual(themeConfig); + expect(consoleMock).toBeCalledWith( + expect.stringMatching(/"foo" deprecated/), + ); + }); +}); describe('validateFrontMatter', () => { - test('should accept good values', () => { + it('accepts good values', () => { const schema = Joi.object<{test: string}>({ test: Joi.string(), }); @@ -20,8 +124,10 @@ describe('validateFrontMatter', () => { expect(validateFrontMatter(frontMatter, schema)).toEqual(frontMatter); }); - test('should reject bad values', () => { - const consoleError = jest.spyOn(console, 'error').mockImplementation(); + it('rejects bad values', () => { + const consoleError = jest + .spyOn(console, 'error') + .mockImplementation(() => {}); const schema = Joi.object<{test: string}>({ test: Joi.string(), }); @@ -36,7 +142,7 @@ describe('validateFrontMatter', () => { ); }); - test('should not convert simple values', () => { + it('does not convert simple values', () => { const schema = Joi.object({ test: JoiFrontMatter.string(), }); @@ -48,8 +154,9 @@ describe('validateFrontMatter', () => { }); // Fix Yaml trying to convert strings to numbers automatically - // We only want to deal with a single type in the final front matter (not string | number) - test('should convert number values to string when string schema', () => { + // We only want to deal with a single type in the final front matter + // (not string | number) + it('converts number values to string when string schema', () => { const schema = Joi.object<{test: string}>({ test: JoiFrontMatter.string(), }); @@ -60,8 +167,9 @@ describe('validateFrontMatter', () => { }); // Helps to fix Yaml trying to convert strings to dates automatically - // We only want to deal with a single type in the final front matter (not string | Date) - test('should convert date values when string schema', () => { + // We only want to deal with a single type in the final front matter + // (not string | Date) + it('converts date values when string schema', () => { const schema = Joi.object<{test: string}>({ test: JoiFrontMatter.string(), }); diff --git a/packages/docusaurus-utils-validation/src/index.ts b/packages/docusaurus-utils-validation/src/index.ts index daaac61bd4d8..40924a6da012 100644 --- a/packages/docusaurus-utils-validation/src/index.ts +++ b/packages/docusaurus-utils-validation/src/index.ts @@ -9,5 +9,19 @@ export {default as Joi} from './Joi'; export {JoiFrontMatter} from './JoiFrontMatter'; -export * from './validationUtils'; -export * from './validationSchemas'; +export { + printWarning, + normalizePluginOptions, + normalizeThemeConfig, + validateFrontMatter, +} from './validationUtils'; +export { + PluginIdSchema, + RemarkPluginsSchema, + RehypePluginsSchema, + AdmonitionsSchema, + URISchema, + PathnameSchema, + FrontMatterTagsSchema, + FrontMatterTOCHeadingLevels, +} from './validationSchemas'; diff --git a/packages/docusaurus-utils-validation/src/validationSchemas.ts b/packages/docusaurus-utils-validation/src/validationSchemas.ts index e87d7c2a8ccb..f49b962a853b 100644 --- a/packages/docusaurus-utils-validation/src/validationSchemas.ts +++ b/packages/docusaurus-utils-validation/src/validationSchemas.ts @@ -11,15 +11,23 @@ import type {Tag} from '@docusaurus/utils'; import {JoiFrontMatter} from './JoiFrontMatter'; export const PluginIdSchema = Joi.string() - .regex(/^[a-zA-Z_-]+$/) + .regex(/^[\w-]+$/) + .message( + 'Illegal plugin ID value "{#value}": it should only contain alphanumerics, underscores, and dashes.', + ) .default(DEFAULT_PLUGIN_ID); const MarkdownPluginsSchema = Joi.array() .items( - Joi.array().ordered(Joi.function().required(), Joi.object().required()), + Joi.array().ordered(Joi.function().required(), Joi.any().required()), Joi.function(), Joi.object(), ) + .messages({ + 'array.includes': `{#label} does not look like a valid MDX plugin config. A plugin config entry should be one of: +- A tuple, like \`[require("rehype-katex"), \\{ strict: false \\}]\`, or +- A simple module, like \`require("remark-math")\``, + }) .default([]); export const RemarkPluginsSchema = MarkdownPluginsSchema; @@ -31,7 +39,8 @@ export const AdmonitionsSchema = Joi.object().default({}); // Joi is such a pain, good luck to annoying trying to improve this export const URISchema = Joi.alternatives( Joi.string().uri({allowRelative: true}), - // This custom validation logic is required notably because Joi does not accept paths like /a/b/c ... + // This custom validation logic is required notably because Joi does not + // accept paths like /a/b/c ... Joi.custom((val, helpers) => { try { // eslint-disable-next-line no-new diff --git a/packages/docusaurus-utils-validation/src/validationUtils.ts b/packages/docusaurus-utils-validation/src/validationUtils.ts index b524a96717b8..33145624da4d 100644 --- a/packages/docusaurus-utils-validation/src/validationUtils.ts +++ b/packages/docusaurus-utils-validation/src/validationUtils.ts @@ -7,28 +7,10 @@ import type Joi from './Joi'; import logger from '@docusaurus/logger'; +import Yaml from 'js-yaml'; import {PluginIdSchema} from './validationSchemas'; -// TODO temporary escape hatch for alpha-60: to be removed soon -// Our validation schemas might be buggy at first -// will permit users to bypass validation until we fix all validation errors -// see for example: https://github.com/facebook/docusaurus/pull/3120 -// Undocumented on purpose, as we don't want users to keep using it over time -// Maybe we'll make this escape hatch official some day, with a better api? -export const isValidationDisabledEscapeHatch = - process.env.DISABLE_DOCUSAURUS_VALIDATION === 'true'; - -if (isValidationDisabledEscapeHatch) { - logger.error`You should avoid using code=${'DISABLE_DOCUSAURUS_VALIDATION'} escape hatch, this will be removed.`; -} - -export const logValidationBugReportHint = (): void => { - logger.error('A validation error occurred.'); - logger.info(`The validation system was added recently to Docusaurus as an attempt to avoid user configuration errors. -We may have made some mistakes. -If you think your configuration is valid and should keep working, please open a bug report.`); -}; - +/** Print warnings returned from Joi validation. */ export function printWarning(warning?: Joi.ValidationError): void { if (warning) { const warningMessages = warning.details @@ -38,9 +20,15 @@ export function printWarning(warning?: Joi.ValidationError): void { } } +/** + * The callback that should be used to validate plugin options. Handles plugin + * IDs on a generic level: no matter what the schema declares, this callback + * would require a string ID or default to "default". + */ export function normalizePluginOptions( schema: Joi.ObjectSchema, - options: Partial, + // This allows us to automatically normalize undefined to { id: "default" } + options: Partial = {}, ): T { // All plugins can be provided an "id" option (multi-instance support) // we add schema validation automatically @@ -54,23 +42,21 @@ export function normalizePluginOptions( printWarning(warning); if (error) { - logValidationBugReportHint(); - if (isValidationDisabledEscapeHatch) { - logger.error(error); - return options as T; - } else { - throw error; - } + throw error; } - return value!; // TODO remove ! this in TS 4.6, see https://twitter.com/sebastienlorber/status/1481950042277793793 + return value; } +/** + * The callback that should be used to validate theme config. No matter what the + * schema declares, this callback would allow unknown attributes. + */ export function normalizeThemeConfig( schema: Joi.ObjectSchema, themeConfig: Partial, ): T { - // A theme should only validate his "slice" of the full themeConfig, + // A theme should only validate its "slice" of the full themeConfig, // not the whole object, so we allow unknown attributes // otherwise one theme would fail validating the data of another theme const finalSchema = schema.unknown(); @@ -82,19 +68,16 @@ export function normalizeThemeConfig( printWarning(warning); if (error) { - logValidationBugReportHint(); - if (isValidationDisabledEscapeHatch) { - logger.error(error); - return themeConfig as T; - } else { - throw error; - } + throw error; } - return value!; // TODO remove ! this in TS 4.6, see https://twitter.com/sebastienlorber/status/1481950042277793793 + return value; } +/** + * Validate front matter with better error message + */ export function validateFrontMatter( - frontMatter: Record, + frontMatter: {[key: string]: unknown}, schema: Joi.ObjectSchema, ): T { const {value, error, warning} = schema.validate(frontMatter, { @@ -106,19 +89,17 @@ export function validateFrontMatter( printWarning(warning); if (error) { - const frontMatterString = JSON.stringify(frontMatter, null, 2); const errorDetails = error.details; const invalidFields = errorDetails.map(({path}) => path).join(', '); - logValidationBugReportHint(); - logger.error`The following front matter: -${logger.yellow(frontMatterString)} -contains invalid values for field(s): ${logger.yellow(invalidFields)}. +--- +${Yaml.dump(frontMatter)}--- +contains invalid values for field(s): code=${invalidFields}. ${errorDetails.map(({message}) => message)} `; throw error; } - return value!; // TODO remove ! this in TS 4.6, see https://twitter.com/sebastienlorber/status/1481950042277793793 + return value; } diff --git a/packages/docusaurus-utils/package.json b/packages/docusaurus-utils/package.json index e7df00d69e1f..51435bb08ab7 100644 --- a/packages/docusaurus-utils/package.json +++ b/packages/docusaurus-utils/package.json @@ -1,6 +1,6 @@ { "name": "@docusaurus/utils", - "version": "2.0.0-beta.14", + "version": "2.0.0-beta.18", "description": "Node utility functions for Docusaurus packages.", "main": "./lib/index.js", "types": "./lib/index.d.ts", @@ -18,39 +18,31 @@ }, "license": "MIT", "dependencies": { - "@docusaurus/logger": "2.0.0-beta.14", - "@mdx-js/runtime": "^1.6.22", - "@svgr/webpack": "^6.0.0", + "@docusaurus/logger": "2.0.0-beta.18", + "@svgr/webpack": "^6.2.1", "file-loader": "^6.2.0", - "fs-extra": "^10.0.0", + "fs-extra": "^10.0.1", "github-slugger": "^1.4.0", - "globby": "^11.0.4", + "globby": "^11.1.0", "gray-matter": "^4.0.3", - "js-yaml": "^4.0.0", - "lodash": "^4.17.20", - "micromatch": "^4.0.4", - "remark-mdx-remove-exports": "^1.6.22", - "remark-mdx-remove-imports": "^1.6.22", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "micromatch": "^4.0.5", "resolve-pathname": "^3.0.0", + "shelljs": "^0.8.5", "tslib": "^2.3.1", - "url-loader": "^4.1.1" + "url-loader": "^4.1.1", + "webpack": "^5.70.0" }, "engines": { "node": ">=14" }, "devDependencies": { - "@docusaurus/types": "2.0.0-beta.14", + "@docusaurus/types": "2.0.0-beta.18", "@types/dedent": "^0.7.0", "@types/github-slugger": "^1.3.0", "@types/micromatch": "^4.0.2", - "@types/react-dom": "^17.0.1", - "dedent": "^0.7.0", - "tslib": "^2.3.1" - }, - "peerDependencies": { - "@babel/core": "^7.0.0", - "react": "*", - "react-dom": "*", - "webpack": "5.x" + "@types/react-dom": "^17.0.14", + "dedent": "^0.7.0" } } diff --git a/packages/docusaurus-utils/src/__tests__/__fixtures__/build-snap/file.html b/packages/docusaurus-utils/src/__tests__/__fixtures__/build-snap/file.html new file mode 100644 index 000000000000..f73f3093ff86 --- /dev/null +++ b/packages/docusaurus-utils/src/__tests__/__fixtures__/build-snap/file.html @@ -0,0 +1 @@ +file diff --git a/packages/docusaurus-utils/src/__tests__/__fixtures__/build-snap/folder/index.html b/packages/docusaurus-utils/src/__tests__/__fixtures__/build-snap/folder/index.html new file mode 100644 index 000000000000..9db6f38c1853 --- /dev/null +++ b/packages/docusaurus-utils/src/__tests__/__fixtures__/build-snap/folder/index.html @@ -0,0 +1 @@ +folder diff --git a/packages/docusaurus-utils/src/__tests__/__snapshots__/markdownLinks.test.ts.snap b/packages/docusaurus-utils/src/__tests__/__snapshots__/markdownLinks.test.ts.snap new file mode 100644 index 000000000000..f8d2b1f78741 --- /dev/null +++ b/packages/docusaurus-utils/src/__tests__/__snapshots__/markdownLinks.test.ts.snap @@ -0,0 +1,136 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`replaceMarkdownLinks does basic replace 1`] = ` +{ + "brokenMarkdownLinks": [ + { + "contentPaths": { + "contentPath": "docs", + "contentPathLocalized": "i18n/docs-localized", + }, + "filePath": "docs/intro.md", + "link": "hmmm.md", + }, + ], + "newContent": " +[foo](/doc/foo) +[baz](/doc/baz) +[foo](/doc/foo) +[http](http://github.com/facebook/docusaurus/README.md) +[https](https://github.com/facebook/docusaurus/README.md) +[asset](./foo.js) +[asset as well](@site/docs/_partial.md) +[looks like http...](/doc/http) +[nonexistent](hmmm.md) +", +} +`; + +exports[`replaceMarkdownLinks ignores links in HTML comments 1`] = ` +{ + "brokenMarkdownLinks": [ + { + "contentPaths": { + "contentPath": "docs", + "contentPathLocalized": "i18n/docs-localized", + }, + "filePath": "docs/intro.md", + "link": "./foo.md", + }, + { + "contentPaths": { + "contentPath": "docs", + "contentPathLocalized": "i18n/docs-localized", + }, + "filePath": "docs/intro.md", + "link": "./foo.md", + }, + ], + "newContent": " + + +", +} +`; + +exports[`replaceMarkdownLinks ignores links in fenced blocks 1`] = ` +{ + "brokenMarkdownLinks": [], + "newContent": " +\`\`\` +[foo](foo.md) +\`\`\` + +\`\`\`\`js +[foo](foo.md) +\`\`\` +[foo](foo.md) +\`\`\` +[foo](foo.md) +\`\`\`\` + +\`\`\`\`js +[foo](foo.md) +\`\`\` +[foo](foo.md) +\`\`\`\` +", +} +`; + +exports[`replaceMarkdownLinks ignores links in inline code 1`] = ` +{ + "brokenMarkdownLinks": [ + { + "contentPaths": { + "contentPath": "docs", + "contentPathLocalized": "i18n/docs-localized", + }, + "filePath": "docs/intro.md", + "link": "foo.md", + }, + ], + "newContent": " +\`[foo](foo.md)\` +", +} +`; + +exports[`replaceMarkdownLinks replaces links with same title as URL 1`] = ` +{ + "brokenMarkdownLinks": [], + "newContent": " +[/docs/foo](foo.md) +[/docs/foo](./foo.md) +[foo.md](/docs/foo) +[.//docs/foo](foo.md) +", +} +`; + +exports[`replaceMarkdownLinks replaces multiple links on same line 1`] = ` +{ + "brokenMarkdownLinks": [], + "newContent": " +[a](/docs/a), [a](/docs/a), [b](/docs/b), [c](/docs/c) +", +} +`; + +exports[`replaceMarkdownLinks replaces reference style Markdown links 1`] = ` +{ + "brokenMarkdownLinks": [], + "newContent": " +The following operations are defined for [URI]s: + +* [info]: Returns metadata about the resource, +* [list]: Returns metadata about the resource's children (like getting the content of a local directory). + +[URI]: /docs/api/classes/uri +[info]: /docs/api/classes/uri#info +[list]: /docs/api/classes/uri#list + ", +} +`; diff --git a/packages/docusaurus-utils/src/__tests__/__snapshots__/markdownUtils.test.ts.snap b/packages/docusaurus-utils/src/__tests__/__snapshots__/markdownUtils.test.ts.snap new file mode 100644 index 000000000000..5a65f0bb828f --- /dev/null +++ b/packages/docusaurus-utils/src/__tests__/__snapshots__/markdownUtils.test.ts.snap @@ -0,0 +1,200 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`parseMarkdownString deletes only first heading 1`] = ` +{ + "content": "# Markdown Title + +test test test # test bar + +# Markdown Title 2 + +### Markdown Title h3", + "contentTitle": "Markdown Title", + "excerpt": "test test test # test bar", + "frontMatter": {}, +} +`; + +exports[`parseMarkdownString deletes only first heading 2 1`] = ` +{ + "content": "# test + +test test test test test test +test test test # test bar +# test2 +### test +test3", + "contentTitle": "test", + "excerpt": "test test test test test test", + "frontMatter": {}, +} +`; + +exports[`parseMarkdownString does not warn for duplicate title if markdown title is not at the top 1`] = ` +{ + "content": "foo + +# Markdown Title", + "contentTitle": undefined, + "excerpt": "foo", + "frontMatter": { + "title": "Frontmatter title", + }, +} +`; + +exports[`parseMarkdownString handles code blocks 1`] = ` +{ + "content": "\`\`\`js +code +\`\`\` + +Content", + "contentTitle": undefined, + "excerpt": "Content", + "frontMatter": {}, +} +`; + +exports[`parseMarkdownString handles code blocks 2`] = ` +{ + "content": "\`\`\`\`js +Foo +\`\`\`diff +code +\`\`\` +Bar +\`\`\`\` + +Content", + "contentTitle": undefined, + "excerpt": "Content", + "frontMatter": {}, +} +`; + +exports[`parseMarkdownString handles code blocks 3`] = ` +{ + "content": "\`\`\`\`js +Foo +\`\`\`diff +code +\`\`\`\` + +Content", + "contentTitle": undefined, + "excerpt": "Content", + "frontMatter": {}, +} +`; + +exports[`parseMarkdownString ignores markdown title if its not a first text 1`] = ` +{ + "content": "foo +# test", + "contentTitle": undefined, + "excerpt": "foo", + "frontMatter": {}, +} +`; + +exports[`parseMarkdownString parse markdown with front matter 1`] = ` +{ + "content": "Some text", + "contentTitle": undefined, + "excerpt": "Some text", + "frontMatter": { + "title": "Frontmatter title", + }, +} +`; + +exports[`parseMarkdownString parses first heading as contentTitle 1`] = ` +{ + "content": "# Markdown Title + +Some text", + "contentTitle": "Markdown Title", + "excerpt": "Some text", + "frontMatter": {}, +} +`; + +exports[`parseMarkdownString parses front-matter and ignore h2 1`] = ` +{ + "content": "## test", + "contentTitle": undefined, + "excerpt": "test", + "frontMatter": { + "title": "Frontmatter title", + }, +} +`; + +exports[`parseMarkdownString parses title only 1`] = ` +{ + "content": "# test", + "contentTitle": "test", + "excerpt": undefined, + "frontMatter": {}, +} +`; + +exports[`parseMarkdownString parses title only alternate 1`] = ` +{ + "content": "test +===", + "contentTitle": "test", + "excerpt": undefined, + "frontMatter": {}, +} +`; + +exports[`parseMarkdownString reads front matter only 1`] = ` +{ + "content": "", + "contentTitle": undefined, + "excerpt": undefined, + "frontMatter": { + "title": "test", + }, +} +`; + +exports[`parseMarkdownString warns about duplicate titles (front matter + markdown alternate) 1`] = ` +{ + "content": "Markdown Title alternate +================ + +Some text", + "contentTitle": "Markdown Title alternate", + "excerpt": "Some text", + "frontMatter": { + "title": "Frontmatter title", + }, +} +`; + +exports[`parseMarkdownString warns about duplicate titles (front matter + markdown) 1`] = ` +{ + "content": "# Markdown Title + +Some text", + "contentTitle": "Markdown Title", + "excerpt": "Some text", + "frontMatter": { + "title": "Frontmatter title", + }, +} +`; + +exports[`parseMarkdownString warns about duplicate titles 1`] = ` +{ + "content": "# test", + "contentTitle": "test", + "excerpt": undefined, + "frontMatter": { + "title": "Frontmatter title", + }, +} +`; diff --git a/packages/docusaurus-utils/src/__tests__/dataFileUtils.test.ts b/packages/docusaurus-utils/src/__tests__/dataFileUtils.test.ts index 54f92601cc06..251a1f6dbe2c 100644 --- a/packages/docusaurus-utils/src/__tests__/dataFileUtils.test.ts +++ b/packages/docusaurus-utils/src/__tests__/dataFileUtils.test.ts @@ -22,7 +22,7 @@ describe('getDataFilePath', () => { const contentPathEmpty = path.join(fixturesDir, 'contentPathEmpty'); const contentPathNestedYml = path.join(fixturesDir, 'contentPathNestedYml'); - test('getDataFilePath returns localized Yml path in priority', async () => { + it('getDataFilePath returns localized Yml path in priority', async () => { await expect( getDataFilePath({ filePath: 'authors.yml', @@ -43,7 +43,7 @@ describe('getDataFilePath', () => { ).resolves.toEqual(path.join(contentPathYml2, 'authors.yml')); }); - test('getDataFilePath returns localized Json path in priority', async () => { + it('getDataFilePath returns localized Json path in priority', async () => { await expect( getDataFilePath({ filePath: 'authors.json', @@ -64,7 +64,7 @@ describe('getDataFilePath', () => { ).resolves.toEqual(path.join(contentPathJson2, 'authors.json')); }); - test('getDataFilePath returns unlocalized Yml path as fallback', async () => { + it('getDataFilePath returns unlocalized Yml path as fallback', async () => { await expect( getDataFilePath({ filePath: 'authors.yml', @@ -76,7 +76,7 @@ describe('getDataFilePath', () => { ).resolves.toEqual(path.join(contentPathYml2, 'authors.yml')); }); - test('getDataFilePath returns unlocalized Json path as fallback', async () => { + it('getDataFilePath returns unlocalized Json path as fallback', async () => { await expect( getDataFilePath({ filePath: 'authors.json', @@ -88,7 +88,7 @@ describe('getDataFilePath', () => { ).resolves.toEqual(path.join(contentPathJson1, 'authors.json')); }); - test('getDataFilePath can return undefined (file not found)', async () => { + it('getDataFilePath can return undefined (file not found)', async () => { await expect( getDataFilePath({ filePath: 'authors.json', @@ -109,7 +109,7 @@ describe('getDataFilePath', () => { ).resolves.toBeUndefined(); }); - test('getDataFilePath can return nested path', async () => { + it('getDataFilePath can return nested path', async () => { await expect( getDataFilePath({ filePath: 'sub/folder/authors.yml', @@ -143,25 +143,25 @@ describe('getDataFileData', () => { ); } - test('returns undefined for nonexistent file', async () => { + it('returns undefined for nonexistent file', async () => { await expect(readDataFile('nonexistent.yml')).resolves.toBeUndefined(); }); - test('read valid yml author file', async () => { + it('read valid yml author file', async () => { await expect(readDataFile('valid.yml')).resolves.toEqual({a: 1}); }); - test('read valid json author file', async () => { + it('read valid json author file', async () => { await expect(readDataFile('valid.json')).resolves.toEqual({a: 1}); }); - test('fail to read invalid yml', async () => { + it('fail to read invalid yml', async () => { await expect( readDataFile('bad.yml'), ).rejects.toThrowErrorMatchingInlineSnapshot(`"Nope"`); }); - test('fail to read invalid json', async () => { + it('fail to read invalid json', async () => { await expect( readDataFile('bad.json'), ).rejects.toThrowErrorMatchingInlineSnapshot(`"Nope"`); @@ -169,40 +169,43 @@ describe('getDataFileData', () => { }); describe('findFolderContainingFile', () => { - test('find appropriate folder', async () => { + it('find appropriate folder', async () => { await expect( findFolderContainingFile( - ['/abcdef', '/gehij', __dirname, '/klmn'], - 'index.test.ts', + ['/foo', '/baz', __dirname, '/bar'], + 'dataFileUtils.test.ts', ), ).resolves.toEqual(__dirname); }); - test('return undefined if no folder contain such file', async () => { + it('return undefined if no folder contain such file', async () => { await expect( - findFolderContainingFile(['/abcdef', '/gehij', '/klmn'], 'index.test.ts'), + findFolderContainingFile(['/foo', '/bar', '/baz'], 'index.test.ts'), ).resolves.toBeUndefined(); }); }); describe('getFolderContainingFile', () => { - test('get appropriate folder', async () => { + it('get appropriate folder', async () => { await expect( getFolderContainingFile( - ['/abcdef', '/gehij', __dirname, '/klmn'], - 'index.test.ts', + ['/foo', '/baz', __dirname, '/bar'], + 'dataFileUtils.test.ts', ), ).resolves.toEqual(__dirname); }); - test('throw if no folder contain such file', async () => { + it('throw if no folder contain such file', async () => { await expect( - getFolderContainingFile(['/abcdef', '/gehij', '/klmn'], 'index.test.ts'), + getFolderContainingFile( + ['/foo', '/bar', '/baz'], + 'dataFileUtils.test.ts', + ), ).rejects.toThrowErrorMatchingInlineSnapshot(` - "File \\"index.test.ts\\" does not exist in any of these folders: - - /abcdef - - /gehij - - /klmn]" + "File \\"dataFileUtils.test.ts\\" does not exist in any of these folders: + - /foo + - /bar + - /baz" `); }); }); diff --git a/packages/docusaurus-utils/src/__tests__/emitUtils.test.ts b/packages/docusaurus-utils/src/__tests__/emitUtils.test.ts new file mode 100644 index 000000000000..e5f975abb9f0 --- /dev/null +++ b/packages/docusaurus-utils/src/__tests__/emitUtils.test.ts @@ -0,0 +1,114 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {jest} from '@jest/globals'; +import {readOutputHTMLFile, generate} from '../emitUtils'; +import path from 'path'; +import fs from 'fs-extra'; + +describe('readOutputHTMLFile', () => { + it('trailing slash undefined', async () => { + await expect( + readOutputHTMLFile( + '/file', + path.join(__dirname, '__fixtures__/build-snap'), + undefined, + ).then(String), + ).resolves.toBe('file\n'); + await expect( + readOutputHTMLFile( + '/folder', + path.join(__dirname, '__fixtures__/build-snap'), + undefined, + ).then(String), + ).resolves.toBe('folder\n'); + await expect( + readOutputHTMLFile( + '/file/', + path.join(__dirname, '__fixtures__/build-snap'), + undefined, + ).then(String), + ).resolves.toBe('file\n'); + await expect( + readOutputHTMLFile( + '/folder/', + path.join(__dirname, '__fixtures__/build-snap'), + undefined, + ).then(String), + ).resolves.toBe('folder\n'); + }); + it('trailing slash true', async () => { + await expect( + readOutputHTMLFile( + '/folder', + path.join(__dirname, '__fixtures__/build-snap'), + true, + ).then(String), + ).resolves.toBe('folder\n'); + await expect( + readOutputHTMLFile( + '/folder/', + path.join(__dirname, '__fixtures__/build-snap'), + true, + ).then(String), + ).resolves.toBe('folder\n'); + }); + it('trailing slash false', async () => { + await expect( + readOutputHTMLFile( + '/file', + path.join(__dirname, '__fixtures__/build-snap'), + false, + ).then(String), + ).resolves.toBe('file\n'); + await expect( + readOutputHTMLFile( + '/file/', + path.join(__dirname, '__fixtures__/build-snap'), + false, + ).then(String), + ).resolves.toBe('file\n'); + }); +}); + +describe('generate', () => { + const writeMock = jest.spyOn(fs, 'outputFile').mockImplementation(() => {}); + const existsMock = jest.spyOn(fs, 'pathExists'); + const readMock = jest.spyOn(fs, 'readFile'); + + it('works with no file and no cache', async () => { + existsMock.mockImplementationOnce(() => false); + await generate(__dirname, 'foo', 'bar'); + expect(writeMock).toHaveBeenNthCalledWith( + 1, + path.join(__dirname, 'foo'), + 'bar', + ); + }); + + it('works with existing cache', async () => { + await generate(__dirname, 'foo', 'bar'); + expect(writeMock).toBeCalledTimes(1); + }); + + it('works with existing file but no cache', async () => { + existsMock.mockImplementationOnce(() => true); + // @ts-expect-error: seems the typedef doesn't understand overload + readMock.mockImplementationOnce(() => Promise.resolve('bar')); + await generate(__dirname, 'baz', 'bar'); + expect(writeMock).toBeCalledTimes(1); + }); + + it('works when force skipping cache', async () => { + await generate(__dirname, 'foo', 'bar', true); + expect(writeMock).toHaveBeenNthCalledWith( + 2, + path.join(__dirname, 'foo'), + 'bar', + ); + }); +}); diff --git a/packages/docusaurus-utils/src/__tests__/gitUtils.test.ts b/packages/docusaurus-utils/src/__tests__/gitUtils.test.ts new file mode 100644 index 000000000000..1d4519536cc4 --- /dev/null +++ b/packages/docusaurus-utils/src/__tests__/gitUtils.test.ts @@ -0,0 +1,135 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {FileNotTrackedError, getFileCommitDate} from '../gitUtils'; +import fs from 'fs-extra'; +import path from 'path'; +import {createTempRepo} from '@testing-utils/git'; + +/* eslint-disable no-restricted-properties */ +function initializeTempRepo() { + const {repoDir, git} = createTempRepo(); + + fs.writeFileSync(path.join(repoDir, 'test.txt'), 'Some content'); + git.commit( + 'Create test.txt', + '2020-06-19', + 'Caroline ', + ); + fs.writeFileSync(path.join(repoDir, 'test.txt'), 'Updated content'); + git.commit( + 'Update test.txt', + '2020-06-20', + 'Josh-Cena ', + ); + fs.writeFileSync(path.join(repoDir, 'test.txt'), 'Updated content (2)'); + fs.writeFileSync(path.join(repoDir, 'moved.txt'), 'This file is moved'); + git.commit( + 'Update test.txt again, create moved.txt', + '2020-09-13', + 'Caroline ', + ); + fs.moveSync(path.join(repoDir, 'moved.txt'), path.join(repoDir, 'dest.txt')); + git.commit( + 'Rename moved.txt to dest.txt', + '2020-11-13', + 'Josh-Cena ', + ); + fs.writeFileSync(path.join(repoDir, 'untracked.txt'), "I'm untracked"); + + return repoDir; +} + +describe('getFileCommitDate', () => { + const repoDir = initializeTempRepo(); + it('returns earliest commit date', async () => { + expect(getFileCommitDate(path.join(repoDir, 'test.txt'), {})).toEqual({ + date: new Date('2020-06-19'), + timestamp: new Date('2020-06-19').getTime() / 1000, + }); + expect(getFileCommitDate(path.join(repoDir, 'dest.txt'), {})).toEqual({ + date: new Date('2020-09-13'), + timestamp: new Date('2020-09-13').getTime() / 1000, + }); + }); + it('returns latest commit date', async () => { + expect( + getFileCommitDate(path.join(repoDir, 'test.txt'), {age: 'newest'}), + ).toEqual({ + date: new Date('2020-09-13'), + timestamp: new Date('2020-09-13').getTime() / 1000, + }); + expect( + getFileCommitDate(path.join(repoDir, 'dest.txt'), {age: 'newest'}), + ).toEqual({ + date: new Date('2020-11-13'), + timestamp: new Date('2020-11-13').getTime() / 1000, + }); + }); + it('returns latest commit date with author', async () => { + expect( + getFileCommitDate(path.join(repoDir, 'test.txt'), { + age: 'oldest', + includeAuthor: true, + }), + ).toEqual({ + date: new Date('2020-06-19'), + timestamp: new Date('2020-06-19').getTime() / 1000, + author: 'Caroline', + }); + expect( + getFileCommitDate(path.join(repoDir, 'dest.txt'), { + age: 'oldest', + includeAuthor: true, + }), + ).toEqual({ + date: new Date('2020-09-13'), + timestamp: new Date('2020-09-13').getTime() / 1000, + author: 'Caroline', + }); + }); + it('returns earliest commit date with author', async () => { + expect( + getFileCommitDate(path.join(repoDir, 'test.txt'), { + age: 'newest', + includeAuthor: true, + }), + ).toEqual({ + date: new Date('2020-09-13'), + timestamp: new Date('2020-09-13').getTime() / 1000, + author: 'Caroline', + }); + expect( + getFileCommitDate(path.join(repoDir, 'dest.txt'), { + age: 'newest', + includeAuthor: true, + }), + ).toEqual({ + date: new Date('2020-11-13'), + timestamp: new Date('2020-11-13').getTime() / 1000, + author: 'Josh-Cena', + }); + }); + it('throws custom error when file is not tracked', async () => { + expect(() => + getFileCommitDate(path.join(repoDir, 'untracked.txt'), { + age: 'newest', + includeAuthor: true, + }), + ).toThrowError(FileNotTrackedError); + }); + it('throws when file not found', async () => { + expect(() => + getFileCommitDate(path.join(repoDir, 'nonexistent.txt'), { + age: 'newest', + includeAuthor: true, + }), + ).toThrowError( + /Failed to retrieve git history for ".*nonexistent.txt" because the file does not exist./, + ); + }); +}); diff --git a/packages/docusaurus-utils/src/__tests__/globUtils.test.ts b/packages/docusaurus-utils/src/__tests__/globUtils.test.ts index 95555e08d788..e2f3d52f2c1f 100644 --- a/packages/docusaurus-utils/src/__tests__/globUtils.test.ts +++ b/packages/docusaurus-utils/src/__tests__/globUtils.test.ts @@ -14,54 +14,54 @@ import { describe('createMatcher', () => { const matcher = createMatcher(GlobExcludeDefault); - test('match default exclude MD/MDX partials correctly', () => { - expect(matcher('doc.md')).toEqual(false); - expect(matcher('category/doc.md')).toEqual(false); - expect(matcher('category/subcategory/doc.md')).toEqual(false); + it('match default exclude MD/MDX partials correctly', () => { + expect(matcher('doc.md')).toBe(false); + expect(matcher('category/doc.md')).toBe(false); + expect(matcher('category/subcategory/doc.md')).toBe(false); // - expect(matcher('doc.mdx')).toEqual(false); - expect(matcher('category/doc.mdx')).toEqual(false); - expect(matcher('category/subcategory/doc.mdx')).toEqual(false); + expect(matcher('doc.mdx')).toBe(false); + expect(matcher('category/doc.mdx')).toBe(false); + expect(matcher('category/subcategory/doc.mdx')).toBe(false); // - expect(matcher('_doc.md')).toEqual(true); - expect(matcher('category/_doc.md')).toEqual(true); - expect(matcher('category/subcategory/_doc.md')).toEqual(true); - expect(matcher('_category/doc.md')).toEqual(true); - expect(matcher('_category/subcategory/doc.md')).toEqual(true); - expect(matcher('category/_subcategory/doc.md')).toEqual(true); + expect(matcher('_doc.md')).toBe(true); + expect(matcher('category/_doc.md')).toBe(true); + expect(matcher('category/subcategory/_doc.md')).toBe(true); + expect(matcher('_category/doc.md')).toBe(true); + expect(matcher('_category/subcategory/doc.md')).toBe(true); + expect(matcher('category/_subcategory/doc.md')).toBe(true); }); - test('match default exclude tests correctly', () => { - expect(matcher('xyz.js')).toEqual(false); - expect(matcher('xyz.ts')).toEqual(false); - expect(matcher('xyz.jsx')).toEqual(false); - expect(matcher('xyz.tsx')).toEqual(false); - expect(matcher('folder/xyz.js')).toEqual(false); - expect(matcher('folder/xyz.ts')).toEqual(false); - expect(matcher('folder/xyz.jsx')).toEqual(false); - expect(matcher('folder/xyz.tsx')).toEqual(false); + it('match default exclude tests correctly', () => { + expect(matcher('xyz.js')).toBe(false); + expect(matcher('xyz.ts')).toBe(false); + expect(matcher('xyz.jsx')).toBe(false); + expect(matcher('xyz.tsx')).toBe(false); + expect(matcher('folder/xyz.js')).toBe(false); + expect(matcher('folder/xyz.ts')).toBe(false); + expect(matcher('folder/xyz.jsx')).toBe(false); + expect(matcher('folder/xyz.tsx')).toBe(false); // - expect(matcher('xyz.test.js')).toEqual(true); - expect(matcher('xyz.test.ts')).toEqual(true); - expect(matcher('xyz.test.jsx')).toEqual(true); - expect(matcher('xyz.test.tsx')).toEqual(true); - expect(matcher('folder/xyz.test.js')).toEqual(true); - expect(matcher('folder/xyz.test.ts')).toEqual(true); - expect(matcher('folder/xyz.test.jsx')).toEqual(true); - expect(matcher('folder/xyz.test.tsx')).toEqual(true); - expect(matcher('folder/subfolder/xyz.test.js')).toEqual(true); - expect(matcher('folder/subfolder/xyz.test.ts')).toEqual(true); - expect(matcher('folder/subfolder/xyz.test.jsx')).toEqual(true); - expect(matcher('folder/subfolder/xyz.test.tsx')).toEqual(true); + expect(matcher('xyz.test.js')).toBe(true); + expect(matcher('xyz.test.ts')).toBe(true); + expect(matcher('xyz.test.jsx')).toBe(true); + expect(matcher('xyz.test.tsx')).toBe(true); + expect(matcher('folder/xyz.test.js')).toBe(true); + expect(matcher('folder/xyz.test.ts')).toBe(true); + expect(matcher('folder/xyz.test.jsx')).toBe(true); + expect(matcher('folder/xyz.test.tsx')).toBe(true); + expect(matcher('folder/subfolder/xyz.test.js')).toBe(true); + expect(matcher('folder/subfolder/xyz.test.ts')).toBe(true); + expect(matcher('folder/subfolder/xyz.test.jsx')).toBe(true); + expect(matcher('folder/subfolder/xyz.test.tsx')).toBe(true); // - expect(matcher('__tests__/subfolder/xyz.js')).toEqual(true); - expect(matcher('__tests__/subfolder/xyz.ts')).toEqual(true); - expect(matcher('__tests__/subfolder/xyz.jsx')).toEqual(true); - expect(matcher('__tests__/subfolder/xyz.tsx')).toEqual(true); - expect(matcher('folder/__tests__/xyz.js')).toEqual(true); - expect(matcher('folder/__tests__/xyz.ts')).toEqual(true); - expect(matcher('folder/__tests__/xyz.jsx')).toEqual(true); - expect(matcher('folder/__tests__/xyz.tsx')).toEqual(true); + expect(matcher('__tests__/subfolder/xyz.js')).toBe(true); + expect(matcher('__tests__/subfolder/xyz.ts')).toBe(true); + expect(matcher('__tests__/subfolder/xyz.jsx')).toBe(true); + expect(matcher('__tests__/subfolder/xyz.tsx')).toBe(true); + expect(matcher('folder/__tests__/xyz.js')).toBe(true); + expect(matcher('folder/__tests__/xyz.ts')).toBe(true); + expect(matcher('folder/__tests__/xyz.jsx')).toBe(true); + expect(matcher('folder/__tests__/xyz.tsx')).toBe(true); }); }); @@ -73,37 +73,37 @@ describe('createAbsoluteFilePathMatcher', () => { rootFolders, ); - test('match default exclude MD/MDX partials correctly', () => { - expect(matcher('/_root/docs/myDoc.md')).toEqual(false); - expect(matcher('/_root/docs/myDoc.mdx')).toEqual(false); - expect(matcher('/root/_docs/myDoc.md')).toEqual(false); - expect(matcher('/root/_docs/myDoc.mdx')).toEqual(false); - expect(matcher('/_root/docs/category/myDoc.md')).toEqual(false); - expect(matcher('/_root/docs/category/myDoc.mdx')).toEqual(false); - expect(matcher('/root/_docs/category/myDoc.md')).toEqual(false); - expect(matcher('/root/_docs/category/myDoc.mdx')).toEqual(false); + it('match default exclude MD/MDX partials correctly', () => { + expect(matcher('/_root/docs/myDoc.md')).toBe(false); + expect(matcher('/_root/docs/myDoc.mdx')).toBe(false); + expect(matcher('/root/_docs/myDoc.md')).toBe(false); + expect(matcher('/root/_docs/myDoc.mdx')).toBe(false); + expect(matcher('/_root/docs/category/myDoc.md')).toBe(false); + expect(matcher('/_root/docs/category/myDoc.mdx')).toBe(false); + expect(matcher('/root/_docs/category/myDoc.md')).toBe(false); + expect(matcher('/root/_docs/category/myDoc.mdx')).toBe(false); // - expect(matcher('/_root/docs/_myDoc.md')).toEqual(true); - expect(matcher('/_root/docs/_myDoc.mdx')).toEqual(true); - expect(matcher('/root/_docs/_myDoc.md')).toEqual(true); - expect(matcher('/root/_docs/_myDoc.mdx')).toEqual(true); - expect(matcher('/_root/docs/_category/myDoc.md')).toEqual(true); - expect(matcher('/_root/docs/_category/myDoc.mdx')).toEqual(true); - expect(matcher('/root/_docs/_category/myDoc.md')).toEqual(true); - expect(matcher('/root/_docs/_category/myDoc.mdx')).toEqual(true); + expect(matcher('/_root/docs/_myDoc.md')).toBe(true); + expect(matcher('/_root/docs/_myDoc.mdx')).toBe(true); + expect(matcher('/root/_docs/_myDoc.md')).toBe(true); + expect(matcher('/root/_docs/_myDoc.mdx')).toBe(true); + expect(matcher('/_root/docs/_category/myDoc.md')).toBe(true); + expect(matcher('/_root/docs/_category/myDoc.mdx')).toBe(true); + expect(matcher('/root/_docs/_category/myDoc.md')).toBe(true); + expect(matcher('/root/_docs/_category/myDoc.mdx')).toBe(true); }); - test('match default exclude tests correctly', () => { - expect(matcher('/__test__/website/src/xyz.js')).toEqual(false); - expect(matcher('/__test__/website/src/__test__/xyz.js')).toEqual(true); - expect(matcher('/__test__/website/src/xyz.test.js')).toEqual(true); + it('match default exclude tests correctly', () => { + expect(matcher('/__test__/website/src/xyz.js')).toBe(false); + expect(matcher('/__test__/website/src/__test__/xyz.js')).toBe(true); + expect(matcher('/__test__/website/src/xyz.test.js')).toBe(true); }); - test('throw if file is not contained in any root doc', () => { + it('throw if file is not contained in any root doc', () => { expect(() => matcher('/bad/path/myDoc.md'), ).toThrowErrorMatchingInlineSnapshot( - `"createAbsoluteFilePathMatcher unexpected error, absoluteFilePath=/bad/path/myDoc.md was not contained in any of the root folders [\\"/_root/docs\\",\\"/root/_docs/\\",\\"/__test__/website/src\\"]"`, + `"createAbsoluteFilePathMatcher unexpected error, absoluteFilePath=/bad/path/myDoc.md was not contained in any of the root folders: /_root/docs, /root/_docs/, /__test__/website/src"`, ); }); }); diff --git a/packages/docusaurus-utils/src/__tests__/hashUtils.test.ts b/packages/docusaurus-utils/src/__tests__/hashUtils.test.ts index 4fae659dae6b..6cc32c0e4640 100644 --- a/packages/docusaurus-utils/src/__tests__/hashUtils.test.ts +++ b/packages/docusaurus-utils/src/__tests__/hashUtils.test.ts @@ -8,8 +8,8 @@ import {simpleHash, docuHash} from '../hashUtils'; describe('hashUtils', () => { - test('simpleHash', () => { - const asserts: Record = { + it('simpleHash', () => { + const asserts: {[key: string]: string} = { '': 'd41', '/foo-bar': '096', '/foo/bar': '1df', @@ -29,8 +29,8 @@ describe('hashUtils', () => { }); describe('docuHash', () => { - test('docuHash works', () => { - const asserts: Record = { + it('docuHash works', () => { + const asserts: {[key: string]: string} = { '': '-d41', '/': 'index', '/foo-bar': 'foo-bar-096', diff --git a/packages/docusaurus-utils/src/__tests__/i18nUtils.test.ts b/packages/docusaurus-utils/src/__tests__/i18nUtils.test.ts new file mode 100644 index 000000000000..6e66d7e4a056 --- /dev/null +++ b/packages/docusaurus-utils/src/__tests__/i18nUtils.test.ts @@ -0,0 +1,179 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import path from 'path'; +import { + mergeTranslations, + updateTranslationFileMessages, + getPluginI18nPath, + localizePath, +} from '../i18nUtils'; + +describe('mergeTranslations', () => { + it('works', () => { + expect( + mergeTranslations([ + { + T1: {message: 'T1 message', description: 'T1 desc'}, + T2: {message: 'T2 message', description: 'T2 desc'}, + T3: {message: 'T3 message', description: 'T3 desc'}, + }, + { + T4: {message: 'T4 message', description: 'T4 desc'}, + }, + {T2: {message: 'T2 message 2', description: 'T2 desc 2'}}, + ]), + ).toEqual({ + T1: {message: 'T1 message', description: 'T1 desc'}, + T2: {message: 'T2 message 2', description: 'T2 desc 2'}, + T3: {message: 'T3 message', description: 'T3 desc'}, + T4: {message: 'T4 message', description: 'T4 desc'}, + }); + }); +}); + +describe('updateTranslationFileMessages', () => { + it('works', () => { + expect( + updateTranslationFileMessages( + { + path: 'abc', + content: { + t1: {message: 't1 message', description: 't1 desc'}, + t2: {message: 't2 message', description: 't2 desc'}, + t3: {message: 't3 message', description: 't3 desc'}, + }, + }, + (message) => `prefix ${message} suffix`, + ), + ).toEqual({ + path: 'abc', + content: { + t1: {message: 'prefix t1 message suffix', description: 't1 desc'}, + t2: {message: 'prefix t2 message suffix', description: 't2 desc'}, + t3: {message: 'prefix t3 message suffix', description: 't3 desc'}, + }, + }); + }); +}); + +describe('getPluginI18nPath', () => { + it('gets correct path', () => { + expect( + getPluginI18nPath({ + siteDir: __dirname, + locale: 'zh-Hans', + pluginName: 'plugin-content-docs', + pluginId: 'community', + subPaths: ['foo'], + }), + ).toMatchInlineSnapshot( + `"/packages/docusaurus-utils/src/__tests__/i18n/zh-Hans/plugin-content-docs-community/foo"`, + ); + }); + it('gets correct path for default plugin', () => { + expect( + getPluginI18nPath({ + siteDir: __dirname, + locale: 'zh-Hans', + pluginName: 'plugin-content-docs', + subPaths: ['foo'], + }).replace(__dirname, ''), + ).toMatchInlineSnapshot(`"/i18n/zh-Hans/plugin-content-docs/foo"`); + }); + it('gets correct path when no sub-paths', () => { + expect( + getPluginI18nPath({ + siteDir: __dirname, + locale: 'zh-Hans', + pluginName: 'plugin-content-docs', + }).replace(__dirname, ''), + ).toMatchInlineSnapshot(`"/i18n/zh-Hans/plugin-content-docs"`); + }); +}); + +describe('localizePath', () => { + it('localizes url path with current locale', () => { + expect( + localizePath({ + pathType: 'url', + path: '/baseUrl', + i18n: { + defaultLocale: 'en', + locales: ['en', 'fr'], + currentLocale: 'fr', + localeConfigs: {}, + }, + options: {localizePath: true}, + }), + ).toBe('/baseUrl/fr/'); + }); + + it('localizes fs path with current locale', () => { + expect( + localizePath({ + pathType: 'fs', + path: '/baseFsPath', + i18n: { + defaultLocale: 'en', + locales: ['en', 'fr'], + currentLocale: 'fr', + localeConfigs: {}, + }, + options: {localizePath: true}, + }), + ).toBe(`${path.sep}baseFsPath${path.sep}fr`); + }); + + it('localizes path for default locale, if requested', () => { + expect( + localizePath({ + pathType: 'url', + path: '/baseUrl/', + i18n: { + defaultLocale: 'en', + locales: ['en', 'fr'], + currentLocale: 'en', + localeConfigs: {}, + }, + options: {localizePath: true}, + }), + ).toBe('/baseUrl/en/'); + }); + + it('does not localize path for default locale by default', () => { + expect( + localizePath({ + pathType: 'url', + path: '/baseUrl/', + i18n: { + defaultLocale: 'en', + locales: ['en', 'fr'], + currentLocale: 'en', + localeConfigs: {}, + }, + // options: {localizePath: true}, + }), + ).toBe('/baseUrl/'); + }); + + it('localizes path for non-default locale by default', () => { + expect( + localizePath({ + pathType: 'url', + path: '/baseUrl/', + i18n: { + defaultLocale: 'en', + locales: ['en', 'fr'], + currentLocale: 'en', + localeConfigs: {}, + }, + // options: {localizePath: true}, + }), + ).toBe('/baseUrl/'); + }); +}); diff --git a/packages/docusaurus-utils/src/__tests__/index.test.ts b/packages/docusaurus-utils/src/__tests__/index.test.ts deleted file mode 100644 index dfbeb26021f6..000000000000 --- a/packages/docusaurus-utils/src/__tests__/index.test.ts +++ /dev/null @@ -1,462 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import { - fileToPath, - genChunkName, - isValidPathname, - addTrailingSlash, - removeTrailingSlash, - removeSuffix, - removePrefix, - addLeadingSlash, - getElementsAround, - mergeTranslations, - mapAsyncSequential, - findAsyncSequential, - updateTranslationFileMessages, - encodePath, - addTrailingPathSeparator, - resolvePathname, - getPluginI18nPath, - generate, - reportMessage, - posixPath, -} from '../index'; -import {sum} from 'lodash'; -import fs from 'fs-extra'; -import path from 'path'; - -describe('load utils', () => { - test('fileToPath', () => { - const asserts: Record = { - 'index.md': '/', - 'hello/index.md': '/hello/', - 'foo.md': '/foo', - 'foo/bar.md': '/foo/bar', - 'index.js': '/', - 'hello/index.js': '/hello/', - 'foo.js': '/foo', - 'foo/bar.js': '/foo/bar', - }; - Object.keys(asserts).forEach((file) => { - expect(fileToPath(file)).toBe(asserts[file]); - }); - }); - - test('encodePath', () => { - expect(encodePath('a/foo/')).toEqual('a/foo/'); - expect(encodePath('a//')).toEqual('a/%3Cfoo%3E/'); - expect(encodePath('a/你好/')).toEqual('a/%E4%BD%A0%E5%A5%BD/'); - }); - - test('genChunkName', () => { - const firstAssert: Record = { - '/docs/adding-blog': 'docs-adding-blog-062', - '/docs/versioning': 'docs-versioning-8a8', - '/': 'index', - '/blog/2018/04/30/How-I-Converted-Profilo-To-Docusaurus': - 'blog-2018-04-30-how-i-converted-profilo-to-docusaurus-4f2', - '/youtube': 'youtube-429', - '/users/en/': 'users-en-f7a', - '/blog': 'blog-c06', - }; - Object.keys(firstAssert).forEach((str) => { - expect(genChunkName(str)).toBe(firstAssert[str]); - }); - - // Don't allow different chunk name for same path. - expect(genChunkName('path/is/similar', 'oldPrefix')).toEqual( - genChunkName('path/is/similar', 'newPrefix'), - ); - - // Even with same preferred name, still different chunk name for different path - const secondAssert: Record = { - '/blog/1': 'blog-85-f-089', - '/blog/2': 'blog-353-489', - }; - Object.keys(secondAssert).forEach((str) => { - expect(genChunkName(str, undefined, 'blog')).toBe(secondAssert[str]); - }); - - // Only generate short unique id - const thirdAssert: Record = { - a: '0cc175b9', - b: '92eb5ffe', - c: '4a8a08f0', - d: '8277e091', - }; - Object.keys(thirdAssert).forEach((str) => { - expect(genChunkName(str, undefined, undefined, true)).toBe( - thirdAssert[str], - ); - }); - expect(genChunkName('d', undefined, undefined, true)).toBe('8277e091'); - }); - - test('addTrailingPathSeparator', () => { - expect(addTrailingPathSeparator('foo')).toEqual( - process.platform === 'win32' ? 'foo\\' : 'foo/', - ); - expect(addTrailingPathSeparator('foo/')).toEqual( - process.platform === 'win32' ? 'foo\\' : 'foo/', - ); - }); - - test('resolvePathname', () => { - // These tests are directly copied from https://github.com/mjackson/resolve-pathname/blob/master/modules/__tests__/resolvePathname-test.js - // Maybe we want to wrap that logic in the future? - expect(resolvePathname('c')).toEqual('c'); - expect(resolvePathname('c', 'a/b')).toEqual('a/c'); - expect(resolvePathname('/c', '/a/b')).toEqual('/c'); - expect(resolvePathname('', '/a/b')).toEqual('/a/b'); - expect(resolvePathname('../c', '/a/b')).toEqual('/c'); - expect(resolvePathname('c', '/a/b')).toEqual('/a/c'); - expect(resolvePathname('c', '/a/')).toEqual('/a/c'); - expect(resolvePathname('..', '/a/b')).toEqual('/'); - }); - - test('isValidPathname', () => { - expect(isValidPathname('/')).toBe(true); - expect(isValidPathname('/hey')).toBe(true); - expect(isValidPathname('/hey/ho')).toBe(true); - expect(isValidPathname('/hey/ho/')).toBe(true); - expect(isValidPathname('/hey/h%C3%B4/')).toBe(true); - expect(isValidPathname('/hey///ho///')).toBe(true); // Unexpected but valid - expect(isValidPathname('/hey/héllô you')).toBe(true); - - expect(isValidPathname('')).toBe(false); - expect(isValidPathname('hey')).toBe(false); - expect(isValidPathname('/hey?qs=ho')).toBe(false); - expect(isValidPathname('https://fb.com/hey')).toBe(false); - expect(isValidPathname('//hey')).toBe(false); - expect(isValidPathname('////')).toBe(false); - }); -}); - -describe('generate', () => { - test('behaves correctly', async () => { - const writeMock = jest.spyOn(fs, 'writeFile').mockImplementation(() => {}); - const existsMock = jest.spyOn(fs, 'existsSync'); - const readMock = jest.spyOn(fs, 'readFile'); - - // First call: no file, no cache - existsMock.mockImplementationOnce(() => false); - await generate(__dirname, 'foo', 'bar'); - expect(writeMock).toHaveBeenNthCalledWith( - 1, - path.join(__dirname, 'foo'), - 'bar', - ); - - // Second call: cache exists - await generate(__dirname, 'foo', 'bar'); - expect(writeMock).toBeCalledTimes(1); - - // Generate another: file exists, cache doesn't - existsMock.mockImplementationOnce(() => true); - // @ts-expect-error: seems the typedef doesn't understand overload - readMock.mockImplementationOnce(() => Promise.resolve('bar')); - await generate(__dirname, 'baz', 'bar'); - expect(writeMock).toBeCalledTimes(1); - - // Generate again: force skip cache - await generate(__dirname, 'foo', 'bar', true); - expect(writeMock).toHaveBeenNthCalledWith( - 2, - path.join(__dirname, 'foo'), - 'bar', - ); - }); -}); - -describe('addTrailingSlash', () => { - test('should no-op', () => { - expect(addTrailingSlash('/abcd/')).toEqual('/abcd/'); - }); - test('should add /', () => { - expect(addTrailingSlash('/abcd')).toEqual('/abcd/'); - }); -}); - -describe('addLeadingSlash', () => { - test('should no-op', () => { - expect(addLeadingSlash('/abc')).toEqual('/abc'); - }); - test('should add /', () => { - expect(addLeadingSlash('abc')).toEqual('/abc'); - }); -}); - -describe('removeTrailingSlash', () => { - test('should no-op', () => { - expect(removeTrailingSlash('/abcd')).toEqual('/abcd'); - }); - test('should remove /', () => { - expect(removeTrailingSlash('/abcd/')).toEqual('/abcd'); - }); -}); - -describe('removeSuffix', () => { - test('should no-op 1', () => { - expect(removeSuffix('abcdef', 'ijk')).toEqual('abcdef'); - }); - test('should no-op 2', () => { - expect(removeSuffix('abcdef', 'abc')).toEqual('abcdef'); - }); - test('should no-op 3', () => { - expect(removeSuffix('abcdef', '')).toEqual('abcdef'); - }); - test('should remove suffix', () => { - expect(removeSuffix('abcdef', 'ef')).toEqual('abcd'); - }); -}); - -describe('removePrefix', () => { - test('should no-op 1', () => { - expect(removePrefix('abcdef', 'ijk')).toEqual('abcdef'); - }); - test('should no-op 2', () => { - expect(removePrefix('abcdef', 'def')).toEqual('abcdef'); - }); - test('should no-op 3', () => { - expect(removePrefix('abcdef', '')).toEqual('abcdef'); - }); - test('should remove prefix', () => { - expect(removePrefix('abcdef', 'ab')).toEqual('cdef'); - }); -}); - -describe('getElementsAround', () => { - test('can return elements around', () => { - expect(getElementsAround(['a', 'b', 'c', 'd'], 0)).toEqual({ - previous: undefined, - next: 'b', - }); - expect(getElementsAround(['a', 'b', 'c', 'd'], 1)).toEqual({ - previous: 'a', - next: 'c', - }); - expect(getElementsAround(['a', 'b', 'c', 'd'], 2)).toEqual({ - previous: 'b', - next: 'd', - }); - expect(getElementsAround(['a', 'b', 'c', 'd'], 3)).toEqual({ - previous: 'c', - next: undefined, - }); - }); - - test('throws if bad index is provided', () => { - expect(() => - getElementsAround(['a', 'b', 'c', 'd'], -1), - ).toThrowErrorMatchingInlineSnapshot( - `"Valid \\"aroundIndex\\" for array (of size 4) are between 0 and 3, but you provided -1."`, - ); - expect(() => - getElementsAround(['a', 'b', 'c', 'd'], 4), - ).toThrowErrorMatchingInlineSnapshot( - `"Valid \\"aroundIndex\\" for array (of size 4) are between 0 and 3, but you provided 4."`, - ); - }); -}); - -describe('mergeTranslations', () => { - test('should merge translations', () => { - expect( - mergeTranslations([ - { - T1: {message: 'T1 message', description: 'T1 desc'}, - T2: {message: 'T2 message', description: 'T2 desc'}, - T3: {message: 'T3 message', description: 'T3 desc'}, - }, - { - T4: {message: 'T4 message', description: 'T4 desc'}, - }, - {T2: {message: 'T2 message 2', description: 'T2 desc 2'}}, - ]), - ).toEqual({ - T1: {message: 'T1 message', description: 'T1 desc'}, - T2: {message: 'T2 message 2', description: 'T2 desc 2'}, - T3: {message: 'T3 message', description: 'T3 desc'}, - T4: {message: 'T4 message', description: 'T4 desc'}, - }); - }); -}); - -describe('mapAsyncSequential', () => { - function sleep(timeout: number): Promise { - return new Promise((resolve) => { - setTimeout(resolve, timeout); - }); - } - - test('map sequentially', async () => { - const itemToTimeout: Record = { - '1': 50, - '2': 150, - '3': 100, - }; - const items = Object.keys(itemToTimeout); - - const itemMapStartsAt: Record = {}; - const itemMapEndsAt: Record = {}; - - const timeBefore = Date.now(); - await expect( - mapAsyncSequential(items, async (item) => { - const itemTimeout = itemToTimeout[item]; - itemMapStartsAt[item] = Date.now(); - await sleep(itemTimeout); - itemMapEndsAt[item] = Date.now(); - return `${item} mapped`; - }), - ).resolves.toEqual(['1 mapped', '2 mapped', '3 mapped']); - const timeAfter = Date.now(); - - const timeTotal = timeAfter - timeBefore; - - const totalTimeouts = sum(Object.values(itemToTimeout)); - expect(timeTotal).toBeGreaterThanOrEqual(totalTimeouts - 20); - - expect(itemMapStartsAt['1']).toBeGreaterThanOrEqual(0); - expect(itemMapStartsAt['2']).toBeGreaterThanOrEqual( - itemMapEndsAt['1'] - 20, - ); - expect(itemMapStartsAt['3']).toBeGreaterThanOrEqual( - itemMapEndsAt['2'] - 20, - ); - }); -}); - -describe('findAsyncSequential', () => { - function sleep(timeout: number): Promise { - return new Promise((resolve) => { - setTimeout(resolve, timeout); - }); - } - - test('find sequentially', async () => { - const items = ['1', '2', '3']; - - const findFn = jest.fn(async (item: string) => { - await sleep(50); - return item === '2'; - }); - - const timeBefore = Date.now(); - await expect(findAsyncSequential(items, findFn)).resolves.toEqual('2'); - const timeAfter = Date.now(); - - expect(findFn).toHaveBeenCalledTimes(2); - expect(findFn).toHaveBeenNthCalledWith(1, '1'); - expect(findFn).toHaveBeenNthCalledWith(2, '2'); - - const timeTotal = timeAfter - timeBefore; - expect(timeTotal).toBeGreaterThanOrEqual(80); - expect(timeTotal).toBeLessThan(120); - }); -}); - -describe('updateTranslationFileMessages', () => { - test('should update messages', () => { - expect( - updateTranslationFileMessages( - { - path: 'abc', - content: { - t1: {message: 't1 message', description: 't1 desc'}, - t2: {message: 't2 message', description: 't2 desc'}, - t3: {message: 't3 message', description: 't3 desc'}, - }, - }, - (message) => `prefix ${message} suffix`, - ), - ).toEqual({ - path: 'abc', - content: { - t1: {message: 'prefix t1 message suffix', description: 't1 desc'}, - t2: {message: 'prefix t2 message suffix', description: 't2 desc'}, - t3: {message: 'prefix t3 message suffix', description: 't3 desc'}, - }, - }); - }); -}); - -describe('getPluginI18nPath', () => { - test('gets correct path', () => { - expect( - posixPath( - getPluginI18nPath({ - siteDir: __dirname, - locale: 'zh-Hans', - pluginName: 'plugin-content-docs', - pluginId: 'community', - subPaths: ['foo'], - }).replace(__dirname, ''), - ), - ).toEqual('/i18n/zh-Hans/plugin-content-docs-community/foo'); - }); - test('gets correct path for default plugin', () => { - expect( - posixPath( - getPluginI18nPath({ - siteDir: __dirname, - locale: 'zh-Hans', - pluginName: 'plugin-content-docs', - subPaths: ['foo'], - }).replace(__dirname, ''), - ), - ).toEqual('/i18n/zh-Hans/plugin-content-docs/foo'); - }); - test('gets correct path when no subpaths', () => { - expect( - posixPath( - getPluginI18nPath({ - siteDir: __dirname, - locale: 'zh-Hans', - pluginName: 'plugin-content-docs', - }).replace(__dirname, ''), - ), - ).toEqual('/i18n/zh-Hans/plugin-content-docs'); - }); -}); - -describe('reportMessage', () => { - test('all severities', () => { - const consoleLog = jest.spyOn(console, 'info').mockImplementation(() => {}); - const consoleWarn = jest - .spyOn(console, 'warn') - .mockImplementation(() => {}); - const consoleError = jest - .spyOn(console, 'error') - .mockImplementation(() => {}); - reportMessage('hey', 'ignore'); - reportMessage('hey', 'log'); - reportMessage('hey', 'warn'); - reportMessage('hey', 'error'); - expect(() => - reportMessage('hey', 'throw'), - ).toThrowErrorMatchingInlineSnapshot(`"hey"`); - expect(() => - // @ts-expect-error: for test - reportMessage('hey', 'foo'), - ).toThrowErrorMatchingInlineSnapshot( - `"Unexpected \\"reportingSeverity\\" value: foo."`, - ); - expect(consoleLog).toBeCalledTimes(1); - expect(consoleLog).toBeCalledWith(expect.stringMatching(/.*\[INFO].* hey/)); - expect(consoleWarn).toBeCalledTimes(1); - expect(consoleWarn).toBeCalledWith( - expect.stringMatching(/.*\[WARNING].* hey/), - ); - expect(consoleError).toBeCalledTimes(1); - expect(consoleError).toBeCalledWith( - expect.stringMatching(/.*\[ERROR].* hey/), - ); - }); -}); diff --git a/packages/docusaurus-utils/src/__tests__/jsUtils.test.ts b/packages/docusaurus-utils/src/__tests__/jsUtils.test.ts new file mode 100644 index 000000000000..49a2844b4721 --- /dev/null +++ b/packages/docusaurus-utils/src/__tests__/jsUtils.test.ts @@ -0,0 +1,149 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {jest} from '@jest/globals'; +import { + removeSuffix, + removePrefix, + mapAsyncSequential, + findAsyncSequential, + reportMessage, +} from '../jsUtils'; +import _ from 'lodash'; + +describe('removeSuffix', () => { + it("is no-op when suffix doesn't exist", () => { + expect(removeSuffix('abcdef', 'ijk')).toBe('abcdef'); + expect(removeSuffix('abcdef', 'abc')).toBe('abcdef'); + expect(removeSuffix('abcdef', '')).toBe('abcdef'); + }); + it('removes suffix', () => { + expect(removeSuffix('abcdef', 'ef')).toBe('abcd'); + }); +}); + +describe('removePrefix', () => { + it("is no-op when prefix doesn't exist", () => { + expect(removePrefix('abcdef', 'ijk')).toBe('abcdef'); + expect(removePrefix('abcdef', 'def')).toBe('abcdef'); + expect(removePrefix('abcdef', '')).toBe('abcdef'); + }); + it('removes prefix', () => { + expect(removePrefix('prefix', 'pre')).toBe('fix'); + }); +}); + +describe('mapAsyncSequential', () => { + function sleep(timeout: number): Promise { + return new Promise((resolve) => { + setTimeout(resolve, timeout); + }); + } + + it('maps sequentially', async () => { + const itemToTimeout: {[key: string]: number} = { + '1': 200, + '2': 600, + '3': 400, + }; + const items = Object.keys(itemToTimeout); + + const itemMapStartsAt: {[key: string]: number} = {}; + const itemMapEndsAt: {[key: string]: number} = {}; + + const timeBefore = Date.now(); + await expect( + mapAsyncSequential(items, async (item) => { + const itemTimeout = itemToTimeout[item]; + itemMapStartsAt[item] = Date.now(); + await sleep(itemTimeout); + itemMapEndsAt[item] = Date.now(); + return `${item} mapped`; + }), + ).resolves.toEqual(['1 mapped', '2 mapped', '3 mapped']); + const timeAfter = Date.now(); + + const timeTotal = timeAfter - timeBefore; + + const totalTimeouts = _.sum(Object.values(itemToTimeout)); + expect(timeTotal).toBeGreaterThanOrEqual(totalTimeouts - 100); + + expect(itemMapStartsAt['1']).toBeGreaterThanOrEqual(0); + expect(itemMapStartsAt['2']).toBeGreaterThanOrEqual( + itemMapEndsAt['1'] - 100, + ); + expect(itemMapStartsAt['3']).toBeGreaterThanOrEqual( + itemMapEndsAt['2'] - 100, + ); + }); +}); + +describe('findAsyncSequential', () => { + function sleep(timeout: number): Promise { + return new Promise((resolve) => { + setTimeout(resolve, timeout); + }); + } + + it('finds sequentially', async () => { + const items = ['1', '2', '3']; + + const findFn = jest.fn(async (item: string) => { + await sleep(400); + return item === '2'; + }); + + const timeBefore = Date.now(); + await expect(findAsyncSequential(items, findFn)).resolves.toBe('2'); + const timeAfter = Date.now(); + + expect(findFn).toHaveBeenCalledTimes(2); + expect(findFn).toHaveBeenNthCalledWith(1, '1'); + expect(findFn).toHaveBeenNthCalledWith(2, '2'); + + const timeTotal = timeAfter - timeBefore; + expect(timeTotal).toBeGreaterThanOrEqual(600); + expect(timeTotal).toBeLessThan(1000); + }); +}); + +describe('reportMessage', () => { + it('works with all severities', () => { + const consoleLog = jest.spyOn(console, 'info').mockImplementation(() => {}); + const consoleWarn = jest + .spyOn(console, 'warn') + .mockImplementation(() => {}); + const consoleError = jest + .spyOn(console, 'error') + .mockImplementation(() => {}); + reportMessage('hey', 'ignore'); + reportMessage('hey', 'log'); + reportMessage('hey', 'warn'); + reportMessage('hey', 'error'); + expect(() => + reportMessage('hey', 'throw'), + ).toThrowErrorMatchingInlineSnapshot(`"hey"`); + expect(() => + // @ts-expect-error: for test + reportMessage('hey', 'foo'), + ).toThrowErrorMatchingInlineSnapshot( + `"Unexpected \\"reportingSeverity\\" value: foo."`, + ); + expect(consoleLog).toBeCalledTimes(1); + expect(consoleLog).toBeCalledWith( + expect.stringMatching(/.*\[INFO\].* hey/), + ); + expect(consoleWarn).toBeCalledTimes(1); + expect(consoleWarn).toBeCalledWith( + expect.stringMatching(/.*\[WARNING\].* hey/), + ); + expect(consoleError).toBeCalledTimes(1); + expect(consoleError).toBeCalledWith( + expect.stringMatching(/.*\[ERROR\].* hey/), + ); + }); +}); diff --git a/packages/docusaurus-utils/src/__tests__/markdownLinks.test.ts b/packages/docusaurus-utils/src/__tests__/markdownLinks.test.ts index 09d9f52195d2..586a0ab7302d 100644 --- a/packages/docusaurus-utils/src/__tests__/markdownLinks.test.ts +++ b/packages/docusaurus-utils/src/__tests__/markdownLinks.test.ts @@ -8,7 +8,7 @@ import {replaceMarkdownLinks} from '../markdownLinks'; describe('replaceMarkdownLinks', () => { - test('basic replace', () => { + it('does basic replace', () => { expect( replaceMarkdownLinks({ siteDir: '.', @@ -21,6 +21,7 @@ describe('replaceMarkdownLinks', () => { '@site/docs/intro.md': '/docs/intro', '@site/docs/foo.md': '/doc/foo', '@site/docs/bar/baz.md': '/doc/baz', + '@site/docs/http.foo.md': '/doc/http', }, fileString: ` [foo](./foo.md) @@ -29,36 +30,45 @@ describe('replaceMarkdownLinks', () => { [http](http://github.com/facebook/docusaurus/README.md) [https](https://github.com/facebook/docusaurus/README.md) [asset](./foo.js) +[asset as well](@site/docs/_partial.md) +[looks like http...](http.foo.md) [nonexistent](hmmm.md) `, }), - ).toMatchInlineSnapshot(` - Object { - "brokenMarkdownLinks": Array [ - Object { - "contentPaths": Object { - "contentPath": "docs", - "contentPathLocalized": "i18n/docs-localized", - }, - "filePath": "docs/intro.md", - "link": "hmmm.md", - }, - ], - "newContent": " - [foo](/doc/foo) - [baz](/doc/baz) - [foo](/doc/foo) - [http](http://github.com/facebook/docusaurus/README.md) - [https](https://github.com/facebook/docusaurus/README.md) - [asset](./foo.js) - [nonexistent](hmmm.md) - ", - } - `); + ).toMatchSnapshot(); + }); + + it('replaces reference style Markdown links', () => { + expect( + replaceMarkdownLinks({ + siteDir: '.', + filePath: 'docs/intro/intro.md', + contentPaths: { + contentPath: 'docs', + contentPathLocalized: 'i18n/docs-localized', + }, + + sourceToPermalink: { + '@site/docs/intro/intro.md': '/docs/intro', + '@site/docs/api/classes/divine_uri.URI.md': '/docs/api/classes/uri', + }, + + fileString: ` +The following operations are defined for [URI]s: + +* [info]: Returns metadata about the resource, +* [list]: Returns metadata about the resource's children (like getting the content of a local directory). + +[URI]: ../api/classes/divine_uri.URI.md +[info]: ../api/classes/divine_uri.URI.md#info +[list]: ../api/classes/divine_uri.URI.md#list + `, + }), + ).toMatchSnapshot(); }); // TODO bad - test('links in HTML comments', () => { + it('ignores links in HTML comments', () => { expect( replaceMarkdownLinks({ siteDir: '.', @@ -77,37 +87,10 @@ describe('replaceMarkdownLinks', () => { --> `, }), - ).toMatchInlineSnapshot(` - Object { - "brokenMarkdownLinks": Array [ - Object { - "contentPaths": Object { - "contentPath": "docs", - "contentPathLocalized": "i18n/docs-localized", - }, - "filePath": "docs/intro.md", - "link": "./foo.md", - }, - Object { - "contentPaths": Object { - "contentPath": "docs", - "contentPathLocalized": "i18n/docs-localized", - }, - "filePath": "docs/intro.md", - "link": "./foo.md", - }, - ], - "newContent": " - - - ", - } - `); + ).toMatchSnapshot(); }); - test('links in fenced blocks', () => { + it('ignores links in fenced blocks', () => { expect( replaceMarkdownLinks({ siteDir: '.', @@ -139,34 +122,11 @@ describe('replaceMarkdownLinks', () => { \`\`\`\` `, }), - ).toMatchInlineSnapshot(` - Object { - "brokenMarkdownLinks": Array [], - "newContent": " - \`\`\` - [foo](foo.md) - \`\`\` - - \`\`\`\`js - [foo](foo.md) - \`\`\` - [foo](foo.md) - \`\`\` - [foo](foo.md) - \`\`\`\` - - \`\`\`\`js - [foo](foo.md) - \`\`\` - [foo](foo.md) - \`\`\`\` - ", - } - `); + ).toMatchSnapshot(); }); // TODO bad - test('links in inline code', () => { + it('ignores links in inline code', () => { expect( replaceMarkdownLinks({ siteDir: '.', @@ -182,27 +142,11 @@ describe('replaceMarkdownLinks', () => { \`[foo](foo.md)\` `, }), - ).toMatchInlineSnapshot(` - Object { - "brokenMarkdownLinks": Array [ - Object { - "contentPaths": Object { - "contentPath": "docs", - "contentPathLocalized": "i18n/docs-localized", - }, - "filePath": "docs/intro.md", - "link": "foo.md", - }, - ], - "newContent": " - \`[foo](foo.md)\` - ", - } - `); + ).toMatchSnapshot(); }); // TODO bad - test('links with same title as URL', () => { + it('replaces links with same title as URL', () => { expect( replaceMarkdownLinks({ siteDir: '.', @@ -222,20 +166,10 @@ describe('replaceMarkdownLinks', () => { [./foo.md](foo.md) `, }), - ).toMatchInlineSnapshot(` - Object { - "brokenMarkdownLinks": Array [], - "newContent": " - [/docs/foo](foo.md) - [/docs/foo](./foo.md) - [foo.md](/docs/foo) - [.//docs/foo](foo.md) - ", - } - `); + ).toMatchSnapshot(); }); - test('multiple links on same line', () => { + it('replaces multiple links on same line', () => { expect( replaceMarkdownLinks({ siteDir: '.', @@ -254,13 +188,6 @@ describe('replaceMarkdownLinks', () => { [a](a.md), [a](a.md), [b](b.md), [c](c.md) `, }), - ).toMatchInlineSnapshot(` - Object { - "brokenMarkdownLinks": Array [], - "newContent": " - [a](/docs/a), [a](/docs/a), [b](/docs/b), [c](/docs/c) - ", - } - `); + ).toMatchSnapshot(); }); }); diff --git a/packages/docusaurus-utils/src/__tests__/markdownParser.test.ts b/packages/docusaurus-utils/src/__tests__/markdownUtils.test.ts similarity index 66% rename from packages/docusaurus-utils/src/__tests__/markdownParser.test.ts rename to packages/docusaurus-utils/src/__tests__/markdownUtils.test.ts index 92acd07e7092..5d32b07f1702 100644 --- a/packages/docusaurus-utils/src/__tests__/markdownParser.test.ts +++ b/packages/docusaurus-utils/src/__tests__/markdownUtils.test.ts @@ -10,23 +10,24 @@ import { parseMarkdownContentTitle, parseMarkdownString, parseMarkdownHeadingId, -} from '../markdownParser'; + writeMarkdownHeadingId, +} from '../markdownUtils'; import dedent from 'dedent'; describe('createExcerpt', () => { - test('should create excerpt for text-only content', () => { + it('creates excerpt for text-only content', () => { expect( createExcerpt(dedent` Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum ex urna, molestie et sagittis ut, varius ac justo. Nunc porttitor libero nec vulputate venenatis. Nam nec rhoncus mauris. Morbi tempus est et nibh maximus, tempus venenatis arcu lobortis. `), - ).toEqual( + ).toBe( 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum ex urna, molestie et sagittis ut, varius ac justo.', ); }); - test('should create excerpt for regular content with regular title', () => { + it('creates excerpt for regular content with regular title', () => { expect( createExcerpt(dedent` @@ -36,13 +37,14 @@ describe('createExcerpt', () => { Nunc porttitor libero nec vulputate venenatis. Nam nec rhoncus mauris. Morbi tempus est et nibh maximus, tempus venenatis arcu lobortis. `), - ).toEqual( - // h1 title is skipped on purpose, because we don't want the page to have SEO metadata title === description + ).toBe( + // h1 title is skipped on purpose, because we don't want the page to have + // SEO metadata title === description 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum ex urna, molestie et sagittis ut, varius ac justo.', ); }); - test('should create excerpt for regular content with alternate title', () => { + it('creates excerpt for regular content with alternate title', () => { expect( createExcerpt(dedent` @@ -53,41 +55,42 @@ describe('createExcerpt', () => { Nunc porttitor libero nec vulputate venenatis. Nam nec rhoncus mauris. Morbi tempus est et nibh maximus, tempus venenatis arcu lobortis. `), - ).toEqual( - // h1 title is skipped on purpose, because we don't want the page to have SEO metadata title === description + ).toBe( + // h1 title is skipped on purpose, because we don't want the page to have + // SEO metadata title === description 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum ex urna, molestie et sagittis ut, varius ac justo.', ); }); - test('should create excerpt for content with h2 heading', () => { + it('creates excerpt for content with h2 heading', () => { expect( createExcerpt(dedent` ## Lorem ipsum dolor sit amet Nunc porttitor libero nec vulputate venenatis. Nam nec rhoncus mauris. Morbi tempus est et nibh maximus, tempus venenatis arcu lobortis. `), - ).toEqual('Lorem ipsum dolor sit amet'); + ).toBe('Lorem ipsum dolor sit amet'); }); - test('should create excerpt for content beginning with blockquote', () => { + it('creates excerpt for content beginning with blockquote', () => { expect( createExcerpt(dedent` > Lorem ipsum dolor sit amet Nunc porttitor libero nec vulputate venenatis. Nam nec rhoncus mauris. Morbi tempus est et nibh maximus, tempus venenatis arcu lobortis. `), - ).toEqual('Lorem ipsum dolor sit amet'); + ).toBe('Lorem ipsum dolor sit amet'); }); - test('should create excerpt for content beginning with image (eg. blog post)', () => { + it('creates excerpt for content beginning with image (eg. blog post)', () => { expect( createExcerpt(dedent` ![Lorem ipsum](/img/lorem-ipsum.svg) `), - ).toEqual('Lorem ipsum'); + ).toBe('Lorem ipsum'); }); - test('should create excerpt for content beginning with admonitions', () => { + it('creates excerpt for content beginning with admonitions', () => { expect( createExcerpt(dedent` import Component from '@site/src/components/Component' @@ -100,38 +103,42 @@ describe('createExcerpt', () => { Nunc porttitor libero nec vulputate venenatis. Nam nec rhoncus mauris. Morbi tempus est et nibh maximus, tempus venenatis arcu lobortis. `), - ).toEqual('Lorem ipsum dolor sit amet, consectetur adipiscing elit.'); + ).toBe('Lorem ipsum dolor sit amet, consectetur adipiscing elit.'); }); - test('should create excerpt for content with imports/exports declarations and Markdown markup, as well as Emoji', () => { + it('creates excerpt for content with imports/exports declarations and Markdown markup, as well as Emoji', () => { expect( createExcerpt(dedent` import Component from '@site/src/components/Component'; import Component from '@site/src/components/Component' import './styles.css'; - export function ItemCol(props) { return } + export function ItemCol(props) { + return + } - export function ItemCol(props) { return }; + export function ItemCol(props) { + return + }; - Lorem **ipsum** dolor sit \`amet\`[^1], consectetur _adipiscing_ elit. [**Vestibulum**](https://wiktionary.org/wiki/vestibulum) ex urna[^bignote], ~molestie~ et sagittis ut, varius ac justo :wink:. + Lorem **ipsum** dolor sit \`amet\`[^1], consectetur _adipiscing_ elit. [**Vestibulum**](https://wiktionary.org/wiki/vestibulum) ex urna[^note], ~~molestie~~ et sagittis ut, varius ac justo :wink:. Nunc porttitor libero nec vulputate venenatis. Nam nec rhoncus mauris. Morbi tempus est et nibh maximus, tempus venenatis arcu lobortis. `), - ).toEqual( + ).toBe( 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum ex urna, molestie et sagittis ut, varius ac justo.', ); }); - test('should create excerpt for heading specified with anchor-id syntax', () => { + it('creates excerpt for heading specified with anchor-id syntax', () => { expect( createExcerpt(dedent` ## Markdown title {#my-anchor-id} `), - ).toEqual('Markdown title'); + ).toBe('Markdown title'); }); - test('should create excerpt for content with various code blocks', () => { + it('creates excerpt for content with various code blocks', () => { expect( createExcerpt(dedent` \`\`\`jsx @@ -141,12 +148,24 @@ describe('createExcerpt', () => { Lorem \`ipsum\` dolor sit amet, consectetur \`adipiscing elit\`. `), - ).toEqual('Lorem ipsum dolor sit amet, consectetur adipiscing elit.'); + ).toBe('Lorem ipsum dolor sit amet, consectetur adipiscing elit.'); + }); + + it('creates excerpt after multi-line imports', () => { + expect( + createExcerpt(dedent` + import React, { + type ReactNode, + } from 'react'; + + Lorem \`ipsum\` dolor sit amet, consectetur \`adipiscing elit\`. + `), + ).toBe('Lorem ipsum dolor sit amet, consectetur adipiscing elit.'); }); }); describe('parseMarkdownContentTitle', () => { - test('Should parse markdown h1 title at the top', () => { + it('parses markdown h1 title at the top', () => { const markdown = dedent` # Markdown Title @@ -160,7 +179,7 @@ describe('parseMarkdownContentTitle', () => { }); }); - test('Should parse markdown h1 title at the top and remove it', () => { + it('parses markdown h1 title at the top and remove it', () => { const markdown = dedent` # Markdown Title @@ -176,7 +195,7 @@ describe('parseMarkdownContentTitle', () => { }); }); - test('Should parse markdown h1 title at the top and unwrap inline code block', () => { + it('parses markdown h1 title at the top and unwrap inline code block', () => { const markdown = dedent` # \`Markdown Title\` @@ -190,7 +209,7 @@ describe('parseMarkdownContentTitle', () => { }); }); - test('Should parse markdown h1 title and trim content', () => { + it('parses markdown h1 title and trim content', () => { const markdown = ` # Markdown Title @@ -207,7 +226,7 @@ Lorem Ipsum }); }); - test('Should parse not parse markdown h1 title and trim content', () => { + it('parses not parse markdown h1 title and trim content', () => { const markdown = ` Lorem Ipsum @@ -220,7 +239,7 @@ Lorem Ipsum }); }); - test('Should parse markdown h1 title with fixed anchor-id syntax', () => { + it('parses markdown h1 title with fixed anchor-id syntax', () => { const markdown = dedent` # Markdown Title {#my-anchor-id} @@ -234,7 +253,23 @@ Lorem Ipsum }); }); - test('Should parse markdown h1 title at the top (atx style with closing #)', () => { + it('parses markdown h1 title with CRLF break', () => { + const markdown = `# Markdown Title\r\n\r\nLorem Ipsum`; + expect(parseMarkdownContentTitle(markdown)).toEqual({ + content: markdown, + contentTitle: 'Markdown Title', + }); + }); + + it('parses markdown h1 setext title with CRLF break', () => { + const markdown = `Markdown Title\r\n=====\r\n\r\nLorem Ipsum`; + expect(parseMarkdownContentTitle(markdown)).toEqual({ + content: markdown, + contentTitle: 'Markdown Title', + }); + }); + + it('parses markdown h1 title at the top (atx style with closing #)', () => { const markdown = dedent` # Markdown Title # @@ -248,7 +283,7 @@ Lorem Ipsum }); }); - test('Should parse markdown h1 title at the top followed by h2 title', () => { + it('parses markdown h1 title at the top followed by h2 title', () => { const markdown = dedent` # Markdown Title @@ -264,7 +299,7 @@ Lorem Ipsum }); }); - test('Should parse only first h1 title', () => { + it('parses only first h1 title', () => { const markdown = dedent` # Markdown Title @@ -280,7 +315,7 @@ Lorem Ipsum }); }); - test('Should not parse title that is not at the top', () => { + it('does not parse title that is not at the top', () => { const markdown = dedent` Lorem Ipsum @@ -296,7 +331,7 @@ Lorem Ipsum }); }); - test('Should parse markdown h1 alternate title', () => { + it('parses markdown h1 alternate title', () => { const markdown = dedent` Markdown Title @@ -311,7 +346,7 @@ Lorem Ipsum }); }); - test('Should parse markdown h1 alternate title and remove it', () => { + it('parses markdown h1 alternate title and remove it', () => { const markdown = dedent` Markdown Title @@ -328,7 +363,7 @@ Lorem Ipsum }); }); - test('Should parse markdown h1 title placed after import declarations', () => { + it('parses markdown h1 title placed after import declarations', () => { const markdown = dedent` import Component1 from '@site/src/components/Component1'; @@ -349,7 +384,7 @@ Lorem Ipsum }); }); - test('Should parse markdown h1 title placed after various import declarations', () => { + it('parses markdown h1 title placed after various import declarations', () => { const markdown = ` import DefaultComponent from '@site/src/components/Component1'; import DefaultComponent2 from '../relative/path/Component2'; @@ -377,7 +412,7 @@ Lorem Ipsum }); }); - test('Should parse markdown h1 title placed after various import declarations and remove it', () => { + it('parses markdown h1 title placed after various import declarations and remove it', () => { const markdown = ` import DefaultComponent from '@site/src/components/Component1'; import DefaultComponent2 from '../relative/path/Component2'; @@ -402,12 +437,12 @@ Lorem Ipsum expect( parseMarkdownContentTitle(markdown, {removeContentTitle: true}), ).toEqual({ - content: markdown.trim().replace('# Markdown Title', ''), + content: markdown.trim().replace('# Markdown Title\n', ''), contentTitle: 'Markdown Title', }); }); - test('Should parse markdown h1 alternate title placed after import declarations', () => { + it('parses markdown h1 alternate title placed after import declarations', () => { const markdown = dedent` import Component from '@site/src/components/Component'; @@ -426,7 +461,7 @@ Lorem Ipsum }); }); - test('Should parse markdown h1 alternate title placed after import declarations and remove it', () => { + it('parses markdown h1 alternate title placed after import declarations and remove it', () => { const markdown = dedent` import Component from '@site/src/components/Component'; @@ -442,12 +477,12 @@ Lorem Ipsum expect( parseMarkdownContentTitle(markdown, {removeContentTitle: true}), ).toEqual({ - content: markdown.replace('Markdown Title\n==============\n\n', ''), + content: markdown.replace('Markdown Title\n==============\n', ''), contentTitle: 'Markdown Title', }); }); - test('Should parse title-only', () => { + it('parses title-only', () => { const markdown = '# Document With Only A Title'; expect(parseMarkdownContentTitle(markdown)).toEqual({ content: markdown, @@ -455,7 +490,7 @@ Lorem Ipsum }); }); - test('Should not parse markdown h1 title in the middle of a doc', () => { + it('does not parse markdown h1 title in the middle of a doc', () => { const markdown = dedent` Lorem Ipsum @@ -471,7 +506,7 @@ Lorem Ipsum }); }); - test('Should not parse markdown h1 alternate title in the middle of the doc', () => { + it('does not parse markdown h1 alternate title in the middle of the doc', () => { const markdown = dedent` Lorem Ipsum @@ -488,7 +523,7 @@ Lorem Ipsum }); }); - test('Should parse markdown h1 title placed after multiple import declarations', () => { + it('parses markdown h1 title placed after multiple import declarations', () => { const markdown = dedent` import Component1 from '@site/src/components/Component1'; import Component2 from '@site/src/components/Component2'; @@ -518,7 +553,7 @@ Lorem Ipsum }); }); - test('Should parse markdown h1 title placed after multiple import declarations and remove it', () => { + it('parses markdown h1 title placed after multiple import declarations and remove it', () => { const markdown = dedent` import Component1 from '@site/src/components/Component1'; import Component2 from '@site/src/components/Component2'; @@ -545,14 +580,14 @@ Lorem Ipsum expect( parseMarkdownContentTitle(markdown, {removeContentTitle: true}), ).toEqual({ - content: markdown.replace('# Markdown Title', ''), + content: markdown.replace('# Markdown Title\n', ''), contentTitle: 'Markdown Title', }); }); }); describe('parseMarkdownString', () => { - test('parse markdown with front matter', () => { + it('parse markdown with front matter', () => { expect( parseMarkdownString(dedent` --- @@ -561,38 +596,20 @@ describe('parseMarkdownString', () => { Some text `), - ).toMatchInlineSnapshot(` - Object { - "content": "Some text", - "contentTitle": undefined, - "excerpt": "Some text", - "frontMatter": Object { - "title": "Frontmatter title", - }, - } - `); + ).toMatchSnapshot(); }); - test('should parse first heading as contentTitle', () => { + it('parses first heading as contentTitle', () => { expect( parseMarkdownString(dedent` # Markdown Title Some text `), - ).toMatchInlineSnapshot(` - Object { - "content": "# Markdown Title - - Some text", - "contentTitle": "Markdown Title", - "excerpt": "Some text", - "frontMatter": Object {}, - } - `); + ).toMatchSnapshot(); }); - test('should warn about duplicate titles (front matter + markdown)', () => { + it('warns about duplicate titles (front matter + markdown)', () => { expect( parseMarkdownString(dedent` --- @@ -603,21 +620,10 @@ describe('parseMarkdownString', () => { Some text `), - ).toMatchInlineSnapshot(` - Object { - "content": "# Markdown Title - - Some text", - "contentTitle": "Markdown Title", - "excerpt": "Some text", - "frontMatter": Object { - "title": "Frontmatter title", - }, - } - `); + ).toMatchSnapshot(); }); - test('should warn about duplicate titles (front matter + markdown alternate)', () => { + it('warns about duplicate titles (front matter + markdown alternate)', () => { expect( parseMarkdownString(dedent` --- @@ -629,22 +635,10 @@ describe('parseMarkdownString', () => { Some text `), - ).toMatchInlineSnapshot(` - Object { - "content": "Markdown Title alternate - ================ - - Some text", - "contentTitle": "Markdown Title alternate", - "excerpt": "Some text", - "frontMatter": Object { - "title": "Frontmatter title", - }, - } - `); + ).toMatchSnapshot(); }); - test('should not warn for duplicate title if markdown title is not at the top', () => { + it('does not warn for duplicate title if markdown title is not at the top', () => { expect( parseMarkdownString(dedent` --- @@ -655,21 +649,10 @@ describe('parseMarkdownString', () => { # Markdown Title `), - ).toMatchInlineSnapshot(` - Object { - "content": "foo - - # Markdown Title", - "contentTitle": undefined, - "excerpt": "foo", - "frontMatter": Object { - "title": "Frontmatter title", - }, - } - `); + ).toMatchSnapshot(); }); - test('should delete only first heading', () => { + it('deletes only first heading', () => { expect( parseMarkdownString(dedent` # Markdown Title @@ -680,23 +663,10 @@ describe('parseMarkdownString', () => { ### Markdown Title h3 `), - ).toMatchInlineSnapshot(` - Object { - "content": "# Markdown Title - - test test test # test bar - - # Markdown Title 2 - - ### Markdown Title h3", - "contentTitle": "Markdown Title", - "excerpt": "test test test # test bar", - "frontMatter": Object {}, - } - `); + ).toMatchSnapshot(); }); - test('should parse front-matter and ignore h2', () => { + it('parses front-matter and ignore h2', () => { expect( parseMarkdownString( dedent` @@ -706,66 +676,33 @@ describe('parseMarkdownString', () => { ## test `, ), - ).toMatchInlineSnapshot(` - Object { - "content": "## test", - "contentTitle": undefined, - "excerpt": "test", - "frontMatter": Object { - "title": "Frontmatter title", - }, - } - `); + ).toMatchSnapshot(); }); - test('should read front matter only', () => { + it('reads front matter only', () => { expect( parseMarkdownString(dedent` --- title: test --- `), - ).toMatchInlineSnapshot(` - Object { - "content": "", - "contentTitle": undefined, - "excerpt": undefined, - "frontMatter": Object { - "title": "test", - }, - } - `); + ).toMatchSnapshot(); }); - test('should parse title only', () => { - expect(parseMarkdownString('# test')).toMatchInlineSnapshot(` - Object { - "content": "# test", - "contentTitle": "test", - "excerpt": undefined, - "frontMatter": Object {}, - } - `); + it('parses title only', () => { + expect(parseMarkdownString('# test')).toMatchSnapshot(); }); - test('should parse title only alternate', () => { + it('parses title only alternate', () => { expect( parseMarkdownString(dedent` test === `), - ).toMatchInlineSnapshot(` - Object { - "content": "test - ===", - "contentTitle": "test", - "excerpt": undefined, - "frontMatter": Object {}, - } - `); + ).toMatchSnapshot(); }); - test('should warn about duplicate titles', () => { + it('warns about duplicate titles', () => { expect( parseMarkdownString(dedent` --- @@ -773,36 +710,19 @@ describe('parseMarkdownString', () => { --- # test `), - ).toMatchInlineSnapshot(` - Object { - "content": "# test", - "contentTitle": "test", - "excerpt": undefined, - "frontMatter": Object { - "title": "Frontmatter title", - }, - } - `); + ).toMatchSnapshot(); }); - test('should ignore markdown title if its not a first text', () => { + it('ignores markdown title if its not a first text', () => { expect( parseMarkdownString(dedent` foo # test `), - ).toMatchInlineSnapshot(` - Object { - "content": "foo - # test", - "contentTitle": undefined, - "excerpt": "foo", - "frontMatter": Object {}, - } - `); + ).toMatchSnapshot(); }); - test('should delete only first heading 2', () => { + it('deletes only first heading 2', () => { expect( parseMarkdownString(dedent` # test @@ -813,23 +733,10 @@ describe('parseMarkdownString', () => { ### test test3 `), - ).toMatchInlineSnapshot(` - Object { - "content": "# test - - test test test test test test - test test test # test bar - # test2 - ### test - test3", - "contentTitle": "test", - "excerpt": "test test test test test test", - "frontMatter": Object {}, - } - `); + ).toMatchSnapshot(); }); - test('should handle code blocks', () => { + it('handles code blocks', () => { expect( parseMarkdownString(dedent` \`\`\`js @@ -838,18 +745,7 @@ describe('parseMarkdownString', () => { Content `), - ).toMatchInlineSnapshot(` - Object { - "content": "\`\`\`js - code - \`\`\` - - Content", - "contentTitle": undefined, - "excerpt": "Content", - "frontMatter": Object {}, - } - `); + ).toMatchSnapshot(); expect( parseMarkdownString(dedent` \`\`\`\`js @@ -862,22 +758,7 @@ describe('parseMarkdownString', () => { Content `), - ).toMatchInlineSnapshot(` - Object { - "content": "\`\`\`\`js - Foo - \`\`\`diff - code - \`\`\` - Bar - \`\`\`\` - - Content", - "contentTitle": undefined, - "excerpt": "Content", - "frontMatter": Object {}, - } - `); + ).toMatchSnapshot(); expect( parseMarkdownString(dedent` \`\`\`\`js @@ -888,23 +769,10 @@ describe('parseMarkdownString', () => { Content `), - ).toMatchInlineSnapshot(` - Object { - "content": "\`\`\`\`js - Foo - \`\`\`diff - code - \`\`\`\` - - Content", - "contentTitle": undefined, - "excerpt": "Content", - "frontMatter": Object {}, - } - `); + ).toMatchSnapshot(); }); - test('throws for invalid front matter', () => { + it('throws for invalid front matter', () => { expect(() => parseMarkdownString(dedent` --- @@ -920,35 +788,35 @@ describe('parseMarkdownString', () => { }); describe('parseMarkdownHeadingId', () => { - test('can parse simple heading without id', () => { + it('can parse simple heading without id', () => { expect(parseMarkdownHeadingId('## Some heading')).toEqual({ text: '## Some heading', id: undefined, }); }); - test('can parse simple heading with id', () => { + it('can parse simple heading with id', () => { expect(parseMarkdownHeadingId('## Some heading {#custom-_id}')).toEqual({ text: '## Some heading', id: 'custom-_id', }); }); - test('can parse heading not ending with the id', () => { + it('can parse heading not ending with the id', () => { expect(parseMarkdownHeadingId('## {#custom-_id} Some heading')).toEqual({ text: '## {#custom-_id} Some heading', id: undefined, }); }); - test('can parse heading with multiple id', () => { + it('can parse heading with multiple id', () => { expect(parseMarkdownHeadingId('## Some heading {#id1} {#id2}')).toEqual({ text: '## Some heading {#id1}', id: 'id2', }); }); - test('can parse heading with link and id', () => { + it('can parse heading with link and id', () => { expect( parseMarkdownHeadingId( '## Some heading [facebook](https://facebook.com) {#id}', @@ -959,10 +827,115 @@ describe('parseMarkdownHeadingId', () => { }); }); - test('can parse heading with only id', () => { + it('can parse heading with only id', () => { expect(parseMarkdownHeadingId('## {#id}')).toEqual({ text: '##', id: 'id', }); }); }); + +describe('writeMarkdownHeadingId', () => { + it('works for simple level-2 heading', () => { + expect(writeMarkdownHeadingId('## ABC')).toBe('## ABC {#abc}'); + }); + + it('works for simple level-3 heading', () => { + expect(writeMarkdownHeadingId('### ABC')).toBe('### ABC {#abc}'); + }); + + it('works for simple level-4 heading', () => { + expect(writeMarkdownHeadingId('#### ABC')).toBe('#### ABC {#abc}'); + }); + + it('unwraps markdown links', () => { + const input = `## hello [facebook](https://facebook.com) [crowdin](https://crowdin.com/translate/docusaurus-v2/126/en-fr?filter=basic&value=0)`; + expect(writeMarkdownHeadingId(input)).toBe( + `${input} {#hello-facebook-crowdin}`, + ); + }); + + it('can slugify complex headings', () => { + const input = '## abc [Hello] How are you %Sébastien_-_$)( ## -56756'; + expect(writeMarkdownHeadingId(input)).toBe( + // cSpell:ignore ébastien + `${input} {#abc-hello-how-are-you-sébastien_-_---56756}`, + ); + }); + + it('does not duplicate duplicate id', () => { + expect(writeMarkdownHeadingId('## hello world {#hello-world}')).toBe( + '## hello world {#hello-world}', + ); + }); + + it('respects existing heading', () => { + expect(writeMarkdownHeadingId('## New heading {#old-heading}')).toBe( + '## New heading {#old-heading}', + ); + }); + + it('overwrites heading ID when asked to', () => { + expect( + writeMarkdownHeadingId('## New heading {#old-heading}', { + overwrite: true, + }), + ).toBe('## New heading {#new-heading}'); + }); + + it('maintains casing when asked to', () => { + expect( + writeMarkdownHeadingId('## getDataFromAPI()', { + maintainCase: true, + }), + ).toBe('## getDataFromAPI() {#getDataFromAPI}'); + }); + + it('transform the headings', () => { + const input = ` + +# Ignored title + +## abc + +### Hello world + +\`\`\` +# Heading in code block +\`\`\` + +## Hello world + + \`\`\` + # Heading in escaped code block + \`\`\` + +### abc {#abc} + + `; + + const expected = ` + +# Ignored title + +## abc {#abc-1} + +### Hello world {#hello-world} + +\`\`\` +# Heading in code block +\`\`\` + +## Hello world {#hello-world-1} + + \`\`\` + # Heading in escaped code block + \`\`\` + +### abc {#abc} + + `; + + expect(writeMarkdownHeadingId(input)).toEqual(expected); + }); +}); diff --git a/packages/docusaurus-utils/src/__tests__/pathUtils.test.ts b/packages/docusaurus-utils/src/__tests__/pathUtils.test.ts index 988af6dd810d..eaf2b61cd906 100644 --- a/packages/docusaurus-utils/src/__tests__/pathUtils.test.ts +++ b/packages/docusaurus-utils/src/__tests__/pathUtils.test.ts @@ -5,6 +5,7 @@ * LICENSE file in the root directory of this source tree. */ +import {jest} from '@jest/globals'; import { isNameTooLong, shortName, @@ -12,11 +13,12 @@ import { posixPath, aliasedSitePath, toMessageRelativeFilePath, + addTrailingPathSeparator, } from '../pathUtils'; import path from 'path'; describe('isNameTooLong', () => { - test('behaves correctly', () => { + it('works', () => { const asserts = { '': false, 'foo-bar-096': false, @@ -29,7 +31,7 @@ describe('isNameTooLong', () => { true, 'foo-bar-foo-bar-foo-bar-foo-bar-foo-bar-foo-bar-foo-bar-foo-bar-foo-bar-foo-bar-foo-bar-foo-bar-foo-bar-foo-bar-foo-bar-foo-bar-foo-bar-foo-bar-foo-bar-foo-bar-foo-bar-foo-bar-foo-bar-foo-bar-foo-bar-foo-bar-foo-bar-foo-bar-foo-bar-test-1-test-2-787': true, - // Every Hanzi is three bytes + // Every Han zi is three bytes 字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字: {apfs: false, xfs: true}, }; @@ -57,7 +59,7 @@ describe('isNameTooLong', () => { }); describe('shortName', () => { - test('works', () => { + it('works', () => { const asserts = { '': '', 'foo-bar': 'foo-bar', @@ -70,7 +72,8 @@ describe('shortName', () => { 字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字: { apfs: '字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字', - // This is pretty bad (a character clipped in half), but I doubt if it ever happens + // This is pretty bad (a character clipped in half), but I doubt if it + // ever happens xfs: '字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字字�', }, }; @@ -102,33 +105,33 @@ describe('shortName', () => { const VERY_LONG_PATH = `/${`x`.repeat(256)}/`; const VERY_LONG_PATH_NON_LATIN = `/${`あ`.repeat(255)}/`; - test('Truncates long paths correctly', () => { + it('truncates long paths correctly', () => { const truncatedPathLatin = shortName(VERY_LONG_PATH); const truncatedPathNonLatin = shortName(VERY_LONG_PATH_NON_LATIN); expect(truncatedPathLatin.length).toBeLessThanOrEqual(255); expect(truncatedPathNonLatin.length).toBeLessThanOrEqual(255); }); - test('Does not truncate short paths', () => { + it('does not truncate short paths', () => { const truncatedPath = shortName(SHORT_PATH); expect(truncatedPath).toEqual(SHORT_PATH); }); }); describe('toMessageRelativeFilePath', () => { - test('behaves correctly', () => { + it('works', () => { jest .spyOn(process, 'cwd') .mockImplementationOnce(() => path.join(__dirname, '..')); - expect( - toMessageRelativeFilePath(path.join(__dirname, 'foo/bar.js')), - ).toEqual('__tests__/foo/bar.js'); + expect(toMessageRelativeFilePath(path.join(__dirname, 'foo/bar.js'))).toBe( + '__tests__/foo/bar.js', + ); }); }); describe('escapePath', () => { - test('escapePath works', () => { - const asserts: Record = { + it('works', () => { + const asserts: {[key: string]: string} = { 'c:/aaaa\\bbbb': 'c:/aaaa\\\\bbbb', 'c:\\aaaa\\bbbb\\★': 'c:\\\\aaaa\\\\bbbb\\\\★', '\\\\?\\c:\\aaaa\\bbbb': '\\\\\\\\?\\\\c:\\\\aaaa\\\\bbbb', @@ -144,8 +147,8 @@ describe('escapePath', () => { }); describe('posixPath', () => { - test('posixPath works', () => { - const asserts: Record = { + it('works', () => { + const asserts: {[key: string]: string} = { 'c:/aaaa\\bbbb': 'c:/aaaa/bbbb', 'c:\\aaaa\\bbbb\\★': 'c:\\aaaa\\bbbb\\★', '\\\\?\\c:\\aaaa\\bbbb': '\\\\?\\c:\\aaaa\\bbbb', @@ -161,8 +164,8 @@ describe('posixPath', () => { }); describe('aliasedSitePath', () => { - test('behaves correctly', () => { - const asserts: Record = { + it('works', () => { + const asserts: {[key: string]: string} = { 'user/website/docs/asd.md': '@site/docs/asd.md', 'user/website/versioned_docs/foo/bar.md': '@site/versioned_docs/foo/bar.md', @@ -175,3 +178,14 @@ describe('aliasedSitePath', () => { }); }); }); + +describe('addTrailingPathSeparator', () => { + it('works', () => { + expect(addTrailingPathSeparator('foo')).toEqual( + process.platform === 'win32' ? 'foo\\' : 'foo/', + ); + expect(addTrailingPathSeparator('foo/')).toEqual( + process.platform === 'win32' ? 'foo\\' : 'foo/', + ); + }); +}); diff --git a/packages/docusaurus-utils/src/__tests__/slugger.test.ts b/packages/docusaurus-utils/src/__tests__/slugger.test.ts index d075aff7c494..02eec00fd837 100644 --- a/packages/docusaurus-utils/src/__tests__/slugger.test.ts +++ b/packages/docusaurus-utils/src/__tests__/slugger.test.ts @@ -8,20 +8,21 @@ import {createSlugger} from '../slugger'; describe('createSlugger', () => { - test('can create unique slugs', () => { + it('can create unique slugs', () => { const slugger = createSlugger(); - expect(slugger.slug('Some$/vaLue$!^')).toEqual('somevalue'); - expect(slugger.slug('Some$/vaLue$!^')).toEqual('somevalue-1'); - expect(slugger.slug('Some$/vaLue$!^')).toEqual('somevalue-2'); - expect(slugger.slug('Some$/vaLue$!^-1')).toEqual('somevalue-1-1'); + // cSpell:ignore somevalue + expect(slugger.slug('Some$/vaLue$!^')).toBe('somevalue'); + expect(slugger.slug('Some$/vaLue$!^')).toBe('somevalue-1'); + expect(slugger.slug('Some$/vaLue$!^')).toBe('somevalue-2'); + expect(slugger.slug('Some$/vaLue$!^-1')).toBe('somevalue-1-1'); }); - test('can create unique slugs respecting case', () => { + it('can create unique slugs respecting case', () => { const slugger = createSlugger(); const opt = {maintainCase: true}; - expect(slugger.slug('Some$/vaLue$!^', opt)).toEqual('SomevaLue'); - expect(slugger.slug('Some$/vaLue$!^', opt)).toEqual('SomevaLue-1'); - expect(slugger.slug('Some$/vaLue$!^', opt)).toEqual('SomevaLue-2'); - expect(slugger.slug('Some$/vaLue$!^-1', opt)).toEqual('SomevaLue-1-1'); + expect(slugger.slug('Some$/vaLue$!^', opt)).toBe('SomevaLue'); + expect(slugger.slug('Some$/vaLue$!^', opt)).toBe('SomevaLue-1'); + expect(slugger.slug('Some$/vaLue$!^', opt)).toBe('SomevaLue-2'); + expect(slugger.slug('Some$/vaLue$!^-1', opt)).toBe('SomevaLue-1-1'); }); }); diff --git a/packages/docusaurus-utils/src/__tests__/tags.test.ts b/packages/docusaurus-utils/src/__tests__/tags.test.ts index 908d602516f7..a78053b5d76b 100644 --- a/packages/docusaurus-utils/src/__tests__/tags.test.ts +++ b/packages/docusaurus-utils/src/__tests__/tags.test.ts @@ -5,66 +5,64 @@ * LICENSE file in the root directory of this source tree. */ -import { - normalizeFrontMatterTag, - normalizeFrontMatterTags, - groupTaggedItems, - type Tag, -} from '../tags'; - -describe('normalizeFrontMatterTag', () => { - type Input = Parameters[1]; - type Output = ReturnType; - - test('should normalize simple string tag', () => { +import {normalizeFrontMatterTags, groupTaggedItems, type Tag} from '../tags'; + +describe('normalizeFrontMatterTags', () => { + it('normalizes simple string tag', () => { const tagsPath = '/all/tags'; - const input: Input = 'tag'; - const expectedOutput: Output = { + const input = 'tag'; + const expectedOutput = { label: 'tag', permalink: `${tagsPath}/tag`, }; - expect(normalizeFrontMatterTag(tagsPath, input)).toEqual(expectedOutput); + expect(normalizeFrontMatterTags(tagsPath, [input])).toEqual([ + expectedOutput, + ]); }); - test('should normalize complex string tag', () => { + it('normalizes complex string tag', () => { const tagsPath = '/all/tags'; - const input: Input = 'some more Complex_tag'; - const expectedOutput: Output = { + const input = 'some more Complex_tag'; + const expectedOutput = { label: 'some more Complex_tag', permalink: `${tagsPath}/some-more-complex-tag`, }; - expect(normalizeFrontMatterTag(tagsPath, input)).toEqual(expectedOutput); + expect(normalizeFrontMatterTags(tagsPath, [input])).toEqual([ + expectedOutput, + ]); }); - test('should normalize simple object tag', () => { + it('normalizes simple object tag', () => { const tagsPath = '/all/tags'; - const input: Input = {label: 'tag', permalink: 'tagPermalink'}; - const expectedOutput: Output = { + const input = {label: 'tag', permalink: 'tagPermalink'}; + const expectedOutput = { label: 'tag', permalink: `${tagsPath}/tagPermalink`, }; - expect(normalizeFrontMatterTag(tagsPath, input)).toEqual(expectedOutput); + expect(normalizeFrontMatterTags(tagsPath, [input])).toEqual([ + expectedOutput, + ]); }); - test('should normalize complex string tag with object tag', () => { + it('normalizes complex string tag with object tag', () => { const tagsPath = '/all/tags'; - const input: Input = { + const input = { label: 'tag complex Label', permalink: '/MoreComplex/Permalink', }; - const expectedOutput: Output = { + const expectedOutput = { label: 'tag complex Label', permalink: `${tagsPath}/MoreComplex/Permalink`, }; - expect(normalizeFrontMatterTag(tagsPath, input)).toEqual(expectedOutput); + expect(normalizeFrontMatterTags(tagsPath, [input])).toEqual([ + expectedOutput, + ]); }); -}); -describe('normalizeFrontMatterTags', () => { type Input = Parameters[1]; type Output = ReturnType; - test('should normalize string list', () => { + it('normalizes string list', () => { const tagsPath = '/all/tags'; const input: Input = ['tag 1', 'tag-1', 'tag 3', 'tag1', 'tag-2']; // Keep user input order but remove tags that lead to same permalink @@ -85,11 +83,11 @@ describe('normalizeFrontMatterTags', () => { expect(normalizeFrontMatterTags(tagsPath, input)).toEqual(expectedOutput); }); - test('succeeds for empty list', () => { + it('succeeds for empty list', () => { expect(normalizeFrontMatterTags('/foo')).toEqual([]); }); - test('should normalize complex mixed list', () => { + it('normalizes complex mixed list', () => { const tagsPath = '/all/tags'; const input: Input = [ 'tag 1', @@ -131,7 +129,7 @@ describe('groupTaggedItems', () => { type Input = Parameters[0]; type Output = ReturnType; - test('should group items by tag permalink', () => { + it('groups items by tag permalink', () => { const tagGuide = {label: 'Guide', permalink: '/guide'}; const tagTutorial = {label: 'Tutorial', permalink: '/tutorial'}; const tagAPI = {label: 'API', permalink: '/api'}; diff --git a/packages/docusaurus-utils/src/__tests__/urlUtils.test.ts b/packages/docusaurus-utils/src/__tests__/urlUtils.test.ts index 3acd892bbe45..30625e400554 100644 --- a/packages/docusaurus-utils/src/__tests__/urlUtils.test.ts +++ b/packages/docusaurus-utils/src/__tests__/urlUtils.test.ts @@ -5,11 +5,28 @@ * LICENSE file in the root directory of this source tree. */ -import {normalizeUrl, getEditUrl} from '../urlUtils'; +import { + normalizeUrl, + getEditUrl, + fileToPath, + isValidPathname, + addTrailingSlash, + addLeadingSlash, + removeTrailingSlash, + resolvePathname, + encodePath, + buildSshUrl, + buildHttpsUrl, + hasSSHProtocol, +} from '../urlUtils'; describe('normalizeUrl', () => { - test('should normalize urls correctly', () => { + it('normalizes urls correctly', () => { const asserts = [ + { + input: [], + output: '', + }, { input: ['/', ''], output: '/', @@ -133,20 +150,165 @@ describe('normalizeUrl', () => { }); describe('getEditUrl', () => { - test('returns right path', () => { + it('returns right path', () => { expect( getEditUrl('foo/bar.md', 'https://github.com/facebook/docusaurus'), - ).toEqual('https://github.com/facebook/docusaurus/foo/bar.md'); + ).toBe('https://github.com/facebook/docusaurus/foo/bar.md'); expect( getEditUrl('foo/你好.md', 'https://github.com/facebook/docusaurus'), - ).toEqual('https://github.com/facebook/docusaurus/foo/你好.md'); + ).toBe('https://github.com/facebook/docusaurus/foo/你好.md'); }); - test('always returns valid URL', () => { + it('always returns valid URL', () => { expect( getEditUrl('foo\\你好.md', 'https://github.com/facebook/docusaurus'), - ).toEqual('https://github.com/facebook/docusaurus/foo/你好.md'); + ).toBe('https://github.com/facebook/docusaurus/foo/你好.md'); }); - test('returns undefined for undefined', () => { + it('returns undefined for undefined', () => { expect(getEditUrl('foo/bar.md')).toBeUndefined(); }); }); + +describe('fileToPath', () => { + it('works', () => { + const asserts: {[key: string]: string} = { + 'index.md': '/', + 'hello/index.md': '/hello/', + 'foo.md': '/foo', + 'foo/bar.md': '/foo/bar', + 'index.js': '/', + 'hello/index.js': '/hello/', + 'foo.js': '/foo', + 'foo/bar.js': '/foo/bar', + }; + Object.keys(asserts).forEach((file) => { + expect(fileToPath(file)).toBe(asserts[file]); + }); + }); +}); + +describe('isValidPathname', () => { + it('works', () => { + expect(isValidPathname('/')).toBe(true); + expect(isValidPathname('/hey')).toBe(true); + expect(isValidPathname('/hey/ho')).toBe(true); + expect(isValidPathname('/hey/ho/')).toBe(true); + expect(isValidPathname('/hey/h%C3%B4/')).toBe(true); + expect(isValidPathname('/hey///ho///')).toBe(true); // Unexpected but valid + expect(isValidPathname('/hey/héllô you')).toBe(true); + + expect(isValidPathname('')).toBe(false); + expect(isValidPathname('hey')).toBe(false); + expect(isValidPathname('/hey?qs=ho')).toBe(false); + expect(isValidPathname('https://fb.com/hey')).toBe(false); + expect(isValidPathname('//hey')).toBe(false); + expect(isValidPathname('////')).toBe(false); + }); +}); + +describe('addTrailingSlash', () => { + it('is no-op for path with trailing slash', () => { + expect(addTrailingSlash('/abcd/')).toBe('/abcd/'); + }); + it('adds / for path without trailing slash', () => { + expect(addTrailingSlash('/abcd')).toBe('/abcd/'); + }); +}); + +describe('addLeadingSlash', () => { + it('is no-op for path with leading slash', () => { + expect(addLeadingSlash('/abc')).toBe('/abc'); + }); + it('adds / for path without leading slash', () => { + expect(addLeadingSlash('abc')).toBe('/abc'); + }); +}); + +describe('removeTrailingSlash', () => { + it('is no-op for path without trailing slash', () => { + expect(removeTrailingSlash('/abcd')).toBe('/abcd'); + }); + it('removes / for path with trailing slash', () => { + expect(removeTrailingSlash('/abcd/')).toBe('/abcd'); + }); +}); + +describe('resolvePathname', () => { + it('works', () => { + // These tests are directly copied from https://github.com/mjackson/resolve-pathname/blob/master/modules/__tests__/resolvePathname-test.js + // Maybe we want to wrap that logic in the future? + expect(resolvePathname('c')).toBe('c'); + expect(resolvePathname('c', 'a/b')).toBe('a/c'); + expect(resolvePathname('/c', '/a/b')).toBe('/c'); + expect(resolvePathname('', '/a/b')).toBe('/a/b'); + expect(resolvePathname('../c', '/a/b')).toBe('/c'); + expect(resolvePathname('c', '/a/b')).toBe('/a/c'); + expect(resolvePathname('c', '/a/')).toBe('/a/c'); + expect(resolvePathname('..', '/a/b')).toBe('/'); + }); +}); + +describe('encodePath', () => { + it('works', () => { + expect(encodePath('a/foo/')).toBe('a/foo/'); + // cSpell:ignore cfoo + expect(encodePath('a//')).toBe('a/%3Cfoo%3E/'); + expect(encodePath('a/你好/')).toBe('a/%E4%BD%A0%E5%A5%BD/'); + }); +}); + +describe('buildSshUrl', () => { + it('builds a normal ssh url', () => { + const url = buildSshUrl('github.com', 'facebook', 'docusaurus'); + expect(url).toBe('git@github.com:facebook/docusaurus.git'); + }); + it('builds a ssh url with port', () => { + const url = buildSshUrl('github.com', 'facebook', 'docusaurus', '422'); + expect(url).toBe('ssh://git@github.com:422/facebook/docusaurus.git'); + }); +}); + +describe('buildHttpsUrl', () => { + it('builds a normal http url', () => { + const url = buildHttpsUrl( + 'user:pass', + 'github.com', + 'facebook', + 'docusaurus', + ); + expect(url).toBe('https://user:pass@github.com/facebook/docusaurus.git'); + }); + it('builds a normal http url with port', () => { + const url = buildHttpsUrl( + 'user:pass', + 'github.com', + 'facebook', + 'docusaurus', + '5433', + ); + expect(url).toBe( + 'https://user:pass@github.com:5433/facebook/docusaurus.git', + ); + }); +}); + +describe('hasSSHProtocol', () => { + it('recognizes explicit SSH protocol', () => { + const url = 'ssh://git@github.com:422/facebook/docusaurus.git'; + expect(hasSSHProtocol(url)).toBe(true); + }); + + it('recognizes implied SSH protocol', () => { + const url = 'git@github.com:facebook/docusaurus.git'; + expect(hasSSHProtocol(url)).toBe(true); + }); + + it('does not recognize HTTPS with credentials', () => { + const url = 'https://user:pass@github.com/facebook/docusaurus.git'; + expect(hasSSHProtocol(url)).toBe(false); + }); + + it('does not recognize plain HTTPS URL', () => { + const url = 'https://github.com:5433/facebook/docusaurus.git'; + expect(hasSSHProtocol(url)).toBe(false); + }); +}); diff --git a/packages/docusaurus-utils/src/__tests__/webpackUtils.test.ts b/packages/docusaurus-utils/src/__tests__/webpackUtils.test.ts index f423fa605873..f4ce3578bf1b 100644 --- a/packages/docusaurus-utils/src/__tests__/webpackUtils.test.ts +++ b/packages/docusaurus-utils/src/__tests__/webpackUtils.test.ts @@ -8,7 +8,7 @@ import {getFileLoaderUtils} from '../webpackUtils'; describe('getFileLoaderUtils()', () => { - test('plugin svgo/removeViewBox should be disabled', () => { + it('plugin svgo/removeViewBox and removeTitle should be disabled', () => { const {oneOf} = getFileLoaderUtils().rules.svg(); expect(oneOf[0].use).toContainEqual( expect.objectContaining({ @@ -20,6 +20,7 @@ describe('getFileLoaderUtils()', () => { name: 'preset-default', params: { overrides: { + removeTitle: false, removeViewBox: false, }, }, diff --git a/packages/docusaurus-utils/src/constants.ts b/packages/docusaurus-utils/src/constants.ts index 8d1bb8a2d5cf..0b413e6f4182 100644 --- a/packages/docusaurus-utils/src/constants.ts +++ b/packages/docusaurus-utils/src/constants.ts @@ -5,34 +5,86 @@ * LICENSE file in the root directory of this source tree. */ +/** Node major version, directly read from env. */ export const NODE_MAJOR_VERSION = parseInt( - process.versions.node.split('.')[0], + process.versions.node.split('.')[0]!, 10, ); +/** Node minor version, directly read from env. */ export const NODE_MINOR_VERSION = parseInt( - process.versions.node.split('.')[1], + process.versions.node.split('.')[1]!, 10, ); -// Can be overridden with cli option --out-dir +/** + * Can be overridden with cli option `--out-dir`. Code should generally use + * `context.outDir` instead (which is always absolute and localized). + */ export const DEFAULT_BUILD_DIR_NAME = 'build'; -// Can be overridden with cli option --config +/** + * Can be overridden with cli option `--config`. Code should generally use + * `context.siteConfigPath` instead (which is always absolute). + */ export const DEFAULT_CONFIG_FILE_NAME = 'docusaurus.config.js'; +/** Can be absolute or relative to site directory. */ export const BABEL_CONFIG_FILE_NAME = - process.env.DOCUSAURUS_BABEL_CONFIG_FILE_NAME || 'babel.config.js'; + process.env.DOCUSAURUS_BABEL_CONFIG_FILE_NAME ?? 'babel.config.js'; +/** + * Can be absolute or relative to site directory. Code should generally use + * `context.generatedFilesDir` instead (which is always absolute). + */ export const GENERATED_FILES_DIR_NAME = - process.env.DOCUSAURUS_GENERATED_FILES_DIR_NAME || '.docusaurus'; + process.env.DOCUSAURUS_GENERATED_FILES_DIR_NAME ?? '.docusaurus'; +/** + * We would assume all of the site's JS code lives in here and not outside. + * Relative to the site directory. + */ export const SRC_DIR_NAME = 'src'; -export const STATIC_DIR_NAME = 'static'; -export const OUTPUT_STATIC_ASSETS_DIR_NAME = 'assets'; // files handled by webpack, hashed (can be cached aggressively) + +/** + * Can be overridden with `config.staticDirectories`. Code should use + * `context.siteConfig.staticDirectories` instead (which is always absolute). + */ +export const DEFAULT_STATIC_DIR_NAME = 'static'; + +/** + * Files here are handled by webpack, hashed (can be cached aggressively). + * Relative to the build output folder. + */ +export const OUTPUT_STATIC_ASSETS_DIR_NAME = 'assets'; + +/** + * Components in this directory will receive the `@theme` alias and be able to + * shadow default theme components. + */ export const THEME_PATH = `${SRC_DIR_NAME}/theme`; + +/** + * All translation-related data live here, relative to site directory. Content + * will be namespaced by locale. + */ +export const I18N_DIR_NAME = 'i18n'; + +/** + * Translations for React code. + */ +export const CODE_TRANSLATIONS_FILE_NAME = 'code.json'; + +/** Dev server opens on this port by default. */ export const DEFAULT_PORT = 3000; + +/** Default plugin ID. */ export const DEFAULT_PLUGIN_ID = 'default'; -// Temporary fix for https://github.com/facebook/docusaurus/issues/5493 +/** + * Allow overriding the limit after which the url loader will no longer inline + * assets. + * + * @see https://github.com/facebook/docusaurus/issues/5493 + */ export const WEBPACK_URL_LOADER_LIMIT = process.env.WEBPACK_URL_LOADER_LIMIT ?? 10000; diff --git a/packages/docusaurus-utils/src/dataFileUtils.ts b/packages/docusaurus-utils/src/dataFileUtils.ts index c92bb4a52329..34ed9befeb83 100644 --- a/packages/docusaurus-utils/src/dataFileUtils.ts +++ b/packages/docusaurus-utils/src/dataFileUtils.ts @@ -13,31 +13,47 @@ import type {ContentPaths} from './markdownLinks'; import logger from '@docusaurus/logger'; type DataFileParams = { + /** Path to the potential data file, relative to `contentPaths` */ filePath: string; + /** + * Includes the base path and localized path, both of which are eligible for + * sourcing data files. Both paths should be absolute. + */ contentPaths: ContentPaths; }; +/** + * Looks for a data file in the potential content paths; loads a localized data + * file in priority. + * + * @returns An absolute path to the data file, or `undefined` if there isn't one. + */ export async function getDataFilePath({ filePath, contentPaths, }: DataFileParams): Promise { - // Loads a localized data file in priority const contentPath = await findFolderContainingFile( getContentPathList(contentPaths), filePath, ); if (contentPath) { - return path.join(contentPath, filePath); + return path.resolve(contentPath, filePath); } return undefined; } /** - * Looks up for a data file in the content paths, returns the normalized object. - * Throws when validation fails; returns undefined when file not found + * Looks up for a data file in the content paths, returns the object validated + * and normalized according to the `validate` callback. + * + * @returns `undefined` when file not found + * @throws Throws when validation fails, displaying a helpful context message. */ export async function getDataFileData( - params: DataFileParams & {fileType: string}, + params: DataFileParams & { + /** Used for the "The X file looks invalid" message. */ + fileType: string; + }, validate: (content: unknown) => T, ): Promise { const filePath = await getDataFilePath(params); @@ -48,19 +64,27 @@ export async function getDataFileData( const contentString = await fs.readFile(filePath, {encoding: 'utf8'}); const unsafeContent = Yaml.load(contentString); return validate(unsafeContent); - } catch (e) { - // TODO replace later by error cause, see https://v8.dev/features/error-cause + } catch (err) { logger.error`The ${params.fileType} file at path=${filePath} looks invalid.`; - throw e; + throw err; } } -// Order matters: we look in priority in localized folder +/** + * Takes the `contentPaths` data structure and returns an ordered path list + * indicating their priorities. For all data, we look in the localized folder + * in priority. + */ export function getContentPathList(contentPaths: ContentPaths): string[] { return [contentPaths.contentPathLocalized, contentPaths.contentPath]; } -// return the first folder path in which the file exists in +/** + * @param folderPaths a list of absolute paths. + * @param relativeFilePath file path relative to each `folderPaths`. + * @returns the first folder path in which the file exists, or `undefined` if + * none is found. + */ export async function findFolderContainingFile( folderPaths: string[], relativeFilePath: string, @@ -70,6 +94,16 @@ export async function findFolderContainingFile( ); } +/** + * Fail-fast alternative to `findFolderContainingFile`. + * + * @param folderPaths a list of absolute paths. + * @param relativeFilePath file path relative to each `folderPaths`. + * @returns the first folder path in which the file exists. + * @throws Throws if no file can be found. You should use this method only when + * you actually know the file exists (e.g. when the `relativeFilePath` is read + * with a glob and you are just trying to localize it) + */ export async function getFolderContainingFile( folderPaths: string[], relativeFilePath: string, @@ -78,12 +112,10 @@ export async function getFolderContainingFile( folderPaths, relativeFilePath, ); - // should never happen, as the source was read from the FS anyway... if (!maybeFolderPath) { throw new Error( - `File "${relativeFilePath}" does not exist in any of these folders:\n- ${folderPaths.join( - '\n- ', - )}]`, + `File "${relativeFilePath}" does not exist in any of these folders: +- ${folderPaths.join('\n- ')}`, ); } return maybeFolderPath; diff --git a/packages/docusaurus-utils/src/deps.d.ts b/packages/docusaurus-utils/src/deps.d.ts index 0a591d14db6c..79a7896f34cf 100644 --- a/packages/docusaurus-utils/src/deps.d.ts +++ b/packages/docusaurus-utils/src/deps.d.ts @@ -8,7 +8,3 @@ declare module 'resolve-pathname' { export default function resolvePathname(to: string, from?: string): string; } - -declare module '@mdx-js/runtime'; -declare module 'remark-mdx-remove-imports'; -declare module 'remark-mdx-remove-exports'; diff --git a/packages/docusaurus-utils/src/emitUtils.ts b/packages/docusaurus-utils/src/emitUtils.ts new file mode 100644 index 000000000000..89680f92ad08 --- /dev/null +++ b/packages/docusaurus-utils/src/emitUtils.ts @@ -0,0 +1,101 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import path from 'path'; +import fs from 'fs-extra'; +import {createHash} from 'crypto'; +import {findAsyncSequential} from './jsUtils'; + +const fileHash = new Map(); + +/** + * Outputs a file to the generated files directory. Only writes files if content + * differs from cache (for hot reload performance). + * + * @param generatedFilesDir Absolute path. + * @param file Path relative to `generatedFilesDir`. File will always be + * outputted; no need to ensure directory exists. + * @param content String content to write. + * @param skipCache If `true` (defaults as `true` for production), file is + * force-rewritten, skipping cache. + */ +export async function generate( + generatedFilesDir: string, + file: string, + content: string, + skipCache: boolean = process.env.NODE_ENV === 'production', +): Promise { + const filepath = path.resolve(generatedFilesDir, file); + + if (skipCache) { + await fs.outputFile(filepath, content); + // Cache still needs to be reset, otherwise, writing "A", "B", and "A" where + // "B" skips cache will cause the last "A" not be able to overwrite as the + // first "A" remains in cache. But if the file never existed in cache, no + // need to register it. + if (fileHash.get(filepath)) { + fileHash.set(filepath, createHash('md5').update(content).digest('hex')); + } + return; + } + + let lastHash = fileHash.get(filepath); + + // If file already exists but it's not in runtime cache yet, we try to + // calculate the content hash and then compare. This is to avoid unnecessary + // overwriting and we can reuse old file. + if (!lastHash && (await fs.pathExists(filepath))) { + const lastContent = await fs.readFile(filepath, 'utf8'); + lastHash = createHash('md5').update(lastContent).digest('hex'); + fileHash.set(filepath, lastHash); + } + + const currentHash = createHash('md5').update(content).digest('hex'); + + if (lastHash !== currentHash) { + await fs.outputFile(filepath, content); + fileHash.set(filepath, currentHash); + } +} + +/** + * @param permalink The URL that the HTML file corresponds to, without base URL + * @param outDir Full path to the output directory + * @param trailingSlash The site config option. If provided, only one path will + * be read. + * @returns This returns a buffer, which you have to decode string yourself if + * needed. (Not always necessary since the output isn't for human consumption + * anyways, and most HTML manipulation libs accept buffers) + * @throws Throws when the HTML file is not found at any of the potential paths. + * This should never happen as it would lead to a 404. + */ +export async function readOutputHTMLFile( + permalink: string, + outDir: string, + trailingSlash: boolean | undefined, +): Promise { + const withTrailingSlashPath = path.join(outDir, permalink, 'index.html'); + const withoutTrailingSlashPath = path.join( + outDir, + `${permalink.replace(/\/$/, '')}.html`, + ); + if (trailingSlash) { + return fs.readFile(withTrailingSlashPath); + } else if (trailingSlash === false) { + return fs.readFile(withoutTrailingSlashPath); + } + const HTMLPath = await findAsyncSequential( + [withTrailingSlashPath, withoutTrailingSlashPath], + fs.pathExists, + ); + if (!HTMLPath) { + throw new Error( + `Expected output HTML file to be found at ${withTrailingSlashPath}`, + ); + } + return fs.readFile(HTMLPath); +} diff --git a/packages/docusaurus-utils/src/gitUtils.ts b/packages/docusaurus-utils/src/gitUtils.ts new file mode 100644 index 000000000000..e2f155bb3e05 --- /dev/null +++ b/packages/docusaurus-utils/src/gitUtils.ts @@ -0,0 +1,153 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import path from 'path'; +import shell from 'shelljs'; + +/** Custom error thrown when git is not found in `PATH`. */ +export class GitNotFoundError extends Error {} + +/** Custom error thrown when the current file is not tracked by git. */ +export class FileNotTrackedError extends Error {} + +/** + * Fetches the git history of a file and returns a relevant commit date. + * It gets the commit date instead of author date so that amended commits + * can have their dates updated. + * + * @throws {GitNotFoundError} If git is not found in `PATH`. + * @throws {FileNotTrackedError} If the current file is not tracked by git. + * @throws Also throws when `git log` exited with non-zero, or when it outputs + * unexpected text. + */ +export function getFileCommitDate( + /** Absolute path to the file. */ + file: string, + args: { + /** + * `"oldest"` is the commit that added the file, following renames; + * `"newest"` is the last commit that edited the file. + */ + age?: 'oldest' | 'newest'; + /** Use `includeAuthor: true` to get the author information as well. */ + includeAuthor?: false; + }, +): { + /** Relevant commit date. */ + date: Date; + /** Timestamp in **seconds**, as returned from git. */ + timestamp: number; +}; +/** + * Fetches the git history of a file and returns a relevant commit date. + * It gets the commit date instead of author date so that amended commits + * can have their dates updated. + * + * @throws {GitNotFoundError} If git is not found in `PATH`. + * @throws {FileNotTrackedError} If the current file is not tracked by git. + * @throws Also throws when `git log` exited with non-zero, or when it outputs + * unexpected text. + */ +export function getFileCommitDate( + /** Absolute path to the file. */ + file: string, + args: { + /** + * `"oldest"` is the commit that added the file, following renames; + * `"newest"` is the last commit that edited the file. + */ + age?: 'oldest' | 'newest'; + includeAuthor: true; + }, +): { + /** Relevant commit date. */ + date: Date; + /** Timestamp in **seconds**, as returned from git. */ + timestamp: number; + /** The author's name, as returned from git. */ + author: string; +}; +export function getFileCommitDate( + file: string, + { + age = 'oldest', + includeAuthor = false, + }: { + age?: 'oldest' | 'newest'; + includeAuthor?: boolean; + }, +): { + date: Date; + timestamp: number; + author?: string; +} { + if (!shell.which('git')) { + throw new GitNotFoundError( + `Failed to retrieve git history for "${file}" because git is not installed.`, + ); + } + + if (!shell.test('-f', file)) { + throw new Error( + `Failed to retrieve git history for "${file}" because the file does not exist.`, + ); + } + + let formatArg = '--format=%ct'; + if (includeAuthor) { + formatArg += ',%an'; + } + + let extraArgs = '--max-count=1'; + if (age === 'oldest') { + // --follow is necessary to follow file renames + // --diff-filter=A ensures we only get the commit which (A)dded the file + extraArgs += ' --follow --diff-filter=A'; + } + + const result = shell.exec( + `git log ${extraArgs} ${formatArg} -- "${path.basename(file)}"`, + { + // cwd is important, see: https://github.com/facebook/docusaurus/pull/5048 + cwd: path.dirname(file), + silent: true, + }, + ); + if (result.code !== 0) { + throw new Error( + `Failed to retrieve the git history for file "${file}" with exit code ${result.code}: ${result.stderr}`, + ); + } + let regex = /^(?\d+)$/; + if (includeAuthor) { + regex = /^(?\d+),(?.+)$/; + } + + const output = result.stdout.trim(); + + if (!output) { + throw new FileNotTrackedError( + `Failed to retrieve the git history for file "${file}" because the file is not tracked by git.`, + ); + } + + const match = output.match(regex); + + if (!match) { + throw new Error( + `Failed to retrieve the git history for file "${file}" with unexpected output: ${output}`, + ); + } + + const timestamp = Number(match.groups!.timestamp); + const date = new Date(timestamp * 1000); + + if (includeAuthor) { + return {date, timestamp, author: match.groups!.author!}; + } + return {date, timestamp}; +} diff --git a/packages/docusaurus-utils/src/globUtils.ts b/packages/docusaurus-utils/src/globUtils.ts index fbbf8aa51baf..b70ed4cd53d3 100644 --- a/packages/docusaurus-utils/src/globUtils.ts +++ b/packages/docusaurus-utils/src/globUtils.ts @@ -10,24 +10,31 @@ import Micromatch from 'micromatch'; // Note: Micromatch is used by Globby import path from 'path'; +/** A re-export of the globby instance. */ export {default as Globby} from 'globby'; -// The default patterns we ignore when globbing -// using _ prefix for exclusion by convention +/** + * The default glob patterns we ignore when sourcing content. + * - Ignore files and folders starting with `_` recursively + * - Ignore tests + */ export const GlobExcludeDefault = [ - // Ignore files starting with _ '**/_*.{js,jsx,ts,tsx,md,mdx}', - - // Ignore folders starting with _ (including folder content) '**/_*/**', - - // Ignore tests '**/*.test.{js,jsx,ts,tsx}', '**/__tests__/**', ]; type Matcher = (str: string) => boolean; +/** + * A very thin wrapper around `Micromatch.makeRe`. + * + * @see {@link createAbsoluteFilePathMatcher} + * @param patterns A list of glob patterns. + * @returns A matcher handle that tells if a file path is matched by any of the + * patterns. + */ export function createMatcher(patterns: string[]): Matcher { const regexp = new RegExp( patterns.map((pattern) => Micromatch.makeRe(pattern).source).join('|'), @@ -35,10 +42,19 @@ export function createMatcher(patterns: string[]): Matcher { return (str) => regexp.test(str); } -// We use match patterns like '**/_*/**', -// This function permits to help to: -// Match /user/sebastien/website/docs/_partials/xyz.md -// Ignore /user/_sebastien/website/docs/partials/xyz.md +/** + * We use match patterns like `"** /_* /**"` (ignore the spaces), where `"_*"` + * should only be matched within a subfolder. This function would: + * - Match `/user/sebastien/website/docs/_partials/xyz.md` + * - Ignore `/user/_sebastien/website/docs/partials/xyz.md` + * + * @param patterns A list of glob patterns. + * @param rootFolders A list of root folders to resolve the glob from. + * @returns A matcher handle that tells if a file path is matched by any of the + * patterns, resolved from the first root folder that contains the path. + * @throws Throws when the returned matcher receives a path that doesn't belong + * to any of the `rootFolders`. + */ export function createAbsoluteFilePathMatcher( patterns: string[], rootFolders: string[], @@ -51,8 +67,8 @@ export function createAbsoluteFilePathMatcher( ); if (!rootFolder) { throw new Error( - `createAbsoluteFilePathMatcher unexpected error, absoluteFilePath=${absoluteFilePath} was not contained in any of the root folders ${JSON.stringify( - rootFolders, + `createAbsoluteFilePathMatcher unexpected error, absoluteFilePath=${absoluteFilePath} was not contained in any of the root folders: ${rootFolders.join( + ', ', )}`, ); } diff --git a/packages/docusaurus-utils/src/hashUtils.ts b/packages/docusaurus-utils/src/hashUtils.ts index 40b9bde44691..8d6e344b4732 100644 --- a/packages/docusaurus-utils/src/hashUtils.ts +++ b/packages/docusaurus-utils/src/hashUtils.ts @@ -6,32 +6,33 @@ */ import {createHash} from 'crypto'; -import {kebabCase} from 'lodash'; +import _ from 'lodash'; import {shortName, isNameTooLong} from './pathUtils'; +/** Thin wrapper around `crypto.createHash("md5")`. */ export function md5Hash(str: string): string { return createHash('md5').update(str).digest('hex'); } +/** Creates an MD5 hash and truncates it to the given length. */ export function simpleHash(str: string, length: number): string { - return md5Hash(str).substr(0, length); + return md5Hash(str).substring(0, length); } // Based on https://github.com/gatsbyjs/gatsby/pull/21518/files /** - * Given an input string, convert to kebab-case and append a hash. - * Avoid str collision. - * Also removes part of the string if its larger than the allowed - * filename per OS. Avoids ERRNAMETOOLONG error. + * Given an input string, convert to kebab-case and append a hash, avoiding name + * collision. Also removes part of the string if its larger than the allowed + * filename per OS, avoiding `ERRNAMETOOLONG` error. */ export function docuHash(str: string): string { if (str === '/') { return 'index'; } const shortHash = simpleHash(str, 3); - const parsedPath = `${kebabCase(str)}-${shortHash}`; + const parsedPath = `${_.kebabCase(str)}-${shortHash}`; if (isNameTooLong(parsedPath)) { - return `${shortName(kebabCase(str))}-${shortHash}`; + return `${shortName(_.kebabCase(str))}-${shortHash}`; } return parsedPath; } diff --git a/packages/docusaurus-utils/src/i18nUtils.ts b/packages/docusaurus-utils/src/i18nUtils.ts new file mode 100644 index 000000000000..950f3719a7b0 --- /dev/null +++ b/packages/docusaurus-utils/src/i18nUtils.ts @@ -0,0 +1,115 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import path from 'path'; +import _ from 'lodash'; +import type { + TranslationFileContent, + TranslationFile, + I18n, +} from '@docusaurus/types'; +import {DEFAULT_PLUGIN_ID, I18N_DIR_NAME} from './constants'; +import {normalizeUrl} from './urlUtils'; + +/** + * Takes a list of translation file contents, and shallow-merges them into one. + */ +export function mergeTranslations( + contents: TranslationFileContent[], +): TranslationFileContent { + return contents.reduce((acc, content) => ({...acc, ...content}), {}); +} + +/** + * Useful to update all the messages of a translation file. Used in tests to + * simulate translations. + */ +export function updateTranslationFileMessages( + translationFile: TranslationFile, + updateMessage: (message: string) => string, +): TranslationFile { + return { + ...translationFile, + content: _.mapValues(translationFile.content, (translation) => ({ + ...translation, + message: updateMessage(translation.message), + })), + }; +} + +/** + * Takes everything needed and constructs a plugin i18n path. Plugins should + * expect everything it needs for translations to be found under this path. + */ +export function getPluginI18nPath({ + siteDir, + locale, + pluginName, + pluginId = DEFAULT_PLUGIN_ID, + subPaths = [], +}: { + siteDir: string; + locale: string; + pluginName: string; + pluginId?: string | undefined; + subPaths?: string[]; +}): string { + return path.join( + siteDir, + I18N_DIR_NAME, + // namespace first by locale: convenient to work in a single folder for a + // translator + locale, + // Make it convenient to use for single-instance + // ie: return "docs", not "docs-default" nor "docs/default" + `${pluginName}${pluginId === DEFAULT_PLUGIN_ID ? '' : `-${pluginId}`}`, + ...subPaths, + ); +} + +/** + * Takes a path and returns a localized a version (which is basically `path + + * i18n.currentLocale`). + */ +export function localizePath({ + pathType, + path: originalPath, + i18n, + options = {}, +}: { + /** + * FS paths will treat Windows specially; URL paths will always have a + * trailing slash to make it a valid base URL. + */ + pathType: 'fs' | 'url'; + /** The path, URL or file path, to be localized. */ + path: string; + /** The current i18n context. */ + i18n: I18n; + options?: { + /** + * By default, we don't localize the path of defaultLocale. This option + * would override that behavior. Setting `false` is useful for `yarn build + * -l zh-Hans` to always emit into the root build directory. + */ + localizePath?: boolean; + }; +}): string { + const shouldLocalizePath: boolean = + // + options.localizePath ?? i18n.currentLocale !== i18n.defaultLocale; + + if (!shouldLocalizePath) { + return originalPath; + } + // FS paths need special care, for Windows support + if (pathType === 'fs') { + return path.join(originalPath, i18n.currentLocale); + } + // Url paths; add a trailing slash so it's a valid base URL + return normalizeUrl([originalPath, i18n.currentLocale, '/']); +} diff --git a/packages/docusaurus-utils/src/index.ts b/packages/docusaurus-utils/src/index.ts index bb0cffa26698..7cd871a631e8 100644 --- a/packages/docusaurus-utils/src/index.ts +++ b/packages/docusaurus-utils/src/index.ts @@ -5,311 +5,98 @@ * LICENSE file in the root directory of this source tree. */ -import logger from '@docusaurus/logger'; -import path from 'path'; -import {createHash} from 'crypto'; -import {mapValues} from 'lodash'; -import fs from 'fs-extra'; -import {URL} from 'url'; -import type { - ReportingSeverity, - TranslationFileContent, - TranslationFile, -} from '@docusaurus/types'; - -import resolvePathnameUnsafe from 'resolve-pathname'; - -import {simpleHash, docuHash} from './hashUtils'; -import {DEFAULT_PLUGIN_ID} from './constants'; - -export * from './constants'; -export * from './urlUtils'; -export * from './tags'; -export * from './markdownParser'; -export * from './markdownLinks'; -export * from './slugger'; -export * from './pathUtils'; -export * from './hashUtils'; -export * from './globUtils'; -export * from './webpackUtils'; -export * from './dataFileUtils'; - -const fileHash = new Map(); -export async function generate( - generatedFilesDir: string, - file: string, - content: string, - skipCache: boolean = process.env.NODE_ENV === 'production', -): Promise { - const filepath = path.join(generatedFilesDir, file); - - if (skipCache) { - await fs.ensureDir(path.dirname(filepath)); - await fs.writeFile(filepath, content); - return; - } - - let lastHash = fileHash.get(filepath); - - // If file already exists but its not in runtime cache yet, - // we try to calculate the content hash and then compare - // This is to avoid unnecessary overwriting and we can reuse old file. - if (!lastHash && fs.existsSync(filepath)) { - const lastContent = await fs.readFile(filepath, 'utf8'); - lastHash = createHash('md5').update(lastContent).digest('hex'); - fileHash.set(filepath, lastHash); - } - - const currentHash = createHash('md5').update(content).digest('hex'); - - if (lastHash !== currentHash) { - await fs.ensureDir(path.dirname(filepath)); - await fs.writeFile(filepath, content); - fileHash.set(filepath, currentHash); - } -} - -const indexRE = /(^|.*\/)index\.(md|mdx|js|jsx|ts|tsx)$/i; -const extRE = /\.(md|mdx|js|jsx|ts|tsx)$/; - -/** - * Convert filepath to url path. - * Example: 'index.md' -> '/', 'foo/bar.js' -> '/foo/bar', - */ -export function fileToPath(file: string): string { - if (indexRE.test(file)) { - return file.replace(indexRE, '/$1'); - } - return `/${file.replace(extRE, '').replace(/\\/g, '/')}`; -} - -export function encodePath(userPath: string): string { - return userPath - .split('/') - .map((item) => encodeURIComponent(item)) - .join('/'); -} - -const chunkNameCache = new Map(); -/** - * Generate unique chunk name given a module path. - */ -export function genChunkName( - modulePath: string, - prefix?: string, - preferredName?: string, - shortId: boolean = process.env.NODE_ENV === 'production', -): string { - let chunkName: string | undefined = chunkNameCache.get(modulePath); - if (!chunkName) { - if (shortId) { - chunkName = simpleHash(modulePath, 8); - } else { - let str = modulePath; - if (preferredName) { - const shortHash = simpleHash(modulePath, 3); - str = `${preferredName}${shortHash}`; - } - const name = str === '/' ? 'index' : docuHash(str); - chunkName = prefix ? `${prefix}---${name}` : name; - } - chunkNameCache.set(modulePath, chunkName); - } - return chunkName; -} - -export function isValidPathname(str: string): boolean { - if (!str.startsWith('/')) { - return false; - } - try { - // weird, but is there a better way? - const parsedPathname = new URL(str, 'https://domain.com').pathname; - return parsedPathname === str || parsedPathname === encodeURI(str); - } catch (e) { - return false; - } -} - -// resolve pathname and fail fast if resolution fails -export function resolvePathname(to: string, from?: string): string { - return resolvePathnameUnsafe(to, from); -} -export function addLeadingSlash(str: string): string { - return str.startsWith('/') ? str : `/${str}`; -} - -export function addTrailingPathSeparator(str: string): string { - return str.endsWith(path.sep) - ? str - : // If this is Windows, we need to change the forward slash to backward - `${str.replace(/\/$/, '')}${path.sep}`; -} - -// TODO deduplicate: also present in @docusaurus/utils-common -export function addTrailingSlash(str: string): string { - return str.endsWith('/') ? str : `${str}/`; -} -export function removeTrailingSlash(str: string): string { - return removeSuffix(str, '/'); -} - -export function removeSuffix(str: string, suffix: string): string { - if (suffix === '') { - return str; // always returns "" otherwise! - } - return str.endsWith(suffix) ? str.slice(0, -suffix.length) : str; -} - -export function removePrefix(str: string, prefix: string): string { - return str.startsWith(prefix) ? str.slice(prefix.length) : str; -} - -export function getElementsAround( - array: T[], - aroundIndex: number, -): { - next: T | undefined; - previous: T | undefined; -} { - const min = 0; - const max = array.length - 1; - if (aroundIndex < min || aroundIndex > max) { - throw new Error( - `Valid "aroundIndex" for array (of size ${array.length}) are between ${min} and ${max}, but you provided ${aroundIndex}.`, - ); - } - const previous = aroundIndex === min ? undefined : array[aroundIndex - 1]; - const next = aroundIndex === max ? undefined : array[aroundIndex + 1]; - return {previous, next}; -} - -export function getPluginI18nPath({ - siteDir, - locale, - pluginName, - pluginId = DEFAULT_PLUGIN_ID, - subPaths = [], -}: { - siteDir: string; - locale: string; - pluginName: string; - pluginId?: string | undefined; - subPaths?: string[]; -}): string { - return path.join( - siteDir, - 'i18n', - // namespace first by locale: convenient to work in a single folder for a translator - locale, - // Make it convenient to use for single-instance - // ie: return "docs", not "docs-default" nor "docs/default" - `${pluginName}${pluginId === DEFAULT_PLUGIN_ID ? '' : `-${pluginId}`}`, - ...subPaths, - ); -} - -/** - * @param permalink The URL that the HTML file corresponds to, without base URL - * @param outDir Full path to the output directory - * @param trailingSlash The site config option. If provided, only one path will be read. - * @returns This returns a buffer, which you have to decode string yourself if - * needed. (Not always necessary since the output isn't for human consumption - * anyways, and most HTML manipulation libs accept buffers) - */ -export async function readOutputHTMLFile( - permalink: string, - outDir: string, - trailingSlash: boolean | undefined, -): Promise { - const withTrailingSlashPath = path.join(outDir, permalink, 'index.html'); - const withoutTrailingSlashPath = path.join(outDir, `${permalink}.html`); - if (trailingSlash) { - return fs.readFile(withTrailingSlashPath); - } else if (trailingSlash === false) { - return fs.readFile(withoutTrailingSlashPath); - } else { - const HTMLPath = await findAsyncSequential( - [withTrailingSlashPath, withoutTrailingSlashPath], - fs.pathExists, - ); - if (!HTMLPath) { - throw new Error( - `Expected output HTML file to be found at ${withTrailingSlashPath}`, - ); - } - return fs.readFile(HTMLPath); - } -} - -export async function mapAsyncSequential( - array: T[], - action: (t: T) => Promise, -): Promise { - const results: R[] = []; - // eslint-disable-next-line no-restricted-syntax - for (const t of array) { - const result = await action(t); - results.push(result); - } - return results; -} - -export async function findAsyncSequential( - array: T[], - predicate: (t: T) => Promise, -): Promise { - // eslint-disable-next-line no-restricted-syntax - for (const t of array) { - if (await predicate(t)) { - return t; - } - } - return undefined; -} - -export function reportMessage( - message: string, - reportingSeverity: ReportingSeverity, -): void { - switch (reportingSeverity) { - case 'ignore': - break; - case 'log': - logger.info(message); - break; - case 'warn': - logger.warn(message); - break; - case 'error': - logger.error(message); - break; - case 'throw': - throw new Error(message); - default: - throw new Error( - `Unexpected "reportingSeverity" value: ${reportingSeverity}.`, - ); - } -} - -export function mergeTranslations( - contents: TranslationFileContent[], -): TranslationFileContent { - return contents.reduce((acc, content) => ({...acc, ...content}), {}); -} - -// Useful to update all the messages of a translation file -// Used in tests to simulate translations -export function updateTranslationFileMessages( - translationFile: TranslationFile, - updateMessage: (message: string) => string, -): TranslationFile { - return { - ...translationFile, - content: mapValues(translationFile.content, (translation) => ({ - ...translation, - message: updateMessage(translation.message), - })), - }; -} +export { + NODE_MAJOR_VERSION, + NODE_MINOR_VERSION, + DEFAULT_BUILD_DIR_NAME, + DEFAULT_CONFIG_FILE_NAME, + BABEL_CONFIG_FILE_NAME, + GENERATED_FILES_DIR_NAME, + SRC_DIR_NAME, + DEFAULT_STATIC_DIR_NAME, + OUTPUT_STATIC_ASSETS_DIR_NAME, + THEME_PATH, + I18N_DIR_NAME, + CODE_TRANSLATIONS_FILE_NAME, + DEFAULT_PORT, + DEFAULT_PLUGIN_ID, + WEBPACK_URL_LOADER_LIMIT, +} from './constants'; +export {generate, readOutputHTMLFile} from './emitUtils'; +export { + getFileCommitDate, + FileNotTrackedError, + GitNotFoundError, +} from './gitUtils'; +export { + mergeTranslations, + updateTranslationFileMessages, + getPluginI18nPath, + localizePath, +} from './i18nUtils'; +export { + removeSuffix, + removePrefix, + mapAsyncSequential, + findAsyncSequential, + reportMessage, +} from './jsUtils'; +export { + normalizeUrl, + getEditUrl, + fileToPath, + encodePath, + isValidPathname, + resolvePathname, + addLeadingSlash, + addTrailingSlash, + removeTrailingSlash, + hasSSHProtocol, + buildHttpsUrl, + buildSshUrl, +} from './urlUtils'; +export { + type Tag, + type FrontMatterTag, + normalizeFrontMatterTags, + groupTaggedItems, +} from './tags'; +export { + parseMarkdownHeadingId, + createExcerpt, + parseFrontMatter, + parseMarkdownContentTitle, + parseMarkdownString, + writeMarkdownHeadingId, + type WriteHeadingIDOptions, +} from './markdownUtils'; +export { + type ContentPaths, + type BrokenMarkdownLink, + replaceMarkdownLinks, +} from './markdownLinks'; +export {type SluggerOptions, type Slugger, createSlugger} from './slugger'; +export { + isNameTooLong, + shortName, + posixPath, + toMessageRelativeFilePath, + aliasedSitePath, + escapePath, + addTrailingPathSeparator, +} from './pathUtils'; +export {md5Hash, simpleHash, docuHash} from './hashUtils'; +export { + Globby, + GlobExcludeDefault, + createMatcher, + createAbsoluteFilePathMatcher, +} from './globUtils'; +export {getFileLoaderUtils} from './webpackUtils'; +export { + getDataFilePath, + getDataFileData, + getContentPathList, + findFolderContainingFile, + getFolderContainingFile, +} from './dataFileUtils'; diff --git a/packages/docusaurus-utils/src/jsUtils.ts b/packages/docusaurus-utils/src/jsUtils.ts new file mode 100644 index 000000000000..1e2858fe620c --- /dev/null +++ b/packages/docusaurus-utils/src/jsUtils.ts @@ -0,0 +1,102 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import type {ReportingSeverity} from '@docusaurus/types'; +import logger from '@docusaurus/logger'; + +/** Removes a given string suffix from `str`. */ +export function removeSuffix(str: string, suffix: string): string { + if (suffix === '') { + // str.slice(0, 0) is "" + return str; + } + return str.endsWith(suffix) ? str.slice(0, -suffix.length) : str; +} + +/** Removes a given string prefix from `str`. */ +export function removePrefix(str: string, prefix: string): string { + return str.startsWith(prefix) ? str.slice(prefix.length) : str; +} + +/** + * `Array#map` for async operations where order matters. + * @param array The array to traverse. + * @param action An async action to be performed on every array item. Will be + * awaited before working on the next. + * @returns The list of results returned from every `action(item)` + */ +export async function mapAsyncSequential( + array: T[], + action: (t: T) => Promise, +): Promise { + const results: R[] = []; + for (const t of array) { + const result = await action(t); + results.push(result); + } + return results; +} + +/** + * `Array#find` for async operations where order matters. + * @param array The array to traverse. + * @param predicate An async predicate to be called on every array item. Should + * return a boolean indicating whether the currently element should be returned. + * @returns The function immediately returns the first item on which `predicate` + * returns `true`, or `undefined` if none matches the predicate. + */ +export async function findAsyncSequential( + array: T[], + predicate: (t: T) => Promise, +): Promise { + for (const t of array) { + if (await predicate(t)) { + return t; + } + } + return undefined; +} + +/** + * Takes a message and reports it according to the severity that the user wants. + * + * - `ignore`: completely no-op + * - `log`: uses the `INFO` log level + * - `warn`: uses the `WARN` log level + * - `error`: uses the `ERROR` log level + * - `throw`: aborts the process, throws the error. + * + * Since the logger doesn't have logging level filters yet, these severities + * mostly just differ by their colors. + * + * @throws In addition to throwing when `reportingSeverity === "throw"`, this + * function also throws if `reportingSeverity` is not one of the above. + */ +export function reportMessage( + message: string, + reportingSeverity: ReportingSeverity, +): void { + switch (reportingSeverity) { + case 'ignore': + break; + case 'log': + logger.info(message); + break; + case 'warn': + logger.warn(message); + break; + case 'error': + logger.error(message); + break; + case 'throw': + throw new Error(message); + default: + throw new Error( + `Unexpected "reportingSeverity" value: ${reportingSeverity}.`, + ); + } +} diff --git a/packages/docusaurus-utils/src/markdownLinks.ts b/packages/docusaurus-utils/src/markdownLinks.ts index 17925c201ed4..fe8353646cd1 100644 --- a/packages/docusaurus-utils/src/markdownLinks.ts +++ b/packages/docusaurus-utils/src/markdownLinks.ts @@ -6,41 +6,79 @@ */ import path from 'path'; +import {getContentPathList} from './dataFileUtils'; import {aliasedSitePath} from './pathUtils'; +/** + * Content plugins have a base path and a localized path to source content from. + * We will look into the localized path in priority. + */ export type ContentPaths = { + /** + * The absolute path to the base content directory, like `"/docs"`. + */ contentPath: string; + /** + * The absolute path to the localized content directory, like + * `"/i18n/zh-Hans/plugin-content-docs"`. + */ contentPathLocalized: string; }; +/** Data structure representing each broken Markdown link to be reported. */ export type BrokenMarkdownLink = { + /** Absolute path to the file containing this link. */ filePath: string; + /** + * This is generic because it may contain extra metadata like version name, + * which the reporter can provide for context. + */ contentPaths: T; + /** + * The content of the link, like `"./brokenFile.md"` + */ link: string; }; -export type ReplaceMarkdownLinksParams = { - siteDir: string; - fileString: string; - filePath: string; - contentPaths: T; - sourceToPermalink: Record; -}; - -export type ReplaceMarkdownLinksReturn = { - newContent: string; - brokenMarkdownLinks: BrokenMarkdownLink[]; -}; - +/** + * Takes a Markdown file and replaces relative file references with their URL + * counterparts, e.g. `[link](./intro.md)` => `[link](/docs/intro)`, preserving + * everything else. + * + * This method uses best effort to find a matching file. The file reference can + * be relative to the directory of the current file (most likely) or any of the + * content paths (so `/tutorials/intro.md` can be resolved as + * `/docs/tutorials/intro.md`). Links that contain the `http(s):` or + * `@site/` prefix will always be ignored. + */ export function replaceMarkdownLinks({ siteDir, fileString, filePath, contentPaths, sourceToPermalink, -}: ReplaceMarkdownLinksParams): ReplaceMarkdownLinksReturn { - const {contentPath, contentPathLocalized} = contentPaths; - +}: { + /** Absolute path to the site directory, used to resolve aliased paths. */ + siteDir: string; + /** The Markdown file content to be processed. */ + fileString: string; + /** Absolute path to the current file containing `fileString`. */ + filePath: string; + /** The content paths which the file reference may live in. */ + contentPaths: T; + /** + * A map from source paths to their URLs. Source paths are `@site` aliased. + */ + sourceToPermalink: {[aliasedPath: string]: string}; +}): { + /** + * The content with all Markdown file references replaced with their URLs. + * Unresolved links are left as-is. + */ + newContent: string; + /** The list of broken links, */ + brokenMarkdownLinks: BrokenMarkdownLink[]; +} { const brokenMarkdownLinks: BrokenMarkdownLink[] = []; // Replace internal markdown linking (except in fenced blocks). @@ -48,11 +86,13 @@ export function replaceMarkdownLinks({ let lastCodeFence = ''; const lines = fileString.split('\n').map((line) => { if (line.trim().startsWith('```')) { + const codeFence = line.trim().match(/^`+/)![0]!; if (!fencedBlock) { fencedBlock = true; - [lastCodeFence] = line.trim().match(/^`+/)!; - // If we are in a ````-fenced block, all ``` would be plain text instead of fences - } else if (line.trim().match(/^`+/)![0].length >= lastCodeFence.length) { + lastCodeFence = codeFence; + // If we are in a ````-fenced block, all ``` would be plain text instead + // of fences + } else if (codeFence.length >= lastCodeFence.length) { fencedBlock = false; } } @@ -62,19 +102,19 @@ export function replaceMarkdownLinks({ let modifiedLine = line; // Replace inline-style links or reference-style links e.g: - // This is [Document 1](doc1.md) -> we replace this doc1.md with correct link - // [doc1]: doc1.md -> we replace this doc1.md with correct link - const mdRegex = /(?:(?:\]\()|(?:\]:\s?))(?!https?)([^'")\]\s>]+\.mdx?)/g; + // This is [Document 1](doc1.md) + // [doc1]: doc1.md + const mdRegex = + /(?:\]\(|\]:\s*)(?!https?:\/\/|@site\/)(?[^'")\]\s>]+\.mdx?)/g; let mdMatch = mdRegex.exec(modifiedLine); while (mdMatch !== null) { // Replace it to correct html link. - const mdLink = mdMatch[1]; + const mdLink = mdMatch.groups!.filename!; const sourcesToTry = [ - path.resolve(path.dirname(filePath), decodeURIComponent(mdLink)), - `${contentPathLocalized}/${decodeURIComponent(mdLink)}`, - `${contentPath}/${decodeURIComponent(mdLink)}`, - ]; + path.dirname(filePath), + ...getContentPathList(contentPaths), + ].map((p) => path.join(p, decodeURIComponent(mdLink))); const aliasedSourceMatch = sourcesToTry .map((source) => aliasedSitePath(source, siteDir)) @@ -85,7 +125,8 @@ export function replaceMarkdownLinks({ : undefined; if (permalink) { - // MDX won't be happy if the permalink contains a space, we need to convert it to %20 + // MDX won't be happy if the permalink contains a space, we need to + // convert it to %20 const encodedPermalink = permalink .split('/') .map((part) => part.replace(/\s/g, '%20')) diff --git a/packages/docusaurus-utils/src/markdownParser.ts b/packages/docusaurus-utils/src/markdownParser.ts deleted file mode 100644 index 83da2fd4149b..000000000000 --- a/packages/docusaurus-utils/src/markdownParser.ts +++ /dev/null @@ -1,199 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import logger from '@docusaurus/logger'; -import matter from 'gray-matter'; - -// Input: ## Some heading {#some-heading} -// Output: {text: "## Some heading", id: "some-heading"} -export function parseMarkdownHeadingId(heading: string): { - text: string; - id?: string; -} { - const customHeadingIdRegex = /^(.*?)\s*\{#([\w-]+)\}$/; - const matches = customHeadingIdRegex.exec(heading); - if (matches) { - return { - text: matches[1], - id: matches[2], - }; - } else { - return {text: heading, id: undefined}; - } -} - -// Hacky way of stripping out import statements from the excerpt -// TODO: Find a better way to do so, possibly by compiling the Markdown content, -// stripping out HTML tags and obtaining the first line. -export function createExcerpt(fileString: string): string | undefined { - const fileLines = fileString - .trimLeft() - // Remove Markdown alternate title - .replace(/^[^\n]*\n[=]+/g, '') - .split('\n'); - let inCode = false; - let lastCodeFence = ''; - - /* eslint-disable no-continue */ - // eslint-disable-next-line no-restricted-syntax - for (const fileLine of fileLines) { - // Skip empty line. - if (!fileLine.trim()) { - continue; - } - - // Skip import/export declaration. - if (/^\s*?import\s.*(from.*)?;?|export\s.*{.*};?/.test(fileLine)) { - continue; - } - - // Skip code block line. - if (fileLine.trim().startsWith('```')) { - if (!inCode) { - inCode = true; - [lastCodeFence] = fileLine.trim().match(/^`+/)!; - // If we are in a ````-fenced block, all ``` would be plain text instead of fences - } else if ( - fileLine.trim().match(/^`+/)![0].length >= lastCodeFence.length - ) { - inCode = false; - } - continue; - } else if (inCode) { - continue; - } - - const cleanedLine = fileLine - // Remove HTML tags. - .replace(/<[^>]*>/g, '') - // Remove Title headers - .replace(/^#\s*([^#]*)\s*#?/gm, '') - // Remove Markdown + ATX-style headers - .replace(/^#{1,6}\s*([^#]*)\s*(#{1,6})?/gm, '$1') - // Remove emphasis and strikethroughs. - .replace(/([*_~]{1,3})(\S.*?\S{0,1})\1/g, '$2') - // Remove images. - .replace(/!\[(.*?)\][[(].*?[\])]/g, '$1') - // Remove footnotes. - .replace(/\[\^.+?\](: .*?$)?/g, '') - // Remove inline links. - .replace(/\[(.*?)\][[(].*?[\])]/g, '$1') - // Remove inline code. - .replace(/`(.+?)`/g, '$1') - // Remove blockquotes. - .replace(/^\s{0,3}>\s?/g, '') - // Remove admonition definition. - .replace(/(:{3}.*)/, '') - // Remove Emoji names within colons include preceding whitespace. - .replace(/\s?(:(::|[^:\n])+:)/g, '') - // Remove custom Markdown heading id. - .replace(/{#*[\w-]+}/, '') - .trim(); - - if (cleanedLine) { - return cleanedLine; - } - } - - return undefined; -} - -export function parseFrontMatter(markdownFileContent: string): { - frontMatter: Record; - content: string; -} { - const {data, content} = matter(markdownFileContent); - return { - frontMatter: data, - content: content.trim(), - }; -} - -// Try to convert markdown heading as text -// Does not need to be perfect, it is only used as a fallback when frontMatter.title is not provided -// For now, we just unwrap possible inline code blocks (# `config.js`) -function toTextContentTitle(contentTitle: string): string { - if (contentTitle.startsWith('`') && contentTitle.endsWith('`')) { - return contentTitle.substring(1, contentTitle.length - 1); - } - return contentTitle; -} - -export function parseMarkdownContentTitle( - contentUntrimmed: string, - options?: {removeContentTitle?: boolean}, -): {content: string; contentTitle: string | undefined} { - const removeContentTitleOption = options?.removeContentTitle ?? false; - - const content = contentUntrimmed.trim(); - - const IMPORT_STATEMENT = - /import\s+(([\w*{}\s\n,]+)from\s+)?["'\s]([@\w/_.-]+)["'\s];?|\n/.source; - const REGULAR_TITLE = - /(?#\s*(?[^#\n{]*)+[ \t]*(?<suffix>({#*[\w-]+})|#)?\n*?)/ - .source; - const ALTERNATE_TITLE = /(?<pattern>\s*(?<title>[^\n]*)\s*\n[=]+)/.source; - - const regularTitleMatch = new RegExp( - `^(?:${IMPORT_STATEMENT})*?${REGULAR_TITLE}`, - 'g', - ).exec(content); - const alternateTitleMatch = new RegExp( - `^(?:${IMPORT_STATEMENT})*?${ALTERNATE_TITLE}`, - 'g', - ).exec(content); - - const titleMatch = regularTitleMatch ?? alternateTitleMatch; - const {pattern, title} = titleMatch?.groups ?? {}; - - if (!pattern || !title) { - return {content, contentTitle: undefined}; - } else { - const newContent = removeContentTitleOption - ? content.replace(pattern, '') - : content; - return { - content: newContent.trim(), - contentTitle: toTextContentTitle(title.trim()).trim(), - }; - } -} - -type ParsedMarkdown = { - frontMatter: Record<string, unknown>; - content: string; - contentTitle: string | undefined; - excerpt: string | undefined; -}; - -export function parseMarkdownString( - markdownFileContent: string, - options?: {removeContentTitle?: boolean}, -): ParsedMarkdown { - try { - const {frontMatter, content: contentWithoutFrontMatter} = - parseFrontMatter(markdownFileContent); - - const {content, contentTitle} = parseMarkdownContentTitle( - contentWithoutFrontMatter, - options, - ); - - const excerpt = createExcerpt(content); - - return { - frontMatter, - content, - contentTitle, - excerpt, - }; - } catch (e) { - logger.error(`Error while parsing Markdown front matter. -This can happen if you use special characters in front matter values (try using double quotes around that value).`); - throw e; - } -} diff --git a/packages/docusaurus-utils/src/markdownUtils.ts b/packages/docusaurus-utils/src/markdownUtils.ts new file mode 100644 index 000000000000..a902c6f58fa0 --- /dev/null +++ b/packages/docusaurus-utils/src/markdownUtils.ts @@ -0,0 +1,354 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import logger from '@docusaurus/logger'; +import matter from 'gray-matter'; +import {createSlugger, type Slugger, type SluggerOptions} from './slugger'; + +// Some utilities for parsing Markdown content. These things are only used on +// server-side when we infer metadata like `title` and `description` from the +// content. Most parsing is still done in MDX through the mdx-loader. + +/** + * Parses custom ID from a heading. The ID must be composed of letters, + * underscores, and dashes only. + * + * @param heading e.g. `## Some heading {#some-heading}` where the last + * character must be `}` for the ID to be recognized + */ +export function parseMarkdownHeadingId(heading: string): { + /** + * The heading content sans the ID part, right-trimmed. e.g. `## Some heading` + */ + text: string; + /** The heading ID. e.g. `some-heading` */ + id?: string; +} { + const customHeadingIdRegex = /\s*\{#(?<id>[\w-]+)\}$/; + const matches = customHeadingIdRegex.exec(heading); + if (matches) { + return { + text: heading.replace(matches[0]!, ''), + id: matches.groups!.id!, + }; + } + return {text: heading, id: undefined}; +} + +// TODO: Find a better way to do so, possibly by compiling the Markdown content, +// stripping out HTML tags and obtaining the first line. +/** + * Creates an excerpt of a Markdown file. This function will: + * + * - Ignore h1 headings (setext or atx) + * - Ignore import/export + * - Ignore code blocks + * + * And for the first contentful line, it will strip away most Markdown + * syntax, including HTML tags, emphasis, links (keeping the text), etc. + */ +export function createExcerpt(fileString: string): string | undefined { + const fileLines = fileString + .trimStart() + // Remove Markdown alternate title + .replace(/^[^\n]*\n[=]+/g, '') + .split('\n'); + let inCode = false; + let inImport = false; + let lastCodeFence = ''; + + for (const fileLine of fileLines) { + if (fileLine === '' && inImport) { + inImport = false; + } + // Skip empty line. + if (!fileLine.trim()) { + continue; + } + + // Skip import/export declaration. + if ((/^(?:import|export)\s.*/.test(fileLine) || inImport) && !inCode) { + inImport = true; + continue; + } + + // Skip code block line. + if (fileLine.trim().startsWith('```')) { + const codeFence = fileLine.trim().match(/^`+/)![0]!; + if (!inCode) { + inCode = true; + lastCodeFence = codeFence; + // If we are in a ````-fenced block, all ``` would be plain text instead + // of fences + } else if (codeFence.length >= lastCodeFence.length) { + inCode = false; + } + continue; + } else if (inCode) { + continue; + } + + const cleanedLine = fileLine + // Remove HTML tags. + .replace(/<[^>]*>/g, '') + // Remove Title headers + .replace(/^#[^#]+#?/gm, '') + // Remove Markdown + ATX-style headers + .replace(/^#{1,6}\s*(?<text>[^#]*)\s*#{0,6}/gm, '$1') + // Remove emphasis. + .replace(/(?<opening>[*_]{1,3})(?<text>.*?)\1/g, '$2') + // Remove strikethroughs. + .replace(/~~(?<text>\S.*\S)~~/g, '$1') + // Remove images. + .replace(/!\[(?<alt>.*?)\][[(].*?[\])]/g, '$1') + // Remove footnotes. + .replace(/\[\^.+?\](?:: .*$)?/g, '') + // Remove inline links. + .replace(/\[(?<alt>.*?)\][[(].*?[\])]/g, '$1') + // Remove inline code. + .replace(/`(?<text>.+?)`/g, '$1') + // Remove blockquotes. + .replace(/^\s{0,3}>\s?/g, '') + // Remove admonition definition. + .replace(/:::.*/, '') + // Remove Emoji names within colons include preceding whitespace. + .replace(/\s?:(?:::|[^:\n])+:/g, '') + // Remove custom Markdown heading id. + .replace(/\{#*[\w-]+\}/, '') + .trim(); + + if (cleanedLine) { + return cleanedLine; + } + } + + return undefined; +} + +/** + * Takes a raw Markdown file content, and parses the front matter using + * gray-matter. Worth noting that gray-matter accepts TOML and other markup + * languages as well. + * + * @throws Throws when gray-matter throws. e.g.: + * ```md + * --- + * foo: : bar + * --- + * ``` + */ +export function parseFrontMatter(markdownFileContent: string): { + /** Front matter as parsed by gray-matter. */ + frontMatter: {[key: string]: unknown}; + /** The remaining content, trimmed. */ + content: string; +} { + const {data, content} = matter(markdownFileContent); + return { + frontMatter: data, + content: content.trim(), + }; +} + +function toTextContentTitle(contentTitle: string): string { + if (contentTitle.startsWith('`') && contentTitle.endsWith('`')) { + return contentTitle.substring(1, contentTitle.length - 1); + } + return contentTitle; +} + +type ParseMarkdownContentTitleOptions = { + /** + * If `true`, the matching title will be removed from the returned content. + * We can promise that at least one empty line will be left between the + * content before and after, but you shouldn't make too much assumption + * about what's left. + */ + removeContentTitle?: boolean; +}; + +/** + * Takes the raw Markdown content, without front matter, and tries to find an h1 + * title (setext or atx) to be used as metadata. + * + * It only searches until the first contentful paragraph, ignoring import/export + * declarations. + * + * It will try to convert markdown to reasonable text, but won't be best effort, + * since it's only used as a fallback when `frontMatter.title` is not provided. + * For now, we just unwrap inline code (``# `config.js` `` => `config.js`). + */ +export function parseMarkdownContentTitle( + contentUntrimmed: string, + options?: ParseMarkdownContentTitleOptions, +): { + /** The content, optionally without the content title. */ + content: string; + /** The title, trimmed and without the `#`. */ + contentTitle: string | undefined; +} { + const removeContentTitleOption = options?.removeContentTitle ?? false; + + const content = contentUntrimmed.trim(); + // We only need to detect import statements that will be parsed by MDX as + // `import` nodes, as broken syntax can't render anyways. That means any block + // that has `import` at the very beginning and surrounded by empty lines. + const contentWithoutImport = content + .replace(/^(?:import\s(?:.|\r?\n(?!\r?\n))*(?:\r?\n){2,})*/, '') + .trim(); + + const regularTitleMatch = /^#[ \t]+(?<title>[^ \t].*)(?:\r?\n|$)/.exec( + contentWithoutImport, + ); + const alternateTitleMatch = /^(?<title>.*)\r?\n=+(?:\r?\n|$)/.exec( + contentWithoutImport, + ); + + const titleMatch = regularTitleMatch ?? alternateTitleMatch; + if (!titleMatch) { + return {content, contentTitle: undefined}; + } + const newContent = removeContentTitleOption + ? content.replace(titleMatch[0]!, '') + : content; + if (regularTitleMatch) { + return { + content: newContent.trim(), + contentTitle: toTextContentTitle( + regularTitleMatch + .groups!.title!.trim() + .replace(/\s*(?:\{#*[\w-]+\}|#+)$/, ''), + ).trim(), + }; + } + return { + content: newContent.trim(), + contentTitle: toTextContentTitle( + alternateTitleMatch!.groups!.title!.trim().replace(/\s*=+$/, ''), + ).trim(), + }; +} + +/** + * Makes a full-round parse. + * + * @throws Throws when `parseFrontMatter` throws, usually because of invalid + * syntax. + */ +export function parseMarkdownString( + markdownFileContent: string, + options?: ParseMarkdownContentTitleOptions, +): { + /** @see {@link parseFrontMatter} */ + frontMatter: {[key: string]: unknown}; + /** @see {@link parseMarkdownContentTitle} */ + contentTitle: string | undefined; + /** @see {@link createExcerpt} */ + excerpt: string | undefined; + /** + * Content without front matter and (optionally) without title, depending on + * the `removeContentTitle` option. + */ + content: string; +} { + try { + const {frontMatter, content: contentWithoutFrontMatter} = + parseFrontMatter(markdownFileContent); + + const {content, contentTitle} = parseMarkdownContentTitle( + contentWithoutFrontMatter, + options, + ); + + const excerpt = createExcerpt(content); + + return { + frontMatter, + content, + contentTitle, + excerpt, + }; + } catch (err) { + logger.error(`Error while parsing Markdown front matter. +This can happen if you use special characters in front matter values (try using double quotes around that value).`); + throw err; + } +} + +function unwrapMarkdownLinks(line: string): string { + return line.replace(/\[(?<alt>[^\]]+)\]\([^)]+\)/g, (match, p1) => p1); +} + +function addHeadingId( + line: string, + slugger: Slugger, + maintainCase: boolean, +): string { + let headingLevel = 0; + while (line.charAt(headingLevel) === '#') { + headingLevel += 1; + } + + const headingText = line.slice(headingLevel).trimEnd(); + const headingHashes = line.slice(0, headingLevel); + const slug = slugger.slug(unwrapMarkdownLinks(headingText).trim(), { + maintainCase, + }); + + return `${headingHashes}${headingText} {#${slug}}`; +} + +export type WriteHeadingIDOptions = SluggerOptions & { + /** Overwrite existing heading IDs. */ + overwrite?: boolean; +}; + +/** + * Takes Markdown content, returns new content with heading IDs written. + * Respects existing IDs (unless `overwrite=true`) and never generates colliding + * IDs (through the slugger). + */ +export function writeMarkdownHeadingId( + content: string, + options: WriteHeadingIDOptions = {maintainCase: false, overwrite: false}, +): string { + const {maintainCase = false, overwrite = false} = options; + const lines = content.split('\n'); + const slugger = createSlugger(); + + // If we can't overwrite existing slugs, make sure other headings don't + // generate colliding slugs by first marking these slugs as occupied + if (!overwrite) { + lines.forEach((line) => { + const parsedHeading = parseMarkdownHeadingId(line); + if (parsedHeading.id) { + slugger.slug(parsedHeading.id); + } + }); + } + + let inCode = false; + return lines + .map((line) => { + if (line.startsWith('```')) { + inCode = !inCode; + return line; + } + // Ignore h1 headings, as we don't create anchor links for those + if (inCode || !line.startsWith('##')) { + return line; + } + const parsedHeading = parseMarkdownHeadingId(line); + + // Do not process if id is already there + if (parsedHeading.id && !overwrite) { + return line; + } + return addHeadingId(parsedHeading.text, slugger, maintainCase); + }) + .join('\n'); +} diff --git a/packages/docusaurus-utils/src/pathUtils.ts b/packages/docusaurus-utils/src/pathUtils.ts index 680af93c04b7..f8380ec87d15 100644 --- a/packages/docusaurus-utils/src/pathUtils.ts +++ b/packages/docusaurus-utils/src/pathUtils.ts @@ -5,11 +5,11 @@ * LICENSE file in the root directory of this source tree. */ -// Based on https://github.com/gatsbyjs/gatsby/pull/21518/files - import path from 'path'; -// MacOS (APFS) and Windows (NTFS) filename length limit = 255 chars, Others = 255 bytes +// Based on https://github.com/gatsbyjs/gatsby/pull/21518/files +// MacOS (APFS) and Windows (NTFS) filename length limit = 255 chars, +// Others = 255 bytes const MAX_PATH_SEGMENT_CHARS = 255; const MAX_PATH_SEGMENT_BYTES = 255; // Space for appending things to the string like file extensions and so on @@ -19,12 +19,12 @@ const isMacOs = () => process.platform === 'darwin'; const isWindows = () => process.platform === 'win32'; export const isNameTooLong = (str: string): boolean => - // This is actually not entirely correct: we can't assume FS from OS. But good enough? + // Not entirely correct: we can't assume FS from OS. But good enough? isMacOs() || isWindows() ? str.length + SPACE_FOR_APPENDING > MAX_PATH_SEGMENT_CHARS // MacOS (APFS) and Windows (NTFS) filename length limit (255 chars) : Buffer.from(str).length + SPACE_FOR_APPENDING > MAX_PATH_SEGMENT_BYTES; // Other (255 bytes) -export const shortName = (str: string): string => { +export function shortName(str: string): string { if (isMacOs() || isWindows()) { const overflowingChars = str.length - MAX_PATH_SEGMENT_CHARS; return str.slice( @@ -41,7 +41,7 @@ export const shortName = (str: string): string => { Buffer.byteLength(strBuffer) - overflowingBytes - SPACE_FOR_APPENDING - 1, ) .toString(); -}; +} /** * Convert Windows backslash paths to posix style paths. @@ -56,7 +56,8 @@ export const shortName = (str: string): string => { export function posixPath(str: string): string { const isExtendedLengthPath = /^\\\\\?\\/.test(str); - // Forward slashes are only valid Windows paths when they don't contain non-ascii characters. + // Forward slashes are only valid Windows paths when they don't contain non- + // ascii characters. // eslint-disable-next-line no-control-regex const hasNonAscii = /[^\u0000-\u0080]+/.test(str); @@ -66,13 +67,18 @@ export function posixPath(str: string): string { return str.replace(/\\/g, '/'); } -// When you want to display a path in a message/warning/error, -// it's more convenient to: -// - make it relative to cwd() -// - convert to posix (ie not using windows \ path separator) -// This way, Jest tests can run more reliably on any computer/CI -// on both Unix/Windows -// For Windows users this is not perfect (as they see / instead of \) but it's probably good enough +/** + * When you want to display a path in a message/warning/error, it's more + * convenient to: + * + * - make it relative to `cwd()` + * - convert to posix (ie not using windows \ path separator) + * + * This way, Jest tests can run more reliably on any computer/CI on both + * Unix/Windows + * For Windows users this is not perfect (as they see / instead of \) but it's + * probably good enough + */ export function toMessageRelativeFilePath(filePath: string): string { return posixPath(path.relative(process.cwd(), filePath)); } @@ -92,7 +98,8 @@ export function aliasedSitePath(filePath: string, siteDir: string): string { /** * When you have a path like C:\X\Y * It is not safe to use directly when generating code - * For example, this would fail due to unescaped \: `<img src={require('${filePath}')} />` + * For example, this would fail due to unescaped \: + * `<img src={require('${filePath}')} />` * But this would work: `<img src={require('${escapePath(filePath)}')} />` * * posixPath can't be used in all cases, because forward slashes are only valid @@ -105,3 +112,10 @@ export function escapePath(str: string): string { // Remove the " around the json string; return escaped.substring(1, escaped.length - 1); } + +export function addTrailingPathSeparator(str: string): string { + return str.endsWith(path.sep) + ? str + : // If this is Windows, we need to change the forward slash to backward + `${str.replace(/\/$/, '')}${path.sep}`; +} diff --git a/packages/docusaurus-utils/src/slugger.ts b/packages/docusaurus-utils/src/slugger.ts index f224549a6b68..5edba19b40e5 100644 --- a/packages/docusaurus-utils/src/slugger.ts +++ b/packages/docusaurus-utils/src/slugger.ts @@ -10,12 +10,24 @@ import GithubSlugger from 'github-slugger'; // We create our own abstraction on top of the lib: // - unify usage everywhere in the codebase // - ability to add extra options -export type SluggerOptions = {maintainCase?: boolean}; +export type SluggerOptions = { + /** Keep the headings' casing, otherwise make all lowercase. */ + maintainCase?: boolean; +}; export type Slugger = { + /** + * Takes a Markdown heading like "Josh Cena" and sluggifies it according to + * GitHub semantics (in this case `josh-cena`). Stateful, because if you try + * to sluggify "Josh Cena" again it would return `josh-cena-1`. + */ slug: (value: string, options?: SluggerOptions) => string; }; +/** + * A thin wrapper around github-slugger. This is a factory function that returns + * a stateful Slugger object. + */ export function createSlugger(): Slugger { const githubSlugger = new GithubSlugger(); return { diff --git a/packages/docusaurus-utils/src/tags.ts b/packages/docusaurus-utils/src/tags.ts index 92f85b8941fe..fa5eb4eebd1e 100644 --- a/packages/docusaurus-utils/src/tags.ts +++ b/packages/docusaurus-utils/src/tags.ts @@ -5,32 +5,33 @@ * LICENSE file in the root directory of this source tree. */ -import {kebabCase, uniq, uniqBy} from 'lodash'; +import _ from 'lodash'; import {normalizeUrl} from './urlUtils'; export type Tag = { label: string; + /** Permalink to this tag's page, without the `/tags/` base path. */ permalink: string; }; export type FrontMatterTag = string | Tag; -export function normalizeFrontMatterTag( +function normalizeFrontMatterTag( tagsPath: string, frontMatterTag: FrontMatterTag, ): Tag { function toTagObject(tagString: string): Tag { return { label: tagString, - permalink: kebabCase(tagString), + permalink: _.kebabCase(tagString), }; } // TODO maybe make ensure the permalink is valid url path? function normalizeTagPermalink(permalink: string): string { - // note: we always apply tagsPath on purpose - // for versioned docs, v1/doc.md and v2/doc.md tags with custom permalinks don't lead to the same created page - // tagsPath is different for each doc version + // note: we always apply tagsPath on purpose. For versioned docs, v1/doc.md + // and v2/doc.md tags with custom permalinks don't lead to the same created + // page. tagsPath is different for each doc version return normalizeUrl([tagsPath, permalink]); } @@ -45,56 +46,71 @@ export function normalizeFrontMatterTag( }; } +/** + * Takes tag objects as they are defined in front matter, and normalizes each + * into a standard tag object. The permalink is created by appending the + * sluggified label to `tagsPath`. Front matter tags already containing + * permalinks would still have `tagsPath` prepended. + * + * The result will always be unique by permalinks. The behavior with colliding + * permalinks is undetermined. + */ export function normalizeFrontMatterTags( + /** Base path to append the tag permalinks to. */ tagsPath: string, + /** Can be `undefined`, so that we can directly pipe in `frontMatter.tags`. */ frontMatterTags: FrontMatterTag[] | undefined = [], ): Tag[] { const tags = frontMatterTags.map((tag) => normalizeFrontMatterTag(tagsPath, tag), ); - return uniqBy(tags, (tag) => tag.permalink); + return _.uniqBy(tags, (tag) => tag.permalink); } -export type TaggedItemGroup<Item> = { +type TaggedItemGroup<Item> = { tag: Tag; items: Item[]; }; -// Permits to group docs/blogPosts by tag (provided by FrontMatter) -// Note: groups are indexed by permalink, because routes must be unique in the end -// Labels may vary on 2 md files but they are normalized. -// Docs with label='some label' and label='some-label' should end-up in the same group/page in the end -// We can't create 2 routes /some-label because one would override the other +/** + * Permits to group docs/blog posts by tag (provided by front matter). + * + * @returns a map from tag permalink to the items and other relevant tag data. + * The record is indexed by permalink, because routes must be unique in the end. + * Labels may vary on 2 MD files but they are normalized. Docs with + * label='some label' and label='some-label' should end up in the same page. + */ export function groupTaggedItems<Item>( - items: Item[], - getItemTags: (item: Item) => Tag[], -): Record<string, TaggedItemGroup<Item>> { - const result: Record<string, TaggedItemGroup<Item>> = {}; - - function handleItemTag(item: Item, tag: Tag) { - // Init missing tag groups - // TODO: it's not really clear what should be the behavior if 2 items have the same tag but the permalink is different for each - // For now, the first tag found wins - result[tag.permalink] = result[tag.permalink] ?? { - tag, - items: [], - }; - - // Add item to group - result[tag.permalink].items.push(item); - } + items: readonly Item[], + /** + * A callback telling me how to get the tags list of the current item. Usually + * simply getting it from some metadata of the current item. + */ + getItemTags: (item: Item) => readonly Tag[], +): {[permalink: string]: TaggedItemGroup<Item>} { + const result: {[permalink: string]: TaggedItemGroup<Item>} = {}; items.forEach((item) => { getItemTags(item).forEach((tag) => { - handleItemTag(item, tag); + // Init missing tag groups + // TODO: it's not really clear what should be the behavior if 2 tags have + // the same permalink but the label is different for each + // For now, the first tag found wins + result[tag.permalink] ??= { + tag, + items: [], + }; + + // Add item to group + result[tag.permalink]!.items.push(item); }); }); // If user add twice the same tag to a md doc (weird but possible), // we don't want the item to appear twice in the list... Object.values(result).forEach((group) => { - group.items = uniq(group.items); + group.items = _.uniq(group.items); }); return result; diff --git a/packages/docusaurus-utils/src/urlUtils.ts b/packages/docusaurus-utils/src/urlUtils.ts index ec5e3ead44b2..1c6f6f5c8591 100644 --- a/packages/docusaurus-utils/src/urlUtils.ts +++ b/packages/docusaurus-utils/src/urlUtils.ts @@ -5,6 +5,21 @@ * LICENSE file in the root directory of this source tree. */ +import {removeSuffix} from './jsUtils'; +import resolvePathnameUnsafe from 'resolve-pathname'; + +/** + * Much like `path.join`, but much better. Takes an array of URL segments, and + * joins them into a reasonable URL. + * + * - `["file:", "/home", "/user/", "website"]` => `file:///home/user/website` + * - `["file://", "home", "/user/", "website"]` => `file://home/user/website` (relative!) + * - Remove trailing slash before parameters or hash. + * - Replace `?` in query parameters with `&`. + * - Dedupe forward slashes in the entire path, avoiding protocol slashes. + * + * @throws {TypeError} If any of the URL segment is not a string, this throws. + */ export function normalizeUrl(rawUrls: string[]): string { const urls = [...rawUrls]; const resultArray = []; @@ -12,11 +27,19 @@ export function normalizeUrl(rawUrls: string[]): string { let hasStartingSlash = false; let hasEndingSlash = false; + const isNonEmptyArray = (arr: string[]): arr is [string, ...string[]] => + arr.length > 0; + + if (!isNonEmptyArray(urls)) { + return ''; + } + // If the first part is a plain protocol, we combine it with the next part. if (urls[0].match(/^[^/:]+:\/*$/) && urls.length > 1) { - const first = urls.shift(); - if (first!.startsWith('file:') && urls[0].startsWith('/')) { - // Force a double slash here, else we lose the information that the next segment is an absolute path + const first = urls.shift()!; + if (first.startsWith('file:') && urls[0].startsWith('/')) { + // Force a double slash here, else we lose the information that the next + // segment is an absolute path urls[0] = `${first}//${urls[0]}`; } else { urls[0] = first + urls[0]; @@ -26,10 +49,9 @@ export function normalizeUrl(rawUrls: string[]): string { // There must be two or three slashes in the file protocol, // two slashes in anything else. const replacement = urls[0].match(/^file:\/\/\//) ? '$1:///' : '$1://'; - urls[0] = urls[0].replace(/^([^/:]+):\/*/, replacement); + urls[0] = urls[0].replace(/^(?<protocol>[^/:]+):\/*/, replacement); - // eslint-disable-next-line - for (let i = 0; i < urls.length; i++) { + for (let i = 0; i < urls.length; i += 1) { let component = urls[i]; if (typeof component !== 'string') { @@ -40,7 +62,6 @@ export function normalizeUrl(rawUrls: string[]): string { if (i === urls.length - 1 && hasEndingSlash) { resultArray.push('/'); } - // eslint-disable-next-line continue; } @@ -48,16 +69,17 @@ export function normalizeUrl(rawUrls: string[]): string { if (i > 0) { // Removing the starting slashes for each component but the first. component = component.replace( - /^[/]+/, - // Special case where the first element of rawUrls is empty ["", "/hello"] => /hello + /^\/+/, + // Special case where the first element of rawUrls is empty + // ["", "/hello"] => /hello component[0] === '/' && !hasStartingSlash ? '/' : '', ); } hasEndingSlash = component[component.length - 1] === '/'; - // Removing the ending slashes for each component but the last. - // For the last component we will combine multiple slashes to a single one. - component = component.replace(/[/]+$/, i < urls.length - 1 ? '' : '/'); + // Removing the ending slashes for each component but the last. For the + // last component we will combine multiple slashes to a single one. + component = component.replace(/\/+$/, i < urls.length - 1 ? '' : '/'); } hasStartingSlash = true; @@ -65,18 +87,18 @@ export function normalizeUrl(rawUrls: string[]): string { } let str = resultArray.join('/'); - // Each input component is now separated by a single slash - // except the possible first plain protocol part. + // Each input component is now separated by a single slash except the possible + // first plain protocol part. // Remove trailing slash before parameters or hash. - str = str.replace(/\/(\?|&|#[^!])/g, '$1'); + str = str.replace(/\/(?<search>\?|&|#[^!])/g, '$1'); // Replace ? in parameters with &. const parts = str.split('?'); str = parts.shift() + (parts.length > 0 ? '?' : '') + parts.join('&'); // Dedupe forward slashes in the entire path, avoiding protocol slashes. - str = str.replace(/([^:/]\/)\/+/g, '$1'); + str = str.replace(/(?<textBefore>[^:/]\/)\/+/g, '$1'); // Dedupe forward slashes at the beginning of the path. str = str.replace(/^\/+/g, '/'); @@ -84,6 +106,11 @@ export function normalizeUrl(rawUrls: string[]): string { return str; } +/** + * Takes a file's path, relative to its content folder, and computes its edit + * URL. If `editUrl` is `undefined`, this returns `undefined`, as is the case + * when the user doesn't want an edit URL in her config. + */ export function getEditUrl( fileRelativePath: string, editUrl?: string, @@ -93,3 +120,116 @@ export function getEditUrl( normalizeUrl([editUrl, fileRelativePath.replace(/\\/g, '/')]) : undefined; } + +/** + * Converts file path to a reasonable URL path, e.g. `'index.md'` -> `'/'`, + * `'foo/bar.js'` -> `'/foo/bar'` + */ +export function fileToPath(file: string): string { + const indexRE = /(?<dirname>^|.*\/)index\.(?:mdx?|jsx?|tsx?)$/i; + const extRE = /\.(?:mdx?|jsx?|tsx?)$/; + + if (indexRE.test(file)) { + return file.replace(indexRE, '/$1'); + } + return `/${file.replace(extRE, '').replace(/\\/g, '/')}`; +} + +/** + * Similar to `encodeURI`, but uses `encodeURIComponent` and assumes there's no + * query. + * + * `encodeURI("/question?/answer")` => `"/question?/answer#section"`; + * `encodePath("/question?/answer#section")` => `"/question%3F/answer%23foo"` + */ +export function encodePath(userPath: string): string { + return userPath + .split('/') + .map((item) => encodeURIComponent(item)) + .join('/'); +} + +/** + * Whether `str` is a valid pathname. It must be absolute, and not contain + * special characters. + */ +export function isValidPathname(str: string): boolean { + if (!str.startsWith('/')) { + return false; + } + try { + // weird, but is there a better way? + const parsedPathname = new URL(str, 'https://domain.com').pathname; + return parsedPathname === str || parsedPathname === encodeURI(str); + } catch { + return false; + } +} + +/** + * Resolve pathnames and fail-fast if resolution fails. Uses standard URL + * semantics (provided by `resolve-pathname` which is used internally by React + * router) + */ +export function resolvePathname(to: string, from?: string): string { + return resolvePathnameUnsafe(to, from); +} +/** Appends a leading slash to `str`, if one doesn't exist. */ +export function addLeadingSlash(str: string): string { + return str.startsWith('/') ? str : `/${str}`; +} + +// TODO deduplicate: also present in @docusaurus/utils-common +/** Appends a trailing slash to `str`, if one doesn't exist. */ +export function addTrailingSlash(str: string): string { + return str.endsWith('/') ? str : `${str}/`; +} + +/** Removes the trailing slash from `str`. */ +export function removeTrailingSlash(str: string): string { + return removeSuffix(str, '/'); +} + +/** Constructs an SSH URL that can be used to push to GitHub. */ +export function buildSshUrl( + githubHost: string, + organizationName: string, + projectName: string, + githubPort?: string, +): string { + if (githubPort) { + return `ssh://git@${githubHost}:${githubPort}/${organizationName}/${projectName}.git`; + } + return `git@${githubHost}:${organizationName}/${projectName}.git`; +} + +/** Constructs an HTTP URL that can be used to push to GitHub. */ +export function buildHttpsUrl( + gitCredentials: string, + githubHost: string, + organizationName: string, + projectName: string, + githubPort?: string, +): string { + if (githubPort) { + return `https://${gitCredentials}@${githubHost}:${githubPort}/${organizationName}/${projectName}.git`; + } + return `https://${gitCredentials}@${githubHost}/${organizationName}/${projectName}.git`; +} + +/** + * Whether the current URL is an SSH protocol. In addition to looking for + * `ssh:`, it will also allow protocol-less URLs like + * `git@github.com:facebook/docusaurus.git`. + */ +export function hasSSHProtocol(sourceRepoUrl: string): boolean { + try { + if (new URL(sourceRepoUrl).protocol === 'ssh:') { + return true; + } + return false; + } catch { + // Fails when there isn't a protocol + return /^(?:[\w-]+@)?[\w.-]+:[\w./-]+/.test(sourceRepoUrl); + } +} diff --git a/packages/docusaurus-utils/src/webpackUtils.ts b/packages/docusaurus-utils/src/webpackUtils.ts index ea07e9668177..608e384d50d0 100644 --- a/packages/docusaurus-utils/src/webpackUtils.ts +++ b/packages/docusaurus-utils/src/webpackUtils.ts @@ -31,14 +31,23 @@ type FileLoaderUtils = { }; }; -// Inspired by https://github.com/gatsbyjs/gatsby/blob/8e6e021014da310b9cc7d02e58c9b3efe938c665/packages/gatsby/src/utils/webpack-utils.ts#L447 +/** + * Returns unified loader configurations to be used for various file types. + * + * Inspired by https://github.com/gatsbyjs/gatsby/blob/8e6e021014da310b9cc7d02e58c9b3efe938c665/packages/gatsby/src/utils/webpack-utils.ts#L447 + */ export function getFileLoaderUtils(): FileLoaderUtils { - // files/images < urlLoaderLimit will be inlined as base64 strings directly in the html + // files/images < urlLoaderLimit will be inlined as base64 strings directly in + // the html const urlLoaderLimit = WEBPACK_URL_LOADER_LIMIT; // defines the path/pattern of the assets handled by webpack const fileLoaderFileName = (folder: AssetFolder) => - `${OUTPUT_STATIC_ASSETS_DIR_NAME}/${folder}/[name]-[hash].[ext]`; + path.posix.join( + OUTPUT_STATIC_ASSETS_DIR_NAME, + folder, + '[name]-[contenthash].[ext]', + ); const loaders: FileLoaderUtils['loaders'] = { file: (options: {folder: AssetFolder}) => ({ @@ -56,7 +65,7 @@ export function getFileLoaderUtils(): FileLoaderUtils { }, }), - // TODO find a better solution to avoid conflicts with the ideal-image plugin + // TODO avoid conflicts with the ideal-image plugin // TODO this may require a little breaking change for ideal-image users? // Maybe with the ideal image plugin, all md images should be "ideal"? // This is used to force url-loader+file-loader on markdown images @@ -78,12 +87,12 @@ export function getFileLoaderUtils(): FileLoaderUtils { */ images: () => ({ use: [loaders.url({folder: 'images'})], - test: /\.(ico|jpg|jpeg|png|gif|webp)(\?.*)?$/, + test: /\.(?:ico|jpe?g|png|gif|webp)(?:\?.*)?$/i, }), fonts: () => ({ use: [loaders.url({folder: 'fonts'})], - test: /\.(woff|woff2|eot|ttf|otf)$/, + test: /\.(?:woff2?|eot|ttf|otf)$/i, }), /** @@ -92,11 +101,11 @@ export function getFileLoaderUtils(): FileLoaderUtils { */ media: () => ({ use: [loaders.url({folder: 'medias'})], - test: /\.(mp4|webm|ogv|wav|mp3|m4a|aac|oga|flac)$/, + test: /\.(?:mp4|webm|ogv|wav|mp3|m4a|aac|oga|flac)$/i, }), svg: () => ({ - test: /\.svg?$/, + test: /\.svg$/i, oneOf: [ { use: [ @@ -111,6 +120,7 @@ export function getFileLoaderUtils(): FileLoaderUtils { name: 'preset-default', params: { overrides: { + removeTitle: false, removeViewBox: false, }, }, @@ -125,7 +135,7 @@ export function getFileLoaderUtils(): FileLoaderUtils { // We don't want to use SVGR loader for non-React source code // ie we don't want to use SVGR for CSS files... issuer: { - and: [/\.(ts|tsx|js|jsx|md|mdx)$/], + and: [/\.(?:tsx?|jsx?|mdx?)$/i], }, }, { @@ -136,7 +146,7 @@ export function getFileLoaderUtils(): FileLoaderUtils { otherAssets: () => ({ use: [loaders.file({folder: 'files'})], - test: /\.(pdf|doc|docx|xls|xlsx|zip|rar)$/, + test: /\.(?:pdf|docx?|xlsx?|zip|rar)$/i, }), }; diff --git a/packages/docusaurus/bin/beforeCli.js b/packages/docusaurus/bin/beforeCli.js deleted file mode 100644 index 4c0cf280551d..000000000000 --- a/packages/docusaurus/bin/beforeCli.js +++ /dev/null @@ -1,124 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -// @ts-check - -const logger = require('@docusaurus/logger').default; -const fs = require('fs-extra'); -const semver = require('semver'); -const path = require('path'); -const updateNotifier = require('update-notifier'); -const boxen = require('boxen'); - -const { - name, - version, - engines: {node: requiredVersion}, -} = require('../package.json'); - -// Notify user if @docusaurus packages is outdated -// -// Note: this is a 2-step process to avoid delaying cli usage by awaiting a response: -// - 1st run: trigger background job to check releases + store result -// - 2nd run: display potential update to users -// -// Note: even if the -// -// cache data is stored in ~/.config/configstore/update-notifier-@docusaurus -// -const notifier = updateNotifier({ - pkg: { - name, - version, - }, - // Check is in background so it's fine to use a small value like 1h - // Use 0 for debugging - updateCheckInterval: 1000 * 60 * 60, - // updateCheckInterval: 0 -}); - -// Hacky way to ensure we check for updates on first run -// Note: the notification will only happen in the 2nd run -// See https://github.com/yeoman/update-notifier/issues/209 -try { - if ( - notifier.config && - !notifier.disabled && - Date.now() - notifier.config.get('lastUpdateCheck') < 50 - ) { - notifier.config.set('lastUpdateCheck', 0); - notifier.check(); - } -} catch (e) { - // Do not stop cli if this fails, see https://github.com/facebook/docusaurus/issues/5400 - logger.error(e); -} - -// We don't want to display update message for canary releases -// See https://github.com/facebook/docusaurus/issues/5378 -function ignoreUpdate(update) { - const isCanaryRelease = - update && update.current && update.current.startsWith('0.0.0'); - return isCanaryRelease; -} - -if ( - notifier.config && - notifier.update && - semver.lt(notifier.update.current, notifier.update.latest) -) { - // Because notifier clears cached data after reading it, leading to notifier not consistently displaying the update - // See https://github.com/yeoman/update-notifier/issues/209 - notifier.config.set('update', notifier.update); - - if (ignoreUpdate(notifier.update)) { - // @ts-expect-error: it works - return; - } - - // eslint-disable-next-line import/no-dynamic-require, global-require - const sitePkg = require(path.resolve(process.cwd(), 'package.json')); - const siteDocusaurusPackagesForUpdate = Object.keys({ - ...sitePkg.dependencies, - ...sitePkg.devDependencies, - }) - .filter((p) => p.startsWith('@docusaurus')) - .map((p) => p.concat('@latest')) - .join(' '); - const isYarnUsed = fs.existsSync(path.resolve(process.cwd(), 'yarn.lock')); - const upgradeCommand = isYarnUsed - ? `yarn upgrade ${siteDocusaurusPackagesForUpdate}` - : `npm i ${siteDocusaurusPackagesForUpdate}`; - - /** @type {import('boxen').Options} */ - const boxenOptions = { - padding: 1, - margin: 1, - align: 'center', - borderColor: 'yellow', - borderStyle: 'round', - }; - - const docusaurusUpdateMessage = boxen( - `Update available ${logger.dim( - `${notifier.update.current}`, - )} → ${logger.green(`${notifier.update.latest}`)} - -To upgrade Docusaurus packages with the latest version, run the following command: -${logger.code(upgradeCommand)}`, - boxenOptions, - ); - - console.log(docusaurusUpdateMessage); -} - -// notify user if node version needs to be updated -if (!semver.satisfies(process.version, requiredVersion)) { - logger.error('Minimum Node.js version not met :('); - logger.info`You are using Node.js number=${process.version}, Requirement: Node.js number=${requiredVersion}.`; - process.exit(1); -} diff --git a/packages/docusaurus/bin/beforeCli.mjs b/packages/docusaurus/bin/beforeCli.mjs new file mode 100644 index 000000000000..53c5ee9e84d3 --- /dev/null +++ b/packages/docusaurus/bin/beforeCli.mjs @@ -0,0 +1,133 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +// @ts-check + +import logger from '@docusaurus/logger'; +import fs from 'fs-extra'; +import semver from 'semver'; +import path from 'path'; +import updateNotifier from 'update-notifier'; +import boxen from 'boxen'; +import {createRequire} from 'module'; + +const packageJson = createRequire(import.meta.url)('../package.json'); +const sitePkg = createRequire(path.join(process.cwd(), 'package.json'))( + './package.json', +); + +const { + name, + version, + engines: {node: requiredVersion}, +} = packageJson; + +/** + * Notify user if `@docusaurus` packages are outdated + * + * Note: this is a 2-step process to avoid delaying cli usage by awaiting a + * response: + * - 1st run: trigger background job to check releases + store result + * - 2nd run: display potential update to users + * + * cache data is stored in `~/.config/configstore/update-notifier-@docusaurus` + */ +export default async function beforeCli() { + const notifier = updateNotifier({ + pkg: { + name, + version, + }, + // Check is in background so it's fine to use a small value like 1h + // Use 0 for debugging + updateCheckInterval: 1000 * 60 * 60, + // updateCheckInterval: 0 + }); + + // Hacky way to ensure we check for updates on first run + // Note: the notification will only happen in the 2nd run + // See https://github.com/yeoman/update-notifier/issues/209 + try { + if ( + notifier.config && + // @ts-expect-error: this is an internal API + !notifier.disabled && + Date.now() - notifier.config.get('lastUpdateCheck') < 50 + ) { + notifier.config.set('lastUpdateCheck', 0); + notifier.check(); + } + } catch (err) { + // Do not stop cli if this fails, see https://github.com/facebook/docusaurus/issues/5400 + logger.error(err); + } + + /** + * We don't want to display update message for canary releases. + * See https://github.com/facebook/docusaurus/issues/5378 + * @param {import('update-notifier').UpdateInfo} update + */ + function ignoreUpdate(update) { + const isCanaryRelease = update?.current?.startsWith('0.0.0'); + return isCanaryRelease; + } + + if ( + notifier.config && + notifier.update && + semver.lt(notifier.update.current, notifier.update.latest) + ) { + // Because notifier clears cached data after reading it, leading to notifier + // not consistently displaying the update. + // See https://github.com/yeoman/update-notifier/issues/209 + notifier.config.set('update', notifier.update); + + if (ignoreUpdate(notifier.update)) { + return; + } + + const siteDocusaurusPackagesForUpdate = Object.keys({ + ...sitePkg.dependencies, + ...sitePkg.devDependencies, + }) + .filter((p) => p.startsWith('@docusaurus')) + .map((p) => p.concat('@latest')) + .join(' '); + const isYarnUsed = await fs.pathExists(path.resolve('yarn.lock')); + const upgradeCommand = isYarnUsed + ? `yarn upgrade ${siteDocusaurusPackagesForUpdate}` + : `npm i ${siteDocusaurusPackagesForUpdate}`; + + /** @type {import('boxen').Options} */ + const boxenOptions = { + padding: 1, + margin: 1, + align: 'center', + borderColor: 'yellow', + borderStyle: 'round', + }; + + const docusaurusUpdateMessage = boxen( + `Update available ${logger.dim( + `${notifier.update.current}`, + )} → ${logger.green(`${notifier.update.latest}`)} + + To upgrade Docusaurus packages with the latest version, run the following command: + ${logger.code(upgradeCommand)}`, + boxenOptions, + ); + + console.log(docusaurusUpdateMessage); + } + + // notify user if node version needs to be updated + if (!semver.satisfies(process.version, requiredVersion)) { + logger.error('Minimum Node.js version not met :('); + logger.info`You are using Node.js number=${process.version}, Requirement: Node.js number=${requiredVersion}.`; + process.exit(1); + } +} diff --git a/packages/docusaurus/bin/docusaurus.js b/packages/docusaurus/bin/docusaurus.mjs similarity index 67% rename from packages/docusaurus/bin/docusaurus.js rename to packages/docusaurus/bin/docusaurus.mjs index 898d23949d36..eee4fc9919e9 100755 --- a/packages/docusaurus/bin/docusaurus.js +++ b/packages/docusaurus/bin/docusaurus.mjs @@ -8,10 +8,11 @@ // @ts-check -const logger = require('@docusaurus/logger').default; -const fs = require('fs'); -const cli = require('commander'); -const { +import logger from '@docusaurus/logger'; +import fs from 'fs-extra'; +import cli from 'commander'; +import {createRequire} from 'module'; +import { build, swizzle, deploy, @@ -21,13 +22,16 @@ const { clear, writeTranslations, writeHeadingIds, -} = require('../lib'); +} from '../lib/index.js'; +import beforeCli from './beforeCli.mjs'; -require('./beforeCli'); +await beforeCli(); -const resolveDir = (dir = '.') => fs.realpathSync(dir); +const resolveDir = (dir = '.') => fs.realpath(dir); -cli.version(require('../package.json').version).usage('<command> [options]'); +cli + .version(createRequire(import.meta.url)('../package.json').version) + .usage('<command> [options]'); cli .command('build [siteDir]') @@ -52,8 +56,8 @@ cli '--no-minify', 'build website without minimizing JS bundles (default: false)', ) - .action((siteDir, {bundleAnalyzer, config, outDir, locale, minify}) => { - build(resolveDir(siteDir), { + .action(async (siteDir, {bundleAnalyzer, config, outDir, locale, minify}) => { + build(await resolveDir(siteDir), { bundleAnalyzer, outDir, config, @@ -64,14 +68,28 @@ cli cli .command('swizzle [themeName] [componentName] [siteDir]') - .description('Copy the theme files into website folder for customization.') + .description( + 'Wraps or ejects the original theme files into website folder for customization.', + ) + .option( + '-w, --wrap', + 'Creates a wrapper around the original theme component.\nAllows rendering other components before/after the original theme component.', + ) .option( - '--typescript', + '-e, --eject', + 'Ejects the full source code of the original theme component.\nAllows overriding the original component entirely with your own UI and logic.', + ) + .option( + '-l, --list', + 'only list the available themes/components without further prompting (default: false)', + ) + .option( + '-t, --typescript', 'copy TypeScript theme files when possible (default: false)', ) - .option('--danger', 'enable swizzle for internal component of themes') - .action((themeName, componentName, siteDir, {typescript, danger}) => { - swizzle(resolveDir(siteDir), themeName, componentName, typescript, danger); + .option('--danger', 'enable swizzle for unsafe component of themes') + .action(async (themeName, componentName, siteDir, options) => { + swizzle(await resolveDir(siteDir), themeName, componentName, options); }); cli @@ -93,8 +111,8 @@ cli '--skip-build', 'skip building website before deploy it (default: false)', ) - .action((siteDir, {outDir, skipBuild, config}) => { - deploy(resolveDir(siteDir), { + .action(async (siteDir, {outDir, skipBuild, config}) => { + deploy(await resolveDir(siteDir), { outDir, config, skipBuild, @@ -120,17 +138,19 @@ cli '--poll [interval]', 'use polling rather than watching for reload (default: false). Can specify a poll interval in milliseconds', ) - .action((siteDir, {port, host, locale, config, hotOnly, open, poll}) => { - start(resolveDir(siteDir), { - port, - host, - locale, - config, - hotOnly, - open, - poll, - }); - }); + .action( + async (siteDir, {port, host, locale, config, hotOnly, open, poll}) => { + start(await resolveDir(siteDir), { + port, + host, + locale, + config, + hotOnly, + open, + poll, + }); + }, + ); cli .command('serve [siteDir]') @@ -147,7 +167,7 @@ cli .option('--build', 'build website before serving (default: false)') .option('-h, --host <host>', 'use specified host (default: localhost)') .action( - ( + async ( siteDir, { dir = 'build', @@ -157,7 +177,7 @@ cli config, }, ) => { - serve(resolveDir(siteDir), { + serve(await resolveDir(siteDir), { dir, port, build: buildSite, @@ -170,8 +190,8 @@ cli cli .command('clear [siteDir]') .description('Remove build artifacts.') - .action((siteDir) => { - clear(resolveDir(siteDir)); + .action(async (siteDir) => { + clear(await resolveDir(siteDir)); }); cli @@ -194,11 +214,11 @@ cli 'allows to init new written messages with a given prefix. This might help you to highlight untranslated message to make them stand out in the UI', ) .action( - ( + async ( siteDir, {locale = undefined, override = false, messagePrefix = '', config}, ) => { - writeTranslations(resolveDir(siteDir), { + writeTranslations(await resolveDir(siteDir), { locale, override, config, @@ -208,15 +228,15 @@ cli ); cli - .command('write-heading-ids [contentDir] [files]') + .command('write-heading-ids [siteDir] [files...]') .description('Generate heading ids in Markdown content.') .option( '--maintain-case', "keep the headings' casing, otherwise make all lowercase (default: false)", ) .option('--overwrite', 'overwrite existing heading IDs (default: false)') - .action((siteDir, files, options) => - writeHeadingIds(resolveDir(siteDir), files, options), + .action(async (siteDir, files, options) => + writeHeadingIds(await resolveDir(siteDir), files, options), ); cli.arguments('<command>').action((cmd) => { @@ -224,35 +244,36 @@ cli.arguments('<command>').action((cmd) => { logger.error` Unknown command name=${cmd}.`; }); +/** + * @param {string | undefined} command + */ function isInternalCommand(command) { - return [ - 'start', - 'build', - 'swizzle', - 'deploy', - 'serve', - 'clear', - 'write-translations', - 'write-heading-ids', - ].includes(command); + return ( + command && + [ + 'start', + 'build', + 'swizzle', + 'deploy', + 'serve', + 'clear', + 'write-translations', + 'write-heading-ids', + ].includes(command) + ); } -async function run() { - if (!isInternalCommand(process.argv.slice(2)[0])) { - // @ts-expect-error: Hmmm - await externalCommand(cli, resolveDir('.')); - } - - cli.parse(process.argv); +if (!isInternalCommand(process.argv.slice(2)[0])) { + await externalCommand(cli, await resolveDir('.')); +} - if (!process.argv.slice(2).length) { - cli.outputHelp(); - } +if (!process.argv.slice(2).length) { + cli.outputHelp(); } -run(); +cli.parse(process.argv); process.on('unhandledRejection', (err) => { - logger.error(err.stack); + logger.error(err); process.exit(1); }); diff --git a/packages/docusaurus/package.json b/packages/docusaurus/package.json index f46997cba1af..544e9a85e7b1 100644 --- a/packages/docusaurus/package.json +++ b/packages/docusaurus/package.json @@ -1,7 +1,7 @@ { "name": "@docusaurus/core", "description": "Easy to Maintain Open Source Documentation Websites", - "version": "2.0.0-beta.14", + "version": "2.0.0-beta.18", "license": "MIT", "publishConfig": { "access": "public" @@ -21,67 +21,68 @@ "directory": "packages/docusaurus" }, "bin": { - "docusaurus": "bin/docusaurus.js" + "docusaurus": "bin/docusaurus.mjs" }, "scripts": { - "build": "tsc && tsc -p tsconfig.client.json && node copyUntypedFiles.mjs", - "watch": "node copyUntypedFiles.mjs && concurrently -n \"server,client\" --kill-others \"tsc --watch\" \"tsc -p tsconfig.client.json --watch\"" + "build": "tsc -p tsconfig.server.json && tsc -p tsconfig.client.json && node copyUntypedFiles.mjs", + "watch": "node copyUntypedFiles.mjs && concurrently -n \"server,client\" --kill-others \"tsc -p tsconfig.server.json --watch\" \"tsc -p tsconfig.client.json --watch\"" }, "bugs": { "url": "https://github.com/facebook/docusaurus/issues" }, "dependencies": { - "@babel/core": "^7.16.0", - "@babel/generator": "^7.16.0", + "@babel/core": "^7.17.8", + "@babel/generator": "^7.17.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-transform-runtime": "^7.16.0", - "@babel/preset-env": "^7.16.4", - "@babel/preset-react": "^7.16.0", - "@babel/preset-typescript": "^7.16.0", - "@babel/runtime": "^7.16.3", - "@babel/runtime-corejs3": "^7.16.3", - "@babel/traverse": "^7.16.3", - "@docusaurus/cssnano-preset": "2.0.0-beta.14", - "@docusaurus/logger": "2.0.0-beta.14", - "@docusaurus/mdx-loader": "2.0.0-beta.14", + "@babel/plugin-transform-runtime": "^7.17.0", + "@babel/preset-env": "^7.16.11", + "@babel/preset-react": "^7.16.7", + "@babel/preset-typescript": "^7.16.7", + "@babel/runtime": "^7.17.8", + "@babel/runtime-corejs3": "^7.17.8", + "@babel/traverse": "^7.17.3", + "@docusaurus/cssnano-preset": "2.0.0-beta.18", + "@docusaurus/logger": "2.0.0-beta.18", + "@docusaurus/mdx-loader": "2.0.0-beta.18", "@docusaurus/react-loadable": "5.5.2", - "@docusaurus/utils": "2.0.0-beta.14", - "@docusaurus/utils-common": "2.0.0-beta.14", - "@docusaurus/utils-validation": "2.0.0-beta.14", - "@slorber/static-site-generator-webpack-plugin": "^4.0.0", - "@svgr/webpack": "^6.0.0", - "autoprefixer": "^10.3.5", - "babel-loader": "^8.2.2", + "@docusaurus/utils": "2.0.0-beta.18", + "@docusaurus/utils-common": "2.0.0-beta.18", + "@docusaurus/utils-validation": "2.0.0-beta.18", + "@slorber/static-site-generator-webpack-plugin": "^4.0.4", + "@svgr/webpack": "^6.2.1", + "autoprefixer": "^10.4.4", + "babel-loader": "^8.2.4", "babel-plugin-dynamic-import-node": "2.3.0", - "boxen": "^5.0.1", - "chokidar": "^3.5.2", - "clean-css": "^5.1.5", + "boxen": "^6.2.1", + "chokidar": "^3.5.3", + "clean-css": "^5.2.4", + "cli-table3": "^0.6.1", + "combine-promises": "^1.1.0", "commander": "^5.1.0", - "copy-webpack-plugin": "^10.2.0", - "core-js": "^3.18.0", - "css-loader": "^6.5.1", - "css-minimizer-webpack-plugin": "^3.3.1", - "cssnano": "^5.0.8", + "copy-webpack-plugin": "^10.2.4", + "core-js": "^3.21.1", + "css-loader": "^6.7.1", + "css-minimizer-webpack-plugin": "^3.4.1", + "cssnano": "^5.1.5", "del": "^6.0.0", "detect-port": "^1.3.0", "escape-html": "^1.0.3", "eta": "^1.12.3", "file-loader": "^6.2.0", - "fs-extra": "^10.0.0", - "html-minifier-terser": "^6.0.2", + "fs-extra": "^10.0.1", + "html-minifier-terser": "^6.1.0", "html-tags": "^3.1.0", - "html-webpack-plugin": "^5.4.0", + "html-webpack-plugin": "^5.5.0", "import-fresh": "^3.3.0", - "is-root": "^2.1.0", "leven": "^3.1.0", - "lodash": "^4.17.20", - "mini-css-extract-plugin": "^1.6.0", + "lodash": "^4.17.21", + "mini-css-extract-plugin": "^2.6.0", "nprogress": "^0.2.0", - "postcss": "^8.3.7", - "postcss-loader": "^6.1.1", - "prompts": "^2.4.1", + "postcss": "^8.4.12", + "postcss-loader": "^6.2.1", + "prompts": "^2.4.2", "react-dev-utils": "^12.0.0", - "react-helmet": "^6.1.0", + "react-helmet-async": "^1.2.3", "react-loadable": "npm:@docusaurus/react-loadable@5.5.2", "react-loadable-ssr-addon-v5-slorber": "^1.0.1", "react-router": "^5.2.0", @@ -89,37 +90,35 @@ "react-router-dom": "^5.2.0", "remark-admonitions": "^1.2.1", "rtl-detect": "^1.0.4", - "semver": "^7.3.4", + "semver": "^7.3.5", "serve-handler": "^6.1.3", - "shelljs": "^0.8.4", - "strip-ansi": "^6.0.0", - "terser-webpack-plugin": "^5.2.4", + "shelljs": "^0.8.5", + "terser-webpack-plugin": "^5.3.1", "tslib": "^2.3.1", "update-notifier": "^5.1.0", "url-loader": "^4.1.1", - "wait-on": "^6.0.0", - "webpack": "^5.61.0", - "webpack-bundle-analyzer": "^4.4.2", - "webpack-dev-server": "^4.7.1", + "wait-on": "^6.0.1", + "webpack": "^5.70.0", + "webpack-bundle-analyzer": "^4.5.0", + "webpack-dev-server": "^4.7.4", "webpack-merge": "^5.8.0", "webpackbar": "^5.0.2" }, "devDependencies": { - "@docusaurus/module-type-aliases": "2.0.0-beta.14", - "@docusaurus/types": "2.0.0-beta.14", - "@types/copy-webpack-plugin": "^8.0.1", - "@types/detect-port": "^1.3.0", - "@types/mini-css-extract-plugin": "^1.4.3", + "@docusaurus/module-type-aliases": "2.0.0-beta.18", + "@docusaurus/types": "2.0.0-beta.18", + "@types/detect-port": "^1.3.2", "@types/nprogress": "^0.2.0", - "@types/react-dom": "^17.0.9", - "@types/react-helmet": "^6.0.0", - "@types/react-router-config": "^5.0.1", + "@types/react-dom": "^17.0.14", + "@types/react-router-config": "^5.0.6", "@types/rtl-detect": "^1.0.0", "@types/serve-handler": "^6.1.1", - "@types/wait-on": "^5.2.0", + "@types/update-notifier": "^5.1.0", + "@types/wait-on": "^5.3.1", "@types/webpack-bundle-analyzer": "^4.4.1", "react-test-renderer": "^17.0.2", - "tmp-promise": "^3.0.2" + "tmp-promise": "^3.0.3", + "tree-node-cli": "^1.5.2" }, "peerDependencies": { "react": "^16.8.4 || ^17.0.0", diff --git a/packages/docusaurus/src/babel/preset.ts b/packages/docusaurus/src/babel/preset.ts index cb0579aa19ce..3dd351ef9ac5 100644 --- a/packages/docusaurus/src/babel/preset.ts +++ b/packages/docusaurus/src/babel/preset.ts @@ -13,7 +13,8 @@ function getTransformOptions(isServer: boolean): TransformOptions { require.resolve(`@babel/runtime/package.json`), ); return { - // All optional newlines and whitespace will be omitted when generating code in compact mode + // All optional newlines and whitespace will be omitted when generating code + // in compact mode compact: true, presets: [ isServer @@ -48,9 +49,9 @@ function getTransformOptions(isServer: boolean): TransformOptions { { corejs: false, helpers: true, - // By default, it assumes @babel/runtime@7.0.0. Since we use >7.0.0, better to - // explicitly specify the version so that it can reuse the helper better - // See https://github.com/babel/babel/issues/10261 + // By default, it assumes @babel/runtime@7.0.0. Since we use >7.0.0, + // better to explicitly specify the version so that it can reuse the + // helper better. See https://github.com/babel/babel/issues/10261 // eslint-disable-next-line @typescript-eslint/no-var-requires, global-require version: require('@babel/runtime/package.json').version, regenerator: true, @@ -69,9 +70,7 @@ function getTransformOptions(isServer: boolean): TransformOptions { }; } -function babelPresets(api: ConfigAPI): TransformOptions { +export default function babelPresets(api: ConfigAPI): TransformOptions { const callerName = api.caller((caller) => caller?.name); return getTransformOptions(callerName === 'server'); } - -export default babelPresets; diff --git a/packages/docusaurus/src/choosePort.ts b/packages/docusaurus/src/choosePort.ts deleted file mode 100644 index b18f87551788..000000000000 --- a/packages/docusaurus/src/choosePort.ts +++ /dev/null @@ -1,128 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -/** - * This feature was heavily inspired by create-react-app and - * uses many of the same utility functions to implement it. - */ - -import {execSync} from 'child_process'; -import detect from 'detect-port'; -import isRoot from 'is-root'; -import logger from '@docusaurus/logger'; -import prompts from 'prompts'; - -const isInteractive = process.stdout.isTTY; - -const execOptions: Record<string, unknown> = { - encoding: 'utf8', - stdio: [ - 'pipe', // stdin (default) - 'pipe', // stdout (default) - 'ignore', // stderr - ], -}; - -// Clears console -function clearConsole(): void { - process.stdout.write( - process.platform === 'win32' ? '\x1B[2J\x1B[0f' : '\x1B[2J\x1B[3J\x1B[H', - ); -} - -// Gets process id of what is on port -function getProcessIdOnPort(port: number): string { - return execSync(`lsof -i:${port} -P -t -sTCP:LISTEN`, execOptions) - .toString() - .split('\n')[0] - .trim(); -} - -// Gets process command -function getProcessCommand(processId: string): string { - const command: Buffer = execSync( - `ps -o command -p ${processId} | sed -n 2p`, - execOptions, - ); - - return command.toString().replace(/\n$/, ''); -} - -// Gets directory of a process from its process id -function getDirectoryOfProcessById(processId: string): string { - return execSync( - `lsof -p ${processId} | awk '$4=="cwd" {for (i=9; i<=NF; i++) printf "%s ", $i}'`, - execOptions, - ) - .toString() - .trim(); -} - -// Gets process on port -function getProcessForPort(port: number): string | null { - try { - const processId = getProcessIdOnPort(port); - const directory = getDirectoryOfProcessById(processId); - const command = getProcessCommand(processId); - return logger.interpolate`code=${command} subdue=${`(pid ${processId})`} in path=${directory}`; - } catch (e) { - return null; - } -} - -/** - * Detects if program is running on port and prompts user - * to choose another if port is already being used - */ -export default async function choosePort( - host: string, - defaultPort: number, -): Promise<number | null> { - return detect({port: defaultPort, hostname: host}).then( - (port) => - new Promise((resolve) => { - if (port === defaultPort) { - resolve(port); - return; - } - const message = - process.platform !== 'win32' && defaultPort < 1024 && !isRoot() - ? `Admin permissions are required to run a server on a port below 1024.` - : `Something is already running on port ${defaultPort}.`; - if (isInteractive) { - clearConsole(); - const existingProcess = getProcessForPort(defaultPort); - const question: prompts.PromptObject = { - type: 'confirm', - name: 'shouldChangePort', - message: logger.yellow(`${logger.bold('[WARNING]')} ${message}${ - existingProcess ? ` Probably:\n ${existingProcess}` : '' - } - -Would you like to run the app on another port instead?`), - initial: true, - }; - prompts(question).then((answer) => { - if (answer.shouldChangePort === true) { - resolve(port); - } else { - resolve(null); - } - }); - } else { - logger.error(message); - resolve(null); - } - }), - (err) => { - throw new Error( - `Could not find an open port at ${host}. -${`Network error message: "${err.message || err}".`}`, - ); - }, - ); -} diff --git a/packages/docusaurus/src/client/App.tsx b/packages/docusaurus/src/client/App.tsx index 48a78adfe47a..01d74bb1cdd3 100644 --- a/packages/docusaurus/src/client/App.tsx +++ b/packages/docusaurus/src/client/App.tsx @@ -9,11 +9,13 @@ import React from 'react'; import routes from '@generated/routes'; import renderRoutes from './exports/renderRoutes'; -import {BrowserContextProvider} from './exports/browserContext'; -import {DocusaurusContextProvider} from './exports/docusaurusContext'; +import {BrowserContextProvider} from './browserContext'; +import {DocusaurusContextProvider} from './docusaurusContext'; import PendingNavigation from './PendingNavigation'; import BaseUrlIssueBanner from './baseUrlIssueBanner/BaseUrlIssueBanner'; +import SiteMetadataDefaults from './SiteMetadataDefaults'; import Root from '@theme/Root'; +import SiteMetadata from '@theme/SiteMetadata'; import './client-lifecycles-dispatcher'; @@ -21,12 +23,14 @@ import './client-lifecycles-dispatcher'; import ErrorBoundary from '@docusaurus/ErrorBoundary'; import Error from '@theme/Error'; -function App(): JSX.Element { +export default function App(): JSX.Element { return ( <ErrorBoundary fallback={Error}> <DocusaurusContextProvider> <BrowserContextProvider> <Root> + <SiteMetadataDefaults /> + <SiteMetadata /> <BaseUrlIssueBanner /> <PendingNavigation routes={routes} delay={1000}> {renderRoutes(routes)} @@ -37,5 +41,3 @@ function App(): JSX.Element { </ErrorBoundary> ); } - -export default App; diff --git a/packages/docusaurus/src/client/LinksCollector.tsx b/packages/docusaurus/src/client/LinksCollector.tsx index 70009f093c9a..2df9a8618a2a 100644 --- a/packages/docusaurus/src/client/LinksCollector.tsx +++ b/packages/docusaurus/src/client/LinksCollector.tsx @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import React, {type ReactNode, useContext, createContext} from 'react'; +import React, {type ReactNode, useContext} from 'react'; type LinksCollector = { collectLink: (link: string) => void; @@ -26,7 +26,7 @@ export const createStatefulLinksCollector = (): StatefulLinksCollector => { }; }; -const Context = createContext<LinksCollector>({ +const Context = React.createContext<LinksCollector>({ collectLink: () => { // noop by default for client // we only use the broken links checker server-side @@ -35,7 +35,7 @@ const Context = createContext<LinksCollector>({ export const useLinksCollector = (): LinksCollector => useContext(Context); -export function ProvideLinksCollector({ +export function LinksCollectorProvider({ children, linksCollector, }: { diff --git a/packages/docusaurus/src/client/PendingNavigation.tsx b/packages/docusaurus/src/client/PendingNavigation.tsx index c2c39de31cdd..56e7e9c062b5 100644 --- a/packages/docusaurus/src/client/PendingNavigation.tsx +++ b/packages/docusaurus/src/client/PendingNavigation.tsx @@ -13,7 +13,7 @@ import nprogress from 'nprogress'; import clientLifecyclesDispatcher from './client-lifecycles-dispatcher'; import preload from './preload'; import normalizeLocation from './normalizeLocation'; -import type {Location} from '@docusaurus/history'; +import type {Location} from 'history'; import './nprogress.css'; @@ -45,12 +45,12 @@ class PendingNavigation extends React.Component<Props, State> { // Intercept location update and still show current route until next route // is done loading. - shouldComponentUpdate(nextProps: Props, nextState: State) { + override shouldComponentUpdate(nextProps: Props, nextState: State) { const routeDidChange = nextProps.location !== this.props.location; const {routes, delay} = this.props; - // If `routeDidChange` is true, means the router is trying to navigate to a new - // route. We will preload the new route. + // If `routeDidChange` is true, means the router is trying to navigate to a + // new route. We will preload the new route. if (routeDidChange) { const nextLocation = normalizeLocation(nextProps.location); this.startProgressBar(delay); @@ -69,12 +69,7 @@ class PendingNavigation extends React.Component<Props, State> { }); // Route has loaded, we can reset previousLocation. this.previousLocation = null; - this.setState( - { - nextRouteHasLoaded: true, - }, - this.stopProgressBar, - ); + this.setState({nextRouteHasLoaded: true}, this.stopProgressBar); const {hash} = nextLocation; if (!hash) { window.scrollTo(0, 0); @@ -99,14 +94,14 @@ class PendingNavigation extends React.Component<Props, State> { return true; } - clearProgressBarTimeout() { + private clearProgressBarTimeout() { if (this.progressBarTimeout) { clearTimeout(this.progressBarTimeout); this.progressBarTimeout = null; } } - startProgressBar(delay: number) { + private startProgressBar(delay: number) { this.clearProgressBarTimeout(); this.progressBarTimeout = setTimeout(() => { clientLifecyclesDispatcher.onRouteUpdateDelayed({ @@ -116,12 +111,12 @@ class PendingNavigation extends React.Component<Props, State> { }, delay); } - stopProgressBar() { + private stopProgressBar() { this.clearProgressBarTimeout(); nprogress.done(); } - render() { + override render() { const {children, location} = this.props; return ( <Route location={normalizeLocation(location)} render={() => children} /> diff --git a/packages/docusaurus/src/client/SiteMetadataDefaults.tsx b/packages/docusaurus/src/client/SiteMetadataDefaults.tsx new file mode 100644 index 000000000000..bd2f953aab3e --- /dev/null +++ b/packages/docusaurus/src/client/SiteMetadataDefaults.tsx @@ -0,0 +1,27 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import Head from '@docusaurus/Head'; +import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; +import useBaseUrl from '@docusaurus/useBaseUrl'; + +export default function SiteMetadataDefaults(): JSX.Element { + const { + siteConfig: {favicon, tagline, title}, + i18n: {currentLocale, localeConfigs}, + } = useDocusaurusContext(); + const faviconUrl = useBaseUrl(favicon); + const {htmlLang, direction: htmlDir} = localeConfigs[currentLocale]!; + + return ( + <Head defaultTitle={`${title}${tagline ? ` · ${tagline}` : ''}`}> + <html lang={htmlLang} dir={htmlDir} /> + {favicon && <link rel="icon" href={faviconUrl} />} + </Head> + ); +} diff --git a/packages/docusaurus/src/client/__tests__/browserContext.test.tsx b/packages/docusaurus/src/client/__tests__/browserContext.test.tsx new file mode 100644 index 000000000000..bfb8c211cf03 --- /dev/null +++ b/packages/docusaurus/src/client/__tests__/browserContext.test.tsx @@ -0,0 +1,30 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @jest-environment jsdom + */ + +// Jest doesn't allow pragma below other comments. https://github.com/facebook/jest/issues/12573 +// eslint-disable-next-line header/header +import React from 'react'; +import {renderHook} from '@testing-library/react-hooks/server'; +import {BrowserContextProvider} from '../browserContext'; +import useIsBrowser from '../exports/useIsBrowser'; + +describe('BrowserContextProvider', () => { + const {result, hydrate} = renderHook(() => useIsBrowser(), { + wrapper: ({children}) => ( + <BrowserContextProvider>{children}</BrowserContextProvider> + ), + }); + it('has value false on first render', () => { + expect(result.current).toBe(false); + }); + it('has value true on hydration', () => { + hydrate(); + expect(result.current).toBe(true); + }); +}); diff --git a/packages/docusaurus/src/client/__tests__/docusaurusContext.test.tsx b/packages/docusaurus/src/client/__tests__/docusaurusContext.test.tsx new file mode 100644 index 000000000000..d11bb52a68aa --- /dev/null +++ b/packages/docusaurus/src/client/__tests__/docusaurusContext.test.tsx @@ -0,0 +1,41 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @jest-environment jsdom + */ + +// Jest doesn't allow pragma below other comments. https://github.com/facebook/jest/issues/12573 +// eslint-disable-next-line header/header +import React from 'react'; +import {renderHook} from '@testing-library/react-hooks/server'; +import {DocusaurusContextProvider} from '../docusaurusContext'; +import useDocusaurusContext from '../exports/useDocusaurusContext'; + +// This test currently isn't quite useful because the @generated aliases point +// to the empty modules. Maybe we can point that to fixtures in the future. +describe('DocusaurusContextProvider', () => { + const {result, hydrate} = renderHook(() => useDocusaurusContext(), { + wrapper: ({children}) => ( + <DocusaurusContextProvider>{children}</DocusaurusContextProvider> + ), + }); + const value = result.current; + it('returns right value', () => { + expect(value).toMatchInlineSnapshot(` + { + "codeTranslations": {}, + "globalData": {}, + "i18n": {}, + "siteConfig": {}, + "siteMetadata": {}, + } + `); + }); + it('has reference-equal value on hydration', () => { + hydrate(); + expect(result.current).toBe(value); + }); +}); diff --git a/packages/docusaurus/src/client/__tests__/flat.test.ts b/packages/docusaurus/src/client/__tests__/flat.test.ts index 4d176c008643..f931108938bb 100644 --- a/packages/docusaurus/src/client/__tests__/flat.test.ts +++ b/packages/docusaurus/src/client/__tests__/flat.test.ts @@ -8,7 +8,7 @@ import flat from '../flat'; describe('flat', () => { - test('nested', () => { + it('nested', () => { expect( flat({ foo: { @@ -32,7 +32,7 @@ describe('flat', () => { }); }); - test('primitives', () => { + it('primitives', () => { const primitives = { String: 'good morning', Number: 1234.99, @@ -55,7 +55,7 @@ describe('flat', () => { }); }); - test('multiple keys', () => { + it('multiple keys', () => { expect( flat({ foo: { @@ -69,7 +69,7 @@ describe('flat', () => { }); }); - test('empty object', () => { + it('empty object', () => { expect( flat({ foo: { @@ -81,7 +81,7 @@ describe('flat', () => { }); }); - test('array', () => { + it('array', () => { expect( flat({ hello: [{world: {again: 'foo'}}, {lorem: 'ipsum'}], diff --git a/packages/docusaurus/src/client/__tests__/normalizeLocation.test.ts b/packages/docusaurus/src/client/__tests__/normalizeLocation.test.ts index bb6e92738921..9517a5b0f15d 100644 --- a/packages/docusaurus/src/client/__tests__/normalizeLocation.test.ts +++ b/packages/docusaurus/src/client/__tests__/normalizeLocation.test.ts @@ -5,10 +5,19 @@ * LICENSE file in the root directory of this source tree. */ +import {jest} from '@jest/globals'; import normalizeLocation from '../normalizeLocation'; describe('normalizeLocation', () => { - test('rewrite locations with index.html', () => { + it('rewrites locations with index.html', () => { + expect( + normalizeLocation({ + pathname: '/index.html', + }), + ).toEqual({ + pathname: '/', + }); + expect( normalizeLocation({ pathname: '/docs/introduction/index.html', @@ -34,7 +43,7 @@ describe('normalizeLocation', () => { }); }); - test('untouched pathnames', () => { + it('leaves pathnames untouched', () => { const replaceMock = jest.spyOn(String.prototype, 'replace'); expect( diff --git a/packages/docusaurus/src/client/__tests__/routeContext.test.tsx b/packages/docusaurus/src/client/__tests__/routeContext.test.tsx new file mode 100644 index 000000000000..558cf157a66f --- /dev/null +++ b/packages/docusaurus/src/client/__tests__/routeContext.test.tsx @@ -0,0 +1,89 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import {renderHook} from '@testing-library/react-hooks/server'; +import {RouteContextProvider} from '../routeContext'; +import useRouteContext from '../exports/useRouteContext'; + +describe('RouteContextProvider', () => { + it('creates root route context registered by plugin', () => { + expect( + renderHook(() => useRouteContext(), { + wrapper: ({children}) => ( + <RouteContextProvider value={{plugin: {id: 'test', name: 'test'}}}> + {children} + </RouteContextProvider> + ), + }).result.current, + ).toEqual({plugin: {id: 'test', name: 'test'}}); + }); + it('throws if there is no route context at all', () => { + expect( + () => + renderHook(() => useRouteContext(), { + wrapper: ({children}) => ( + <RouteContextProvider value={null}>{children}</RouteContextProvider> + ), + }).result.current, + ).toThrowErrorMatchingInlineSnapshot( + `"Unexpected: no Docusaurus route context found"`, + ); + }); + it('throws if there is no parent context created by plugin', () => { + expect( + () => + renderHook(() => useRouteContext(), { + wrapper: ({children}) => ( + <RouteContextProvider value={{data: {some: 'data'}}}> + {children} + </RouteContextProvider> + ), + }).result.current, + ).toThrowErrorMatchingInlineSnapshot( + `"Unexpected: Docusaurus topmost route context has no \`plugin\` attribute"`, + ); + }); + it('merges route context created by parent', () => { + expect( + renderHook(() => useRouteContext(), { + wrapper: ({children}) => ( + <RouteContextProvider + value={{plugin: {id: 'test', name: 'test'}, data: {some: 'data'}}}> + <RouteContextProvider value={{data: {someMore: 'data'}}}> + {children} + </RouteContextProvider> + </RouteContextProvider> + ), + }).result.current, + ).toEqual({ + data: {some: 'data', someMore: 'data'}, + plugin: {id: 'test', name: 'test'}, + }); + }); + it('never overrides the plugin attribute', () => { + expect( + renderHook(() => useRouteContext(), { + wrapper: ({children}) => ( + <RouteContextProvider + value={{plugin: {id: 'test', name: 'test'}, data: {some: 'data'}}}> + <RouteContextProvider + value={{ + plugin: {id: 'adversary', name: 'adversary'}, + data: {someMore: 'data'}, + }}> + {children} + </RouteContextProvider> + </RouteContextProvider> + ), + }).result.current, + ).toEqual({ + data: {some: 'data', someMore: 'data'}, + plugin: {id: 'test', name: 'test'}, + }); + }); +}); diff --git a/packages/docusaurus/src/client/baseUrlIssueBanner/BaseUrlIssueBanner.tsx b/packages/docusaurus/src/client/baseUrlIssueBanner/BaseUrlIssueBanner.tsx index eb4194843a0f..59f693931a50 100644 --- a/packages/docusaurus/src/client/baseUrlIssueBanner/BaseUrlIssueBanner.tsx +++ b/packages/docusaurus/src/client/baseUrlIssueBanner/BaseUrlIssueBanner.tsx @@ -101,11 +101,14 @@ function BaseUrlIssueBannerEnabled() { ); } -// We want to help the users with a bad baseUrl configuration (very common error) -// Help message is inlined, and hidden if JS or CSS is able to load -// Note: it might create false positives (ie network failures): not a big deal -// Note: we only inline this for the homepage to avoid polluting all the site's pages -// See https://github.com/facebook/docusaurus/pull/3621 +/** + * We want to help the users with a bad baseUrl configuration (very common + * error) Help message is inlined, and hidden if JS or CSS is able to load + * Note: it might create false positives (ie network failures): not a big deal + * Note: we only inline this for the homepage to avoid polluting all the site's + * pages + * @see https://github.com/facebook/docusaurus/pull/3621 + */ export default function BaseUrlIssueBanner(): JSX.Element | null { const { siteConfig: {baseUrl, baseUrlIssueBanner}, diff --git a/packages/docusaurus/src/client/exports/browserContext.tsx b/packages/docusaurus/src/client/browserContext.tsx similarity index 94% rename from packages/docusaurus/src/client/exports/browserContext.tsx rename to packages/docusaurus/src/client/browserContext.tsx index 56e5e4787220..d18cd22ec1ac 100644 --- a/packages/docusaurus/src/client/exports/browserContext.tsx +++ b/packages/docusaurus/src/client/browserContext.tsx @@ -13,7 +13,8 @@ import React, {type ReactNode, useEffect, useState} from 'react'; // isBrowser is set to true only after a successful hydration // Note, isBrowser is not part of useDocusaurusContext() for perf reasons -// Using useDocusaurusContext() (much more common need) should not trigger re-rendering after a successful hydration +// Using useDocusaurusContext() (much more common need) should not trigger +// re-rendering after a successful hydration export const Context = React.createContext<boolean>(false); diff --git a/packages/docusaurus/src/client/client-lifecycles-dispatcher.ts b/packages/docusaurus/src/client/client-lifecycles-dispatcher.ts index 98a4a206401d..57982c394313 100644 --- a/packages/docusaurus/src/client/client-lifecycles-dispatcher.ts +++ b/packages/docusaurus/src/client/client-lifecycles-dispatcher.ts @@ -6,32 +6,28 @@ */ import clientModules from '@generated/client-modules'; +import type {ClientModule} from '@docusaurus/types'; -interface Dispatchers { - onRouteUpdate: (...args: unknown[]) => void; - onRouteUpdateDelayed: (...args: unknown[]) => void; -} - -function dispatchLifecycleAction( - lifecycleAction: keyof Dispatchers, - ...args: unknown[] +function dispatchLifecycleAction<K extends keyof ClientModule>( + lifecycleAction: K, + args: Parameters<NonNullable<ClientModule[K]>>, ) { clientModules.forEach((clientModule) => { - const lifecycleFunction = - clientModule?.default?.[lifecycleAction] ?? clientModule[lifecycleAction]; + const lifecycleFunction = (clientModule?.default?.[lifecycleAction] ?? + clientModule[lifecycleAction]) as + | ((...a: Parameters<NonNullable<ClientModule[K]>>) => void) + | undefined; - if (lifecycleFunction) { - lifecycleFunction(...args); - } + lifecycleFunction?.(...args); }); } -const clientLifecyclesDispatchers: Dispatchers = { +const clientLifecyclesDispatchers: Required<ClientModule> = { onRouteUpdate(...args) { - dispatchLifecycleAction('onRouteUpdate', ...args); + dispatchLifecycleAction('onRouteUpdate', args); }, onRouteUpdateDelayed(...args) { - dispatchLifecycleAction('onRouteUpdateDelayed', ...args); + dispatchLifecycleAction('onRouteUpdateDelayed', args); }, }; diff --git a/packages/docusaurus/src/client/clientEntry.tsx b/packages/docusaurus/src/client/clientEntry.tsx index 4742ee810569..bb5ecdf3e966 100644 --- a/packages/docusaurus/src/client/clientEntry.tsx +++ b/packages/docusaurus/src/client/clientEntry.tsx @@ -6,8 +6,9 @@ */ import React from 'react'; -import {hydrate, render} from 'react-dom'; +import ReactDOM from 'react-dom'; import {BrowserRouter} from 'react-router-dom'; +import {HelmetProvider} from 'react-helmet-async'; import routes from '@generated/routes'; import ExecutionEnvironment from './exports/ExecutionEnvironment'; @@ -21,18 +22,23 @@ declare global { } } -// Client-side render (e.g: running in browser) to become single-page application (SPA). +// Client-side render (e.g: running in browser) to become single-page +// application (SPA). if (ExecutionEnvironment.canUseDOM) { window.docusaurus = docusaurus; - // For production, attempt to hydrate existing markup for performant first-load experience. + // For production, attempt to hydrate existing markup for performant + // first-load experience. // For development, there is no existing markup so we had to render it. - // Note that we also preload async component to avoid first-load loading screen. - const renderMethod = process.env.NODE_ENV === 'production' ? hydrate : render; + // We also preload async component to avoid first-load loading screen. + const renderMethod = + process.env.NODE_ENV === 'production' ? ReactDOM.hydrate : ReactDOM.render; preload(routes, window.location.pathname).then(() => { renderMethod( - <BrowserRouter> - <App /> - </BrowserRouter>, + <HelmetProvider> + <BrowserRouter> + <App /> + </BrowserRouter> + </HelmetProvider>, document.getElementById('__docusaurus'), ); }); diff --git a/packages/docusaurus/src/client/docusaurus.ts b/packages/docusaurus/src/client/docusaurus.ts index 8849cedd2232..1ee1605b1a35 100644 --- a/packages/docusaurus/src/client/docusaurus.ts +++ b/packages/docusaurus/src/client/docusaurus.ts @@ -12,11 +12,11 @@ import prefetchHelper from './prefetch'; import preloadHelper from './preload'; import flat from './flat'; -const fetched: Record<string, boolean> = {}; -const loaded: Record<string, boolean> = {}; +const fetched: {[key: string]: boolean} = {}; +const loaded: {[key: string]: boolean} = {}; declare global { - // eslint-disable-next-line camelcase + // eslint-disable-next-line camelcase, no-underscore-dangle const __webpack_require__: {gca: (name: string) => string}; interface Navigator { connection: {effectiveType: string; saveData: boolean}; @@ -34,20 +34,16 @@ const canPrefetch = (routePath: string) => const canPreload = (routePath: string) => !isSlowConnection() && !loaded[routePath]; -// Remove the last part containing the route hash -// input: /blog/2018/12/14/Happy-First-Birthday-Slash-fe9 -// output: /blog/2018/12/14/Happy-First-Birthday-Slash -const removeRouteNameHash = (str: string) => str.replace(/(-[^-]+)$/, ''); - const getChunkNamesToLoad = (path: string): string[] => Object.entries(routesChunkNames) .filter( - ([routeNameWithHash]) => removeRouteNameHash(routeNameWithHash) === path, + // Remove the last part containing the route hash + // input: /blog/2018/12/14/Happy-First-Birthday-Slash-fe9 + // output: /blog/2018/12/14/Happy-First-Birthday-Slash + ([routeNameWithHash]) => + routeNameWithHash.replace(/-[^-]+$/, '') === path, ) - .flatMap(([, routeChunks]) => - // flat() is useful for nested chunk names, it's not like array.flat() - Object.values(flat(routeChunks)), - ); + .flatMap(([, routeChunks]) => Object.values(flat(routeChunks))); const docusaurus = { prefetch: (routePath: string): boolean => { @@ -66,12 +62,14 @@ const docusaurus = { // Prefetch all webpack chunk assets file needed. chunkNamesNeeded.forEach((chunkName) => { - // "__webpack_require__.gca" is a custom function provided by ChunkAssetPlugin. - // Pass it the chunkName or chunkId you want to load and it will return the URL for that chunk. + // "__webpack_require__.gca" is a custom function provided by + // ChunkAssetPlugin. Pass it the chunkName or chunkId you want to load and + // it will return the URL for that chunk. // eslint-disable-next-line camelcase const chunkAsset = __webpack_require__.gca(chunkName); - // In some cases, webpack might decide to optimize further & hence the chunk assets are merged to another chunk/previous chunk. + // In some cases, webpack might decide to optimize further & hence the + // chunk assets are merged to another chunk/previous chunk. // Hence, we can safely filter it out/don't need to load it. if (chunkAsset && !/undefined/.test(chunkAsset)) { prefetchHelper(chunkAsset); diff --git a/packages/docusaurus/src/client/exports/docusaurusContext.tsx b/packages/docusaurus/src/client/docusaurusContext.tsx similarity index 100% rename from packages/docusaurus/src/client/exports/docusaurusContext.tsx rename to packages/docusaurus/src/client/docusaurusContext.tsx diff --git a/packages/docusaurus/src/client/exports/BrowserOnly.tsx b/packages/docusaurus/src/client/exports/BrowserOnly.tsx index b1beedb41615..025049b782de 100644 --- a/packages/docusaurus/src/client/exports/BrowserOnly.tsx +++ b/packages/docusaurus/src/client/exports/BrowserOnly.tsx @@ -10,7 +10,7 @@ import useIsBrowser from '@docusaurus/useIsBrowser'; // Similar comp to the one described here: // https://www.joshwcomeau.com/react/the-perils-of-rehydration/#abstractions -function BrowserOnly({ +export default function BrowserOnly({ children, fallback, }: { @@ -30,7 +30,5 @@ Current type: ${isValidElement(children) ? 'React element' : typeof children}`); return <>{children()}</>; } - return fallback || null; + return fallback ?? null; } - -export default BrowserOnly; diff --git a/packages/docusaurus/src/client/exports/ComponentCreator.tsx b/packages/docusaurus/src/client/exports/ComponentCreator.tsx index ad5f73c414b1..7c79b3357530 100644 --- a/packages/docusaurus/src/client/exports/ComponentCreator.tsx +++ b/packages/docusaurus/src/client/exports/ComponentCreator.tsx @@ -11,10 +11,9 @@ import Loading from '@theme/Loading'; import routesChunkNames from '@generated/routesChunkNames'; import registry from '@generated/registry'; import flat from '../flat'; +import {RouteContextProvider} from '../routeContext'; -type OptsLoader = Record<string, typeof registry[keyof typeof registry][0]>; - -function ComponentCreator( +export default function ComponentCreator( path: string, hash: string, ): ReturnType<typeof Loadable> { @@ -22,70 +21,100 @@ function ComponentCreator( if (path === '*') { return Loadable({ loading: Loading, - loader: () => import('@theme/NotFound'), + loader: () => + import('@theme/NotFound').then(({default: NotFound}) => (props) => ( + <RouteContextProvider + // Do we want a better name than native-default? + value={{plugin: {name: 'native', id: 'default'}}}> + <NotFound {...(props as never)} /> + </RouteContextProvider> + )), }); } - const chunkNamesKey = `${path}-${hash}`; - const chunkNames = routesChunkNames[chunkNamesKey]; - const optsModules: string[] = []; + const chunkNames = routesChunkNames[`${path}-${hash}`]!; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const loader: {[key: string]: () => Promise<any>} = {}; + const modules: string[] = []; const optsWebpack: string[] = []; - const optsLoader: OptsLoader = {}; - /* Prepare opts data that react-loadable needs - https://github.com/jamiebuilds/react-loadable#declaring-which-modules-are-being-loaded - Example: - - optsLoader: - { - component: () => import('./Pages.js'), - content.foo: () => import('./doc1.md'), - } - - optsModules: ['./Pages.js', './doc1.md'] - - optsWebpack: [require.resolveWeak('./Pages.js'), require.resolveWeak('./doc1.md')] - */ + // A map from prop names to chunk names. + // e.g. Suppose the plugin added this as route: + // { __comp: "...", prop: { foo: "..." }, items: ["...", "..."] } + // It will become: + // { __comp: "...", "prop.foo": "...", "items.0": "...", "items.1": ... } + // Loadable.Map will _map_ over `loader` and load each key. const flatChunkNames = flat(chunkNames); - Object.keys(flatChunkNames).forEach((key) => { - const chunkRegistry = registry[flatChunkNames[key]]; + Object.entries(flatChunkNames).forEach(([keyPath, chunkName]) => { + const chunkRegistry = registry[chunkName]; if (chunkRegistry) { - /* eslint-disable prefer-destructuring */ - optsLoader[key] = chunkRegistry[0]; - optsModules.push(chunkRegistry[1]); + // eslint-disable-next-line prefer-destructuring + loader[keyPath] = chunkRegistry[0]; + modules.push(chunkRegistry[1]); optsWebpack.push(chunkRegistry[2]); - /* eslint-enable prefer-destructuring */ } }); return Loadable.Map({ loading: Loading, - loader: optsLoader, - modules: optsModules, + loader, + modules, webpack: () => optsWebpack, render: (loaded, props) => { - // Clone the original object since we don't want to alter the original. + // `loaded` will be a map from key path (as returned from the flattened + // chunk names) to the modules loaded from the loaders. We now have to + // restore the chunk names' previous shape from this flat record. + // We do so by taking advantage of the existing `chunkNames` and replacing + // each chunk name with its loaded module, so we don't create another + // object from scratch. const loadedModules = JSON.parse(JSON.stringify(chunkNames)); - Object.keys(loaded).forEach((key) => { - let val = loadedModules; - const keyPath = key.split('.'); - for (let i = 0; i < keyPath.length - 1; i += 1) { - val = val[keyPath[i]]; + Object.entries(loaded).forEach(([keyPath, loadedModule]) => { + // JSON modules are also loaded as `{ default: ... }` (`import()` + // semantics) but we just want to pass the actual value to props. + const chunk = loadedModule.default; + // One loaded chunk can only be one of two things: a module (props) or a + // component. Modules are always JSON, so `default` always exists. This + // could only happen with a user-defined component. + if (!chunk) { + throw new Error( + `The page component at ${path} doesn't have a default export. This makes it impossible to render anything. Consider default-exporting a React component.`, + ); } - val[keyPath[keyPath.length - 1]] = loaded[key].default; - const nonDefaultKeys = Object.keys(loaded[key]).filter( - (k) => k !== 'default', - ); - if (nonDefaultKeys && nonDefaultKeys.length) { - nonDefaultKeys.forEach((nonDefaultKey) => { - val[keyPath[keyPath.length - 1]][nonDefaultKey] = - loaded[key][nonDefaultKey]; - }); + // A module can be a primitive, for example, if the user stored a string + // as a prop. However, there seems to be a bug with swc-loader's CJS + // logic, in that it would load a JSON module with content "foo" as + // `{ default: "foo", 0: "f", 1: "o", 2: "o" }`. Just to be safe, we + // first make sure that the chunk is non-primitive. + if (typeof chunk === 'object' || typeof chunk === 'function') { + Object.keys(loadedModule) + .filter((k) => k !== 'default') + .forEach((nonDefaultKey) => { + chunk[nonDefaultKey] = loadedModule[nonDefaultKey]; + }); } + // We now have this chunk prepared. Go down the key path and replace the + // chunk name with the actual chunk. + let val = loadedModules; + const keyPaths = keyPath.split('.'); + keyPaths.slice(0, -1).forEach((k) => { + val = val[k]; + }); + val[keyPaths[keyPaths.length - 1]!] = chunk; }); - const Component = loadedModules.component; - delete loadedModules.component; - return <Component {...loadedModules} {...props} />; + /* eslint-disable no-underscore-dangle */ + const Component = loadedModules.__comp; + delete loadedModules.__comp; + const routeContext = loadedModules.__context; + delete loadedModules.__context; + /* eslint-enable no-underscore-dangle */ + + // Is there any way to put this RouteContextProvider upper in the tree? + return ( + <RouteContextProvider value={routeContext}> + <Component {...loadedModules} {...props} /> + </RouteContextProvider> + ); }, }); } - -export default ComponentCreator; diff --git a/packages/docusaurus/src/client/exports/ErrorBoundary.tsx b/packages/docusaurus/src/client/exports/ErrorBoundary.tsx index a417bffd903c..9b9515898a84 100644 --- a/packages/docusaurus/src/client/exports/ErrorBoundary.tsx +++ b/packages/docusaurus/src/client/exports/ErrorBoundary.tsx @@ -15,20 +15,20 @@ interface State { error: Error | null; } -class ErrorBoundary extends React.Component<Props, State> { +export default class ErrorBoundary extends React.Component<Props, State> { constructor(props: Props) { super(props); this.state = {error: null}; } - componentDidCatch(error: Error): void { + override componentDidCatch(error: Error): void { // Catch errors in any components below and re-render with error message if (ExecutionEnvironment.canUseDOM) { this.setState({error}); } } - render(): ReactNode { + override render(): ReactNode { const {children} = this.props; const {error} = this.state; @@ -47,5 +47,3 @@ class ErrorBoundary extends React.Component<Props, State> { ); } } - -export default ErrorBoundary; diff --git a/packages/docusaurus/src/client/exports/Head.tsx b/packages/docusaurus/src/client/exports/Head.tsx index ad4d49ef98b9..3dc486b7fc1f 100644 --- a/packages/docusaurus/src/client/exports/Head.tsx +++ b/packages/docusaurus/src/client/exports/Head.tsx @@ -6,11 +6,9 @@ */ import React from 'react'; -import {Helmet} from 'react-helmet'; -import type {HeadProps} from '@docusaurus/Head'; +import {Helmet} from 'react-helmet-async'; +import type {Props} from '@docusaurus/Head'; -function Head(props: HeadProps): JSX.Element { +export default function Head(props: Props): JSX.Element { return <Helmet {...props} />; } - -export default Head; diff --git a/packages/docusaurus/src/client/exports/Interpolate.tsx b/packages/docusaurus/src/client/exports/Interpolate.tsx index 5b608470d601..076c7b1abc1b 100644 --- a/packages/docusaurus/src/client/exports/Interpolate.tsx +++ b/packages/docusaurus/src/client/exports/Interpolate.tsx @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import React, {type ReactNode} from 'react'; +import React, {isValidElement, type ReactNode} from 'react'; import type { InterpolateProps, InterpolateValues, @@ -18,10 +18,10 @@ We don't ship a markdown parser nor a feature-complete i18n library on purpose. More details here: https://github.com/facebook/docusaurus/pull/4295 */ -const ValueRegexp = /{\w+}/g; +const ValueRegexp = /\{\w+\}/g; const ValueFoundMarker = '{}'; // does not care much -// TS function overload: if all the values are plain strings, then interpolate returns a simple string +// If all the values are plain strings, then interpolate returns a simple string export function interpolate<Str extends string>( text: Str, values?: InterpolateValues<Str, string | number>, @@ -49,15 +49,14 @@ export function interpolate<Str extends string, Value extends ReactNode>( const value = values?.[key]; if (typeof value !== 'undefined') { - const element = React.isValidElement(value) + const element = isValidElement(value) ? value : // For non-React elements: basic primitive->string conversion String(value); elements.push(element); return ValueFoundMarker; - } else { - return match; // no match? add warning? } + return match; // no match? add warning? }); // No interpolation to be done: just return the text @@ -65,7 +64,7 @@ export function interpolate<Str extends string, Value extends ReactNode>( return text; } // Basic string interpolation: returns interpolated string - else if (elements.every((el) => typeof el === 'string')) { + if (elements.every((el) => typeof el === 'string')) { return processedText .split(ValueFoundMarker) .reduce<string>( @@ -75,29 +74,27 @@ export function interpolate<Str extends string, Value extends ReactNode>( ); } // JSX interpolation: returns ReactNode - else { - return processedText.split(ValueFoundMarker).reduce<ReactNode[]>( - (array, value, index) => [ - ...array, - <React.Fragment key={index}> - {value} - {elements[index]} - </React.Fragment>, - ], - [], - ); - } + return processedText.split(ValueFoundMarker).reduce<ReactNode[]>( + (array, value, index) => [ + ...array, + <React.Fragment key={index}> + {value} + {elements[index]} + </React.Fragment>, + ], + [], + ); } export default function Interpolate<Str extends string>({ children, values, -}: InterpolateProps<Str>): ReactNode { +}: InterpolateProps<Str>): JSX.Element { if (typeof children !== 'string') { console.warn('Illegal <Interpolate> children', children); throw new Error( 'The Docusaurus <Interpolate> component only accept simple string values', ); } - return interpolate(children, values); + return <>{interpolate(children, values)}</>; } diff --git a/packages/docusaurus/src/client/exports/Link.tsx b/packages/docusaurus/src/client/exports/Link.tsx index f652e11e2720..6850f237611f 100644 --- a/packages/docusaurus/src/client/exports/Link.tsx +++ b/packages/docusaurus/src/client/exports/Link.tsx @@ -5,7 +5,12 @@ * LICENSE file in the root directory of this source tree. */ -import React, {useEffect, useRef, type ComponentType} from 'react'; +import React, { + useEffect, + useImperativeHandle, + useRef, + type ComponentType, +} from 'react'; import {NavLink, Link as RRLink} from 'react-router-dom'; import useDocusaurusContext from './useDocusaurusContext'; @@ -15,7 +20,7 @@ import {useLinksCollector} from '../LinksCollector'; import {useBaseUrlUtils} from './useBaseUrl'; import {applyTrailingSlash} from '@docusaurus/utils-common'; -import type {LinkProps} from '@docusaurus/Link'; +import type {Props} from '@docusaurus/Link'; import type docusaurus from '../docusaurus'; declare global { @@ -31,21 +36,30 @@ declare global { // like "introduction" to "/baseUrl/introduction" => bad behavior to fix const shouldAddBaseUrlAutomatically = (to: string) => to.startsWith('/'); -function Link({ - isNavLink, - to, - href, - activeClassName, - isActive, - 'data-noBrokenLinkCheck': noBrokenLinkCheck, - autoAddBaseUrl = true, - ...props -}: LinkProps): JSX.Element { +function Link( + { + isNavLink, + to, + href, + activeClassName, + isActive, + 'data-noBrokenLinkCheck': noBrokenLinkCheck, + autoAddBaseUrl = true, + ...props + }: Props, + forwardedRef: React.ForwardedRef<HTMLAnchorElement>, +): JSX.Element { const { siteConfig: {trailingSlash, baseUrl}, } = useDocusaurusContext(); const {withBaseUrl} = useBaseUrlUtils(); const linksCollector = useLinksCollector(); + const innerRef = useRef<HTMLAnchorElement | null>(null); + + useImperativeHandle( + forwardedRef, + () => innerRef.current as HTMLAnchorElement, + ); // IMPORTANT: using to or href should not change anything // For example, MDX links will ALWAYS give us the href props @@ -84,9 +98,7 @@ function Link({ } const preloaded = useRef(false); - const LinkComponent = ( - isNavLink ? NavLink : RRLink - ) as ComponentType<LinkProps>; + const LinkComponent = (isNavLink ? NavLink : RRLink) as ComponentType<Props>; const IOSupported = ExecutionEnvironment.canUseIntersectionObserver; @@ -95,7 +107,7 @@ function Link({ ioRef.current = new window.IntersectionObserver((entries) => { entries.forEach((entry) => { if (el === entry.target) { - // If element is in viewport, stop listening/observing and run callback. + // If element is in viewport, stop observing and run callback. // https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API if (entry.isIntersecting || entry.intersectionRatio > 0) { ioRef.current!.unobserve(el); @@ -111,8 +123,10 @@ function Link({ }; const handleRef = (ref: HTMLAnchorElement | null) => { + innerRef.current = ref; + if (IOSupported && ref && isInternal) { - // If IO supported and element reference found, setup Observer functionality. + // If IO supported and element reference found, set up Observer. handleIntersection(ref, () => { if (targetLink != null) { window.docusaurus.prefetch(targetLink); @@ -154,6 +168,7 @@ function Link({ return isRegularHtmlLink ? ( // eslint-disable-next-line jsx-a11y/anchor-has-content <a + ref={innerRef} href={targetLink} {...(targetLinkUnprefixed && !isInternal && {target: '_blank', rel: 'noopener noreferrer'})} @@ -164,11 +179,12 @@ function Link({ {...props} onMouseEnter={onMouseEnter} innerRef={handleRef} - to={targetLink || ''} - // avoid "React does not recognize the `activeClassName` prop on a DOM element" + to={targetLink} + // avoid "React does not recognize the `activeClassName` prop on a DOM + // element" {...(isNavLink && {isActive, activeClassName})} /> ); } -export default Link; +export default React.forwardRef(Link); diff --git a/packages/docusaurus/src/client/exports/Translate.tsx b/packages/docusaurus/src/client/exports/Translate.tsx index d6acd3fc64d7..5cfbef7edb5c 100644 --- a/packages/docusaurus/src/client/exports/Translate.tsx +++ b/packages/docusaurus/src/client/exports/Translate.tsx @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import type {ReactNode} from 'react'; +import React from 'react'; import {interpolate, type InterpolateValues} from '@docusaurus/Interpolate'; import type {TranslateParam, TranslateProps} from '@docusaurus/Translate'; @@ -25,7 +25,7 @@ function getLocalizedMessage({ ); } - return codeTranslations[(id ?? message)!] ?? message ?? id; + return codeTranslations[(id ?? message)!] ?? message ?? id!; } // Imperative translation API is useful for some edge-cases: @@ -40,12 +40,13 @@ export function translate<Str extends string>( } // Maybe we'll want to improve this component with additional features -// Like toggling a translation mode that adds a little translation button near the text? +// Like toggling a translation mode that adds a little translation button near +// the text? export default function Translate<Str extends string>({ children, id, values, -}: TranslateProps<Str>): ReactNode { +}: TranslateProps<Str>): JSX.Element { if (children && typeof children !== 'string') { console.warn('Illegal <Translate> children', children); throw new Error( @@ -54,5 +55,5 @@ export default function Translate<Str extends string>({ } const localizedMessage: string = getLocalizedMessage({message: children, id}); - return interpolate(localizedMessage, values); + return <>{interpolate(localizedMessage, values)}</>; } diff --git a/packages/docusaurus/src/client/exports/__tests__/BrowserOnly.test.tsx b/packages/docusaurus/src/client/exports/__tests__/BrowserOnly.test.tsx index 89161383b5ed..e836cfc855b2 100644 --- a/packages/docusaurus/src/client/exports/__tests__/BrowserOnly.test.tsx +++ b/packages/docusaurus/src/client/exports/__tests__/BrowserOnly.test.tsx @@ -3,53 +3,110 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. + * + * @jest-environment jsdom */ +// Jest doesn't allow pragma below other comments. https://github.com/facebook/jest/issues/12573 +// eslint-disable-next-line header/header import React from 'react'; import renderer from 'react-test-renderer'; import BrowserOnly from '../BrowserOnly'; +import {Context} from '../../browserContext'; + +describe('<BrowserOnly>', () => { + const originalEnv = process.env; -jest.mock('@docusaurus/useIsBrowser', () => () => true); + beforeEach(() => { + jest.resetModules(); + process.env = {...originalEnv}; + }); + + afterAll(() => { + process.env = originalEnv; + }); -describe('BrowserOnly', () => { - test('Should reject react element children', () => { + it('rejects react element children', () => { process.env.NODE_ENV = 'development'; - expect(() => { - renderer.create( - <BrowserOnly> - {/* @ts-expect-error test */} - <span>{window.location.href}</span> - </BrowserOnly>, - ); - }).toThrowErrorMatchingInlineSnapshot(` + expect(() => + renderer + .create( + <Context.Provider value> + <BrowserOnly> + {/* @ts-expect-error test */} + <span>{window.location.href}</span> + </BrowserOnly> + </Context.Provider>, + ) + .toJSON(), + ).toThrowErrorMatchingInlineSnapshot(` "Docusaurus error: The children of <BrowserOnly> must be a \\"render function\\", e.g. <BrowserOnly>{() => <span>{window.location.href}</span>}</BrowserOnly>. Current type: React element" `); }); - test('Should reject string children', () => { + + it('rejects string children', () => { + process.env.NODE_ENV = 'development'; expect(() => { renderer.create( - // @ts-expect-error test - <BrowserOnly> </BrowserOnly>, + <Context.Provider value> + {/* @ts-expect-error test */} + <BrowserOnly> </BrowserOnly> + </Context.Provider>, ); }).toThrowErrorMatchingInlineSnapshot(` "Docusaurus error: The children of <BrowserOnly> must be a \\"render function\\", e.g. <BrowserOnly>{() => <span>{window.location.href}</span>}</BrowserOnly>. Current type: string" `); }); - test('Should accept valid children', () => { + + it('accepts valid children', () => { + expect( + renderer + .create( + <Context.Provider value> + <BrowserOnly fallback={<span>Loading</span>}> + {() => <span>{window.location.href}</span>} + </BrowserOnly> + </Context.Provider>, + ) + .toJSON(), + ).toMatchInlineSnapshot(` + <span> + https://docusaurus.io/ + </span> + `); + }); + + it('returns fallback when not in browser', () => { expect( renderer .create( - <BrowserOnly fallback={<span>Loading</span>}> - {() => <span>{window.location.href}</span>} - </BrowserOnly>, + <Context.Provider value={false}> + <BrowserOnly fallback={<span>Loading</span>}> + {() => <span>{window.location.href}</span>} + </BrowserOnly> + </Context.Provider>, ) .toJSON(), ).toMatchInlineSnapshot(` <span> - https://docusaurus.io + Loading </span> `); }); + + it('gracefully falls back', () => { + expect( + renderer + .create( + <Context.Provider value={false}> + <BrowserOnly> + {() => <span>{window.location.href}</span>} + </BrowserOnly> + </Context.Provider>, + ) + .toJSON(), + ).toMatchInlineSnapshot(`null`); + }); }); diff --git a/packages/docusaurus/src/client/exports/__tests__/Head.test.tsx b/packages/docusaurus/src/client/exports/__tests__/Head.test.tsx new file mode 100644 index 000000000000..847fa52391f7 --- /dev/null +++ b/packages/docusaurus/src/client/exports/__tests__/Head.test.tsx @@ -0,0 +1,44 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import Head from '../Head'; +import {type FilledContext, HelmetProvider} from 'react-helmet-async'; +import renderer from 'react-test-renderer'; + +describe('Head', () => { + it('does exactly what Helmet does', () => { + const context = {}; + expect( + renderer + .create( + <HelmetProvider context={context}> + <Head> + <meta property="og:type" content="article" /> + <meta property="og:description" content="some description" /> + </Head> + <Head> + <meta + property="og:description" + content="some description overridden" + /> + </Head> + <Head> + <meta + property="duplicated?" + content="this property is duplicated" + /> + <meta property="duplicated?" content="another one" /> + </Head> + <div>Content</div> + </HelmetProvider>, + ) + .toJSON(), + ).toMatchSnapshot(); + expect((context as FilledContext).helmet).toMatchSnapshot(); + }); +}); diff --git a/packages/docusaurus/src/client/exports/__tests__/Interpolate.test.tsx b/packages/docusaurus/src/client/exports/__tests__/Interpolate.test.tsx index 3a3b4ec046d2..fdf1033103de 100644 --- a/packages/docusaurus/src/client/exports/__tests__/Interpolate.test.tsx +++ b/packages/docusaurus/src/client/exports/__tests__/Interpolate.test.tsx @@ -6,15 +6,16 @@ */ import React from 'react'; -import {interpolate} from '../Interpolate'; +import renderer from 'react-test-renderer'; +import Interpolate, {interpolate} from '../Interpolate'; -describe('Interpolate', () => { - test('without placeholders', () => { +describe('interpolate', () => { + it('without placeholders', () => { const text = 'Hello how are you?'; expect(interpolate(text)).toEqual(text); }); - test('placeholders with string values', () => { + it('placeholders with string values', () => { const text = 'Hello {name} how are you {day}?'; const values = {name: 'Sébastien', day: 'today'}; expect(interpolate(text, values)).toMatchInlineSnapshot( @@ -22,7 +23,7 @@ describe('Interpolate', () => { ); }); - test('placeholders with string values 2', () => { + it('placeholders with string values 2', () => { const text = '{number} {string} {object} {array}'; const values = { number: 42, @@ -36,7 +37,7 @@ describe('Interpolate', () => { ); }); - test('placeholders with falsy values', () => { + it('placeholders with falsy values', () => { const text = '{number} {string} {boolean}'; const values = { number: 0, @@ -47,7 +48,7 @@ describe('Interpolate', () => { expect(interpolate(text, values)).toMatchInlineSnapshot(`"0 false"`); }); - test('placeholders with string values mismatch', () => { + it('placeholders with string values mismatch', () => { // Should we emit warnings in such case? const text = 'Hello {name} how are you {unprovidedValue}?'; const values = {name: 'Sébastien', extraValue: 'today'}; @@ -56,26 +57,26 @@ describe('Interpolate', () => { ); }); - test('placeholders with values not provided', () => { + it('placeholders with values not provided', () => { // Should we emit warnings in such case? const text = 'Hello {name} how are you {day}?'; expect(interpolate(text)).toEqual(text); expect(interpolate(text, {})).toEqual(text); }); - test('placeholders with JSX values', () => { + it('placeholders with JSX values', () => { const text = 'Hello {name} how are you {day}?'; const values = {name: <b>Sébastien</b>, day: <span>today</span>}; expect(interpolate(text, values)).toMatchSnapshot(); }); - test('placeholders with mixed vales', () => { + it('placeholders with mixed vales', () => { const text = 'Hello {name} how are you {day}?'; const values = {name: 'Sébastien', day: <span>today</span>}; expect(interpolate(text, values)).toMatchSnapshot(); }); - test('acceptance test', () => { + it('acceptance test', () => { const text = 'Hello {name} how are you {day}? Another {unprovidedValue}!'; const values = { name: 'Sébastien', @@ -86,3 +87,50 @@ describe('Interpolate', () => { expect(interpolate(text, values)).toMatchSnapshot(); }); }); + +describe('<Interpolate>', () => { + it('without placeholders', () => { + const text = 'Hello how are you?'; + expect(renderer.create(<Interpolate>{text}</Interpolate>).toJSON()).toEqual( + text, + ); + }); + + it('placeholders with string values', () => { + const text = 'Hello {name} how are you {day}?'; + const values = {name: 'Sébastien', day: 'today'}; + expect( + renderer + .create(<Interpolate values={values}>{text}</Interpolate>) + .toJSON(), + ).toMatchInlineSnapshot(`"Hello Sébastien how are you today?"`); + }); + + it('acceptance test', () => { + const text = 'Hello {name} how are you {day}? Another {unprovidedValue}!'; + const values = { + name: 'Sébastien', + day: <span>today</span>, + extraUselessValue1: <div>test</div>, + extraUselessValue2: 'hi', + }; + expect( + renderer + .create(<Interpolate values={values}>{text}</Interpolate>) + .toJSON(), + ).toMatchSnapshot(); + }); + + it('rejects when children is not string', () => { + expect(() => + renderer.create( + <Interpolate> + {/* @ts-expect-error: for test */} + <span>aaa</span> + </Interpolate>, + ), + ).toThrowErrorMatchingInlineSnapshot( + `"The Docusaurus <Interpolate> component only accept simple string values"`, + ); + }); +}); diff --git a/packages/docusaurus/src/client/exports/__tests__/Translate.test.tsx b/packages/docusaurus/src/client/exports/__tests__/Translate.test.tsx index 24e20d745440..28a8f90fe672 100644 --- a/packages/docusaurus/src/client/exports/__tests__/Translate.test.tsx +++ b/packages/docusaurus/src/client/exports/__tests__/Translate.test.tsx @@ -5,28 +5,75 @@ * LICENSE file in the root directory of this source tree. */ -import {translate} from '../Translate'; +import React from 'react'; +import renderer from 'react-test-renderer'; +import Translate, {translate} from '../Translate'; describe('translate', () => { - test('accept id and use it as fallback', () => { - expect(translate({id: 'some-id'})).toEqual('some-id'); + it('accepts id and uses it as fallback', () => { + expect(translate({id: 'some-id'})).toBe('some-id'); }); - test('accept message and use it as fallback', () => { - expect(translate({message: 'some-message'})).toEqual('some-message'); + it('accepts message and uses it as fallback', () => { + expect(translate({message: 'some-message'})).toBe('some-message'); }); - test('accept id+message and use message as fallback', () => { - expect(translate({id: 'some-id', message: 'some-message'})).toEqual( + it('accepts id+message and uses message as fallback', () => { + expect(translate({id: 'some-id', message: 'some-message'})).toBe( 'some-message', ); }); - test('reject when no id or message', () => { - // TODO tests are not resolving type defs correctly + it('rejects when no id or message', () => { + // TODO tests are not resolving type defs correctly. We need to include test + // files in a tsconfig file // @ts-expect-error: TS should protect when both id/message are missing expect(() => translate({})).toThrowErrorMatchingInlineSnapshot( `"Docusaurus translation declarations must have at least a translation id or a default translation message"`, ); }); }); + +describe('<Translate>', () => { + it('accepts id and uses it as fallback', () => { + expect(renderer.create(<Translate id="some-id" />).toJSON()).toBe( + 'some-id', + ); + }); + + it('accepts message and uses it as fallback', () => { + expect(renderer.create(<Translate>some-message</Translate>).toJSON()).toBe( + 'some-message', + ); + }); + + it('accepts id+message and uses message as fallback', () => { + expect( + renderer + .create(<Translate id="some-id">some-message</Translate>) + .toJSON(), + ).toBe('some-message'); + }); + + it('rejects when no id or message', () => { + expect(() => + // @ts-expect-error: TS should protect when both id/message are missing + renderer.create(<Translate />), + ).toThrowErrorMatchingInlineSnapshot( + `"Docusaurus translation declarations must have at least a translation id or a default translation message"`, + ); + }); + + it('rejects when children is not a string', () => { + expect(() => + renderer.create( + <Translate id="foo"> + {/* @ts-expect-error: for test */} + <span>aaa</span> + </Translate>, + ), + ).toThrowErrorMatchingInlineSnapshot( + `"The Docusaurus <Translate> component only accept simple string values"`, + ); + }); +}); diff --git a/packages/docusaurus/src/client/exports/__tests__/__snapshots__/Head.test.tsx.snap b/packages/docusaurus/src/client/exports/__tests__/__snapshots__/Head.test.tsx.snap new file mode 100644 index 000000000000..419daccd48d6 --- /dev/null +++ b/packages/docusaurus/src/client/exports/__tests__/__snapshots__/Head.test.tsx.snap @@ -0,0 +1,40 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Head does exactly what Helmet does 1`] = ` +<div> + Content +</div> +`; + +exports[`Head does exactly what Helmet does 2`] = ` +<html> + <head> + <meta + content="article" + data-rh={true} + property="og:type" + /> + <meta + content="some description overridden" + data-rh={true} + property="og:description" + /> + <meta + content="this property is duplicated" + data-rh={true} + property="duplicated?" + /> + <meta + content="another one" + data-rh={true} + property="duplicated?" + /> + <title + data-rh={true} + > + + + + + +`; diff --git a/packages/docusaurus/src/client/exports/__tests__/__snapshots__/Interpolate.test.tsx.snap b/packages/docusaurus/src/client/exports/__tests__/__snapshots__/Interpolate.test.tsx.snap index a42c4388ab6e..9136a8e12d29 100644 --- a/packages/docusaurus/src/client/exports/__tests__/__snapshots__/Interpolate.test.tsx.snap +++ b/packages/docusaurus/src/client/exports/__tests__/__snapshots__/Interpolate.test.tsx.snap @@ -1,7 +1,19 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Interpolate acceptance test 1`] = ` -Array [ +exports[` acceptance test 1`] = ` +[ + "Hello ", + "Sébastien", + " how are you ", + + today + , + "? Another {unprovidedValue}!", +] +`; + +exports[`interpolate acceptance test 1`] = ` +[ Hello Sébastien @@ -18,8 +30,8 @@ Array [ ] `; -exports[`Interpolate placeholders with JSX values 1`] = ` -Array [ +exports[`interpolate placeholders with JSX values 1`] = ` +[ Hello @@ -38,8 +50,8 @@ Array [ ] `; -exports[`Interpolate placeholders with mixed vales 1`] = ` -Array [ +exports[`interpolate placeholders with mixed vales 1`] = ` +[ Hello Sébastien diff --git a/packages/docusaurus/src/client/exports/__tests__/isInternalUrl.test.ts b/packages/docusaurus/src/client/exports/__tests__/isInternalUrl.test.ts index cbb52102aeab..a91476457447 100644 --- a/packages/docusaurus/src/client/exports/__tests__/isInternalUrl.test.ts +++ b/packages/docusaurus/src/client/exports/__tests__/isInternalUrl.test.ts @@ -8,39 +8,39 @@ import isInternalUrl from '../isInternalUrl'; describe('isInternalUrl', () => { - test('should be true for empty links', () => { + it('returns true for empty links', () => { expect(isInternalUrl('')).toBeTruthy(); }); - test('should be true for root relative links', () => { + it('returns true for root relative links', () => { expect(isInternalUrl('/foo/bar')).toBeTruthy(); }); - test('should be true for relative links', () => { + it('returns true for relative links', () => { expect(isInternalUrl('foo/bar')).toBeTruthy(); }); - test('should be false for HTTP links', () => { + it('returns false for HTTP links', () => { expect(isInternalUrl('http://foo.com')).toBeFalsy(); }); - test('should be false for HTTPS links', () => { + it('returns false for HTTPS links', () => { expect(isInternalUrl('https://foo.com')).toBeFalsy(); }); - test('should be false for whatever protocol links', () => { + it('returns false for whatever protocol links', () => { expect(isInternalUrl('//foo.com')).toBeFalsy(); }); - test('should be false for telephone links', () => { + it('returns false for telephone links', () => { expect(isInternalUrl('tel:+1234567890')).toBeFalsy(); }); - test('should be false for mailto links', () => { + it('returns false for mailto links', () => { expect(isInternalUrl('mailto:someone@example.com')).toBeFalsy(); }); - test('should be false for undefined links', () => { + it('returns false for undefined links', () => { expect(isInternalUrl(undefined)).toBeFalsy(); }); }); diff --git a/packages/docusaurus/src/client/exports/__tests__/useBaseUrl.test.ts b/packages/docusaurus/src/client/exports/__tests__/useBaseUrl.test.ts deleted file mode 100644 index 2aec76be8ad9..000000000000 --- a/packages/docusaurus/src/client/exports/__tests__/useBaseUrl.test.ts +++ /dev/null @@ -1,139 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import useBaseUrl, {useBaseUrlUtils} from '../useBaseUrl'; -import useDocusaurusContext from '../useDocusaurusContext'; - -jest.mock('../useDocusaurusContext', () => jest.fn(), {virtual: true}); - -const mockedContext = useDocusaurusContext; - -const forcePrepend = {forcePrependBaseUrl: true}; - -describe('useBaseUrl', () => { - test('empty base URL', () => { - mockedContext.mockImplementation(() => ({ - siteConfig: { - baseUrl: '/', - url: 'https://docusaurus.io', - }, - })); - - expect(useBaseUrl('hello')).toEqual('/hello'); - expect(useBaseUrl('/hello')).toEqual('/hello'); - expect(useBaseUrl('hello/')).toEqual('/hello/'); - expect(useBaseUrl('/hello/')).toEqual('/hello/'); - expect(useBaseUrl('hello/byebye')).toEqual('/hello/byebye'); - expect(useBaseUrl('/hello/byebye')).toEqual('/hello/byebye'); - expect(useBaseUrl('hello/byebye/')).toEqual('/hello/byebye/'); - expect(useBaseUrl('/hello/byebye/')).toEqual('/hello/byebye/'); - expect(useBaseUrl('https://github.com')).toEqual('https://github.com'); - expect(useBaseUrl('//reactjs.org')).toEqual('//reactjs.org'); - expect(useBaseUrl('//reactjs.org', forcePrepend)).toEqual('//reactjs.org'); - expect(useBaseUrl('https://site.com', forcePrepend)).toEqual( - 'https://site.com', - ); - expect(useBaseUrl('/hello/byebye', {absolute: true})).toEqual( - 'https://docusaurus.io/hello/byebye', - ); - expect(useBaseUrl('#hello')).toEqual('#hello'); - }); - - test('non-empty base URL', () => { - mockedContext.mockImplementation(() => ({ - siteConfig: { - baseUrl: '/docusaurus/', - url: 'https://docusaurus.io', - }, - })); - - expect(useBaseUrl('')).toEqual(''); - expect(useBaseUrl('hello')).toEqual('/docusaurus/hello'); - expect(useBaseUrl('/hello')).toEqual('/docusaurus/hello'); - expect(useBaseUrl('hello/')).toEqual('/docusaurus/hello/'); - expect(useBaseUrl('/hello/')).toEqual('/docusaurus/hello/'); - expect(useBaseUrl('hello/byebye')).toEqual('/docusaurus/hello/byebye'); - expect(useBaseUrl('/hello/byebye')).toEqual('/docusaurus/hello/byebye'); - expect(useBaseUrl('hello/byebye/')).toEqual('/docusaurus/hello/byebye/'); - expect(useBaseUrl('/hello/byebye/')).toEqual('/docusaurus/hello/byebye/'); - expect(useBaseUrl('https://github.com')).toEqual('https://github.com'); - expect(useBaseUrl('//reactjs.org')).toEqual('//reactjs.org'); - expect(useBaseUrl('//reactjs.org', forcePrepend)).toEqual('//reactjs.org'); - expect(useBaseUrl('/hello', forcePrepend)).toEqual('/docusaurus/hello'); - expect(useBaseUrl('https://site.com', forcePrepend)).toEqual( - 'https://site.com', - ); - expect(useBaseUrl('/hello/byebye', {absolute: true})).toEqual( - 'https://docusaurus.io/docusaurus/hello/byebye', - ); - expect(useBaseUrl('/docusaurus/')).toEqual('/docusaurus/'); - expect(useBaseUrl('/docusaurus/hello')).toEqual('/docusaurus/hello'); - expect(useBaseUrl('#hello')).toEqual('#hello'); - }); -}); - -describe('useBaseUrlUtils().withBaseUrl()', () => { - test('empty base URL', () => { - mockedContext.mockImplementation(() => ({ - siteConfig: { - baseUrl: '/', - url: 'https://docusaurus.io', - }, - })); - const {withBaseUrl} = useBaseUrlUtils(); - - expect(withBaseUrl('hello')).toEqual('/hello'); - expect(withBaseUrl('/hello')).toEqual('/hello'); - expect(withBaseUrl('hello/')).toEqual('/hello/'); - expect(withBaseUrl('/hello/')).toEqual('/hello/'); - expect(withBaseUrl('hello/byebye')).toEqual('/hello/byebye'); - expect(withBaseUrl('/hello/byebye')).toEqual('/hello/byebye'); - expect(withBaseUrl('hello/byebye/')).toEqual('/hello/byebye/'); - expect(withBaseUrl('/hello/byebye/')).toEqual('/hello/byebye/'); - expect(withBaseUrl('https://github.com')).toEqual('https://github.com'); - expect(withBaseUrl('//reactjs.org')).toEqual('//reactjs.org'); - expect(withBaseUrl('//reactjs.org', forcePrepend)).toEqual('//reactjs.org'); - expect(withBaseUrl('https://site.com', forcePrepend)).toEqual( - 'https://site.com', - ); - expect(withBaseUrl('/hello/byebye', {absolute: true})).toEqual( - 'https://docusaurus.io/hello/byebye', - ); - expect(withBaseUrl('#hello')).toEqual('#hello'); - }); - - test('non-empty base URL', () => { - mockedContext.mockImplementation(() => ({ - siteConfig: { - baseUrl: '/docusaurus/', - url: 'https://docusaurus.io', - }, - })); - const {withBaseUrl} = useBaseUrlUtils(); - - expect(withBaseUrl('hello')).toEqual('/docusaurus/hello'); - expect(withBaseUrl('/hello')).toEqual('/docusaurus/hello'); - expect(withBaseUrl('hello/')).toEqual('/docusaurus/hello/'); - expect(withBaseUrl('/hello/')).toEqual('/docusaurus/hello/'); - expect(withBaseUrl('hello/byebye')).toEqual('/docusaurus/hello/byebye'); - expect(withBaseUrl('/hello/byebye')).toEqual('/docusaurus/hello/byebye'); - expect(withBaseUrl('hello/byebye/')).toEqual('/docusaurus/hello/byebye/'); - expect(withBaseUrl('/hello/byebye/')).toEqual('/docusaurus/hello/byebye/'); - expect(withBaseUrl('https://github.com')).toEqual('https://github.com'); - expect(withBaseUrl('//reactjs.org')).toEqual('//reactjs.org'); - expect(withBaseUrl('//reactjs.org', forcePrepend)).toEqual('//reactjs.org'); - expect(withBaseUrl('https://site.com', forcePrepend)).toEqual( - 'https://site.com', - ); - expect(withBaseUrl('/hello/byebye', {absolute: true})).toEqual( - 'https://docusaurus.io/docusaurus/hello/byebye', - ); - expect(withBaseUrl('/docusaurus/')).toEqual('/docusaurus/'); - expect(withBaseUrl('/docusaurus/hello')).toEqual('/docusaurus/hello'); - expect(withBaseUrl('#hello')).toEqual('#hello'); - }); -}); diff --git a/packages/docusaurus/src/client/exports/__tests__/useBaseUrl.test.tsx b/packages/docusaurus/src/client/exports/__tests__/useBaseUrl.test.tsx new file mode 100644 index 000000000000..5478f7c358f9 --- /dev/null +++ b/packages/docusaurus/src/client/exports/__tests__/useBaseUrl.test.tsx @@ -0,0 +1,152 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import {renderHook} from '@testing-library/react-hooks'; +import useBaseUrl, {useBaseUrlUtils} from '../useBaseUrl'; +import {Context} from '../../docusaurusContext'; +import type {DocusaurusContext} from '@docusaurus/types'; +import type {BaseUrlOptions} from '@docusaurus/useBaseUrl'; + +const forcePrepend = {forcePrependBaseUrl: true}; + +describe('useBaseUrl', () => { + const createUseBaseUrlMock = + (context: DocusaurusContext) => (url: string, options?: BaseUrlOptions) => + renderHook(() => useBaseUrl(url, options), { + wrapper: ({children}) => ( + {children} + ), + }).result.current; + it('works with empty base URL', () => { + const mockUseBaseUrl = createUseBaseUrlMock({ + siteConfig: { + baseUrl: '/', + url: 'https://docusaurus.io', + }, + }); + + expect(mockUseBaseUrl('hello')).toBe('/hello'); + expect(mockUseBaseUrl('/hello')).toBe('/hello'); + expect(mockUseBaseUrl('hello/')).toBe('/hello/'); + expect(mockUseBaseUrl('/hello/')).toBe('/hello/'); + expect(mockUseBaseUrl('hello/foo')).toBe('/hello/foo'); + expect(mockUseBaseUrl('/hello/foo')).toBe('/hello/foo'); + expect(mockUseBaseUrl('hello/foo/')).toBe('/hello/foo/'); + expect(mockUseBaseUrl('/hello/foo/')).toBe('/hello/foo/'); + expect(mockUseBaseUrl('https://github.com')).toBe('https://github.com'); + expect(mockUseBaseUrl('//reactjs.org')).toBe('//reactjs.org'); + expect(mockUseBaseUrl('//reactjs.org', forcePrepend)).toBe('//reactjs.org'); + expect(mockUseBaseUrl('https://site.com', forcePrepend)).toBe( + 'https://site.com', + ); + expect(mockUseBaseUrl('/hello/foo', {absolute: true})).toBe( + 'https://docusaurus.io/hello/foo', + ); + expect(mockUseBaseUrl('#hello')).toBe('#hello'); + }); + + it('works with non-empty base URL', () => { + const mockUseBaseUrl = createUseBaseUrlMock({ + siteConfig: { + baseUrl: '/docusaurus/', + url: 'https://docusaurus.io', + }, + }); + + expect(mockUseBaseUrl('')).toBe(''); + expect(mockUseBaseUrl('hello')).toBe('/docusaurus/hello'); + expect(mockUseBaseUrl('/hello')).toBe('/docusaurus/hello'); + expect(mockUseBaseUrl('hello/')).toBe('/docusaurus/hello/'); + expect(mockUseBaseUrl('/hello/')).toBe('/docusaurus/hello/'); + expect(mockUseBaseUrl('hello/foo')).toBe('/docusaurus/hello/foo'); + expect(mockUseBaseUrl('/hello/foo')).toBe('/docusaurus/hello/foo'); + expect(mockUseBaseUrl('hello/foo/')).toBe('/docusaurus/hello/foo/'); + expect(mockUseBaseUrl('/hello/foo/')).toBe('/docusaurus/hello/foo/'); + expect(mockUseBaseUrl('https://github.com')).toBe('https://github.com'); + expect(mockUseBaseUrl('//reactjs.org')).toBe('//reactjs.org'); + expect(mockUseBaseUrl('//reactjs.org', forcePrepend)).toBe('//reactjs.org'); + expect(mockUseBaseUrl('/hello', forcePrepend)).toBe('/docusaurus/hello'); + expect(mockUseBaseUrl('https://site.com', forcePrepend)).toBe( + 'https://site.com', + ); + expect(mockUseBaseUrl('/hello/foo', {absolute: true})).toBe( + 'https://docusaurus.io/docusaurus/hello/foo', + ); + expect(mockUseBaseUrl('/docusaurus')).toBe('/docusaurus/'); + expect(mockUseBaseUrl('/docusaurus/')).toBe('/docusaurus/'); + expect(mockUseBaseUrl('/docusaurus/hello')).toBe('/docusaurus/hello'); + expect(mockUseBaseUrl('#hello')).toBe('#hello'); + }); +}); + +describe('useBaseUrlUtils().withBaseUrl()', () => { + const mockUseBaseUrlUtils = (context: DocusaurusContext) => + renderHook(() => useBaseUrlUtils(), { + wrapper: ({children}) => ( + {children} + ), + }).result.current; + it('empty base URL', () => { + const {withBaseUrl} = mockUseBaseUrlUtils({ + siteConfig: { + baseUrl: '/', + url: 'https://docusaurus.io', + }, + }); + + expect(withBaseUrl('hello')).toBe('/hello'); + expect(withBaseUrl('/hello')).toBe('/hello'); + expect(withBaseUrl('hello/')).toBe('/hello/'); + expect(withBaseUrl('/hello/')).toBe('/hello/'); + expect(withBaseUrl('hello/foo')).toBe('/hello/foo'); + expect(withBaseUrl('/hello/foo')).toBe('/hello/foo'); + expect(withBaseUrl('hello/foo/')).toBe('/hello/foo/'); + expect(withBaseUrl('/hello/foo/')).toBe('/hello/foo/'); + expect(withBaseUrl('https://github.com')).toBe('https://github.com'); + expect(withBaseUrl('//reactjs.org')).toBe('//reactjs.org'); + expect(withBaseUrl('//reactjs.org', forcePrepend)).toBe('//reactjs.org'); + expect(withBaseUrl('https://site.com', forcePrepend)).toBe( + 'https://site.com', + ); + expect(withBaseUrl('/hello/foo', {absolute: true})).toBe( + 'https://docusaurus.io/hello/foo', + ); + expect(withBaseUrl('#hello')).toBe('#hello'); + }); + + it('non-empty base URL', () => { + const {withBaseUrl} = mockUseBaseUrlUtils({ + siteConfig: { + baseUrl: '/docusaurus/', + url: 'https://docusaurus.io', + }, + }); + + expect(withBaseUrl('hello')).toBe('/docusaurus/hello'); + expect(withBaseUrl('/hello')).toBe('/docusaurus/hello'); + expect(withBaseUrl('hello/')).toBe('/docusaurus/hello/'); + expect(withBaseUrl('/hello/')).toBe('/docusaurus/hello/'); + expect(withBaseUrl('hello/foo')).toBe('/docusaurus/hello/foo'); + expect(withBaseUrl('/hello/foo')).toBe('/docusaurus/hello/foo'); + expect(withBaseUrl('hello/foo/')).toBe('/docusaurus/hello/foo/'); + expect(withBaseUrl('/hello/foo/')).toBe('/docusaurus/hello/foo/'); + expect(withBaseUrl('https://github.com')).toBe('https://github.com'); + expect(withBaseUrl('//reactjs.org')).toBe('//reactjs.org'); + expect(withBaseUrl('//reactjs.org', forcePrepend)).toBe('//reactjs.org'); + expect(withBaseUrl('https://site.com', forcePrepend)).toBe( + 'https://site.com', + ); + expect(withBaseUrl('/hello/foo', {absolute: true})).toBe( + 'https://docusaurus.io/docusaurus/hello/foo', + ); + expect(withBaseUrl('/docusaurus')).toBe('/docusaurus/'); + expect(withBaseUrl('/docusaurus/')).toBe('/docusaurus/'); + expect(withBaseUrl('/docusaurus/hello')).toBe('/docusaurus/hello'); + expect(withBaseUrl('#hello')).toBe('#hello'); + }); +}); diff --git a/packages/docusaurus/src/client/exports/__tests__/useGlobalData.test.tsx b/packages/docusaurus/src/client/exports/__tests__/useGlobalData.test.tsx new file mode 100644 index 000000000000..3e5d370c4e0c --- /dev/null +++ b/packages/docusaurus/src/client/exports/__tests__/useGlobalData.test.tsx @@ -0,0 +1,122 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import {renderHook} from '@testing-library/react-hooks'; +import useGlobalData, { + useAllPluginInstancesData, + usePluginData, +} from '../useGlobalData'; +import {Context} from '../../docusaurusContext'; + +describe('useGlobalData', () => { + it('returns global data from context', () => { + expect( + renderHook(() => useGlobalData(), { + wrapper: ({children}) => ( + // eslint-disable-next-line react/jsx-no-constructed-context-values + + {children} + + ), + }).result.current, + ).toEqual({foo: 'bar'}); + }); + + it('throws when global data not found', () => { + // Can it actually happen? + expect( + () => + renderHook(() => useGlobalData(), { + wrapper: ({children}) => ( + // eslint-disable-next-line react/jsx-no-constructed-context-values + {children} + ), + }).result.current, + ).toThrowErrorMatchingInlineSnapshot(`"Docusaurus global data not found."`); + }); +}); + +describe('useAllPluginInstancesData', () => { + it('returns plugin data namespace', () => { + expect( + renderHook(() => useAllPluginInstancesData('foo'), { + wrapper: ({children}) => ( + + {children} + + ), + }).result.current, + ).toEqual({default: 'default', bar: 'bar'}); + }); + + it('throws when plugin data not found', () => { + expect( + () => + renderHook(() => useAllPluginInstancesData('bar'), { + wrapper: ({children}) => ( + + {children} + + ), + }).result.current, + ).toThrowErrorMatchingInlineSnapshot( + `"Docusaurus plugin global data not found for \\"bar\\" plugin."`, + ); + }); +}); + +describe('usePluginData', () => { + it('returns plugin instance data', () => { + expect( + renderHook(() => usePluginData('foo', 'bar'), { + wrapper: ({children}) => ( + + {children} + + ), + }).result.current, + ).toBe('bar'); + }); + + it('defaults to default ID', () => { + expect( + renderHook(() => usePluginData('foo'), { + wrapper: ({children}) => ( + + {children} + + ), + }).result.current, + ).toBe('default'); + }); + + it('throws when plugin instance data not found', () => { + expect( + () => + renderHook(() => usePluginData('foo', 'baz'), { + wrapper: ({children}) => ( + + {children} + + ), + }).result.current, + ).toThrowErrorMatchingInlineSnapshot( + `"Docusaurus plugin global data not found for \\"foo\\" plugin with id \\"baz\\"."`, + ); + }); +}); diff --git a/packages/docusaurus/src/client/exports/__tests__/useRouteContext.test.tsx b/packages/docusaurus/src/client/exports/__tests__/useRouteContext.test.tsx new file mode 100644 index 000000000000..d1c469cdc0ed --- /dev/null +++ b/packages/docusaurus/src/client/exports/__tests__/useRouteContext.test.tsx @@ -0,0 +1,38 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import {renderHook} from '@testing-library/react-hooks/server'; +import {RouteContextProvider} from '../../routeContext'; +import useRouteContext from '../useRouteContext'; + +describe('useRouteContext', () => { + it('throws when there is no route context at all', () => { + expect( + () => renderHook(() => useRouteContext()).result.current, + ).toThrowErrorMatchingInlineSnapshot( + `"Unexpected: no Docusaurus route context found"`, + ); + }); + it('returns merged route contexts', () => { + expect( + renderHook(() => useRouteContext(), { + wrapper: ({children}) => ( + + + {children} + + + ), + }).result.current, + ).toEqual({ + data: {some: 'data', someMore: 'data'}, + plugin: {id: 'test', name: 'test'}, + }); + }); +}); diff --git a/packages/docusaurus/src/client/exports/isInternalUrl.ts b/packages/docusaurus/src/client/exports/isInternalUrl.ts index eb503cb54c5e..f3158e6e9859 100644 --- a/packages/docusaurus/src/client/exports/isInternalUrl.ts +++ b/packages/docusaurus/src/client/exports/isInternalUrl.ts @@ -6,7 +6,7 @@ */ export function hasProtocol(url: string): boolean { - return /^(\w*:|\/\/)/.test(url) === true; + return /^(?:\w*:|\/\/)/.test(url) === true; } export default function isInternalUrl(url?: string): boolean { diff --git a/packages/docusaurus/src/client/exports/renderRoutes.ts b/packages/docusaurus/src/client/exports/renderRoutes.ts index 06674f1f6502..144ae6efd204 100644 --- a/packages/docusaurus/src/client/exports/renderRoutes.ts +++ b/packages/docusaurus/src/client/exports/renderRoutes.ts @@ -5,6 +5,4 @@ * LICENSE file in the root directory of this source tree. */ -import {renderRoutes} from 'react-router-config'; - -export default renderRoutes; +export {renderRoutes as default} from 'react-router-config'; diff --git a/packages/docusaurus/src/client/exports/router.ts b/packages/docusaurus/src/client/exports/router.ts index 18cfa08f7d95..453530b353dd 100644 --- a/packages/docusaurus/src/client/exports/router.ts +++ b/packages/docusaurus/src/client/exports/router.ts @@ -5,4 +5,4 @@ * LICENSE file in the root directory of this source tree. */ -export * from 'react-router-dom'; +export {useHistory, useLocation, Redirect, matchPath} from 'react-router-dom'; diff --git a/packages/docusaurus/src/client/exports/useBaseUrl.ts b/packages/docusaurus/src/client/exports/useBaseUrl.ts index a709e293809f..5ae4640a150c 100644 --- a/packages/docusaurus/src/client/exports/useBaseUrl.ts +++ b/packages/docusaurus/src/client/exports/useBaseUrl.ts @@ -33,6 +33,12 @@ function addBaseUrl( return baseUrl + url.replace(/^\//, ''); } + // /baseUrl -> /baseUrl/ + // https://github.com/facebook/docusaurus/issues/6315 + if (url === baseUrl.replace(/\/$/, '')) { + return baseUrl; + } + // We should avoid adding the baseurl twice if it's already there const shouldAddBaseUrl = !url.startsWith(baseUrl); diff --git a/packages/docusaurus/src/client/exports/useDocusaurusContext.ts b/packages/docusaurus/src/client/exports/useDocusaurusContext.ts index 3f1d8094b965..0f1cb4b3af9b 100644 --- a/packages/docusaurus/src/client/exports/useDocusaurusContext.ts +++ b/packages/docusaurus/src/client/exports/useDocusaurusContext.ts @@ -6,11 +6,9 @@ */ import {useContext} from 'react'; -import {Context} from './docusaurusContext'; +import {Context} from '../docusaurusContext'; import type {DocusaurusContext} from '@docusaurus/types'; -function useDocusaurusContext(): DocusaurusContext { +export default function useDocusaurusContext(): DocusaurusContext { return useContext(Context); } - -export default useDocusaurusContext; diff --git a/packages/docusaurus/src/client/exports/useGlobalData.ts b/packages/docusaurus/src/client/exports/useGlobalData.ts index 155ee22e6fe6..598f87dfe972 100644 --- a/packages/docusaurus/src/client/exports/useGlobalData.ts +++ b/packages/docusaurus/src/client/exports/useGlobalData.ts @@ -7,8 +7,9 @@ import useDocusaurusContext from './useDocusaurusContext'; import {DEFAULT_PLUGIN_ID} from './constants'; +import type {GlobalData} from '@docusaurus/types'; -export default function useGlobalData(): Record { +export default function useGlobalData(): GlobalData { const {globalData} = useDocusaurusContext(); if (!globalData) { throw new Error('Docusaurus global data not found.'); @@ -16,9 +17,9 @@ export default function useGlobalData(): Record { return globalData; } -export function useAllPluginInstancesData( +export function useAllPluginInstancesData( pluginName: string, -): Record { +): GlobalData[string] { const globalData = useGlobalData(); const pluginGlobalData = globalData[pluginName]; if (!pluginGlobalData) { @@ -26,13 +27,13 @@ export function useAllPluginInstancesData( `Docusaurus plugin global data not found for "${pluginName}" plugin.`, ); } - return pluginGlobalData as Record; + return pluginGlobalData; } -export function usePluginData( +export function usePluginData( pluginName: string, pluginId: string = DEFAULT_PLUGIN_ID, -): T { +): GlobalData[string][string] { const pluginGlobalData = useAllPluginInstancesData(pluginName); const pluginInstanceGlobalData = pluginGlobalData[pluginId]; if (!pluginInstanceGlobalData) { @@ -40,5 +41,5 @@ export function usePluginData( `Docusaurus plugin global data not found for "${pluginName}" plugin with id "${pluginId}".`, ); } - return pluginInstanceGlobalData as T; + return pluginInstanceGlobalData; } diff --git a/packages/docusaurus/src/client/exports/useIsBrowser.ts b/packages/docusaurus/src/client/exports/useIsBrowser.ts index e1e7caff51ed..ce1ebb739296 100644 --- a/packages/docusaurus/src/client/exports/useIsBrowser.ts +++ b/packages/docusaurus/src/client/exports/useIsBrowser.ts @@ -6,7 +6,7 @@ */ import {useContext} from 'react'; -import {Context} from './browserContext'; +import {Context} from '../browserContext'; export default function useIsBrowser(): boolean { return useContext(Context); diff --git a/packages/docusaurus/src/client/exports/useRouteContext.tsx b/packages/docusaurus/src/client/exports/useRouteContext.tsx new file mode 100644 index 000000000000..9ba1a1f57039 --- /dev/null +++ b/packages/docusaurus/src/client/exports/useRouteContext.tsx @@ -0,0 +1,18 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import type {PluginRouteContext} from '@docusaurus/types'; +import {Context} from '../routeContext'; + +export default function useRouteContext(): PluginRouteContext { + const context = React.useContext(Context); + if (!context) { + throw new Error('Unexpected: no Docusaurus route context found'); + } + return context; +} diff --git a/packages/docusaurus/src/client/flat.ts b/packages/docusaurus/src/client/flat.ts index b3a7ecb71f36..c015d106955d 100644 --- a/packages/docusaurus/src/client/flat.ts +++ b/packages/docusaurus/src/client/flat.ts @@ -5,18 +5,28 @@ * LICENSE file in the root directory of this source tree. */ -import type {RouteChunksTree} from '@docusaurus/types'; +import type {ChunkNames} from '@docusaurus/types'; -const isTree = (x: string | RouteChunksTree): x is RouteChunksTree => +type Chunk = ChunkNames[string]; +type Tree = Exclude; + +const isTree = (x: Chunk): x is Tree => typeof x === 'object' && !!x && Object.keys(x).length > 0; -function flat(target: RouteChunksTree): Record { +/** + * Takes a tree, and flattens it into a map of keyPath -> value. + * + * ```js + * flat({ a: { b: 1 } }) === { "a.b": 1 }; + * flat({ a: [1, 2] }) === { "a.0": 1, "a.1": 2 }; + * ``` + */ +export default function flat(target: ChunkNames): {[keyPath: string]: string} { const delimiter = '.'; - const output: Record = {}; + const output: {[keyPath: string]: string} = {}; - function step(object: RouteChunksTree, prefix?: string | number) { - Object.keys(object).forEach((key: string | number) => { - const value = object[key]; + function step(object: Tree, prefix?: string | number) { + Object.entries(object).forEach(([key, value]) => { const newKey = prefix ? `${prefix}${delimiter}${key}` : key; if (isTree(value)) { @@ -30,5 +40,3 @@ function flat(target: RouteChunksTree): Record { step(target); return output; } - -export default flat; diff --git a/packages/docusaurus/src/client/normalizeLocation.ts b/packages/docusaurus/src/client/normalizeLocation.ts index 19aa8a3895aa..94a74da70044 100644 --- a/packages/docusaurus/src/client/normalizeLocation.ts +++ b/packages/docusaurus/src/client/normalizeLocation.ts @@ -5,12 +5,12 @@ * LICENSE file in the root directory of this source tree. */ -import type {Location} from '@docusaurus/history'; +import type {Location} from 'history'; // Memoize previously normalized pathnames. -const pathnames: Record = {}; +const pathnames: {[rawPathname: string]: string} = {}; -function normalizeLocation(location: T): T { +export default function normalizeLocation(location: T): T { if (pathnames[location.pathname]) { return { ...location, @@ -18,12 +18,8 @@ function normalizeLocation(location: T): T { }; } - let pathname = location.pathname || '/'; - pathname = pathname.trim().replace(/\/index\.html$/, ''); - - if (pathname === '') { - pathname = '/'; - } + const pathname = + location.pathname.trim().replace(/\/index\.html$/, '') || '/'; pathnames[location.pathname] = pathname; @@ -32,5 +28,3 @@ function normalizeLocation(location: T): T { pathname, }; } - -export default normalizeLocation; diff --git a/packages/docusaurus/src/client/prefetch.ts b/packages/docusaurus/src/client/prefetch.ts index d86ec70bda11..5c28025eef1d 100644 --- a/packages/docusaurus/src/client/prefetch.ts +++ b/packages/docusaurus/src/client/prefetch.ts @@ -5,21 +5,13 @@ * LICENSE file in the root directory of this source tree. */ -function support(feature: string) { - if (typeof document === 'undefined') { - return false; - } - - const fakeLink = document.createElement('link'); +function supports(feature: string) { try { - if (fakeLink.relList && typeof fakeLink.relList.supports === 'function') { - return fakeLink.relList.supports(feature); - } + const fakeLink = document.createElement('link'); + return fakeLink.relList?.supports?.(feature); } catch (err) { return false; } - - return false; } function linkPrefetchStrategy(url: string) { @@ -37,9 +29,9 @@ function linkPrefetchStrategy(url: string) { link.onerror = reject; const parentElement = - document.getElementsByTagName('head')[0] || - document.getElementsByName('script')[0].parentNode; - parentElement.appendChild(link); + document.getElementsByTagName('head')[0] ?? + document.getElementsByName('script')[0]?.parentNode; + parentElement?.appendChild(link); }); } @@ -61,13 +53,13 @@ function xhrPrefetchStrategy(url: string): Promise { }); } -const supportedPrefetchStrategy = support('prefetch') +const supportedPrefetchStrategy = supports('prefetch') ? linkPrefetchStrategy : xhrPrefetchStrategy; -const preFetched: Record = {}; +const preFetched: {[url: string]: boolean} = {}; -function prefetch(url: string): Promise { +export default function prefetch(url: string): Promise { return new Promise((resolve) => { if (preFetched[url]) { resolve(); @@ -82,5 +74,3 @@ function prefetch(url: string): Promise { .catch(() => {}); // 404s are logged to the console anyway. }); } - -export default prefetch; diff --git a/packages/docusaurus/src/client/preload.ts b/packages/docusaurus/src/client/preload.ts index 8163807724c4..09405b4524f2 100644 --- a/packages/docusaurus/src/client/preload.ts +++ b/packages/docusaurus/src/client/preload.ts @@ -9,7 +9,8 @@ import {matchRoutes, type RouteConfig} from 'react-router-config'; /** * Helper function to make sure all async components for that particular route - * is preloaded before rendering. This is especially useful to avoid loading screens. + * is preloaded before rendering. This is especially useful to avoid loading + * screens. * * @param routes react-router-config * @param pathname the route pathname, example: /docs/installation @@ -22,16 +23,7 @@ export default function preload( const matches = matchRoutes(routes, pathname); return Promise.all( - matches.map((match) => { - const {component} = match.route; - - // @ts-expect-error: ComponentCreator injected this method. - if (component && component.preload) { - // @ts-expect-error: checked above. - return component.preload(); - } - - return undefined; - }), + // @ts-expect-error: ComponentCreator injected this method. + matches.map((match) => match.route.component?.preload?.()), ); } diff --git a/packages/docusaurus/src/client/routeContext.tsx b/packages/docusaurus/src/client/routeContext.tsx new file mode 100644 index 000000000000..1b285859b841 --- /dev/null +++ b/packages/docusaurus/src/client/routeContext.tsx @@ -0,0 +1,57 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, {useMemo, type ReactNode} from 'react'; +import type {PluginRouteContext, RouteContext} from '@docusaurus/types'; + +export const Context = React.createContext(null); + +function mergeContexts({ + parent, + value, +}: { + parent: PluginRouteContext | null; + value: RouteContext | null; +}): PluginRouteContext { + if (!parent) { + if (!value) { + throw new Error('Unexpected: no Docusaurus route context found'); + } else if (!('plugin' in value)) { + throw new Error( + 'Unexpected: Docusaurus topmost route context has no `plugin` attribute', + ); + } + return value; + } + + // TODO deep merge this + const data = {...parent.data, ...value?.data}; + + return { + // nested routes are not supposed to override plugin attribute + plugin: parent.plugin, + data, + }; +} + +export function RouteContextProvider({ + children, + value, +}: { + children: ReactNode; + // Only topmost route has the `plugin` attribute + value: PluginRouteContext | RouteContext | null; +}): JSX.Element { + const parent = React.useContext(Context); + + const mergedValue = useMemo( + () => mergeContexts({parent, value}), + [parent, value], + ); + + return {children}; +} diff --git a/packages/docusaurus/src/client/serverEntry.tsx b/packages/docusaurus/src/client/serverEntry.tsx index b6349f49fccd..d8cbfee47d33 100644 --- a/packages/docusaurus/src/client/serverEntry.tsx +++ b/packages/docusaurus/src/client/serverEntry.tsx @@ -9,7 +9,7 @@ import * as eta from 'eta'; import React from 'react'; import {StaticRouter} from 'react-router-dom'; import ReactDOMServer from 'react-dom/server'; -import {Helmet} from 'react-helmet'; +import {HelmetProvider, type FilledContext} from 'react-helmet-async'; import {getBundles, type Manifest} from 'react-loadable-ssr-addon-v5-slorber'; import Loadable from 'react-loadable'; @@ -21,17 +21,17 @@ import preload from './preload'; import App from './App'; import { createStatefulLinksCollector, - ProvideLinksCollector, + LinksCollectorProvider, } from './LinksCollector'; import logger from '@docusaurus/logger'; // eslint-disable-next-line no-restricted-imports -import {memoize} from 'lodash'; +import _ from 'lodash'; import type {Locals} from '@slorber/static-site-generator-webpack-plugin'; // eslint-disable-next-line @typescript-eslint/no-var-requires const packageJson = require('../../package.json'); -const getCompiledSSRTemplate = memoize((template: string) => +const getCompiledSSRTemplate = _.memoize((template: string) => eta.compile(template.trim(), { rmWhitespace: true, }), @@ -47,26 +47,23 @@ export default async function render( ): Promise { try { return await doRender(locals); - } catch (e) { - logger.error`Docusaurus Node/SSR could not render static page with path path=${ - locals.path - } because of following error: -${(e as Error).stack!}`; + } catch (err) { + logger.error`Docusaurus server-side rendering could not render static page with path path=${locals.path}.`; const isNotDefinedErrorRegex = - /(window|document|localStorage|navigator|alert|location|buffer|self) is not defined/i; + /(?:window|document|localStorage|navigator|alert|location|buffer|self) is not defined/i; - if (isNotDefinedErrorRegex.test((e as Error).message)) { + if (isNotDefinedErrorRegex.test((err as Error).message)) { logger.info`It looks like you are using code that should run on the client-side only. -To get around it, try using code=${''} (path=${'https://docusaurus.io/docs/docusaurus-core/#browseronly'}) or code=${'ExecutionEnvironment'} (path=${'https://docusaurus.io/docs/docusaurus-core/#executionenvironment'}). +To get around it, try using code=${''} (url=${'https://docusaurus.io/docs/docusaurus-core/#browseronly'}) or code=${'ExecutionEnvironment'} (url=${'https://docusaurus.io/docs/docusaurus-core/#executionenvironment'}). It might also require to wrap your client code in code=${'useEffect'} hook and/or import a third-party library dynamically (if any).`; } - throw new Error('Server-side rendering fails due to the error above.'); + throw err; } } -// Renderer for static-site-generator-webpack-plugin (async rendering via promises). +// Renderer for static-site-generator-webpack-plugin (async rendering). async function doRender(locals: Locals & {path: string}) { const { routesLocation, @@ -78,24 +75,27 @@ async function doRender(locals: Locals & {path: string}) { ssrTemplate, noIndex, } = locals; - const location = routesLocation[locals.path]; + const location = routesLocation[locals.path]!; await preload(routes, location); const modules = new Set(); - const context = {}; + const routerContext = {}; + const helmetContext = {}; const linksCollector = createStatefulLinksCollector(); const appHtml = ReactDOMServer.renderToString( modules.add(moduleName)}> - - - - - + + + + + + + , ); onLinksCollected(location, linksCollector.getCollectedLinks()); - const helmet = Helmet.renderStatic(); + const {helmet} = helmetContext as FilledContext; const htmlAttributes = helmet.htmlAttributes.toString(); const bodyAttributes = helmet.bodyAttributes.toString(); const metaStrings = [ @@ -116,14 +116,14 @@ async function doRender(locals: Locals & {path: string}) { // manifest information. const modulesToBeLoaded = [...manifest.entrypoints, ...Array.from(modules)]; const bundles = getBundles(manifest, modulesToBeLoaded); - const stylesheets = (bundles.css || []).map((b) => b.file); - const scripts = (bundles.js || []).map((b) => b.file); + const stylesheets = (bundles.css ?? []).map((b) => b.file); + const scripts = (bundles.js ?? []).map((b) => b.file); const renderedHtml = renderSSRTemplate(ssrTemplate, { appHtml, baseUrl, - htmlAttributes: htmlAttributes || '', - bodyAttributes: bodyAttributes || '', + htmlAttributes, + bodyAttributes, headTags, preBodyTags, postBodyTags, @@ -145,11 +145,8 @@ async function doRender(locals: Locals & {path: string}) { useShortDoctype: true, minifyJS: true, }); - } catch (e) { - logger.error`Minification of page path=${ - locals.path - } failed because of following error: -${(e as Error).stack!}`; - throw e; + } catch (err) { + logger.error`Minification of page path=${locals.path} failed.`; + throw err; } } diff --git a/packages/docusaurus/src/client/theme-fallback/Error/index.js b/packages/docusaurus/src/client/theme-fallback/Error/index.tsx similarity index 69% rename from packages/docusaurus/src/client/theme-fallback/Error/index.js rename to packages/docusaurus/src/client/theme-fallback/Error/index.tsx index 1c6dd9a7448e..de8ded15b7df 100644 --- a/packages/docusaurus/src/client/theme-fallback/Error/index.js +++ b/packages/docusaurus/src/client/theme-fallback/Error/index.tsx @@ -8,8 +8,10 @@ import React from 'react'; import Layout from '@theme/Layout'; import ErrorBoundary from '@docusaurus/ErrorBoundary'; +import type {Props} from '@theme/Error'; +import Head from '@docusaurus/Head'; -function ErrorDisplay({error, tryAgain}) { +function ErrorDisplay({error, tryAgain}: Props): JSX.Element { return (
      }> - + + Page Error + + ); } - -export default Error; diff --git a/packages/docusaurus/src/client/theme-fallback/Layout/index.js b/packages/docusaurus/src/client/theme-fallback/Layout/index.js deleted file mode 100644 index 93254bf0e01d..000000000000 --- a/packages/docusaurus/src/client/theme-fallback/Layout/index.js +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import React from 'react'; -import Head from '@docusaurus/Head'; -import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; -import useBaseUrl from '@docusaurus/useBaseUrl'; - -function Layout(props) { - const context = useDocusaurusContext(); - const {siteConfig = {}} = context; - const {favicon, tagline = '', title: defaultTitle} = siteConfig; - const {children, title, description} = props; - const faviconUrl = useBaseUrl(favicon); - return ( - <> - - {title && {`${title} · ${tagline}`}} - {favicon && } - {description && } - {description && ( - - )} - - {children} - - ); -} - -export default Layout; diff --git a/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/plugin-foo-bar.js b/packages/docusaurus/src/client/theme-fallback/Layout/index.tsx similarity index 53% rename from packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/plugin-foo-bar.js rename to packages/docusaurus/src/client/theme-fallback/Layout/index.tsx index 52d26e15a638..e897396623b4 100644 --- a/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/plugin-foo-bar.js +++ b/packages/docusaurus/src/client/theme-fallback/Layout/index.tsx @@ -5,11 +5,9 @@ * LICENSE file in the root directory of this source tree. */ -module.exports = function() { - return { - name: 'plugin-foo-bar', - getClientModules() { - return ['foo', 'bar']; - }, - }; -}; +import React from 'react'; +import type {Props} from '@theme/Layout'; + +export default function Layout({children}: Props): JSX.Element { + return <>{children}; +} diff --git a/packages/docusaurus/src/client/theme-fallback/Loading/index.js b/packages/docusaurus/src/client/theme-fallback/Loading/index.tsx similarity index 94% rename from packages/docusaurus/src/client/theme-fallback/Loading/index.js rename to packages/docusaurus/src/client/theme-fallback/Loading/index.tsx index 2d088a26a8cf..761c9c3a77b5 100644 --- a/packages/docusaurus/src/client/theme-fallback/Loading/index.js +++ b/packages/docusaurus/src/client/theme-fallback/Loading/index.tsx @@ -6,13 +6,18 @@ */ import React from 'react'; +import type {LoadingComponentProps} from 'react-loadable'; -export default function Loading({error, retry, pastDelay}) { +export default function Loading({ + error, + retry, + pastDelay, +}: LoadingComponentProps): JSX.Element | null { if (error) { return (
      -
      -

      Oops, page not found

      -
      - - ); -} - -export default NotFound; diff --git a/packages/docusaurus/src/client/theme-fallback/NotFound/index.tsx b/packages/docusaurus/src/client/theme-fallback/NotFound/index.tsx new file mode 100644 index 000000000000..15ccfaefe6ab --- /dev/null +++ b/packages/docusaurus/src/client/theme-fallback/NotFound/index.tsx @@ -0,0 +1,32 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import Layout from '@theme/Layout'; +import Head from '@docusaurus/Head'; + +export default function NotFound(): JSX.Element { + return ( + <> + + Page Not Found + + +
      +

      Oops, page not found

      +
      +
      + + ); +} diff --git a/packages/docusaurus/src/client/theme-fallback/Root/index.js b/packages/docusaurus/src/client/theme-fallback/Root/index.tsx similarity index 76% rename from packages/docusaurus/src/client/theme-fallback/Root/index.js rename to packages/docusaurus/src/client/theme-fallback/Root/index.tsx index 575e8172a323..27ef85e88a7f 100644 --- a/packages/docusaurus/src/client/theme-fallback/Root/index.js +++ b/packages/docusaurus/src/client/theme-fallback/Root/index.tsx @@ -5,6 +5,9 @@ * LICENSE file in the root directory of this source tree. */ +import React from 'react'; +import type {Props} from '@theme/Root'; + // Wrapper at the very top of the app, that is applied constantly // and does not depend on current route (unlike the layout) // @@ -12,8 +15,6 @@ // and these providers won't reset state when we navigate // // See https://github.com/facebook/docusaurus/issues/3919 -function Root({children}) { - return children; +export default function Root({children}: Props): JSX.Element { + return <>{children}; } - -export default Root; diff --git a/packages/docusaurus/src/client/theme-fallback/SiteMetadata/index.tsx b/packages/docusaurus/src/client/theme-fallback/SiteMetadata/index.tsx new file mode 100644 index 000000000000..600acae8f23e --- /dev/null +++ b/packages/docusaurus/src/client/theme-fallback/SiteMetadata/index.tsx @@ -0,0 +1,11 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +// To be implemented by the theme with +export default function SiteMetadata(): JSX.Element | null { + return null; +} diff --git a/packages/docusaurus/src/commands/__tests__/deploy.test.ts b/packages/docusaurus/src/commands/__tests__/deploy.test.ts deleted file mode 100644 index bd35291b19f3..000000000000 --- a/packages/docusaurus/src/commands/__tests__/deploy.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import {buildSshUrl, buildHttpsUrl, hasSSHProtocol} from '../deploy'; - -describe('remoteBranchUrl', () => { - test('should build a normal ssh url', () => { - const url = buildSshUrl('github.com', 'facebook', 'docusaurus'); - expect(url).toEqual('git@github.com:facebook/docusaurus.git'); - }); - test('should build a ssh url with port', () => { - const url = buildSshUrl('github.com', 'facebook', 'docusaurus', '422'); - expect(url).toEqual('ssh://git@github.com:422/facebook/docusaurus.git'); - }); - test('should build a normal http url', () => { - const url = buildHttpsUrl( - 'user:pass', - 'github.com', - 'facebook', - 'docusaurus', - ); - expect(url).toEqual('https://user:pass@github.com/facebook/docusaurus.git'); - }); - test('should build a normal http url with port', () => { - const url = buildHttpsUrl( - 'user:pass', - 'github.com', - 'facebook', - 'docusaurus', - '5433', - ); - expect(url).toEqual( - 'https://user:pass@github.com:5433/facebook/docusaurus.git', - ); - }); -}); - -describe('hasSSHProtocol', () => { - test('should recognize explicit SSH protocol', () => { - const url = 'ssh://git@github.com:422/facebook/docusaurus.git'; - expect(hasSSHProtocol(url)).toEqual(true); - }); - - test('should recognize implied SSH protocol', () => { - const url = 'git@github.com:facebook/docusaurus.git'; - expect(hasSSHProtocol(url)).toEqual(true); - }); - - test('should not recognize HTTPS with credentials', () => { - const url = 'https://user:pass@github.com/facebook/docusaurus.git'; - expect(hasSSHProtocol(url)).toEqual(false); - }); - - test('should not recognize plain HTTPS URL', () => { - const url = 'https://github.com:5433/facebook/docusaurus.git'; - expect(hasSSHProtocol(url)).toEqual(false); - }); -}); diff --git a/packages/docusaurus/src/commands/__tests__/writeHeadingIds.test.ts b/packages/docusaurus/src/commands/__tests__/writeHeadingIds.test.ts deleted file mode 100644 index 22e4a6dcb8b9..000000000000 --- a/packages/docusaurus/src/commands/__tests__/writeHeadingIds.test.ts +++ /dev/null @@ -1,143 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import { - transformMarkdownHeadingLine, - transformMarkdownContent, -} from '../writeHeadingIds'; -import {createSlugger} from '@docusaurus/utils'; - -describe('transformMarkdownHeadingLine', () => { - test('throws when not a heading', () => { - expect(() => - transformMarkdownHeadingLine('ABC', createSlugger()), - ).toThrowErrorMatchingInlineSnapshot( - `"Line is not a Markdown heading: ABC."`, - ); - }); - - test('works for simple level-2 heading', () => { - expect(transformMarkdownHeadingLine('## ABC', createSlugger())).toEqual( - '## ABC {#abc}', - ); - }); - - test('works for simple level-3 heading', () => { - expect(transformMarkdownHeadingLine('### ABC', createSlugger())).toEqual( - '### ABC {#abc}', - ); - }); - - test('works for simple level-4 heading', () => { - expect(transformMarkdownHeadingLine('#### ABC', createSlugger())).toEqual( - '#### ABC {#abc}', - ); - }); - - test('unwraps markdown links', () => { - const input = `## hello [facebook](https://facebook.com) [crowdin](https://crowdin.com/translate/docusaurus-v2/126/en-fr?filter=basic&value=0)`; - expect(transformMarkdownHeadingLine(input, createSlugger())).toEqual( - `${input} {#hello-facebook-crowdin}`, - ); - }); - - test('can slugify complex headings', () => { - const input = '## abc [Hello] How are you %Sébastien_-_$)( ## -56756'; - expect(transformMarkdownHeadingLine(input, createSlugger())).toEqual( - `${input} {#abc-hello-how-are-you-sébastien_-_---56756}`, - ); - }); - - test('does not duplicate duplicate id', () => { - expect( - transformMarkdownHeadingLine( - '## hello world {#hello-world}', - createSlugger(), - ), - ).toEqual('## hello world {#hello-world}'); - }); - - test('respects existing heading', () => { - expect( - transformMarkdownHeadingLine( - '## New heading {#old-heading}', - createSlugger(), - ), - ).toEqual('## New heading {#old-heading}'); - }); - - test('overwrites heading ID when asked to', () => { - expect( - transformMarkdownHeadingLine( - '## New heading {#old-heading}', - createSlugger(), - {overwrite: true}, - ), - ).toEqual('## New heading {#new-heading}'); - }); - - test('maintains casing when asked to', () => { - expect( - transformMarkdownHeadingLine('## getDataFromAPI()', createSlugger(), { - maintainCase: true, - }), - ).toEqual('## getDataFromAPI() {#getDataFromAPI}'); - }); -}); - -describe('transformMarkdownContent', () => { - test('transform the headings', () => { - const input = ` - -# Ignored title - -## abc - -### Hello world - -\`\`\` -# Heading in code block -\`\`\` - -## Hello world - - \`\`\` - # Heading in escaped code block - \`\`\` - -### abc {#abc} - - `; - - // TODO the first heading should probably rather be slugified to abc-1 - // otherwise we end up with 2 x "abc" anchors - // not sure how to implement that atm - const expected = ` - -# Ignored title - -## abc {#abc} - -### Hello world {#hello-world} - -\`\`\` -# Heading in code block -\`\`\` - -## Hello world {#hello-world-1} - - \`\`\` - # Heading in escaped code block - \`\`\` - -### abc {#abc} - - `; - - expect(transformMarkdownContent(input)).toEqual(expected); - }); -}); diff --git a/packages/docusaurus/src/commands/build.ts b/packages/docusaurus/src/commands/build.ts index 2f7d64c7d7ff..252c797d6a09 100644 --- a/packages/docusaurus/src/commands/build.ts +++ b/packages/docusaurus/src/commands/build.ts @@ -28,7 +28,7 @@ import CleanWebpackPlugin from '../webpack/plugins/CleanWebpackPlugin'; import {loadI18n} from '../server/i18n'; import {mapAsyncSequential} from '@docusaurus/utils'; -export default async function build( +export async function build( siteDir: string, cliOptions: Partial = {}, // When running build, we force terminate the process to prevent async @@ -56,12 +56,13 @@ export default async function build( forceTerminate, isLastLocale, }); - } catch (e) { + } catch (err) { logger.error`Unable to build website for locale name=${locale}.`; - throw e; + throw err; } } - const context = await loadContext(siteDir, { + const context = await loadContext({ + siteDir, customOutDir: cliOptions.outDir, customConfigFilePath: cliOptions.config, locale: cliOptions.locale, @@ -72,25 +73,24 @@ export default async function build( }); if (cliOptions.locale) { return tryToBuildLocale({locale: cliOptions.locale, isLastLocale: true}); - } else { - if (i18n.locales.length > 1) { - logger.info`Website will be built for all these locales: ${i18n.locales}`; - } - - // We need the default locale to always be the 1st in the list - // If we build it last, it would "erase" the localized sites built in sub-folders - const orderedLocales: string[] = [ - i18n.defaultLocale, - ...i18n.locales.filter((locale) => locale !== i18n.defaultLocale), - ]; - - const results = await mapAsyncSequential(orderedLocales, (locale) => { - const isLastLocale = - orderedLocales.indexOf(locale) === orderedLocales.length - 1; - return tryToBuildLocale({locale, isLastLocale}); - }); - return results[0]; } + if (i18n.locales.length > 1) { + logger.info`Website will be built for all these locales: ${i18n.locales}`; + } + + // We need the default locale to always be the 1st in the list. If we build it + // last, it would "erase" the localized sites built in sub-folders + const orderedLocales: [string, ...string[]] = [ + i18n.defaultLocale, + ...i18n.locales.filter((locale) => locale !== i18n.defaultLocale), + ]; + + const results = await mapAsyncSequential(orderedLocales, (locale) => { + const isLastLocale = + orderedLocales.indexOf(locale) === orderedLocales.length - 1; + return tryToBuildLocale({locale, isLastLocale}); + }); + return results[0]!; } async function buildLocale({ @@ -110,7 +110,8 @@ async function buildLocale({ process.env.NODE_ENV = 'production'; logger.info`name=${`[${locale}]`} Creating an optimized production build...`; - const props: Props = await load(siteDir, { + const props: Props = await load({ + siteDir, customOutDir: cliOptions.outDir, customConfigFilePath: cliOptions.config, locale, @@ -131,12 +132,13 @@ async function buildLocale({ 'client-manifest.json', ); let clientConfig: Configuration = merge( - createClientConfig(props, cliOptions.minify), + await createClientConfig(props, cliOptions.minify), { plugins: [ // Remove/clean build folders before building bundles. new CleanWebpackPlugin({verbose: false}), - // Visualize size of webpack output files with an interactive zoomable tree map. + // Visualize size of webpack output files with an interactive zoomable + // tree map. cliOptions.bundleAnalyzer && new BundleAnalyzerPlugin(), // Generate client manifests file that will be used for server bundle. new ReactLoadableSSRAddon({ @@ -146,32 +148,38 @@ async function buildLocale({ }, ); - const allCollectedLinks: Record = {}; + const allCollectedLinks: {[location: string]: string[]} = {}; - let serverConfig: Configuration = createServerConfig({ + let serverConfig: Configuration = await createServerConfig({ props, onLinksCollected: (staticPagePath, links) => { allCollectedLinks[staticPagePath] = links; }, }); - serverConfig = merge(serverConfig, { - plugins: [ - new CopyWebpackPlugin({ - patterns: staticDirectories - .map((dir) => path.resolve(siteDir, dir)) - .filter(fs.existsSync) - .map((dir) => ({from: dir, to: outDir})), - }), - ], - }); + if (staticDirectories.length > 0) { + await Promise.all(staticDirectories.map((dir) => fs.ensureDir(dir))); + + serverConfig = merge(serverConfig, { + plugins: [ + new CopyWebpackPlugin({ + patterns: staticDirectories + .map((dir) => path.resolve(siteDir, dir)) + .map((dir) => ({from: dir, to: outDir})), + }), + ], + }); + } // Plugin Lifecycle - configureWebpack and configurePostCss. plugins.forEach((plugin) => { const {configureWebpack, configurePostCss} = plugin; if (configurePostCss) { - clientConfig = applyConfigurePostCss(configurePostCss, clientConfig); + clientConfig = applyConfigurePostCss( + configurePostCss.bind(plugin), + clientConfig, + ); } if (configureWebpack) { @@ -203,11 +211,7 @@ async function buildLocale({ await compile([clientConfig, serverConfig]); // Remove server.bundle.js because it is not needed. - if ( - serverConfig.output && - serverConfig.output.filename && - typeof serverConfig.output.filename === 'string' - ) { + if (typeof serverConfig.output?.filename === 'string') { const serverBundle = path.join(outDir, serverConfig.output.filename); if (await fs.pathExists(serverBundle)) { await fs.unlink(serverBundle); @@ -220,7 +224,6 @@ async function buildLocale({ if (!plugin.postBuild) { return; } - // The plugin may reference `this`. We manually bind it again to prevent any bugs. await plugin.postBuild({...props, content: plugin.content}); }), ); diff --git a/packages/docusaurus/src/commands/clear.ts b/packages/docusaurus/src/commands/clear.ts index 327db8c767ad..600ab0703472 100644 --- a/packages/docusaurus/src/commands/clear.ts +++ b/packages/docusaurus/src/commands/clear.ts @@ -13,20 +13,34 @@ import { GENERATED_FILES_DIR_NAME, } from '@docusaurus/utils'; -async function removePath(fsPath: string) { +async function removePath(entry: {path: string; description: string}) { + if (!(await fs.pathExists(entry.path))) { + return; + } try { - fs.remove(path.join(fsPath)); - logger.success`Removed the path=${fsPath} directory.`; - } catch (e) { - logger.error`Could not remove path=${fsPath} directory. -${e as string}`; + await fs.remove(entry.path); + logger.success`Removed the ${entry.description} at path=${entry.path}.`; + } catch (err) { + logger.error`Could not remove the ${entry.description} at path=${entry.path}.`; + logger.error(err); } } -export default async function clear(siteDir: string): Promise { - return Promise.all([ - removePath(path.join(siteDir, GENERATED_FILES_DIR_NAME)), - removePath(path.join(siteDir, DEFAULT_BUILD_DIR_NAME)), - removePath(path.join(siteDir, 'node_modules', '.cache')), - ]); +export async function clear(siteDir: string): Promise { + const generatedFolder = { + path: path.join(siteDir, GENERATED_FILES_DIR_NAME), + description: 'generated folder', + }; + const buildFolder = { + path: path.join(siteDir, DEFAULT_BUILD_DIR_NAME), + description: 'build output folder', + }; + // In Yarn PnP, cache is stored in `.yarn/.cache` because n_m doesn't exist + const cacheFolders = ['node_modules', '.yarn'].map((p) => ({ + path: path.join(siteDir, p, '.cache'), + description: 'Webpack persistent cache folder', + })); + return Promise.all( + [generatedFolder, buildFolder, ...cacheFolders].map(removePath), + ); } diff --git a/packages/docusaurus/src/commands/commandUtils.ts b/packages/docusaurus/src/commands/commandUtils.ts index 17f661982f5f..d14d5b8977ad 100644 --- a/packages/docusaurus/src/commands/commandUtils.ts +++ b/packages/docusaurus/src/commands/commandUtils.ts @@ -5,14 +5,14 @@ * LICENSE file in the root directory of this source tree. */ -import choosePort from '../choosePort'; +import {choosePort} from '../server/choosePort'; import type {HostPortCLIOptions} from '@docusaurus/types'; import {DEFAULT_PORT} from '@docusaurus/utils'; export function getCLIOptionHost( hostOption: HostPortCLIOptions['host'], ): string { - return hostOption || 'localhost'; + return hostOption ?? 'localhost'; } export async function getCLIOptionPort( diff --git a/packages/docusaurus/src/commands/deploy.ts b/packages/docusaurus/src/commands/deploy.ts index 7916961bd865..2de40addb16e 100644 --- a/packages/docusaurus/src/commands/deploy.ts +++ b/packages/docusaurus/src/commands/deploy.ts @@ -8,8 +8,9 @@ import fs from 'fs-extra'; import shell from 'shelljs'; import logger from '@docusaurus/logger'; +import {hasSSHProtocol, buildSshUrl, buildHttpsUrl} from '@docusaurus/utils'; import {loadContext} from '../server'; -import build from './build'; +import {build} from './build'; import type {BuildCLIOptions} from '@docusaurus/types'; import path from 'path'; import os from 'os'; @@ -27,54 +28,18 @@ function shellExecLog(cmd: string) { const result = shell.exec(cmd); logger.info`code=${obfuscateGitPass(cmd)} subdue=${`code: ${result.code}`}`; return result; - } catch (e) { + } catch (err) { logger.error`code=${obfuscateGitPass(cmd)}`; - throw e; + throw err; } } -export function buildSshUrl( - githubHost: string, - organizationName: string, - projectName: string, - githubPort?: string, -): string { - if (githubPort) { - return `ssh://git@${githubHost}:${githubPort}/${organizationName}/${projectName}.git`; - } - return `git@${githubHost}:${organizationName}/${projectName}.git`; -} - -export function buildHttpsUrl( - gitCredentials: string, - githubHost: string, - organizationName: string, - projectName: string, - githubPort?: string, -): string { - if (githubPort) { - return `https://${gitCredentials}@${githubHost}:${githubPort}/${organizationName}/${projectName}.git`; - } - return `https://${gitCredentials}@${githubHost}/${organizationName}/${projectName}.git`; -} - -export function hasSSHProtocol(sourceRepoUrl: string): boolean { - try { - if (new URL(sourceRepoUrl).protocol === 'ssh:') { - return true; - } - return false; - } catch { - // Fails when there isn't a protocol - return /^([\w-]+@)?[\w.-]+:[\w./_-]+(\.git)?/.test(sourceRepoUrl); // git@github.com:facebook/docusaurus.git - } -} - -export default async function deploy( +export async function deploy( siteDir: string, cliOptions: Partial = {}, ): Promise { - const {outDir, siteConfig, siteConfigPath} = await loadContext(siteDir, { + const {outDir, siteConfig, siteConfigPath} = await loadContext({ + siteDir, customConfigFilePath: cliOptions.config, customOutDir: cliOptions.outDir, }); @@ -148,8 +113,8 @@ This behavior can have SEO impacts and create relative link issues. shell.exit(0); } - // github.io indicates organization repos that deploy via default branch. All others use gh-pages. - // Organization deploys looks like: + // github.io indicates organization repos that deploy via default branch. + // All others use gh-pages. Organization deploys looks like: // - Git repo: https://github.com//.github.io // - Site url: https://.github.io const isGitHubPagesOrganizationDeploy = projectName.includes('.github.io'); @@ -235,10 +200,9 @@ You can also set the deploymentBranch property in docusaurus.config.js .`); try { await fs.copy(fromPath, toPath); - } catch (error) { - throw new Error( - `Copying build assets from "${fromPath}" to "${toPath}" failed with error "${error}".`, - ); + } catch (err) { + logger.error`Copying build assets from path=${fromPath} to path=${toPath} failed.`; + throw err; } shellExecLog('git add --all'); @@ -269,12 +233,12 @@ You can also set the deploymentBranch property in docusaurus.config.js .`); }; if (!cliOptions.skipBuild) { - // Build static html files, then push to deploymentBranch branch of specified repo. + // Build site, then push to deploymentBranch branch of specified repo. try { await runDeploy(await build(siteDir, cliOptions, false)); - } catch (buildError) { - logger.error((buildError as Error).message); - process.exit(1); + } catch (err) { + logger.error('Deployment of the build output failed.'); + throw err; } } else { // Push current build to deploymentBranch branch of specified repo. diff --git a/packages/docusaurus/src/commands/external.ts b/packages/docusaurus/src/commands/external.ts index 68e72db053b1..eb8c1b94f26b 100644 --- a/packages/docusaurus/src/commands/external.ts +++ b/packages/docusaurus/src/commands/external.ts @@ -5,26 +5,19 @@ * LICENSE file in the root directory of this source tree. */ -import type {Command} from 'commander'; -import {loadContext, loadPluginConfigs} from '../server'; -import initPlugins from '../server/plugins/init'; +import type {CommanderStatic} from 'commander'; +import {loadContext} from '../server'; +import {initPlugins} from '../server/plugins/init'; -export default async function externalCommand( - cli: Command, +export async function externalCommand( + cli: CommanderStatic, siteDir: string, ): Promise { - const context = await loadContext(siteDir); - const pluginConfigs = loadPluginConfigs(context); - const plugins = await initPlugins({pluginConfigs, context}); + const context = await loadContext({siteDir}); + const plugins = await initPlugins(context); // Plugin Lifecycle - extendCli. plugins.forEach((plugin) => { - const {extendCli} = plugin; - - if (!extendCli) { - return; - } - - extendCli(cli); + plugin.extendCli?.(cli); }); } diff --git a/packages/docusaurus/src/commands/serve.ts b/packages/docusaurus/src/commands/serve.ts index bf578a2908e6..c1ef7e9e6e00 100644 --- a/packages/docusaurus/src/commands/serve.ts +++ b/packages/docusaurus/src/commands/serve.ts @@ -9,18 +9,16 @@ import http from 'http'; import serveHandler from 'serve-handler'; import logger from '@docusaurus/logger'; import path from 'path'; -import {loadSiteConfig} from '../server'; -import build from './build'; +import {loadSiteConfig} from '../server/config'; +import {build} from './build'; import {getCLIOptionHost, getCLIOptionPort} from './commandUtils'; import type {ServeCLIOptions} from '@docusaurus/types'; -export default async function serve( +export async function serve( siteDir: string, cliOptions: ServeCLIOptions, ): Promise { - let dir = path.isAbsolute(cliOptions.dir) - ? cliOptions.dir - : path.join(siteDir, cliOptions.dir); + let dir = path.resolve(siteDir, cliOptions.dir); if (cliOptions.build) { dir = await build( @@ -59,18 +57,19 @@ export default async function serve( return; } - // Remove baseUrl before calling serveHandler - // Reason: /baseUrl/ should serve /build/index.html, not /build/baseUrl/index.html (does not exist) + // Remove baseUrl before calling serveHandler, because /baseUrl/ should + // serve /build/index.html, not /build/baseUrl/index.html (does not exist) req.url = req.url?.replace(baseUrl, '/'); serveHandler(req, res, { cleanUrls: true, public: dir, trailingSlash, + directoryListing: false, }); }); - logger.success`Serving path=${cliOptions.dir} directory at path=${ + logger.success`Serving path=${cliOptions.dir} directory at url=${ servingUrl + baseUrl }.`; server.listen(port); diff --git a/packages/docusaurus/src/commands/start.ts b/packages/docusaurus/src/commands/start.ts index 035a2b529788..39da01713433 100644 --- a/packages/docusaurus/src/commands/start.ts +++ b/packages/docusaurus/src/commands/start.ts @@ -10,7 +10,7 @@ import logger from '@docusaurus/logger'; import chokidar from 'chokidar'; import HtmlWebpackPlugin from 'html-webpack-plugin'; import path from 'path'; -import {debounce} from 'lodash'; +import _ from 'lodash'; import openBrowser from 'react-dev-utils/openBrowser'; import {prepareUrls} from 'react-dev-utils/WebpackDevServerUtils'; import evalSourceMapMiddleware from 'react-dev-utils/evalSourceMapMiddleware'; @@ -28,7 +28,7 @@ import { import {getCLIOptionHost, getCLIOptionPort} from './commandUtils'; import {getTranslationsLocaleDirPath} from '../server/translations/translations'; -export default async function start( +export async function start( siteDir: string, cliOptions: Partial, ): Promise { @@ -37,7 +37,8 @@ export default async function start( logger.info('Starting the development server...'); function loadSite() { - return load(siteDir, { + return load({ + siteDir, customConfigFilePath: cliOptions.config, locale: cliOptions.locale, localizePath: undefined, // should this be configurable? @@ -60,22 +61,22 @@ export default async function start( const urls = prepareUrls(protocol, host, port); const openUrl = normalizeUrl([urls.localUrlForBrowser, baseUrl]); - logger.success`Docusaurus website is running at path=${openUrl}.`; + logger.success`Docusaurus website is running at url=${openUrl}.`; // Reload files processing. - const reload = debounce(() => { + const reload = _.debounce(() => { loadSite() .then(({baseUrl: newBaseUrl}) => { const newOpenUrl = normalizeUrl([urls.localUrlForBrowser, newBaseUrl]); if (newOpenUrl !== openUrl) { - logger.success`Docusaurus website is running at path=${newOpenUrl}.`; + logger.success`Docusaurus website is running at url=${newOpenUrl}.`; } }) .catch((err) => { logger.error(err.stack); }); }, 500); - const {siteConfig, plugins = []} = props; + const {siteConfig, plugins} = props; const normalizeToSiteDir = (filepath: string) => { if (filepath && path.isAbsolute(filepath)) { @@ -84,12 +85,9 @@ export default async function start( return posixPath(filepath); }; - const pluginPaths = ([] as string[]) - .concat( - ...plugins - .map((plugin) => plugin.getPathsToWatch?.() ?? []) - .filter(Boolean), - ) + const pluginPaths = plugins + .flatMap((plugin) => plugin.getPathsToWatch?.() ?? []) + .filter(Boolean) .map(normalizeToSiteDir); const pathsToWatch = [ @@ -107,7 +105,7 @@ export default async function start( ? (cliOptions.poll as number) : undefined, }; - const httpsConfig = getHttpsConfig(); + const httpsConfig = await getHttpsConfig(); const fsWatcher = chokidar.watch(pathsToWatch, { cwd: siteDir, ignoreInitial: true, @@ -118,7 +116,7 @@ export default async function start( fsWatcher.on(event, reload), ); - let config: webpack.Configuration = merge(createClientConfig(props), { + let config: webpack.Configuration = merge(await createClientConfig(props), { infrastructureLogging: { // Reduce log verbosity, see https://github.com/facebook/docusaurus/pull/5420#issuecomment-906613105 level: 'warn', @@ -126,7 +124,7 @@ export default async function start( plugins: [ // Generates an `index.html` file with the ', + ], + }; + }, +}; + +const pluginMaybeInjectHeadTags: LoadedPlugin = { + name: 'plugin-postBody-tags', + injectHtmlTags() { + return undefined; + }, +}; + +describe('loadHtmlTags', () => { + it('works for an empty plugin', () => { + const htmlTags = loadHtmlTags([pluginEmpty]); + expect(htmlTags).toMatchInlineSnapshot(` + { + "headTags": "", + "postBodyTags": "", + "preBodyTags": "", + } + `); + }); + + it('only injects headTags', () => { + const htmlTags = loadHtmlTags([pluginHeadTags]); + expect(htmlTags).toMatchInlineSnapshot(` + { + "headTags": " + + ", + "postBodyTags": "", + "preBodyTags": "", + } + `); + }); + + it('only injects preBodyTags', () => { + const htmlTags = loadHtmlTags([pluginPreBodyTags]); + expect(htmlTags).toMatchInlineSnapshot(` + { + "headTags": "", + "postBodyTags": "", + "preBodyTags": "", + } + `); + }); + + it('only injects postBodyTags', () => { + const htmlTags = loadHtmlTags([pluginPostBodyTags]); + expect(htmlTags).toMatchInlineSnapshot(` + { + "headTags": "", + "postBodyTags": "
      Test content
      + ", + "preBodyTags": "", + } + `); + }); + + it('allows multiple plugins that inject different part of html tags', () => { + const htmlTags = loadHtmlTags([ + pluginHeadTags, + pluginPostBodyTags, + pluginPreBodyTags, + ]); + expect(htmlTags).toMatchInlineSnapshot(` + { + "headTags": " + + ", + "postBodyTags": "
      Test content
      + ", + "preBodyTags": "", + } + `); + }); + + it('allows multiple plugins that might/might not inject html tags', () => { + const htmlTags = loadHtmlTags([ + pluginEmpty, + pluginHeadTags, + pluginPostBodyTags, + pluginMaybeInjectHeadTags, + ]); + expect(htmlTags).toMatchInlineSnapshot(` + { + "headTags": " + + ", + "postBodyTags": "
      Test content
      + ", + "preBodyTags": "", + } + `); + }); + it('throws for invalid tag', () => { + expect(() => + loadHtmlTags([ + { + injectHtmlTags() { + return { + headTags: { + tagName: 'endiliey', + attributes: { + this: 'is invalid', + }, + }, + }; + }, + }, + ]), + ).toThrowErrorMatchingInlineSnapshot( + `"Error loading {\\"tagName\\":\\"endiliey\\",\\"attributes\\":{\\"this\\":\\"is invalid\\"}}, \\"endiliey\\" is not a valid HTML tag."`, + ); + }); + + it('throws for invalid tagName', () => { + expect(() => + loadHtmlTags([ + { + injectHtmlTags() { + return { + headTags: { + tagName: true, + }, + }; + }, + }, + ]), + ).toThrowErrorMatchingInlineSnapshot( + `"{\\"tagName\\":true} is not a valid HTML tag object. \\"tagName\\" must be defined as a string."`, + ); + }); + + it('throws for invalid tag object', () => { + expect(() => + loadHtmlTags([ + { + injectHtmlTags() { + return { + headTags: 2, + }; + }, + }, + ]), + ).toThrowErrorMatchingInlineSnapshot( + `"\\"2\\" is not a valid HTML tag object."`, + ); + }); +}); diff --git a/packages/docusaurus/src/server/__tests__/i18n.test.ts b/packages/docusaurus/src/server/__tests__/i18n.test.ts index 399bd928bf83..258adfbe7c65 100644 --- a/packages/docusaurus/src/server/__tests__/i18n.test.ts +++ b/packages/docusaurus/src/server/__tests__/i18n.test.ts @@ -5,22 +5,15 @@ * LICENSE file in the root directory of this source tree. */ -import { - loadI18n, - localizePath, - getDefaultLocaleConfig, - shouldWarnAboutNodeVersion, -} from '../i18n'; +import {jest} from '@jest/globals'; +import {loadI18n, getDefaultLocaleConfig} from '../i18n'; import {DEFAULT_I18N_CONFIG} from '../configValidation'; -import path from 'path'; -import {chain, identity} from 'lodash'; import type {I18nConfig} from '@docusaurus/types'; function testLocaleConfigsFor(locales: string[]) { - return chain(locales) - .keyBy(identity) - .mapValues(getDefaultLocaleConfig) - .value(); + return Object.fromEntries( + locales.map((locale) => [locale, getDefaultLocaleConfig(locale)]), + ); } function loadI18nTest(i18nConfig: I18nConfig, locale?: string) { @@ -34,78 +27,64 @@ function loadI18nTest(i18nConfig: I18nConfig, locale?: string) { } describe('defaultLocaleConfig', () => { - const canComputeLabel = typeof Intl.DisplayNames !== 'undefined'; - - test('returns correct labels', () => { + it('returns correct labels', () => { expect(getDefaultLocaleConfig('fr')).toEqual({ - label: canComputeLabel ? 'Français' : 'fr', + label: 'Français', direction: 'ltr', htmlLang: 'fr', }); expect(getDefaultLocaleConfig('fr-FR')).toEqual({ - label: canComputeLabel ? 'Français (France)' : 'fr-FR', + label: 'Français (France)', direction: 'ltr', htmlLang: 'fr-FR', }); expect(getDefaultLocaleConfig('en')).toEqual({ - label: canComputeLabel ? 'English' : 'en', + label: 'English', direction: 'ltr', htmlLang: 'en', }); expect(getDefaultLocaleConfig('en-US')).toEqual({ - label: canComputeLabel ? 'American English' : 'en-US', + label: 'American English', direction: 'ltr', htmlLang: 'en-US', }); expect(getDefaultLocaleConfig('zh')).toEqual({ - label: canComputeLabel ? '中文' : 'zh', + label: '中文', direction: 'ltr', htmlLang: 'zh', }); expect(getDefaultLocaleConfig('zh-CN')).toEqual({ - label: canComputeLabel ? '中文(中国)' : 'zh-CN', + label: '中文(中国)', direction: 'ltr', htmlLang: 'zh-CN', }); expect(getDefaultLocaleConfig('en-US')).toEqual({ - label: canComputeLabel ? 'American English' : 'en-US', + label: 'American English', direction: 'ltr', htmlLang: 'en-US', }); expect(getDefaultLocaleConfig('fa')).toEqual({ - label: canComputeLabel ? 'فارسی' : 'fa', + // cSpell:ignore فارسی + label: 'فارسی', direction: 'rtl', htmlLang: 'fa', }); expect(getDefaultLocaleConfig('fa-IR')).toEqual({ - label: canComputeLabel ? 'فارسی (ایران)' : 'fa-IR', + // cSpell:ignore ایران فارسیا + label: 'فارسی (ایران)', direction: 'rtl', htmlLang: 'fa-IR', }); }); }); -describe('shouldWarnAboutNodeVersion', () => { - test('warns for old NodeJS version and [en,fr]', () => { - expect(shouldWarnAboutNodeVersion(12, ['en', 'fr'])).toEqual(true); - }); - - test('not warn for old NodeJS version and [en]', () => { - expect(shouldWarnAboutNodeVersion(12, ['en'])).toEqual(false); - }); - - test('not warn for recent NodeJS version and [en,fr]', () => { - expect(shouldWarnAboutNodeVersion(14, ['en', 'fr'])).toEqual(false); - }); -}); - describe('loadI18n', () => { - const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(); + const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); beforeEach(() => { consoleSpy.mockClear(); }); - test('should load I18n for default config', async () => { + it('loads I18n for default config', async () => { await expect(loadI18nTest(DEFAULT_I18N_CONFIG)).resolves.toEqual({ defaultLocale: 'en', locales: ['en'], @@ -114,7 +93,7 @@ describe('loadI18n', () => { }); }); - test('should load I18n for multi-lang config', async () => { + it('loads I18n for multi-lang config', async () => { await expect( loadI18nTest({ defaultLocale: 'fr', @@ -129,7 +108,7 @@ describe('loadI18n', () => { }); }); - test('should load I18n for multi-locale config with specified locale', async () => { + it('loads I18n for multi-locale config with specified locale', async () => { await expect( loadI18nTest( { @@ -147,7 +126,7 @@ describe('loadI18n', () => { }); }); - test('should load I18n for multi-locale config with some xcustom locale configs', async () => { + it('loads I18n for multi-locale config with some custom locale configs', async () => { await expect( loadI18nTest( { @@ -172,7 +151,7 @@ describe('loadI18n', () => { }); }); - test('should warn when trying to load undeclared locale', async () => { + it('warns when trying to load undeclared locale', async () => { await loadI18nTest( { defaultLocale: 'fr', @@ -186,85 +165,3 @@ describe('loadI18n', () => { ); }); }); - -describe('localizePath', () => { - test('should localize url path with current locale', () => { - expect( - localizePath({ - pathType: 'url', - path: '/baseUrl', - i18n: { - defaultLocale: 'en', - locales: ['en', 'fr'], - currentLocale: 'fr', - localeConfigs: {}, - }, - options: {localizePath: true}, - }), - ).toEqual('/baseUrl/fr/'); - }); - - test('should localize fs path with current locale', () => { - expect( - localizePath({ - pathType: 'fs', - path: '/baseFsPath', - i18n: { - defaultLocale: 'en', - locales: ['en', 'fr'], - currentLocale: 'fr', - localeConfigs: {}, - }, - options: {localizePath: true}, - }), - ).toEqual(`${path.sep}baseFsPath${path.sep}fr${path.sep}`); - }); - - test('should localize path for default locale, if requested', () => { - expect( - localizePath({ - pathType: 'url', - path: '/baseUrl/', - i18n: { - defaultLocale: 'en', - locales: ['en', 'fr'], - currentLocale: 'en', - localeConfigs: {}, - }, - options: {localizePath: true}, - }), - ).toEqual('/baseUrl/en/'); - }); - - test('should not localize path for default locale by default', () => { - expect( - localizePath({ - pathType: 'url', - path: '/baseUrl/', - i18n: { - defaultLocale: 'en', - locales: ['en', 'fr'], - currentLocale: 'en', - localeConfigs: {}, - }, - // options: {localizePath: true}, - }), - ).toEqual('/baseUrl/'); - }); - - test('should localize path for non-default locale by default', () => { - expect( - localizePath({ - pathType: 'url', - path: '/baseUrl/', - i18n: { - defaultLocale: 'en', - locales: ['en', 'fr'], - currentLocale: 'en', - localeConfigs: {}, - }, - // options: {localizePath: true}, - }), - ).toEqual('/baseUrl/'); - }); -}); diff --git a/packages/docusaurus/src/server/__tests__/routes.test.ts b/packages/docusaurus/src/server/__tests__/routes.test.ts index f9dfbca2da4e..faf37563fd94 100644 --- a/packages/docusaurus/src/server/__tests__/routes.test.ts +++ b/packages/docusaurus/src/server/__tests__/routes.test.ts @@ -5,11 +5,103 @@ * LICENSE file in the root directory of this source tree. */ -import loadRoutes from '../routes'; +import {jest} from '@jest/globals'; +import {loadRoutes, handleDuplicateRoutes, genChunkName} from '../routes'; import type {RouteConfig} from '@docusaurus/types'; +describe('genChunkName', () => { + it('works', () => { + const firstAssert: {[key: string]: string} = { + '/docs/adding-blog': 'docs-adding-blog-062', + '/docs/versioning': 'docs-versioning-8a8', + '/': 'index', + '/blog/2018/04/30/How-I-Converted-Profilo-To-Docusaurus': + 'blog-2018-04-30-how-i-converted-profilo-to-docusaurus-4f2', + '/youtube': 'youtube-429', + '/users/en/': 'users-en-f7a', + '/blog': 'blog-c06', + }; + Object.keys(firstAssert).forEach((str) => { + expect(genChunkName(str)).toBe(firstAssert[str]); + }); + }); + + it("doesn't allow different chunk name for same path", () => { + expect(genChunkName('path/is/similar', 'oldPrefix')).toEqual( + genChunkName('path/is/similar', 'newPrefix'), + ); + }); + + it('emits different chunk names for different paths even with same preferred name', () => { + const secondAssert: {[key: string]: string} = { + '/blog/1': 'blog-85-f-089', + '/blog/2': 'blog-353-489', + }; + Object.keys(secondAssert).forEach((str) => { + expect(genChunkName(str, undefined, 'blog')).toBe(secondAssert[str]); + }); + }); + + it('only generates short unique IDs', () => { + const thirdAssert: {[key: string]: string} = { + a: '0cc175b9', + b: '92eb5ffe', + c: '4a8a08f0', + d: '8277e091', + }; + Object.keys(thirdAssert).forEach((str) => { + expect(genChunkName(str, undefined, undefined, true)).toBe( + thirdAssert[str], + ); + }); + expect(genChunkName('d', undefined, undefined, true)).toBe('8277e091'); + }); +}); + +describe('handleDuplicateRoutes', () => { + const routes: RouteConfig[] = [ + { + path: '/', + component: '', + routes: [ + {path: '/search', component: ''}, + {path: '/sameDoc', component: ''}, + ], + }, + { + path: '/', + component: '', + routes: [ + {path: '/search', component: ''}, + {path: '/sameDoc', component: ''}, + {path: '/uniqueDoc', component: ''}, + ], + }, + { + path: '/', + component: '', + }, + { + path: '/', + component: '', + }, + { + path: '/', + component: '', + }, + ]; + it('works', () => { + expect(() => { + handleDuplicateRoutes(routes, 'throw'); + }).toThrowErrorMatchingSnapshot(); + const consoleMock = jest.spyOn(console, 'log').mockImplementation(() => {}); + handleDuplicateRoutes(routes, 'ignore'); + expect(consoleMock).toBeCalledTimes(0); + }); +}); + describe('loadRoutes', () => { - test('nested route config', async () => { + it('loads nested route config', async () => { const nestedRouteConfig: RouteConfig = { component: '@theme/DocPage', path: '/docs:route', @@ -35,14 +127,20 @@ describe('loadRoutes', () => { metadata: 'docs-foo-baz-dd9.json', }, sidebar: 'secondary', + 'key:a': 'containing colon', + "key'b": 'containing quote', + 'key"c': 'containing double quote', + 'key,d': 'containing comma', + 字段: 'containing unicode', }, ], }; - const result = await loadRoutes([nestedRouteConfig], '/'); - expect(result).toMatchSnapshot(); + await expect( + loadRoutes([nestedRouteConfig], '/', 'ignore'), + ).resolves.toMatchSnapshot(); }); - test('flat route config', async () => { + it('loads flat route config', async () => { const flatRouteConfig: RouteConfig = { path: '/blog', component: '@theme/BlogListPage', @@ -61,44 +159,51 @@ describe('loadRoutes', () => { }, { content: 'blog/2018-12-14-Happy-First-Birthday-Slash.md', - metadata: null, + }, + { + content: { + __import: true, + path: 'blog/2018-12-14-Happy-First-Birthday-Slash.md', + }, }, ], }, }; - const result = await loadRoutes([flatRouteConfig], '/'); - expect(result).toMatchSnapshot(); + await expect( + loadRoutes([flatRouteConfig], '/', 'ignore'), + ).resolves.toMatchSnapshot(); }); - test('invalid route config', async () => { + it('rejects invalid route config', async () => { const routeConfigWithoutPath = { component: 'hello/world.js', } as RouteConfig; - await expect(loadRoutes([routeConfigWithoutPath], '/')).rejects - .toMatchInlineSnapshot(` - [Error: Invalid route config: path must be a string and component is required. - {"component":"hello/world.js"}] - `); + await expect(loadRoutes([routeConfigWithoutPath], '/', 'ignore')).rejects + .toThrowErrorMatchingInlineSnapshot(` + "Invalid route config: path must be a string and component is required. + {\\"component\\":\\"hello/world.js\\"}" + `); const routeConfigWithoutComponent = { path: '/hello/world', } as RouteConfig; - await expect(loadRoutes([routeConfigWithoutComponent], '/')).rejects - .toMatchInlineSnapshot(` - [Error: Invalid route config: path must be a string and component is required. - {"path":"/hello/world"}] - `); + await expect(loadRoutes([routeConfigWithoutComponent], '/', 'ignore')) + .rejects.toThrowErrorMatchingInlineSnapshot(` + "Invalid route config: path must be a string and component is required. + {\\"path\\":\\"/hello/world\\"}" + `); }); - test('route config with empty (but valid) path string', async () => { + it('loads route config with empty (but valid) path string', async () => { const routeConfig = { path: '', component: 'hello/world.js', } as RouteConfig; - const result = await loadRoutes([routeConfig], '/'); - expect(result).toMatchSnapshot(); + await expect( + loadRoutes([routeConfig], '/', 'ignore'), + ).resolves.toMatchSnapshot(); }); }); diff --git a/packages/docusaurus/src/server/__tests__/siteMetadata.test.ts b/packages/docusaurus/src/server/__tests__/siteMetadata.test.ts new file mode 100644 index 000000000000..b12e0a7e172c --- /dev/null +++ b/packages/docusaurus/src/server/__tests__/siteMetadata.test.ts @@ -0,0 +1,35 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {getPluginVersion} from '../siteMetadata'; +import path from 'path'; + +describe('getPluginVersion', () => { + it('detects external packages plugins versions', async () => { + await expect( + getPluginVersion( + path.join(__dirname, '__fixtures__/siteMetadata/dummy-plugin.js'), + // Make the plugin appear external. + path.join(__dirname, '..', '..', '..', '..', '..', '..', 'website'), + ), + ).resolves.toEqual({type: 'package', version: 'random-version'}); + }); + + it('detects project plugins versions', async () => { + await expect( + getPluginVersion( + path.join(__dirname, '__fixtures__/siteMetadata/dummy-plugin.js'), + // Make the plugin appear project local. + path.join(__dirname, '__fixtures__/siteMetadata'), + ), + ).resolves.toEqual({type: 'project'}); + }); + + it('detects local packages versions', async () => { + await expect(getPluginVersion('/', '/')).resolves.toEqual({type: 'local'}); + }); +}); diff --git a/packages/docusaurus/src/server/loadSetup.ts b/packages/docusaurus/src/server/__tests__/testUtils.ts similarity index 66% rename from packages/docusaurus/src/server/loadSetup.ts rename to packages/docusaurus/src/server/__tests__/testUtils.ts index e51e83f0570e..1d42d255f9e8 100644 --- a/packages/docusaurus/src/server/loadSetup.ts +++ b/packages/docusaurus/src/server/__tests__/testUtils.ts @@ -6,22 +6,20 @@ */ import path from 'path'; -import {load} from './index'; +import {load} from '../index'; import type {Props} from '@docusaurus/types'; // Helper methods to setup dummy/fake projects. -const loadSetup = async (name: string): Promise => { - const fixtures = path.join(__dirname, '__tests__', '__fixtures__'); +export default async function loadSetup(name: string): Promise { + const fixtures = path.join(__dirname, '__fixtures__'); const simpleSite = path.join(fixtures, 'simple-site'); const customSite = path.join(fixtures, 'custom-site'); switch (name) { case 'custom': - return load(customSite); + return load({siteDir: customSite}); case 'simple': default: - return load(simpleSite); + return load({siteDir: simpleSite}); } -}; - -export default loadSetup; +} diff --git a/packages/docusaurus/src/server/__tests__/utils.test.ts b/packages/docusaurus/src/server/__tests__/utils.test.ts index 0cea2621b1a3..b8ef5b59faaa 100644 --- a/packages/docusaurus/src/server/__tests__/utils.test.ts +++ b/packages/docusaurus/src/server/__tests__/utils.test.ts @@ -9,7 +9,7 @@ import type {RouteConfig} from '@docusaurus/types'; import {getAllFinalRoutes} from '../utils'; describe('getAllFinalRoutes', () => { - test('should get final routes correctly', () => { + it('gets final routes correctly', () => { const routes: RouteConfig[] = [ { path: '/docs', diff --git a/packages/docusaurus/src/server/brokenLinks.ts b/packages/docusaurus/src/server/brokenLinks.ts index 1e3ca6dc4d0e..c213ca19ef84 100644 --- a/packages/docusaurus/src/server/brokenLinks.ts +++ b/packages/docusaurus/src/server/brokenLinks.ts @@ -5,12 +5,9 @@ * LICENSE file in the root directory of this source tree. */ -import { - matchRoutes, - type RouteConfig as RRRouteConfig, -} from 'react-router-config'; +import {matchRoutes} from 'react-router-config'; import fs from 'fs-extra'; -import {mapValues, pickBy, countBy} from 'lodash'; +import _ from 'lodash'; import type {RouteConfig, ReportingSeverity} from '@docusaurus/types'; import { removePrefix, @@ -20,11 +17,8 @@ import { } from '@docusaurus/utils'; import {getAllFinalRoutes} from './utils'; import path from 'path'; - -function toReactRouterRoutes(routes: RouteConfig[]): RRRouteConfig[] { - // @ts-expect-error: types incompatible??? - return routes as RRRouteConfig[]; -} +import combinePromises from 'combine-promises'; +import logger from '@docusaurus/logger'; type BrokenLink = { link: string; @@ -33,7 +27,7 @@ type BrokenLink = { // matchRoutes does not support qs/anchors, so we remove it! function onlyPathname(link: string) { - return link.split('#')[0].split('?')[0]; + return link.split('#')[0]!.split('?')[0]!; } function getPageBrokenLinks({ @@ -45,10 +39,9 @@ function getPageBrokenLinks({ pageLinks: string[]; routes: RouteConfig[]; }): BrokenLink[] { - // ReactRouter is able to support links like ./../somePath - // but matchRoutes does not do this resolving internally - // we must resolve the links before using matchRoutes - // resolvePathname is used internally by ReactRouter + // ReactRouter is able to support links like ./../somePath but `matchRoutes` + // does not do this resolution internally. We must resolve the links before + // using `matchRoutes`. `resolvePathname` is used internally by React Router function resolveLink(link: string) { const resolvedLink = resolvePathname(onlyPathname(link), pagePath); return {link, resolvedLink}; @@ -56,7 +49,10 @@ function getPageBrokenLinks({ function isBrokenLink(link: string) { const matchedRoutes = [link, decodeURI(link)] - .map((l) => matchRoutes(toReactRouterRoutes(routes), l)) + // @ts-expect-error: React router types RouteConfig with an actual React + // component, but we load route components with string paths. + // We don't actually access component here, so it's fine. + .map((l) => matchRoutes(routes, l)) .reduce((prev, cur) => prev.concat(cur)); return matchedRoutes.length === 0; } @@ -64,35 +60,37 @@ function getPageBrokenLinks({ return pageLinks.map(resolveLink).filter((l) => isBrokenLink(l.resolvedLink)); } -// The route defs can be recursive, and have a parent match-all route -// We don't want to match broken links like /docs/brokenLink against /docs/* -// For this reason, we only consider the "final routes", that do not have subroutes -// We also need to remove the match all 404 route +/** + * The route defs can be recursive, and have a parent match-all route. We don't + * want to match broken links like /docs/brokenLink against /docs/*. For this + * reason, we only consider the "final routes" that do not have subroutes. + * We also need to remove the match-all 404 route + */ function filterIntermediateRoutes(routesInput: RouteConfig[]): RouteConfig[] { const routesWithout404 = routesInput.filter((route) => route.path !== '*'); return getAllFinalRoutes(routesWithout404); } -export function getAllBrokenLinks({ +function getAllBrokenLinks({ allCollectedLinks, routes, }: { - allCollectedLinks: Record; + allCollectedLinks: {[location: string]: string[]}; routes: RouteConfig[]; -}): Record { +}): {[location: string]: BrokenLink[]} { const filteredRoutes = filterIntermediateRoutes(routes); - const allBrokenLinks = mapValues(allCollectedLinks, (pageLinks, pagePath) => + const allBrokenLinks = _.mapValues(allCollectedLinks, (pageLinks, pagePath) => getPageBrokenLinks({pageLinks, pagePath, routes: filteredRoutes}), ); // remove pages without any broken link - return pickBy(allBrokenLinks, (brokenLinks) => brokenLinks.length > 0); + return _.pickBy(allBrokenLinks, (brokenLinks) => brokenLinks.length > 0); } -export function getBrokenLinksErrorMessage( - allBrokenLinks: Record, -): string | undefined { +function getBrokenLinksErrorMessage(allBrokenLinks: { + [location: string]: BrokenLink[]; +}): string | undefined { if (Object.keys(allBrokenLinks).length === 0) { return undefined; } @@ -108,21 +106,25 @@ export function getBrokenLinksErrorMessage( pagePath: string, brokenLinks: BrokenLink[], ): string { - return `\n- On source page path = ${pagePath}:\n -> linking to ${brokenLinks - .map(brokenLinkMessage) - .join('\n -> linking to ')}`; + return ` +- On source page path = ${pagePath}: + -> linking to ${brokenLinks + .map(brokenLinkMessage) + .join('\n -> linking to ')}`; } - // If there's a broken link appearing very often, it is probably a broken link on the layout! - // Add an additional message in such case to help user figure this out. - // see https://github.com/facebook/docusaurus/issues/3567#issuecomment-706973805 + /** + * If there's a broken link appearing very often, it is probably a broken link + * on the layout. Add an additional message in such case to help user figure + * this out. See https://github.com/facebook/docusaurus/issues/3567#issuecomment-706973805 + */ function getLayoutBrokenLinksHelpMessage() { const flatList = Object.entries(allBrokenLinks).flatMap( ([pagePage, brokenLinks]) => brokenLinks.map((brokenLink) => ({pagePage, brokenLink})), ); - const countedBrokenLinks = countBy( + const countedBrokenLinks = _.countBy( flatList, (item) => item.brokenLink.link, ); @@ -136,49 +138,51 @@ export function getBrokenLinksErrorMessage( return ''; } - return `\n\nIt looks like some of the broken links we found appear in many pages of your site.\nMaybe those broken links appear on all pages through your site layout?\nWe recommend that you check your theme configuration for such links (particularly, theme navbar and footer).\nFrequent broken links are linking to:\n- ${frequentLinks.join( - `\n- `, - )}\n`; + return logger.interpolate` + +It looks like some of the broken links we found appear in many pages of your site. +Maybe those broken links appear on all pages through your site layout? +We recommend that you check your theme configuration for such links (particularly, theme navbar and footer). +Frequent broken links are linking to:${frequentLinks}`; } - return ( - `Docusaurus found broken links!\n\nPlease check the pages of your site in the list below, and make sure you don't reference any path that does not exist.\nNote: it's possible to ignore broken links with the 'onBrokenLinks' Docusaurus configuration, and let the build pass.${getLayoutBrokenLinksHelpMessage()}` + - `\n\nExhaustive list of all broken links found:\n${Object.entries( - allBrokenLinks, - ) - .map(([pagePath, brokenLinks]) => - pageBrokenLinksMessage(pagePath, brokenLinks), - ) - .join('\n')} -` - ); + return `Docusaurus found broken links! + +Please check the pages of your site in the list below, and make sure you don't reference any path that does not exist. +Note: it's possible to ignore broken links with the 'onBrokenLinks' Docusaurus configuration, and let the build pass.${getLayoutBrokenLinksHelpMessage()} + +Exhaustive list of all broken links found: +${Object.entries(allBrokenLinks) + .map(([pagePath, brokenLinks]) => + pageBrokenLinksMessage(pagePath, brokenLinks), + ) + .join('\n')} +`; } -function isExistingFile(filePath: string) { +async function isExistingFile(filePath: string) { try { - return fs.statSync(filePath).isFile(); - } catch (e) { + return (await fs.stat(filePath)).isFile(); + } catch { return false; } } // If a file actually exist on the file system, we know the link is valid // even if docusaurus does not know about this file, so we don't report it -export async function filterExistingFileLinks({ +async function filterExistingFileLinks({ baseUrl, outDir, allCollectedLinks, }: { baseUrl: string; outDir: string; - allCollectedLinks: Record; -}): Promise> { - // not easy to make this async :'( - function linkFileExists(link: string): boolean { + allCollectedLinks: {[location: string]: string[]}; +}): Promise<{[location: string]: string[]}> { + async function linkFileExists(link: string) { // /baseUrl/javadoc/ -> /outDir/javadoc - const baseFilePath = removeSuffix( - `${outDir}/${removePrefix(link, baseUrl)}`, - '/', + const baseFilePath = onlyPathname( + removeSuffix(`${outDir}/${removePrefix(link, baseUrl)}`, '/'), ); // -> /outDir/javadoc @@ -186,15 +190,28 @@ export async function filterExistingFileLinks({ // -> /outDir/javadoc/index.html const filePathsToTry: string[] = [baseFilePath]; if (!path.extname(baseFilePath)) { - filePathsToTry.push(`${baseFilePath}.html`); - filePathsToTry.push(path.join(baseFilePath, 'index.html')); + filePathsToTry.push( + `${baseFilePath}.html`, + path.join(baseFilePath, 'index.html'), + ); } - return filePathsToTry.some(isExistingFile); + for (const file of filePathsToTry) { + if (await isExistingFile(file)) { + return true; + } + } + return false; } - return mapValues(allCollectedLinks, (links) => - links.filter((link) => !linkFileExists(link)), + return combinePromises( + _.mapValues(allCollectedLinks, async (links) => + ( + await Promise.all( + links.map(async (link) => ((await linkFileExists(link)) ? '' : link)), + ) + ).filter(Boolean), + ), ); } @@ -205,7 +222,7 @@ export async function handleBrokenLinks({ baseUrl, outDir, }: { - allCollectedLinks: Record; + allCollectedLinks: {[location: string]: string[]}; onBrokenLinks: ReportingSeverity; routes: RouteConfig[]; baseUrl: string; @@ -215,8 +232,9 @@ export async function handleBrokenLinks({ return; } - // If we link to a file like /myFile.zip, and the file actually exist for the file system - // it is not a broken link, it may simply be a link to an existing static file... + // If we link to a file like /myFile.zip, and the file actually exist for the + // file system. It is not a broken link, it may simply be a link to an + // existing static file... const allCollectedLinksFiltered = await filterExistingFileLinks({ allCollectedLinks, baseUrl, diff --git a/packages/docusaurus/src/server/choosePort.ts b/packages/docusaurus/src/server/choosePort.ts new file mode 100644 index 000000000000..11f06ea0c9e2 --- /dev/null +++ b/packages/docusaurus/src/server/choosePort.ts @@ -0,0 +1,107 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {execSync} from 'child_process'; +import detect from 'detect-port'; +import logger from '@docusaurus/logger'; +import prompts from 'prompts'; + +const execOptions = { + encoding: 'utf8' as const, + stdio: [ + 'pipe' as const, // stdin (default) + 'pipe' as const, // stdout (default) + 'ignore' as const, // stderr + ], +}; + +// Clears console +function clearConsole(): void { + process.stdout.write( + process.platform === 'win32' ? '\x1B[2J\x1B[0f' : '\x1B[2J\x1B[3J\x1B[H', + ); +} + +// Gets process id of what is on port +function getProcessIdOnPort(port: number): string { + return execSync(`lsof -i:${port} -P -t -sTCP:LISTEN`, execOptions) + .split('\n')[0]! + .trim(); +} + +// Gets process command +function getProcessCommand(processId: string): string { + const command = execSync( + `ps -o command -p ${processId} | sed -n 2p`, + execOptions, + ); + + return command.replace(/\n$/, ''); +} + +// Gets directory of a process from its process id +function getDirectoryOfProcessById(processId: string): string { + return execSync( + `lsof -p ${processId} | awk '$4=="cwd" {for (i=9; i<=NF; i++) printf "%s ", $i}'`, + execOptions, + ).trim(); +} + +// Gets process on port +function getProcessForPort(port: number): string | null { + try { + const processId = getProcessIdOnPort(port); + const directory = getDirectoryOfProcessById(processId); + const command = getProcessCommand(processId); + return logger.interpolate`code=${command} subdue=${`(pid ${processId})`} in path=${directory}`; + } catch { + return null; + } +} + +/** + * Detects if program is running on port, and prompts user to choose another if + * port is already being used. This feature was heavily inspired by + * create-react-app and uses many of the same utility functions to implement it. + */ +export async function choosePort( + host: string, + defaultPort: number, +): Promise { + try { + const port = await detect({port: defaultPort, hostname: host}); + if (port === defaultPort) { + return port; + } + const isRoot = process.getuid?.() === 0; + const isInteractive = process.stdout.isTTY; + const message = + process.platform !== 'win32' && defaultPort < 1024 && !isRoot + ? `Admin permissions are required to run a server on a port below 1024.` + : `Something is already running on port ${defaultPort}.`; + if (!isInteractive) { + logger.error(message); + return null; + } + clearConsole(); + const existingProcess = getProcessForPort(defaultPort); + const {shouldChangePort} = await prompts({ + type: 'confirm', + name: 'shouldChangePort', + message: logger.yellow(`${logger.bold('[WARNING]')} ${message}${ + existingProcess ? ` Probably:\n ${existingProcess}` : '' + } + +Would you like to run the app on another port instead?`), + initial: true, + }); + return shouldChangePort ? port : null; + } catch (err) { + logger.error`Could not find an open port at ${host}.`; + throw err; + } +} diff --git a/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/plugin-hello-world.js b/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/plugin-hello-world.js deleted file mode 100644 index 9fa02b19e68e..000000000000 --- a/packages/docusaurus/src/server/client-modules/__tests__/__fixtures__/plugin-hello-world.js +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -module.exports = function() { - return { - plugin: 'plugin-hello-world', - getClientModules() { - return ['hello', 'world']; - }, - }; -}; diff --git a/packages/docusaurus/src/server/client-modules/__tests__/index.test.ts b/packages/docusaurus/src/server/client-modules/__tests__/index.test.ts deleted file mode 100644 index 55feae00455f..000000000000 --- a/packages/docusaurus/src/server/client-modules/__tests__/index.test.ts +++ /dev/null @@ -1,91 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import loadClientModules from '../index'; - -import pluginEmpty from './__fixtures__/plugin-empty'; -import pluginFooBar from './__fixtures__/plugin-foo-bar'; -import pluginHelloWorld from './__fixtures__/plugin-hello-world'; - -describe('loadClientModules', () => { - test('empty', () => { - const clientModules = loadClientModules([pluginEmpty()]); - expect(clientModules).toMatchInlineSnapshot(`Array []`); - }); - - test('non-empty', () => { - const clientModules = loadClientModules([pluginFooBar()]); - expect(clientModules).toMatchInlineSnapshot(` - Array [ - "foo", - "bar", - ] - `); - }); - - test('multiple non-empty', () => { - const clientModules = loadClientModules([ - pluginFooBar(), - pluginHelloWorld(), - ]); - expect(clientModules).toMatchInlineSnapshot(` - Array [ - "foo", - "bar", - "hello", - "world", - ] - `); - }); - - test('multiple non-empty different order', () => { - const clientModules = loadClientModules([ - pluginHelloWorld(), - pluginFooBar(), - ]); - expect(clientModules).toMatchInlineSnapshot(` - Array [ - "hello", - "world", - "foo", - "bar", - ] - `); - }); - - test('empty and non-empty', () => { - const clientModules = loadClientModules([ - pluginHelloWorld(), - pluginEmpty(), - pluginFooBar(), - ]); - expect(clientModules).toMatchInlineSnapshot(` - Array [ - "hello", - "world", - "foo", - "bar", - ] - `); - }); - - test('empty and non-empty different order', () => { - const clientModules = loadClientModules([ - pluginHelloWorld(), - pluginFooBar(), - pluginEmpty(), - ]); - expect(clientModules).toMatchInlineSnapshot(` - Array [ - "hello", - "world", - "foo", - "bar", - ] - `); - }); -}); diff --git a/packages/docusaurus/src/server/client-modules/index.ts b/packages/docusaurus/src/server/client-modules/index.ts deleted file mode 100644 index 68080ee72c9b..000000000000 --- a/packages/docusaurus/src/server/client-modules/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import type {Plugin} from '@docusaurus/types'; - -export default function loadClientModules( - plugins: Plugin[], -): string[] { - return plugins.flatMap((plugin) => plugin.getClientModules?.() ?? []); -} diff --git a/packages/docusaurus/src/server/clientModules.ts b/packages/docusaurus/src/server/clientModules.ts new file mode 100644 index 000000000000..5a2057fe611c --- /dev/null +++ b/packages/docusaurus/src/server/clientModules.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import path from 'path'; +import type {LoadedPlugin} from '@docusaurus/types'; + +/** + * Runs the `getClientModules` lifecycle. The returned file paths are all + * absolute. + */ +export function loadClientModules(plugins: LoadedPlugin[]): string[] { + return plugins.flatMap( + (plugin) => + plugin.getClientModules?.().map((p) => path.resolve(plugin.path, p)) ?? + [], + ); +} diff --git a/packages/docusaurus/src/server/config.ts b/packages/docusaurus/src/server/config.ts index 53bfec0a950a..84b4975f1249 100644 --- a/packages/docusaurus/src/server/config.ts +++ b/packages/docusaurus/src/server/config.ts @@ -5,28 +5,36 @@ * LICENSE file in the root directory of this source tree. */ +import path from 'path'; import fs from 'fs-extra'; import importFresh from 'import-fresh'; -import type {DocusaurusConfig} from '@docusaurus/types'; +import {DEFAULT_CONFIG_FILE_NAME} from '@docusaurus/utils'; +import type {LoadContext} from '@docusaurus/types'; import {validateConfig} from './configValidation'; -export default async function loadConfig( - configPath: string, -): Promise { - if (!fs.existsSync(configPath)) { - throw new Error(`Config file at "${configPath}" not found.`); +export async function loadSiteConfig({ + siteDir, + customConfigFilePath, +}: { + siteDir: string; + customConfigFilePath?: string; +}): Promise> { + const siteConfigPath = path.resolve( + siteDir, + customConfigFilePath ?? DEFAULT_CONFIG_FILE_NAME, + ); + + if (!(await fs.pathExists(siteConfigPath))) { + throw new Error(`Config file at "${siteConfigPath}" not found.`); } - const importedConfig = importFresh(configPath) as - | Partial - | Promise> - | (() => Partial) - | (() => Promise>); + const importedConfig = importFresh(siteConfigPath); const loadedConfig = - importedConfig instanceof Function + typeof importedConfig === 'function' ? await importedConfig() : await importedConfig; - return validateConfig(loadedConfig); + const siteConfig = validateConfig(loadedConfig); + return {siteConfig, siteConfigPath}; } diff --git a/packages/docusaurus/src/server/configValidation.ts b/packages/docusaurus/src/server/configValidation.ts index 420315ff0da9..a25659ae892e 100644 --- a/packages/docusaurus/src/server/configValidation.ts +++ b/packages/docusaurus/src/server/configValidation.ts @@ -5,16 +5,12 @@ * LICENSE file in the root directory of this source tree. */ -import logger from '@docusaurus/logger'; import type {DocusaurusConfig, I18nConfig} from '@docusaurus/types'; -import {DEFAULT_CONFIG_FILE_NAME, STATIC_DIR_NAME} from '@docusaurus/utils'; import { - Joi, - logValidationBugReportHint, - isValidationDisabledEscapeHatch, - URISchema, - printWarning, -} from '@docusaurus/utils-validation'; + DEFAULT_CONFIG_FILE_NAME, + DEFAULT_STATIC_DIR_NAME, +} from '@docusaurus/utils'; +import {Joi, URISchema, printWarning} from '@docusaurus/utils-validation'; const DEFAULT_I18N_LOCALE = 'en'; @@ -33,10 +29,14 @@ export const DEFAULT_CONFIG: Pick< | 'plugins' | 'themes' | 'presets' + | 'stylesheets' + | 'scripts' + | 'clientModules' | 'customFields' | 'themeConfig' | 'titleDelimiter' | 'noIndex' + | 'tagline' | 'baseUrlIssueBanner' | 'staticDirectories' > = { @@ -47,29 +47,47 @@ export const DEFAULT_CONFIG: Pick< plugins: [], themes: [], presets: [], + stylesheets: [], + scripts: [], + clientModules: [], customFields: {}, themeConfig: {}, titleDelimiter: '|', noIndex: false, + tagline: '', baseUrlIssueBanner: true, - staticDirectories: [STATIC_DIR_NAME], + staticDirectories: [DEFAULT_STATIC_DIR_NAME], }; -const PluginSchema = Joi.alternatives() - .try( - Joi.function(), - Joi.array().ordered(Joi.function().required(), Joi.object().required()), - Joi.string(), - Joi.array() - .ordered(Joi.string().required(), Joi.object().required()) - .length(2), - Joi.bool().equal(false), // In case of conditional adding of plugins. - ) - // @ts-expect-error: bad lib def, doesn't recognize an array of reports - .error((errors) => { - errors.forEach((error) => { - error.message = ` => Bad Docusaurus plugin value as path [${error.path}]. -Example valid plugin config: +function createPluginSchema(theme: boolean) { + return ( + Joi.alternatives() + .try( + Joi.function(), + Joi.array() + .ordered(Joi.function().required(), Joi.object().required()) + .length(2), + Joi.string(), + Joi.array() + .ordered(Joi.string().required(), Joi.object().required()) + .length(2), + Joi.bool().equal(false), // In case of conditional adding of plugins. + ) + // @ts-expect-error: bad lib def, doesn't recognize an array of reports + .error((errors) => { + errors.forEach((error) => { + const validConfigExample = theme + ? `Example valid theme config: +{ + themes: [ + ["@docusaurus/theme-classic",options], + "./myTheme", + ["./myTheme",{someOption: 42}], + function myTheme() { }, + [function myTheme() { },options] + ], +};` + : `Example valid plugin config: { plugins: [ ["@docusaurus/plugin-content-docs",options], @@ -78,21 +96,35 @@ Example valid plugin config: function myPlugin() { }, [function myPlugin() { },options] ], -}; +};`; + + error.message = ` => Bad Docusaurus ${ + theme ? 'theme' : 'plugin' + } value as path [${error.path}]. +${validConfigExample} `; - }); - return errors; - }); + }); + return errors; + }) + ); +} + +const PluginSchema = createPluginSchema(false); -const ThemeSchema = Joi.alternatives().try( - Joi.string(), - Joi.array().items(Joi.string().required(), Joi.object().required()).length(2), -); +const ThemeSchema = createPluginSchema(true); -const PresetSchema = Joi.alternatives().try( - Joi.string(), - Joi.array().items(Joi.string().required(), Joi.object().required()).length(2), -); +const PresetSchema = Joi.alternatives() + .try( + Joi.string(), + Joi.array() + .items(Joi.string().required(), Joi.object().required()) + .length(2), + ) + .messages({ + 'alternatives.types': `{#label} does not look like a valid preset config. A preset config entry should be one of: +- A tuple of [presetName, options], like \`["classic", \\{ blog: false \\}]\`, or +- A simple string, like \`"classic"\``, + }); const LocaleConfigSchema = Joi.object({ label: Joi.string(), @@ -118,7 +150,7 @@ const SiteUrlSchema = URISchema.required().custom((value, helpers) => { warningMessage: `the url is not supposed to contain a sub-path like '${pathname}', please use the baseUrl field for sub-paths`, }); } - } catch (e) {} + } catch {} return value; }, 'siteUrlCustomValidation'); @@ -155,26 +187,40 @@ export const ConfigSchema = Joi.object({ themes: Joi.array().items(ThemeSchema).default(DEFAULT_CONFIG.themes), presets: Joi.array().items(PresetSchema).default(DEFAULT_CONFIG.presets), themeConfig: Joi.object().unknown().default(DEFAULT_CONFIG.themeConfig), - scripts: Joi.array().items( - Joi.string(), - Joi.object({ - src: Joi.string().required(), - async: Joi.bool(), - defer: Joi.bool(), + scripts: Joi.array() + .items( + Joi.string(), + Joi.object({ + src: Joi.string().required(), + async: Joi.bool(), + defer: Joi.bool(), + }) + // See https://github.com/facebook/docusaurus/issues/3378 + .unknown(), + ) + .messages({ + 'array.includes': + '{#label} is invalid. A script must be a plain string (the src), or an object with at least a "src" property.', }) - // See https://github.com/facebook/docusaurus/issues/3378 - .unknown(), - ), + .default(DEFAULT_CONFIG.scripts), ssrTemplate: Joi.string(), - stylesheets: Joi.array().items( - Joi.string(), - Joi.object({ - href: Joi.string().required(), - type: Joi.string(), - }).unknown(), - ), - clientModules: Joi.array().items(Joi.string()), - tagline: Joi.string().allow(''), + stylesheets: Joi.array() + .items( + Joi.string(), + Joi.object({ + href: Joi.string().required(), + type: Joi.string(), + }).unknown(), + ) + .messages({ + 'array.includes': + '{#label} is invalid. A stylesheet must be a plain string (the href), or an object with at least a "href" property.', + }) + .default(DEFAULT_CONFIG.stylesheets), + clientModules: Joi.array() + .items(Joi.string()) + .default(DEFAULT_CONFIG.clientModules), + tagline: Joi.string().allow('').default(DEFAULT_CONFIG.tagline), titleDelimiter: Joi.string().default('|'), noIndex: Joi.bool().default(false), webpack: Joi.object({ @@ -198,12 +244,6 @@ export function validateConfig( printWarning(warning); if (error) { - logValidationBugReportHint(); - if (isValidationDisabledEscapeHatch) { - logger.error(error.message); - return config as DocusaurusConfig; - } - const unknownFields = error.details.reduce((formattedError, err) => { if (err.type === 'object.unknown') { return `${formattedError}"${err.path}",`; @@ -218,7 +258,7 @@ export function validateConfig( '', ); formattedError = unknownFields - ? `${formattedError}These field(s) (${unknownFields}) are not recognized in ${DEFAULT_CONFIG_FILE_NAME}.\nIf you still want these fields to be in your configuration, put them in the "customFields" field.\nSee https://docusaurus.io/docs/docusaurus.config.js/#customfields` + ? `${formattedError}These field(s) (${unknownFields}) are not recognized in ${DEFAULT_CONFIG_FILE_NAME}.\nIf you still want these fields to be in your configuration, put them in the "customFields" field.\nSee https://docusaurus.io/docs/api/docusaurus-config/#customfields` : formattedError; throw new Error(formattedError); } else { diff --git a/packages/docusaurus/src/server/duplicateRoutes.ts b/packages/docusaurus/src/server/duplicateRoutes.ts deleted file mode 100644 index 42ae39306699..000000000000 --- a/packages/docusaurus/src/server/duplicateRoutes.ts +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import type {ReportingSeverity, RouteConfig} from '@docusaurus/types'; -import {reportMessage} from '@docusaurus/utils'; -import {getAllFinalRoutes} from './utils'; - -export function getAllDuplicateRoutes( - pluginsRouteConfigs: RouteConfig[], -): string[] { - const allRoutes: string[] = getAllFinalRoutes(pluginsRouteConfigs).map( - (routeConfig) => routeConfig.path, - ); - const seenRoutes: Record = {}; - return allRoutes.filter((route) => { - if (Object.prototype.hasOwnProperty.call(seenRoutes, route)) { - return true; - } else { - seenRoutes[route] = true; - return false; - } - }); -} - -export function getDuplicateRoutesMessage( - allDuplicateRoutes: string[], -): string { - const message = allDuplicateRoutes - .map( - (duplicateRoute) => - `Attempting to create page at ${duplicateRoute}, but a page already exists at this route`, - ) - .join('\n'); - return message; -} - -export function handleDuplicateRoutes( - pluginsRouteConfigs: RouteConfig[], - onDuplicateRoutes: ReportingSeverity, -): void { - if (onDuplicateRoutes === 'ignore') { - return; - } - const duplicatePaths: string[] = getAllDuplicateRoutes(pluginsRouteConfigs); - const message: string = getDuplicateRoutesMessage(duplicatePaths); - if (message) { - const finalMessage = `Duplicate routes found!\n${message}\nThis could lead to non-deterministic routing behavior`; - reportMessage(finalMessage, onDuplicateRoutes); - } -} diff --git a/packages/docusaurus/src/server/html-tags/__tests__/__fixtures__/plugin-headTags.js b/packages/docusaurus/src/server/html-tags/__tests__/__fixtures__/plugin-headTags.js deleted file mode 100644 index 99b0d07b9ac2..000000000000 --- a/packages/docusaurus/src/server/html-tags/__tests__/__fixtures__/plugin-headTags.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -module.exports = function() { - return { - name: 'plugin-headTags-only', - injectHtmlTags() { - return { - headTags: [ - { - tagName: 'link', - attributes: { - rel: 'preconnect', - href: 'www.google-analytics.com', - }, - }, - ``, - ], - }; - }, - }; -}; diff --git a/packages/docusaurus/src/server/html-tags/__tests__/__fixtures__/plugin-postBodyTags.js b/packages/docusaurus/src/server/html-tags/__tests__/__fixtures__/plugin-postBodyTags.js deleted file mode 100644 index 6ce0398aed1c..000000000000 --- a/packages/docusaurus/src/server/html-tags/__tests__/__fixtures__/plugin-postBodyTags.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -module.exports = function() { - return { - name: 'plugin-postBody-tags', - injectHtmlTags() { - return { - postBodyTags: [ - { - tagName: 'div', - innerHTML: 'Test content', - }, - ], - }; - }, - }; -}; diff --git a/packages/docusaurus/src/server/html-tags/__tests__/__fixtures__/plugin-preBodyTags.js b/packages/docusaurus/src/server/html-tags/__tests__/__fixtures__/plugin-preBodyTags.js deleted file mode 100644 index db23f8c883d5..000000000000 --- a/packages/docusaurus/src/server/html-tags/__tests__/__fixtures__/plugin-preBodyTags.js +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -module.exports = function() { - return { - name: 'plugin-preBodyTags', - injectHtmlTags() { - return { - preBodyTags: { - tagName: 'script', - attributes: { - type: 'text/javascript', - }, - innerHTML: 'window.foo = null;', - }, - }; - }, - }; -}; diff --git a/packages/docusaurus/src/server/html-tags/__tests__/htmlTags.test.ts b/packages/docusaurus/src/server/html-tags/__tests__/htmlTags.test.ts deleted file mode 100644 index fa931c50d673..000000000000 --- a/packages/docusaurus/src/server/html-tags/__tests__/htmlTags.test.ts +++ /dev/null @@ -1,122 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import htmlTagObjectToString from '../htmlTags'; - -describe('htmlTagObjectToString', () => { - test('valid html tag', () => { - expect( - htmlTagObjectToString({ - tagName: 'script', - attributes: { - type: 'text/javascript', - src: 'https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.0/clipboard.min.js', - async: true, - 'data-options': '{"prop":true}', - }, - }), - ).toMatchInlineSnapshot( - `""`, - ); - - expect( - htmlTagObjectToString({ - tagName: 'link', - attributes: { - rel: 'preconnect', - href: 'www.google-analytics.com', - }, - }), - ).toMatchInlineSnapshot( - `""`, - ); - - expect( - htmlTagObjectToString({ - tagName: 'div', - attributes: { - style: 'background-color:lightblue', - }, - innerHTML: 'Lightblue color here', - }), - ).toMatchInlineSnapshot( - `"
      Lightblue color here
      "`, - ); - - expect( - htmlTagObjectToString({ - tagName: 'div', - innerHTML: 'Test', - }), - ).toMatchInlineSnapshot(`"
      Test
      "`); - }); - - test('valid html void tag', () => { - expect( - htmlTagObjectToString({ - tagName: 'meta', - attributes: { - name: 'generator', - content: 'Docusaurus', - }, - }), - ).toMatchInlineSnapshot( - `""`, - ); - - expect( - htmlTagObjectToString({ - tagName: 'img', - attributes: { - src: '/img/docusaurus.png', - alt: 'Docusaurus logo', - height: '42', - width: '42', - }, - }), - ).toMatchInlineSnapshot( - `"\\"Docusaurus"`, - ); - }); - - test('invalid tag', () => { - expect(() => - htmlTagObjectToString({ - tagName: 'endiliey', - attributes: { - this: 'is invalid', - }, - }), - ).toThrowErrorMatchingInlineSnapshot( - `"Error loading {\\"tagName\\":\\"endiliey\\",\\"attributes\\":{\\"this\\":\\"is invalid\\"}}, \\"endiliey\\" is not a valid HTML tags."`, - ); - }); - - test('invalid tagName', () => { - expect(() => - htmlTagObjectToString({ - tagName: true, - }), - ).toThrowErrorMatchingInlineSnapshot( - `"{\\"tagName\\":true} is not a valid HTML tag object. \\"tagName\\" must be defined as a string."`, - ); - }); - - test('invalid html tag object', () => { - expect(() => - htmlTagObjectToString('fooofofoofo'), - ).toThrowErrorMatchingInlineSnapshot( - `"\\"fooofofoofo\\" is not a valid HTML tag object."`, - ); - - expect(() => - htmlTagObjectToString(null), - ).toThrowErrorMatchingInlineSnapshot( - `"\\"null\\" is not a valid HTML tag object."`, - ); - }); -}); diff --git a/packages/docusaurus/src/server/html-tags/__tests__/index.test.ts b/packages/docusaurus/src/server/html-tags/__tests__/index.test.ts deleted file mode 100644 index 751368320b94..000000000000 --- a/packages/docusaurus/src/server/html-tags/__tests__/index.test.ts +++ /dev/null @@ -1,92 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import {loadHtmlTags} from '../index'; - -import pluginEmpty from './__fixtures__/plugin-empty'; -import pluginPreBodyTags from './__fixtures__/plugin-preBodyTags'; -import pluginHeadTags from './__fixtures__/plugin-headTags'; -import pluginPostBodyTags from './__fixtures__/plugin-postBodyTags'; - -describe('loadHtmlTags', () => { - test('empty plugin', () => { - const htmlTags = loadHtmlTags([pluginEmpty()]); - expect(htmlTags).toMatchInlineSnapshot(` - Object { - "headTags": "", - "postBodyTags": "", - "preBodyTags": "", - } - `); - }); - - test('only inject headTags', () => { - const htmlTags = loadHtmlTags([pluginHeadTags()]); - expect(htmlTags).toMatchInlineSnapshot(` - Object { - "headTags": " - ", - "postBodyTags": "", - "preBodyTags": "", - } - `); - }); - - test('only inject preBodyTags', () => { - const htmlTags = loadHtmlTags([pluginPreBodyTags()]); - expect(htmlTags).toMatchInlineSnapshot(` - Object { - "headTags": "", - "postBodyTags": "", - "preBodyTags": "", - } - `); - }); - - test('only inject postBodyTags', () => { - const htmlTags = loadHtmlTags([pluginPostBodyTags()]); - expect(htmlTags).toMatchInlineSnapshot(` - Object { - "headTags": "", - "postBodyTags": "
      Test content
      ", - "preBodyTags": "", - } - `); - }); - - test('multiple plugins that inject different part of html tags', () => { - const htmlTags = loadHtmlTags([ - pluginHeadTags(), - pluginPostBodyTags(), - pluginPreBodyTags(), - ]); - expect(htmlTags).toMatchInlineSnapshot(` - Object { - "headTags": " - ", - "postBodyTags": "
      Test content
      ", - "preBodyTags": "", - } - `); - }); - - test('multiple plugins that might/might not inject html tags', () => { - const htmlTags = loadHtmlTags([ - pluginEmpty(), - pluginHeadTags(), - pluginPostBodyTags(), - ]); - expect(htmlTags).toMatchInlineSnapshot(` - Object { - "headTags": " - ", - "postBodyTags": "
      Test content
      ", - "preBodyTags": "", - } - `); - }); -}); diff --git a/packages/docusaurus/src/server/html-tags/htmlTags.ts b/packages/docusaurus/src/server/html-tags/htmlTags.ts deleted file mode 100644 index 56d671ec8f80..000000000000 --- a/packages/docusaurus/src/server/html-tags/htmlTags.ts +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import {isPlainObject} from 'lodash'; -import type {HtmlTagObject} from '@docusaurus/types'; -import htmlTags from 'html-tags'; -import voidHtmlTags from 'html-tags/void'; -import escapeHTML from 'escape-html'; - -function assertIsHtmlTagObject(val: unknown): asserts val is HtmlTagObject { - if (!isPlainObject(val)) { - throw new Error(`"${val}" is not a valid HTML tag object.`); - } - if (typeof (val as HtmlTagObject).tagName !== 'string') { - throw new Error( - `${JSON.stringify( - val, - )} is not a valid HTML tag object. "tagName" must be defined as a string.`, - ); - } -} - -export default function htmlTagObjectToString(tagDefinition: unknown): string { - assertIsHtmlTagObject(tagDefinition); - if (htmlTags.indexOf(tagDefinition.tagName) === -1) { - throw new Error( - `Error loading ${JSON.stringify(tagDefinition)}, "${ - tagDefinition.tagName - }" is not a valid HTML tags.`, - ); - } - const isVoidTag = voidHtmlTags.indexOf(tagDefinition.tagName) !== -1; - const tagAttributes = tagDefinition.attributes || {}; - const attributes = Object.keys(tagAttributes) - .filter((attributeName) => tagAttributes[attributeName] !== false) - .map((attributeName) => { - if (tagAttributes[attributeName] === true) { - return attributeName; - } - return `${attributeName}="${escapeHTML( - tagAttributes[attributeName] as string, - )}"`; - }); - return `<${[tagDefinition.tagName].concat(attributes).join(' ')}>${ - (!isVoidTag && tagDefinition.innerHTML) || '' - }${isVoidTag ? '' : ``}`; -} diff --git a/packages/docusaurus/src/server/html-tags/index.ts b/packages/docusaurus/src/server/html-tags/index.ts deleted file mode 100644 index 8837b0f5baf6..000000000000 --- a/packages/docusaurus/src/server/html-tags/index.ts +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import htmlTagObjectToString from './htmlTags'; -import type { - InjectedHtmlTags, - HtmlTagObject, - HtmlTags, - LoadedPlugin, -} from '@docusaurus/types'; - -function toString(val: string | HtmlTagObject): string { - return typeof val === 'string' ? val : htmlTagObjectToString(val); -} - -export function createHtmlTagsString(tags: HtmlTags): string { - return Array.isArray(tags) ? tags.map(toString).join('\n') : toString(tags); -} - -export function loadHtmlTags(plugins: LoadedPlugin[]): InjectedHtmlTags { - const htmlTags = plugins.reduce( - (acc, plugin) => { - if (!plugin.injectHtmlTags) { - return acc; - } - const {headTags, preBodyTags, postBodyTags} = - plugin.injectHtmlTags({content: plugin.content}) || {}; - return { - headTags: headTags - ? `${acc.headTags}\n${createHtmlTagsString(headTags)}` - : acc.headTags, - preBodyTags: preBodyTags - ? `${acc.preBodyTags}\n${createHtmlTagsString(preBodyTags)}` - : acc.preBodyTags, - postBodyTags: postBodyTags - ? `${acc.postBodyTags}\n${createHtmlTagsString(postBodyTags)}` - : acc.postBodyTags, - }; - }, - {headTags: '', preBodyTags: '', postBodyTags: ''}, - ); - - return { - headTags: htmlTags.headTags.trim(), - preBodyTags: htmlTags.preBodyTags.trim(), - postBodyTags: htmlTags.postBodyTags.trim(), - }; -} diff --git a/packages/docusaurus/src/server/htmlTags.ts b/packages/docusaurus/src/server/htmlTags.ts new file mode 100644 index 000000000000..66c497d8f1ce --- /dev/null +++ b/packages/docusaurus/src/server/htmlTags.ts @@ -0,0 +1,87 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import htmlTags from 'html-tags'; +import voidHtmlTags from 'html-tags/void'; +import escapeHTML from 'escape-html'; +import _ from 'lodash'; +import type { + Props, + HtmlTagObject, + HtmlTags, + LoadedPlugin, +} from '@docusaurus/types'; + +function assertIsHtmlTagObject(val: unknown): asserts val is HtmlTagObject { + if (typeof val !== 'object' || !val) { + throw new Error(`"${val}" is not a valid HTML tag object.`); + } + if (typeof (val as HtmlTagObject).tagName !== 'string') { + throw new Error( + `${JSON.stringify( + val, + )} is not a valid HTML tag object. "tagName" must be defined as a string.`, + ); + } + if (!htmlTags.includes((val as HtmlTagObject).tagName)) { + throw new Error( + `Error loading ${JSON.stringify(val)}, "${ + (val as HtmlTagObject).tagName + }" is not a valid HTML tag.`, + ); + } +} + +function htmlTagObjectToString(tag: unknown): string { + assertIsHtmlTagObject(tag); + const isVoidTag = voidHtmlTags.includes(tag.tagName); + const tagAttributes = tag.attributes ?? {}; + const attributes = Object.keys(tagAttributes) + .map((attr) => { + const value = tagAttributes[attr]!; + if (typeof value === 'boolean') { + return value ? attr : undefined; + } + return `${attr}="${escapeHTML(value)}"`; + }) + .filter((str): str is string => Boolean(str)); + const openingTag = `<${[tag.tagName].concat(attributes).join(' ')}>`; + const innerHTML = (!isVoidTag && tag.innerHTML) || ''; + const closingTag = isVoidTag ? '' : ``; + return openingTag + innerHTML + closingTag; +} + +function createHtmlTagsString(tags: HtmlTags | undefined): string { + return (Array.isArray(tags) ? tags : [tags]) + .filter(Boolean) + .map((val) => (typeof val === 'string' ? val : htmlTagObjectToString(val))) + .join('\n'); +} + +/** + * Runs the `injectHtmlTags` lifecycle, and aggregates all plugins' tags into + * directly render-able HTML markup. + */ +export function loadHtmlTags( + plugins: LoadedPlugin[], +): Pick { + const pluginHtmlTags = plugins.map( + (plugin) => plugin.injectHtmlTags?.({content: plugin.content}) ?? {}, + ); + const tagTypes = ['headTags', 'preBodyTags', 'postBodyTags'] as const; + return Object.fromEntries( + _.zip( + tagTypes, + tagTypes.map((type) => + pluginHtmlTags + .map((tags) => createHtmlTagsString(tags[type])) + .join('\n') + .trim(), + ), + ), + ); +} diff --git a/packages/docusaurus/src/server/i18n.ts b/packages/docusaurus/src/server/i18n.ts index 6a847d6e9b8a..e171b21f78aa 100644 --- a/packages/docusaurus/src/server/i18n.ts +++ b/packages/docusaurus/src/server/i18n.ts @@ -5,16 +5,15 @@ * LICENSE file in the root directory of this source tree. */ -import type {I18n, DocusaurusConfig, I18nLocaleConfig} from '@docusaurus/types'; -import path from 'path'; -import {normalizeUrl} from '@docusaurus/utils'; import {getLangDir} from 'rtl-detect'; import logger from '@docusaurus/logger'; +import type {I18n, DocusaurusConfig, I18nLocaleConfig} from '@docusaurus/types'; +import type {LoadContextOptions} from './index'; function getDefaultLocaleLabel(locale: string) { const languageName = new Intl.DisplayNames(locale, {type: 'language'}).of( locale, - ); + )!; return ( languageName.charAt(0).toLocaleUpperCase(locale) + languageName.substring(1) ); @@ -28,18 +27,9 @@ export function getDefaultLocaleConfig(locale: string): I18nLocaleConfig { }; } -export function shouldWarnAboutNodeVersion( - version: number, - locales: string[], -): boolean { - const isOnlyEnglish = locales.length === 1 && locales.includes('en'); - const isOlderNodeVersion = version < 14; - return isOlderNodeVersion && !isOnlyEnglish; -} - export async function loadI18n( config: DocusaurusConfig, - options: {locale?: string} = {}, + options: Pick, ): Promise { const {i18n: i18nConfig} = config; @@ -73,38 +63,3 @@ Note: Docusaurus only support running one locale at a time.`; localeConfigs, }; } - -export function localizePath({ - pathType, - path: originalPath, - i18n, - options = {}, -}: { - pathType: 'fs' | 'url'; - path: string; - i18n: I18n; - options?: {localizePath?: boolean}; -}): string { - const shouldLocalizePath: boolean = - typeof options.localizePath === 'undefined' - ? // By default, we don't localize the path of defaultLocale - i18n.currentLocale !== i18n.defaultLocale - : options.localizePath; - - if (shouldLocalizePath) { - // FS paths need special care, for Windows support - if (pathType === 'fs') { - return path.join(originalPath, path.sep, i18n.currentLocale, path.sep); - } - // Url paths - else if (pathType === 'url') { - return normalizeUrl([originalPath, '/', i18n.currentLocale, '/']); - } - // should never happen - else { - throw new Error(`Unhandled path type "${pathType}".`); - } - } else { - return originalPath; - } -} diff --git a/packages/docusaurus/src/server/index.ts b/packages/docusaurus/src/server/index.ts index cdf104567216..fecb98e3111f 100644 --- a/packages/docusaurus/src/server/index.ts +++ b/packages/docusaurus/src/server/index.ts @@ -8,84 +8,61 @@ import { generate, escapePath, + localizePath, DEFAULT_BUILD_DIR_NAME, DEFAULT_CONFIG_FILE_NAME, GENERATED_FILES_DIR_NAME, } from '@docusaurus/utils'; +import _ from 'lodash'; import path from 'path'; -import logger from '@docusaurus/logger'; +import {loadSiteConfig} from './config'; import ssrDefaultTemplate from '../webpack/templates/ssr.html.template'; -import loadClientModules from './client-modules'; -import loadConfig from './config'; +import {loadClientModules} from './clientModules'; import {loadPlugins} from './plugins'; -import loadPresets from './presets'; -import loadRoutes from './routes'; -import type { - DocusaurusConfig, - DocusaurusSiteMetadata, - HtmlTagObject, - LoadContext, - LoadedPlugin, - PluginConfig, - Props, -} from '@docusaurus/types'; -import {loadHtmlTags} from './html-tags'; -import {getPackageJsonVersion} from './versions'; -import {handleDuplicateRoutes} from './duplicateRoutes'; -import {loadI18n, localizePath} from './i18n'; +import {loadRoutes} from './routes'; +import {loadHtmlTags} from './htmlTags'; +import {loadSiteMetadata} from './siteMetadata'; +import {loadI18n} from './i18n'; import { readCodeTranslationFileContent, getPluginsDefaultCodeTranslationMessages, } from './translations/translations'; -import {mapValues} from 'lodash'; -import type {RuleSetRule} from 'webpack'; -import admonitions from 'remark-admonitions'; -import {createRequire} from 'module'; -import {resolveModuleName} from './moduleShorthand'; +import type {DocusaurusConfig, LoadContext, Props} from '@docusaurus/types'; export type LoadContextOptions = { + /** Usually the CWD; can be overridden with command argument. */ + siteDir: string; + /** Can be customized with `--out-dir` option */ customOutDir?: string; + /** Can be customized with `--config` option */ customConfigFilePath?: string; + /** Default is `i18n.defaultLocale` */ locale?: string; - localizePath?: boolean; // undefined = only non-default locales paths are localized + /** + * `true` means the paths will have the locale prepended; `false` means they + * won't (useful for `yarn build -l zh-Hans` where the output should be + * emitted into `build/` instead of `build/zh-Hans/`); `undefined` is like the + * "smart" option where only non-default locale paths are localized + */ + localizePath?: boolean; }; -export async function loadSiteConfig({ - siteDir, - customConfigFilePath, -}: { - siteDir: string; - customConfigFilePath?: string; -}): Promise<{siteConfig: DocusaurusConfig; siteConfigPath: string}> { - const siteConfigPathUnresolved = - customConfigFilePath ?? DEFAULT_CONFIG_FILE_NAME; - - const siteConfigPath = path.isAbsolute(siteConfigPathUnresolved) - ? siteConfigPathUnresolved - : path.resolve(siteDir, siteConfigPathUnresolved); - - const siteConfig = await loadConfig(siteConfigPath); - return {siteConfig, siteConfigPath}; -} - +/** + * Loading context is the very first step in site building. Its options are + * directly acquired from CLI options. It mainly loads `siteConfig` and the i18n + * context (which includes code translations). The `LoadContext` will be passed + * to plugin constructors. + */ export async function loadContext( - siteDir: string, - options: LoadContextOptions = {}, + options: LoadContextOptions, ): Promise { - const {customOutDir, locale, customConfigFilePath} = options; - const generatedFilesDir = path.isAbsolute(GENERATED_FILES_DIR_NAME) - ? GENERATED_FILES_DIR_NAME - : path.resolve(siteDir, GENERATED_FILES_DIR_NAME); + const {siteDir, customOutDir, locale, customConfigFilePath} = options; + const generatedFilesDir = path.resolve(siteDir, GENERATED_FILES_DIR_NAME); const {siteConfig: initialSiteConfig, siteConfigPath} = await loadSiteConfig({ siteDir, customConfigFilePath, }); - const {ssrTemplate} = initialSiteConfig; - - const baseOutDir = customOutDir - ? path.resolve(customOutDir) - : path.resolve(siteDir, DEFAULT_BUILD_DIR_NAME); const i18n = await loadI18n(initialSiteConfig, {locale}); @@ -96,7 +73,7 @@ export async function loadContext( pathType: 'url', }); const outDir = localizePath({ - path: baseOutDir, + path: path.resolve(siteDir, customOutDir ?? DEFAULT_BUILD_DIR_NAME), i18n, options, pathType: 'fs', @@ -111,7 +88,7 @@ export async function loadContext( })) ?? {}; // We only need key->message for code translations - const codeTranslations = mapValues( + const codeTranslations = _.mapValues( codeTranslationFileContent, (value) => value.message, ); @@ -122,169 +99,22 @@ export async function loadContext( siteConfig, siteConfigPath, outDir, - baseUrl, // TODO to remove: useless, there's already siteConfig.baseUrl! (and yes, it's the same value, cf code above) + baseUrl, i18n, - ssrTemplate: ssrTemplate ?? ssrDefaultTemplate, + ssrTemplate: siteConfig.ssrTemplate ?? ssrDefaultTemplate, codeTranslations, }; } -export function loadPluginConfigs(context: LoadContext): PluginConfig[] { - let {plugins: presetPlugins, themes: presetThemes} = loadPresets(context); - const {siteConfig, siteConfigPath} = context; - const require = createRequire(siteConfigPath); - function normalizeShorthand( - pluginConfig: PluginConfig, - pluginType: 'plugin' | 'theme', - ): PluginConfig { - if (typeof pluginConfig === 'string') { - return resolveModuleName(pluginConfig, require, pluginType); - } else if ( - Array.isArray(pluginConfig) && - typeof pluginConfig[0] === 'string' - ) { - return [ - resolveModuleName(pluginConfig[0], require, pluginType), - pluginConfig[1] ?? {}, - ]; - } - return pluginConfig; - } - presetPlugins = presetPlugins.map((plugin) => - normalizeShorthand(plugin, 'plugin'), - ); - presetThemes = presetThemes.map((theme) => - normalizeShorthand(theme, 'theme'), - ); - const standalonePlugins = (siteConfig.plugins || []).map((plugin) => - normalizeShorthand(plugin, 'plugin'), - ); - const standaloneThemes = (siteConfig.themes || []).map((theme) => - normalizeShorthand(theme, 'theme'), - ); - return [ - ...presetPlugins, - ...presetThemes, - // Site config should be the highest priority. - ...standalonePlugins, - ...standaloneThemes, - ]; -} - -// Make a fake plugin to: -// - Resolve aliased theme components -// - Inject scripts/stylesheets -function createBootstrapPlugin({ - siteConfig, -}: { - siteConfig: DocusaurusConfig; -}): LoadedPlugin { - const { - stylesheets = [], - scripts = [], - clientModules: siteConfigClientModules = [], - } = siteConfig; - return { - name: 'docusaurus-bootstrap-plugin', - content: null, - options: {}, - version: {type: 'synthetic'}, - getClientModules() { - return siteConfigClientModules; - }, - injectHtmlTags: () => { - const stylesheetsTags = stylesheets.map((source) => - typeof source === 'string' - ? `` - : ({ - tagName: 'link', - attributes: { - rel: 'stylesheet', - ...source, - }, - } as HtmlTagObject), - ); - const scriptsTags = scripts.map((source) => - typeof source === 'string' - ? `` - : ({ - tagName: 'script', - attributes: { - ...source, - }, - } as HtmlTagObject), - ); - return { - headTags: [...stylesheetsTags, ...scriptsTags], - }; - }, - }; -} - -// Configure Webpack fallback mdx loader for md/mdx files out of content-plugin folders -// Adds a "fallback" mdx loader for mdx files that are not processed by content plugins -// This allows to do things such as importing repo/README.md as a partial from another doc -// Not ideal solution though, but good enough for now -function createMDXFallbackPlugin({ - siteDir, - siteConfig, -}: { - siteDir: string; - siteConfig: DocusaurusConfig; -}): LoadedPlugin { - return { - name: 'docusaurus-mdx-fallback-plugin', - content: null, - options: {}, - version: {type: 'synthetic'}, - configureWebpack(config, isServer, {getJSLoader}) { - // We need the mdx fallback loader to exclude files that were already processed by content plugins mdx loaders - // This works, but a bit hacky... - // Not sure there's a way to handle that differently in webpack :s - function getMDXFallbackExcludedPaths(): string[] { - const rules: RuleSetRule[] = config?.module?.rules as RuleSetRule[]; - return rules.flatMap((rule) => { - const isMDXRule = - rule.test instanceof RegExp && rule.test.test('x.mdx'); - return isMDXRule ? (rule.include as string[]) : []; - }); - } - - return { - module: { - rules: [ - { - test: /(\.mdx?)$/, - exclude: getMDXFallbackExcludedPaths(), - use: [ - getJSLoader({isServer}), - { - loader: require.resolve('@docusaurus/mdx-loader'), - options: { - staticDirs: siteConfig.staticDirectories.map((dir) => - path.resolve(siteDir, dir), - ), - siteDir, - isMDXPartial: (_filename: string) => true, // External mdx files are always meant to be imported as partials - isMDXPartialFrontMatterWarningDisabled: true, // External mdx files might have front matter, let's just disable the warning - remarkPlugins: [admonitions], - }, - }, - ], - }, - ], - }, - }; - }, - }; -} - -export async function load( - siteDir: string, - options: LoadContextOptions = {}, -): Promise { - // Context. - const context: LoadContext = await loadContext(siteDir, options); +/** + * This is the crux of the Docusaurus server-side. It reads everything it needs— + * code translations, config file, plugin modules... Plugins then use their + * lifecycles to generate content and other data. It is side-effect-ful because + * it generates temp files in the `.docusaurus` folder for the bundler. + */ +export async function load(options: LoadContextOptions): Promise { + const {siteDir} = options; + const context = await loadContext(options); const { generatedFilesDir, siteConfig, @@ -293,21 +123,30 @@ export async function load( baseUrl, i18n, ssrTemplate, - codeTranslations, + codeTranslations: siteCodeTranslations, } = context; - // Plugins. - const pluginConfigs: PluginConfig[] = loadPluginConfigs(context); - const {plugins, pluginsRouteConfigs, globalData, themeConfigTranslated} = - await loadPlugins({pluginConfigs, context}); + const {plugins, pluginsRouteConfigs, globalData} = await loadPlugins(context); + const clientModules = loadClientModules(plugins); + const {headTags, preBodyTags, postBodyTags} = loadHtmlTags(plugins); + const {registry, routesChunkNames, routesConfig, routesPaths} = + await loadRoutes( + pluginsRouteConfigs, + baseUrl, + siteConfig.onDuplicateRoutes, + ); + const codeTranslations = { + ...(await getPluginsDefaultCodeTranslationMessages(plugins)), + ...siteCodeTranslations, + }; + const siteMetadata = await loadSiteMetadata({plugins, siteDir}); - // Side-effect to replace the untranslated themeConfig by the translated one - context.siteConfig.themeConfig = themeConfigTranslated; + // === Side-effects part === - handleDuplicateRoutes(pluginsRouteConfigs, siteConfig.onDuplicateRoutes); const genWarning = generate( generatedFilesDir, 'DONT-EDIT-THIS-FOLDER', `This folder stores temp files that Docusaurus' client bundler accesses. + DO NOT hand-modify files in this folder because they will be overwritten in the next build. You can clear all build artifacts (including this folder) with the \`docusaurus clear\` command. @@ -320,48 +159,41 @@ next build. You can clear all build artifacts (including this folder) with the generatedFilesDir, DEFAULT_CONFIG_FILE_NAME, `/* -AUTOGENERATED - DON'T EDIT -Your edits in this file will be overwritten in the next build! -Modify the docusaurus.config.js file at your site's root instead. -*/ -export default ${JSON.stringify(siteConfig, null, 2)};`, + * AUTOGENERATED - DON'T EDIT + * Your edits in this file will be overwritten in the next build! + * Modify the docusaurus.config.js file at your site's root instead. + */ +export default ${JSON.stringify(siteConfig, null, 2)}; +`, ); - plugins.push(createBootstrapPlugin({siteConfig})); - plugins.push(createMDXFallbackPlugin({siteDir, siteConfig})); - - // Load client modules. - const clientModules = loadClientModules(plugins); const genClientModules = generate( generatedFilesDir, 'client-modules.js', - `export default [\n${clientModules - // import() is async so we use require() because client modules can have - // CSS and the order matters for loading CSS. - .map((module) => ` require('${escapePath(module)}'),`) - .join('\n')}\n];\n`, + `export default [ +${clientModules + // import() is async so we use require() because client modules can have + // CSS and the order matters for loading CSS. + .map((module) => ` require('${escapePath(module)}'),`) + .join('\n')} +]; +`, ); - // Load extra head & body html tags. - const {headTags, preBodyTags, postBodyTags} = loadHtmlTags(plugins); - - // Routing. - const {registry, routesChunkNames, routesConfig, routesPaths} = - await loadRoutes(pluginsRouteConfigs, baseUrl); - const genRegistry = generate( generatedFilesDir, 'registry.js', `export default { -${Object.keys(registry) - .sort() +${Object.entries(registry) + .sort((a, b) => a[0].localeCompare(b[0])) .map( - (key) => - ` '${key}': [${registry[key].loader}, '${escapePath( - registry[key].modulePath, - )}', require.resolveWeak('${escapePath(registry[key].modulePath)}')],`, + ([key, chunk]) => + ` '${key}': [${chunk.loader}, '${escapePath( + chunk.modulePath, + )}', require.resolveWeak('${escapePath(chunk.modulePath)}')],`, ) - .join('\n')}};\n`, + .join('\n')}}; +`, ); const genRoutesChunkNames = generate( @@ -384,31 +216,12 @@ ${Object.keys(registry) JSON.stringify(i18n, null, 2), ); - const codeTranslationsWithFallbacks: Record = { - ...(await getPluginsDefaultCodeTranslationMessages(plugins)), - ...codeTranslations, - }; - const genCodeTranslations = generate( generatedFilesDir, 'codeTranslations.json', - JSON.stringify(codeTranslationsWithFallbacks, null, 2), + JSON.stringify(codeTranslations, null, 2), ); - // Version metadata. - const siteMetadata: DocusaurusSiteMetadata = { - docusaurusVersion: getPackageJsonVersion( - path.join(__dirname, '../../package.json'), - )!, - siteVersion: getPackageJsonVersion(path.join(siteDir, 'package.json')), - pluginVersions: {}, - }; - plugins - .filter(({version: {type}}) => type !== 'synthetic') - .forEach(({name, version}) => { - siteMetadata.pluginVersions[name] = version; - }); - checkDocusaurusPackagesVersion(siteMetadata); const genSiteMetadata = generate( generatedFilesDir, 'site-metadata.json', @@ -428,7 +241,7 @@ ${Object.keys(registry) genCodeTranslations, ]); - const props: Props = { + return { siteConfig, siteConfigPath, siteMetadata, @@ -446,29 +259,4 @@ ${Object.keys(registry) ssrTemplate, codeTranslations, }; - - return props; -} - -// We want all @docusaurus/* packages to have the exact same version! -// See https://github.com/facebook/docusaurus/issues/3371 -// See https://github.com/facebook/docusaurus/pull/3386 -function checkDocusaurusPackagesVersion(siteMetadata: DocusaurusSiteMetadata) { - const {docusaurusVersion} = siteMetadata; - Object.entries(siteMetadata.pluginVersions).forEach( - ([plugin, versionInfo]) => { - if ( - versionInfo.type === 'package' && - versionInfo.name?.startsWith('@docusaurus/') && - versionInfo.version && - versionInfo.version !== docusaurusVersion - ) { - // should we throw instead? - // It still could work with different versions - logger.error`Invalid name=${plugin} version number=${versionInfo.version}. -All official @docusaurus/* packages should have the exact same version as @docusaurus/core (number=${docusaurusVersion}). -Maybe you want to check, or regenerate your yarn.lock or package-lock.json file?`; - } - }, - ); } diff --git a/packages/docusaurus/src/server/presets/__tests__/__fixtures__/preset-qux.js b/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/presets/preset-mixed.js similarity index 100% rename from packages/docusaurus/src/server/presets/__tests__/__fixtures__/preset-qux.js rename to packages/docusaurus/src/server/plugins/__tests__/__fixtures__/presets/preset-mixed.js diff --git a/packages/docusaurus/src/server/presets/__tests__/__fixtures__/preset-bar.js b/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/presets/preset-plugins.js similarity index 100% rename from packages/docusaurus/src/server/presets/__tests__/__fixtures__/preset-bar.js rename to packages/docusaurus/src/server/plugins/__tests__/__fixtures__/presets/preset-plugins.js diff --git a/packages/docusaurus/src/server/presets/__tests__/__fixtures__/preset-foo.js b/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/presets/preset-themes.js similarity index 68% rename from packages/docusaurus/src/server/presets/__tests__/__fixtures__/preset-foo.js rename to packages/docusaurus/src/server/plugins/__tests__/__fixtures__/presets/preset-themes.js index b2fbdfedde34..fa6c08dfa66a 100644 --- a/packages/docusaurus/src/server/presets/__tests__/__fixtures__/preset-foo.js +++ b/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/presets/preset-themes.js @@ -7,9 +7,9 @@ module.exports = function preset(context, opts = {}) { return { - plugins: [ - ['@docusaurus/plugin-content-pages', opts.pages], - ['@docusaurus/plugin-sitemap', opts.sitemap], + themes: [ + ['@docusaurus/theme-live-codeblock', opts.codeblock], + ['@docusaurus/theme-algolia', opts.algolia], ], }; }; diff --git a/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin/docusaurus.config.js b/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin/docusaurus.config.js index d2f0d0862f19..29c66283fdb6 100644 --- a/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin/docusaurus.config.js +++ b/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin/docusaurus.config.js @@ -24,4 +24,7 @@ module.exports = { './plugin3.js', ['./plugin4.js', {}], ], + presets: [ + './preset.js', + ], }; diff --git a/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin/plugin3.js b/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin/plugin3.js index b220cc30b733..4e49b55a8235 100644 --- a/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin/plugin3.js +++ b/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin/plugin3.js @@ -3,3 +3,7 @@ module.exports = function (context, options) { name: 'third-plugin', }; }; + +module.exports.validateThemeConfig = function ({validate, themeConfig}) { + return {a: 1}; +}; diff --git a/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin/preset.js b/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin/preset.js new file mode 100644 index 000000000000..1e0ded622bfe --- /dev/null +++ b/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin/preset.js @@ -0,0 +1,12 @@ +module.exports = function () { + return { + plugins: [ + [() => ({name: 'preset-plugin1'}), {}], + () => ({name: 'preset-plugin2'}), + ], + themes: [ + [() => ({name: 'preset-theme1'}), {}], + () => ({name: 'preset-theme2'}), + ], + }; +}; diff --git a/packages/docusaurus/src/server/plugins/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus/src/server/plugins/__tests__/__snapshots__/index.test.ts.snap new file mode 100644 index 000000000000..688eaa837af2 --- /dev/null +++ b/packages/docusaurus/src/server/plugins/__tests__/__snapshots__/index.test.ts.snap @@ -0,0 +1,68 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`loadPlugins loads plugins 1`] = ` +{ + "globalData": { + "test1": { + "default": { + "content": "a", + "prop": "a", + }, + }, + }, + "plugins": [ + { + "content": "a", + "contentLoaded": [Function], + "loadContent": [Function], + "name": "test1", + "options": { + "id": "default", + }, + "path": "/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin", + "prop": "a", + "version": { + "type": "local", + }, + }, + { + "configureWebpack": [Function], + "content": undefined, + "name": "test2", + "options": { + "id": "default", + }, + "path": "/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin", + "version": { + "type": "local", + }, + }, + { + "content": undefined, + "getClientModules": [Function], + "injectHtmlTags": [Function], + "name": "docusaurus-bootstrap-plugin", + "options": { + "id": "default", + }, + "path": "/packages/docusaurus/src/server/plugins/__tests__/__fixtures__/site-with-plugin", + "version": { + "type": "synthetic", + }, + }, + { + "configureWebpack": [Function], + "content": undefined, + "name": "docusaurus-mdx-fallback-plugin", + "options": { + "id": "default", + }, + "path": ".", + "version": { + "type": "synthetic", + }, + }, + ], + "pluginsRouteConfigs": [], +} +`; diff --git a/packages/docusaurus/src/server/plugins/__tests__/__snapshots__/init.test.ts.snap b/packages/docusaurus/src/server/plugins/__tests__/__snapshots__/init.test.ts.snap index 8f50acccbed8..3c982b11453f 100644 --- a/packages/docusaurus/src/server/plugins/__tests__/__snapshots__/init.test.ts.snap +++ b/packages/docusaurus/src/server/plugins/__tests__/__snapshots__/init.test.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`initPlugins plugins with bad values throw user-friendly error message 1`] = ` +exports[`initPlugins throws user-friendly error message for plugins with bad values 1`] = ` " => Bad Docusaurus plugin value as path [plugins,0]. Example valid plugin config: { @@ -27,103 +27,3 @@ Example valid plugin config: " `; - -exports[`sortConfig should sort route config correctly 1`] = ` -Array [ - Object { - "component": "", - "path": "/community", - }, - Object { - "component": "", - "path": "/some-page", - }, - Object { - "component": "", - "path": "/docs", - "routes": Array [ - Object { - "component": "", - "path": "/docs/someDoc", - }, - Object { - "component": "", - "path": "/docs/someOtherDoc", - }, - ], - }, - Object { - "component": "", - "path": "/", - }, - Object { - "component": "", - "path": "/", - "routes": Array [ - Object { - "component": "", - "path": "/someDoc", - }, - Object { - "component": "", - "path": "/someOtherDoc", - }, - ], - }, - Object { - "component": "", - "path": "/", - "routes": Array [ - Object { - "component": "", - "path": "/subroute", - }, - ], - }, -] -`; - -exports[`sortConfig should sort route config given a baseURL 1`] = ` -Array [ - Object { - "component": "", - "path": "/latest/community", - }, - Object { - "component": "", - "path": "/latest/example", - }, - Object { - "component": "", - "path": "/latest/some-page", - }, - Object { - "component": "", - "path": "/latest/docs", - "routes": Array [ - Object { - "component": "", - "path": "/latest/docs/someDoc", - }, - Object { - "component": "", - "path": "/latest/docs/someOtherDoc", - }, - ], - }, - Object { - "component": "", - "path": "/latest", - "routes": Array [ - Object { - "component": "", - "path": "/latest/someDoc", - }, - Object { - "component": "", - "path": "/latest/someOtherDoc", - }, - ], - }, -] -`; diff --git a/packages/docusaurus/src/server/plugins/__tests__/__snapshots__/presets.test.ts.snap b/packages/docusaurus/src/server/plugins/__tests__/__snapshots__/presets.test.ts.snap new file mode 100644 index 000000000000..84f8e1be4bfe --- /dev/null +++ b/packages/docusaurus/src/server/plugins/__tests__/__snapshots__/presets.test.ts.snap @@ -0,0 +1,167 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`loadPresets array form 1`] = ` +{ + "plugins": [ + [ + "@docusaurus/plugin-content-docs", + undefined, + ], + [ + "@docusaurus/plugin-content-blog", + undefined, + ], + ], + "themes": [], +} +`; + +exports[`loadPresets array form composite 1`] = ` +{ + "plugins": [ + [ + "@docusaurus/plugin-content-docs", + { + "path": "../", + }, + ], + [ + "@docusaurus/plugin-content-blog", + undefined, + ], + ], + "themes": [ + [ + "@docusaurus/theme-live-codeblock", + undefined, + ], + [ + "@docusaurus/theme-algolia", + { + "trackingID": "foo", + }, + ], + ], +} +`; + +exports[`loadPresets array form with options 1`] = ` +{ + "plugins": [ + [ + "@docusaurus/plugin-content-docs", + { + "path": "../", + }, + ], + [ + "@docusaurus/plugin-content-blog", + undefined, + ], + ], + "themes": [], +} +`; + +exports[`loadPresets mixed form 1`] = ` +{ + "plugins": [ + [ + "@docusaurus/plugin-content-docs", + { + "path": "../", + }, + ], + [ + "@docusaurus/plugin-content-blog", + undefined, + ], + ], + "themes": [ + [ + "@docusaurus/theme-live-codeblock", + undefined, + ], + [ + "@docusaurus/theme-algolia", + undefined, + ], + ], +} +`; + +exports[`loadPresets mixed form with themes 1`] = ` +{ + "plugins": [ + [ + "@docusaurus/plugin-content-docs", + { + "path": "../", + }, + ], + [ + "@docusaurus/plugin-content-blog", + undefined, + ], + [ + "@docusaurus/plugin-test", + undefined, + ], + ], + "themes": [ + [ + "@docusaurus/theme-live-codeblock", + undefined, + ], + [ + "@docusaurus/theme-algolia", + undefined, + ], + [ + "@docusaurus/theme-classic", + undefined, + ], + ], +} +`; + +exports[`loadPresets string form 1`] = ` +{ + "plugins": [ + [ + "@docusaurus/plugin-content-docs", + undefined, + ], + [ + "@docusaurus/plugin-content-blog", + undefined, + ], + ], + "themes": [], +} +`; + +exports[`loadPresets string form composite 1`] = ` +{ + "plugins": [ + [ + "@docusaurus/plugin-content-docs", + undefined, + ], + [ + "@docusaurus/plugin-content-blog", + undefined, + ], + ], + "themes": [ + [ + "@docusaurus/theme-live-codeblock", + undefined, + ], + [ + "@docusaurus/theme-algolia", + undefined, + ], + ], +} +`; diff --git a/packages/docusaurus/src/server/plugins/__tests__/__snapshots__/routeConfig.test.ts.snap b/packages/docusaurus/src/server/plugins/__tests__/__snapshots__/routeConfig.test.ts.snap new file mode 100644 index 000000000000..e38931b90952 --- /dev/null +++ b/packages/docusaurus/src/server/plugins/__tests__/__snapshots__/routeConfig.test.ts.snap @@ -0,0 +1,105 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`sortConfig sorts route config correctly 1`] = ` +[ + { + "component": "", + "path": "/community", + }, + { + "component": "", + "path": "/some-page", + }, + { + "component": "", + "path": "/docs", + "routes": [ + { + "component": "", + "path": "/docs/someDoc", + }, + { + "component": "", + "path": "/docs/someOtherDoc", + }, + ], + }, + { + "component": "", + "path": "/", + }, + { + "component": "", + "path": "/", + "routes": [ + { + "component": "", + "path": "/someDoc", + }, + { + "component": "", + "path": "/someOtherDoc", + }, + ], + }, + { + "component": "", + "path": "/", + "routes": [ + { + "component": "", + "path": "/subroute", + }, + ], + }, +] +`; + +exports[`sortConfig sorts route config given a baseURL 1`] = ` +[ + { + "component": "", + "path": "/latest/community", + }, + { + "component": "", + "path": "/latest/example", + }, + { + "component": "", + "path": "/latest/some-page", + }, + { + "component": "", + "path": "/latest/docs", + "routes": [ + { + "component": "", + "path": "/latest/docs/someDoc", + }, + { + "component": "", + "path": "/latest/docs/someOtherDoc", + }, + ], + }, + { + "component": "", + "path": "/latest/", + }, + { + "component": "", + "path": "/latest/", + "routes": [ + { + "component": "", + "path": "/latest/someDoc", + }, + { + "component": "", + "path": "/latest/someOtherDoc", + }, + ], + }, +] +`; diff --git a/packages/docusaurus/src/server/plugins/__tests__/index.test.ts b/packages/docusaurus/src/server/plugins/__tests__/index.test.ts new file mode 100644 index 000000000000..d585d644c4af --- /dev/null +++ b/packages/docusaurus/src/server/plugins/__tests__/index.test.ts @@ -0,0 +1,51 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import path from 'path'; +import {loadPlugins} from '..'; + +describe('loadPlugins', () => { + it('loads plugins', async () => { + const siteDir = path.join(__dirname, '__fixtures__/site-with-plugin'); + await expect( + loadPlugins({ + siteDir, + generatedFilesDir: path.join(siteDir, '.docusaurus'), + outDir: path.join(siteDir, 'build'), + // @ts-expect-error: good enough + siteConfig: { + baseUrl: '/', + trailingSlash: true, + themeConfig: {}, + presets: [], + plugins: [ + () => ({ + name: 'test1', + prop: 'a', + async loadContent() { + // Testing that plugin lifecycle is bound to the plugin instance + return this.prop; + }, + async contentLoaded({content, actions}) { + actions.setGlobalData({content, prop: this.prop}); + }, + }), + ], + themes: [ + () => ({ + name: 'test2', + configureWebpack() { + return {}; + }, + }), + ], + }, + siteConfigPath: path.join(siteDir, 'docusaurus.config.js'), + }), + ).resolves.toMatchSnapshot(); + }); +}); diff --git a/packages/docusaurus/src/server/plugins/__tests__/init.test.ts b/packages/docusaurus/src/server/plugins/__tests__/init.test.ts index d9aa114518e3..3ecf57b0d860 100644 --- a/packages/docusaurus/src/server/plugins/__tests__/init.test.ts +++ b/packages/docusaurus/src/server/plugins/__tests__/init.test.ts @@ -7,40 +7,35 @@ import path from 'path'; -import { - loadContext, - loadPluginConfigs, - type LoadContextOptions, -} from '../../index'; -import initPlugins from '../init'; -import {sortConfig} from '../index'; -import type {RouteConfig} from '@docusaurus/types'; +import {loadContext, type LoadContextOptions} from '../../index'; +import {initPlugins} from '../init'; describe('initPlugins', () => { - async function loadSite(options: LoadContextOptions = {}) { + async function loadSite(options: Omit = {}) { const siteDir = path.join(__dirname, '__fixtures__', 'site-with-plugin'); - const context = await loadContext(siteDir, options); - const pluginConfigs = loadPluginConfigs(context); - const plugins = await initPlugins({ - pluginConfigs, - context, - }); + const context = await loadContext({...options, siteDir}); + const plugins = await initPlugins(context); return {siteDir, context, plugins}; } - test('plugins gets parsed correctly and loads in correct order', async () => { + it('parses plugins correctly and loads them in correct order', async () => { const {context, plugins} = await loadSite(); expect(context.siteConfig.plugins?.length).toBe(4); - expect(plugins.length).toBe(4); - - expect(plugins[0].name).toBe('first-plugin'); - expect(plugins[1].name).toBe('second-plugin'); - expect(plugins[2].name).toBe('third-plugin'); - expect(plugins[3].name).toBe('fourth-plugin'); + expect(plugins).toHaveLength(8); + + expect(plugins[0].name).toBe('preset-plugin1'); + expect(plugins[1].name).toBe('preset-plugin2'); + expect(plugins[2].name).toBe('preset-theme1'); + expect(plugins[3].name).toBe('preset-theme2'); + expect(plugins[4].name).toBe('first-plugin'); + expect(plugins[5].name).toBe('second-plugin'); + expect(plugins[6].name).toBe('third-plugin'); + expect(plugins[7].name).toBe('fourth-plugin'); + expect(context.siteConfig.themeConfig).toEqual({a: 1}); }); - test('plugins with bad values throw user-friendly error message', async () => { + it('throws user-friendly error message for plugins with bad values', async () => { await expect(() => loadSite({ customConfigFilePath: 'badPlugins.docusaurus.config.js', @@ -48,85 +43,3 @@ describe('initPlugins', () => { ).rejects.toThrowErrorMatchingSnapshot(); }); }); - -describe('sortConfig', () => { - test('should sort route config correctly', () => { - const routes: RouteConfig[] = [ - { - path: '/', - component: '', - routes: [ - {path: '/someDoc', component: ''}, - {path: '/someOtherDoc', component: ''}, - ], - }, - { - path: '/', - component: '', - }, - { - path: '/', - component: '', - routes: [{path: '/subroute', component: ''}], - }, - { - path: '/docs', - component: '', - routes: [ - {path: '/docs/someDoc', component: ''}, - {path: '/docs/someOtherDoc', component: ''}, - ], - }, - { - path: '/community', - component: '', - }, - { - path: '/some-page', - component: '', - }, - ]; - - sortConfig(routes); - - expect(routes).toMatchSnapshot(); - }); - - test('should sort route config given a baseURL', () => { - const baseURL = '/latest'; - const routes: RouteConfig[] = [ - { - path: baseURL, - component: '', - routes: [ - {path: `${baseURL}/someDoc`, component: ''}, - {path: `${baseURL}/someOtherDoc`, component: ''}, - ], - }, - { - path: `${baseURL}/example`, - component: '', - }, - { - path: `${baseURL}/docs`, - component: '', - routes: [ - {path: `${baseURL}/docs/someDoc`, component: ''}, - {path: `${baseURL}/docs/someOtherDoc`, component: ''}, - ], - }, - { - path: `${baseURL}/community`, - component: '', - }, - { - path: `${baseURL}/some-page`, - component: '', - }, - ]; - - sortConfig(routes, baseURL); - - expect(routes).toMatchSnapshot(); - }); -}); diff --git a/packages/docusaurus/src/server/__tests__/moduleShorthand.test.ts b/packages/docusaurus/src/server/plugins/__tests__/moduleShorthand.test.ts similarity index 86% rename from packages/docusaurus/src/server/__tests__/moduleShorthand.test.ts rename to packages/docusaurus/src/server/plugins/__tests__/moduleShorthand.test.ts index 095fc6d53d7f..239811e3427b 100644 --- a/packages/docusaurus/src/server/__tests__/moduleShorthand.test.ts +++ b/packages/docusaurus/src/server/plugins/__tests__/moduleShorthand.test.ts @@ -8,7 +8,7 @@ import {getNamePatterns, resolveModuleName} from '../moduleShorthand'; describe('getNamePatterns', () => { - test('should resolve plain names', () => { + it('resolves plain names', () => { expect(getNamePatterns('awesome', 'plugin')).toEqual([ 'awesome', '@docusaurus/plugin-awesome', @@ -22,7 +22,7 @@ describe('getNamePatterns', () => { ]); }); - test('should expand bare scopes', () => { + it('expands bare scopes', () => { expect(getNamePatterns('@joshcena', 'plugin')).toEqual([ '@joshcena/docusaurus-plugin', ]); @@ -32,7 +32,7 @@ describe('getNamePatterns', () => { ]); }); - test('should expand scoped names', () => { + it('expands scoped names', () => { expect(getNamePatterns('@joshcena/awesome', 'plugin')).toEqual([ '@joshcena/awesome', '@joshcena/docusaurus-plugin-awesome', @@ -44,7 +44,7 @@ describe('getNamePatterns', () => { ]); }); - test('should expand deep scoped paths', () => { + it('expands deep scoped paths', () => { expect(getNamePatterns('@joshcena/awesome/web', 'plugin')).toEqual([ '@joshcena/awesome/web', '@joshcena/docusaurus-plugin-awesome/web', @@ -58,17 +58,17 @@ describe('getNamePatterns', () => { }); describe('resolveModuleName', () => { - test('should resolve longhand', () => { + it('resolves longhand', () => { expect( resolveModuleName('@docusaurus/plugin-content-docs', require, 'plugin'), ).toBeDefined(); }); - test('should resolve shorthand', () => { + it('resolves shorthand', () => { expect(resolveModuleName('content-docs', require, 'plugin')).toBeDefined(); }); - test('should throw good error message for longhand', () => { + it('throws good error message for longhand', () => { expect(() => resolveModuleName('@docusaurus/plugin-content-doc', require, 'plugin'), ).toThrowErrorMatchingInlineSnapshot(` @@ -78,7 +78,7 @@ describe('resolveModuleName', () => { `); }); - test('should throw good error message for shorthand', () => { + it('throws good error message for shorthand', () => { expect(() => resolveModuleName('content-doc', require, 'plugin')) .toThrowErrorMatchingInlineSnapshot(` "Docusaurus was unable to resolve the \\"content-doc\\" plugin. Make sure one of the following packages are installed: diff --git a/packages/docusaurus/src/server/plugins/__tests__/pluginIds.test.ts b/packages/docusaurus/src/server/plugins/__tests__/pluginIds.test.ts index 8621ac521703..0c04a3fc03bd 100644 --- a/packages/docusaurus/src/server/plugins/__tests__/pluginIds.test.ts +++ b/packages/docusaurus/src/server/plugins/__tests__/pluginIds.test.ts @@ -17,7 +17,7 @@ function createTestPlugin(name: string, id?: string): InitializedPlugin { } describe('ensureUniquePluginInstanceIds', () => { - test('accept single instance plugins', async () => { + it('accept single instance plugins', async () => { ensureUniquePluginInstanceIds([ createTestPlugin('plugin-docs'), createTestPlugin('plugin-blog'), @@ -25,7 +25,7 @@ describe('ensureUniquePluginInstanceIds', () => { ]); }); - test('accept single instance plugins, all with sameId', async () => { + it('accept single instance plugins, all with sameId', async () => { ensureUniquePluginInstanceIds([ createTestPlugin('plugin-docs', 'sameId'), createTestPlugin('plugin-blog', 'sameId'), @@ -33,7 +33,7 @@ describe('ensureUniquePluginInstanceIds', () => { ]); }); - test('accept multi instance plugins without id', async () => { + it('accept multi instance plugins without id', async () => { ensureUniquePluginInstanceIds([ createTestPlugin('plugin-docs', 'ios'), createTestPlugin('plugin-docs', 'android'), @@ -41,7 +41,7 @@ describe('ensureUniquePluginInstanceIds', () => { ]); }); - test('reject multi instance plugins without id', async () => { + it('reject multi instance plugins without id', async () => { expect(() => ensureUniquePluginInstanceIds([ createTestPlugin('plugin-docs'), @@ -50,7 +50,7 @@ describe('ensureUniquePluginInstanceIds', () => { ).toThrowErrorMatchingSnapshot(); }); - test('reject multi instance plugins with same id', async () => { + it('reject multi instance plugins with same id', async () => { expect(() => ensureUniquePluginInstanceIds([ createTestPlugin('plugin-docs', 'sameId'), @@ -59,7 +59,7 @@ describe('ensureUniquePluginInstanceIds', () => { ).toThrowErrorMatchingSnapshot(); }); - test('reject multi instance plugins with some without id', async () => { + it('reject multi instance plugins with some without id', async () => { expect(() => ensureUniquePluginInstanceIds([ createTestPlugin('plugin-docs'), diff --git a/packages/docusaurus/src/server/plugins/__tests__/presets.test.ts b/packages/docusaurus/src/server/plugins/__tests__/presets.test.ts new file mode 100644 index 000000000000..a50117e371c6 --- /dev/null +++ b/packages/docusaurus/src/server/plugins/__tests__/presets.test.ts @@ -0,0 +1,140 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import path from 'path'; + +import {loadPresets} from '../presets'; +import type {LoadContext} from '@docusaurus/types'; + +describe('loadPresets', () => { + it('no presets', async () => { + const context = { + siteConfigPath: __dirname, + siteConfig: { + presets: [], + }, + } as LoadContext; + const presets = await loadPresets(context); + expect(presets).toMatchInlineSnapshot(` + { + "plugins": [], + "themes": [], + } + `); + }); + + it('string form', async () => { + const context = { + siteConfigPath: __dirname, + siteConfig: { + presets: [ + path.join(__dirname, '__fixtures__/presets/preset-plugins.js'), + ], + }, + } as LoadContext; + const presets = await loadPresets(context); + expect(presets).toMatchSnapshot(); + }); + + it('string form composite', async () => { + const context = { + siteConfigPath: __dirname, + siteConfig: { + presets: [ + path.join(__dirname, '__fixtures__/presets/preset-plugins.js'), + path.join(__dirname, '__fixtures__/presets/preset-themes.js'), + ], + }, + } as LoadContext; + const presets = await loadPresets(context); + expect(presets).toMatchSnapshot(); + }); + + it('array form', async () => { + const context = { + siteConfigPath: __dirname, + siteConfig: { + presets: [ + [path.join(__dirname, '__fixtures__/presets/preset-plugins.js')], + ], + }, + } as Partial; + const presets = await loadPresets(context); + expect(presets).toMatchSnapshot(); + }); + + it('array form with options', async () => { + const context = { + siteConfigPath: __dirname, + siteConfig: { + presets: [ + [ + path.join(__dirname, '__fixtures__/presets/preset-plugins.js'), + {docs: {path: '../'}}, + ], + ], + }, + } as Partial; + const presets = await loadPresets(context); + expect(presets).toMatchSnapshot(); + }); + + it('array form composite', async () => { + const context = { + siteConfigPath: __dirname, + siteConfig: { + presets: [ + [ + path.join(__dirname, '__fixtures__/presets/preset-plugins.js'), + {docs: {path: '../'}}, + ], + [ + path.join(__dirname, '__fixtures__/presets/preset-themes.js'), + {algolia: {trackingID: 'foo'}}, + ], + ], + }, + } as Partial; + const presets = await loadPresets(context); + expect(presets).toMatchSnapshot(); + }); + + it('mixed form', async () => { + const context = { + siteConfigPath: __dirname, + siteConfig: { + presets: [ + [ + path.join(__dirname, '__fixtures__/presets/preset-plugins.js'), + {docs: {path: '../'}}, + ], + path.join(__dirname, '__fixtures__/presets/preset-themes.js'), + ], + }, + } as LoadContext; + const presets = await loadPresets(context); + expect(presets).toMatchSnapshot(); + }); + + it('mixed form with themes', async () => { + const context = { + siteConfigPath: __dirname, + siteConfig: { + presets: [ + [ + path.join(__dirname, '__fixtures__/presets/preset-plugins.js'), + {docs: {path: '../'}}, + ], + path.join(__dirname, '__fixtures__/presets/preset-themes.js'), + path.join(__dirname, '__fixtures__/presets/preset-mixed.js'), + ], + }, + } as LoadContext; + const presets = await loadPresets(context); + expect(presets).toMatchSnapshot(); + }); +}); diff --git a/packages/docusaurus/src/server/plugins/__tests__/applyRouteTrailingSlash.test.ts b/packages/docusaurus/src/server/plugins/__tests__/routeConfig.test.ts similarity index 65% rename from packages/docusaurus/src/server/plugins/__tests__/applyRouteTrailingSlash.test.ts rename to packages/docusaurus/src/server/plugins/__tests__/routeConfig.test.ts index 713e4a1a6c8e..3d0e6a5a8ec7 100644 --- a/packages/docusaurus/src/server/plugins/__tests__/applyRouteTrailingSlash.test.ts +++ b/packages/docusaurus/src/server/plugins/__tests__/routeConfig.test.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import applyRouteTrailingSlash from '../applyRouteTrailingSlash'; +import {applyRouteTrailingSlash, sortConfig} from '../routeConfig'; import type {RouteConfig} from '@docusaurus/types'; import type {ApplyTrailingSlashParams} from '@docusaurus/utils-common'; @@ -27,7 +27,7 @@ function params( } describe('applyRouteTrailingSlash', () => { - test('apply to empty', () => { + it('apply to empty', () => { expect(applyRouteTrailingSlash(route(''), params(true))).toEqual( route('/'), ); @@ -39,7 +39,7 @@ describe('applyRouteTrailingSlash', () => { ); }); - test('apply to /', () => { + it('apply to /', () => { expect(applyRouteTrailingSlash(route('/'), params(true))).toEqual( route('/'), ); @@ -51,7 +51,7 @@ describe('applyRouteTrailingSlash', () => { ); }); - test('apply to /abc', () => { + it('apply to /abc', () => { expect(applyRouteTrailingSlash(route('/abc'), params(true))).toEqual( route('/abc/'), ); @@ -63,7 +63,7 @@ describe('applyRouteTrailingSlash', () => { ); }); - test('apply to /abc/', () => { + it('apply to /abc/', () => { expect(applyRouteTrailingSlash(route('/abc/'), params(true))).toEqual( route('/abc/'), ); @@ -75,7 +75,7 @@ describe('applyRouteTrailingSlash', () => { ); }); - test('apply to /abc?search#anchor', () => { + it('apply to /abc?search#anchor', () => { expect( applyRouteTrailingSlash(route('/abc?search#anchor'), params(true)), ).toEqual(route('/abc/?search#anchor')); @@ -87,7 +87,7 @@ describe('applyRouteTrailingSlash', () => { ).toEqual(route('/abc?search#anchor')); }); - test('apply to /abc/?search#anchor', () => { + it('apply to /abc/?search#anchor', () => { expect( applyRouteTrailingSlash(route('/abc/?search#anchor'), params(true)), ).toEqual(route('/abc/?search#anchor')); @@ -99,7 +99,7 @@ describe('applyRouteTrailingSlash', () => { ).toEqual(route('/abc/?search#anchor')); }); - test('not apply to /abc/?search#anchor when baseUrl=/abc/', () => { + it('not apply to /abc/?search#anchor when baseUrl=/abc/', () => { const baseUrl = '/abc/'; expect( applyRouteTrailingSlash( @@ -121,7 +121,7 @@ describe('applyRouteTrailingSlash', () => { ).toEqual(route('/abc/?search#anchor')); }); - test('apply to subroutes', () => { + it('apply to subroutes', () => { expect( applyRouteTrailingSlash( route('/abc', ['/abc/1', '/abc/2']), @@ -142,7 +142,7 @@ describe('applyRouteTrailingSlash', () => { ).toEqual(route('/abc', ['/abc/1', '/abc/2'])); }); - test('apply for complex case', () => { + it('apply for complex case', () => { expect( applyRouteTrailingSlash( route('/abc?search#anchor', ['/abc/1?search', '/abc/2#anchor']), @@ -153,7 +153,7 @@ describe('applyRouteTrailingSlash', () => { ); }); - test('apply for complex case with baseUrl', () => { + it('apply for complex case with baseUrl', () => { const baseUrl = '/abc/'; expect( applyRouteTrailingSlash( @@ -163,3 +163,89 @@ describe('applyRouteTrailingSlash', () => { ).toEqual(route('/abc/?search#anchor', ['/abc/1?search', '/abc/2#anchor'])); }); }); + +describe('sortConfig', () => { + it('sorts route config correctly', () => { + const routes: RouteConfig[] = [ + { + path: '/', + component: '', + routes: [ + {path: '/someDoc', component: ''}, + {path: '/someOtherDoc', component: ''}, + ], + }, + { + path: '/', + component: '', + }, + { + path: '/', + component: '', + routes: [{path: '/subroute', component: ''}], + }, + { + path: '/docs', + component: '', + routes: [ + {path: '/docs/someDoc', component: ''}, + {path: '/docs/someOtherDoc', component: ''}, + ], + }, + { + path: '/community', + component: '', + }, + { + path: '/some-page', + component: '', + }, + ]; + + sortConfig(routes); + + expect(routes).toMatchSnapshot(); + }); + + it('sorts route config given a baseURL', () => { + const baseURL = '/latest/'; + const routes: RouteConfig[] = [ + { + path: baseURL, + component: '', + routes: [ + {path: `${baseURL}someDoc`, component: ''}, + {path: `${baseURL}someOtherDoc`, component: ''}, + ], + }, + { + path: `${baseURL}example`, + component: '', + }, + { + path: `${baseURL}docs`, + component: '', + routes: [ + {path: `${baseURL}docs/someDoc`, component: ''}, + {path: `${baseURL}docs/someOtherDoc`, component: ''}, + ], + }, + { + path: `${baseURL}community`, + component: '', + }, + { + path: `${baseURL}some-page`, + component: '', + }, + { + path: `${baseURL}`, + component: '', + }, + ]; + + sortConfig(routes, baseURL); + + expect(routes).toMatchSnapshot(); + }); +}); diff --git a/packages/docusaurus/src/server/plugins/applyRouteTrailingSlash.ts b/packages/docusaurus/src/server/plugins/applyRouteTrailingSlash.ts deleted file mode 100644 index d81123d03b3a..000000000000 --- a/packages/docusaurus/src/server/plugins/applyRouteTrailingSlash.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import type {RouteConfig} from '@docusaurus/types'; -import { - applyTrailingSlash, - type ApplyTrailingSlashParams, -} from '@docusaurus/utils-common'; - -export default function applyRouteTrailingSlash( - route: RouteConfig, - params: ApplyTrailingSlashParams, -): RouteConfig { - return { - ...route, - path: applyTrailingSlash(route.path, params), - ...(route.routes && { - routes: route.routes.map((subroute) => - applyRouteTrailingSlash(subroute, params), - ), - }), - }; -} diff --git a/packages/docusaurus/src/server/plugins/configs.ts b/packages/docusaurus/src/server/plugins/configs.ts new file mode 100644 index 000000000000..2662ddc69b99 --- /dev/null +++ b/packages/docusaurus/src/server/plugins/configs.ts @@ -0,0 +1,133 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {createRequire} from 'module'; +import importFresh from 'import-fresh'; +import {loadPresets} from './presets'; +import {resolveModuleName} from './moduleShorthand'; +import type { + LoadContext, + PluginConfig, + ImportedPluginModule, + NormalizedPluginConfig, +} from '@docusaurus/types'; + +async function normalizePluginConfig( + pluginConfig: PluginConfig, + configPath: string, + pluginRequire: NodeRequire, +): Promise { + // plugins: ["./plugin"] + if (typeof pluginConfig === 'string') { + const pluginModuleImport = pluginConfig; + const pluginPath = pluginRequire.resolve(pluginModuleImport); + const pluginModule = importFresh(pluginPath); + return { + plugin: pluginModule?.default ?? pluginModule, + options: {}, + pluginModule: { + path: pluginModuleImport, + module: pluginModule, + }, + entryPath: pluginPath, + }; + } + + // plugins: [() => {...}] + if (typeof pluginConfig === 'function') { + return { + plugin: pluginConfig, + options: {}, + entryPath: configPath, + }; + } + + // plugins: [ + // ["./plugin",options], + // ] + if (typeof pluginConfig[0] === 'string') { + const pluginModuleImport = pluginConfig[0]; + const pluginPath = pluginRequire.resolve(pluginModuleImport); + const pluginModule = importFresh(pluginPath); + return { + plugin: pluginModule?.default ?? pluginModule, + options: pluginConfig[1], + pluginModule: { + path: pluginModuleImport, + module: pluginModule, + }, + entryPath: pluginPath, + }; + } + // plugins: [ + // [() => {...}, options], + // ] + return { + plugin: pluginConfig[0], + options: pluginConfig[1], + entryPath: configPath, + }; +} + +/** + * Reads the site config's `presets`, `themes`, and `plugins`, imports them, and + * normalizes the return value. Plugin configs are ordered, mostly for theme + * alias shadowing. Site themes have the highest priority, and preset plugins + * are the lowest. + */ +export async function loadPluginConfigs( + context: LoadContext, +): Promise { + const preset = await loadPresets(context); + const {siteConfig, siteConfigPath} = context; + const pluginRequire = createRequire(siteConfigPath); + function normalizeShorthand( + pluginConfig: PluginConfig, + pluginType: 'plugin' | 'theme', + ): PluginConfig { + if (typeof pluginConfig === 'string') { + return resolveModuleName(pluginConfig, pluginRequire, pluginType); + } else if ( + Array.isArray(pluginConfig) && + typeof pluginConfig[0] === 'string' + ) { + return [ + resolveModuleName(pluginConfig[0], pluginRequire, pluginType), + pluginConfig[1] ?? {}, + ]; + } + return pluginConfig; + } + preset.plugins = preset.plugins.map((plugin) => + normalizeShorthand(plugin, 'plugin'), + ); + preset.themes = preset.themes.map((theme) => + normalizeShorthand(theme, 'theme'), + ); + const standalonePlugins = siteConfig.plugins.map((plugin) => + normalizeShorthand(plugin, 'plugin'), + ); + const standaloneThemes = siteConfig.themes.map((theme) => + normalizeShorthand(theme, 'theme'), + ); + const pluginConfigs = [ + ...preset.plugins, + ...preset.themes, + // Site config should be the highest priority. + ...standalonePlugins, + ...standaloneThemes, + ]; + return Promise.all( + pluginConfigs.map((pluginConfig) => + normalizePluginConfig( + pluginConfig, + context.siteConfigPath, + pluginRequire, + ), + ), + ); +} diff --git a/packages/docusaurus/src/server/plugins/index.ts b/packages/docusaurus/src/server/plugins/index.ts index e3f85404afda..d63b95589bc4 100644 --- a/packages/docusaurus/src/server/plugins/index.ts +++ b/packages/docusaurus/src/server/plugins/index.ts @@ -5,215 +5,163 @@ * LICENSE file in the root directory of this source tree. */ -import {generate, DEFAULT_PLUGIN_ID} from '@docusaurus/utils'; -import fs from 'fs-extra'; +import {docuHash, generate} from '@docusaurus/utils'; import path from 'path'; import type { LoadContext, - PluginConfig, PluginContentLoadedActions, RouteConfig, AllContent, - TranslationFiles, - ThemeConfig, + GlobalData, LoadedPlugin, InitializedPlugin, + PluginRouteContext, } from '@docusaurus/types'; -import initPlugins from './init'; +import {initPlugins} from './init'; +import {createBootstrapPlugin, createMDXFallbackPlugin} from './synthetic'; import logger from '@docusaurus/logger'; -import {chain} from 'lodash'; +import _ from 'lodash'; import {localizePluginTranslationFile} from '../translations/translations'; -import applyRouteTrailingSlash from './applyRouteTrailingSlash'; +import {applyRouteTrailingSlash, sortConfig} from './routeConfig'; -export function sortConfig( - routeConfigs: RouteConfig[], - baseUrl: string = '/', -): void { - // Sort the route config. This ensures that route with nested - // routes is always placed last. - routeConfigs.sort((a, b) => { - // Root route should get placed last. - if (a.path === baseUrl && b.path !== baseUrl) { - return 1; - } - if (a.path !== baseUrl && b.path === baseUrl) { - return -1; - } - - if (a.routes && !b.routes) { - return 1; - } - if (!a.routes && b.routes) { - return -1; - } - // Higher priority get placed first. - if (a.priority || b.priority) { - const priorityA = a.priority || 0; - const priorityB = b.priority || 0; - const score = priorityB - priorityA; - - if (score !== 0) { - return score; - } - } - - return a.path.localeCompare(b.path); - }); - - routeConfigs.forEach((routeConfig) => { - routeConfig.routes?.sort((a, b) => a.path.localeCompare(b.path)); - }); -} - -export async function loadPlugins({ - pluginConfigs, - context, -}: { - pluginConfigs: PluginConfig[]; - context: LoadContext; -}): Promise<{ +/** + * Initializes the plugins, runs `loadContent`, `translateContent`, + * `contentLoaded`, and `translateThemeConfig`. Because `contentLoaded` is + * side-effect-ful (it generates temp files), so is this function. This function + * would also mutate `context.siteConfig.themeConfig` to translate it. + */ +export async function loadPlugins(context: LoadContext): Promise<{ plugins: LoadedPlugin[]; pluginsRouteConfigs: RouteConfig[]; - globalData: unknown; - themeConfigTranslated: ThemeConfig; + globalData: GlobalData; }> { // 1. Plugin Lifecycle - Initialization/Constructor. - const plugins: InitializedPlugin[] = await initPlugins({ - pluginConfigs, - context, - }); + const plugins: InitializedPlugin[] = await initPlugins(context); + + plugins.push( + createBootstrapPlugin(context), + createMDXFallbackPlugin(context), + ); // 2. Plugin Lifecycle - loadContent. - // Currently plugins run lifecycle methods in parallel and are not order-dependent. - // We could change this in future if there are plugins which need to - // run in certain order or depend on others for data. + // Currently plugins run lifecycle methods in parallel and are not + // order-dependent. We could change this in future if there are plugins which + // need to run in certain order or depend on others for data. + // This would also translate theme config and content upfront, given the + // translation files that the plugin declares. const loadedPlugins: LoadedPlugin[] = await Promise.all( plugins.map(async (plugin) => { - const content = plugin.loadContent ? await plugin.loadContent() : null; - return {...plugin, content}; + const content = await plugin.loadContent?.(); + const rawTranslationFiles = + (await plugin?.getTranslationFiles?.({content})) ?? []; + const translationFiles = await Promise.all( + rawTranslationFiles.map((translationFile) => + localizePluginTranslationFile({ + locale: context.i18n.currentLocale, + siteDir: context.siteDir, + translationFile, + plugin, + }), + ), + ); + const translatedContent = + plugin.translateContent?.({content, translationFiles}) ?? content; + const translatedThemeConfigSlice = plugin.translateThemeConfig?.({ + themeConfig: context.siteConfig.themeConfig, + translationFiles, + }); + // Side-effect to merge theme config translations. A plugin should only + // translate its own slice of theme config and should make no assumptions + // about other plugins' keys, so this is safe to run in parallel. + Object.assign(context.siteConfig.themeConfig, translatedThemeConfigSlice); + return {...plugin, content: translatedContent}; }), ); - type ContentLoadedTranslatedPlugin = LoadedPlugin & { - translationFiles: TranslationFiles; - }; - const contentLoadedTranslatedPlugins: ContentLoadedTranslatedPlugin[] = - await Promise.all( - loadedPlugins.map(async (contentLoadedPlugin) => { - const translationFiles = - (await contentLoadedPlugin?.getTranslationFiles?.({ - content: contentLoadedPlugin.content, - })) ?? []; - const localizedTranslationFiles = await Promise.all( - translationFiles.map((translationFile) => - localizePluginTranslationFile({ - locale: context.i18n.currentLocale, - siteDir: context.siteDir, - translationFile, - plugin: contentLoadedPlugin, - }), - ), - ); - return { - ...contentLoadedPlugin, - translationFiles: localizedTranslationFiles, - }; - }), - ); - - const allContent: AllContent = chain(loadedPlugins) + const allContent: AllContent = _.chain(loadedPlugins) .groupBy((item) => item.name) .mapValues((nameItems) => - chain(nameItems) - .groupBy((item) => item.options.id ?? DEFAULT_PLUGIN_ID) - .mapValues((idItems) => idItems[0].content) + _.chain(nameItems) + .groupBy((item) => item.options.id) + .mapValues((idItems) => idItems[0]!.content) .value(), ) .value(); // 3. Plugin Lifecycle - contentLoaded. const pluginsRouteConfigs: RouteConfig[] = []; - - const globalData: Record> = {}; + const globalData: GlobalData = {}; await Promise.all( - contentLoadedTranslatedPlugins.map( - async ({content, translationFiles, ...plugin}) => { - if (!plugin.contentLoaded) { - return; - } - - const pluginId = plugin.options.id ?? DEFAULT_PLUGIN_ID; - - // plugins data files are namespaced by pluginName/pluginId - const dataDirRoot = path.join(context.generatedFilesDir, plugin.name); - const dataDir = path.join(dataDirRoot, pluginId); - - const addRoute: PluginContentLoadedActions['addRoute'] = ( - initialRouteConfig, - ) => { - // Trailing slash behavior is handled in a generic way for all plugins - const finalRouteConfig = applyRouteTrailingSlash(initialRouteConfig, { - trailingSlash: context.siteConfig.trailingSlash, - baseUrl: context.siteConfig.baseUrl, + loadedPlugins.map(async ({content, ...plugin}) => { + if (!plugin.contentLoaded) { + return; + } + const pluginId = plugin.options.id; + // plugins data files are namespaced by pluginName/pluginId + const dataDir = path.join( + context.generatedFilesDir, + plugin.name, + pluginId, + ); + // TODO this would be better to do all that in the codegen phase + // TODO handle context for nested routes + const pluginRouteContext: PluginRouteContext = { + plugin: {name: plugin.name, id: pluginId}, + data: undefined, // TODO allow plugins to provide context data + }; + const pluginRouteContextModulePath = path.join( + dataDir, + `${docuHash('pluginRouteContextModule')}.json`, + ); + await generate( + '/', + pluginRouteContextModulePath, + JSON.stringify(pluginRouteContext, null, 2), + ); + + const actions: PluginContentLoadedActions = { + addRoute(initialRouteConfig) { + // Trailing slash behavior is handled generically for all plugins + const finalRouteConfig = applyRouteTrailingSlash( + initialRouteConfig, + context.siteConfig, + ); + pluginsRouteConfigs.push({ + ...finalRouteConfig, + modules: { + ...finalRouteConfig.modules, + __context: pluginRouteContextModulePath, + }, }); - pluginsRouteConfigs.push(finalRouteConfig); - }; - - const createData: PluginContentLoadedActions['createData'] = async ( - name, - data, - ) => { + }, + async createData(name, data) { const modulePath = path.join(dataDir, name); - await fs.ensureDir(path.dirname(modulePath)); await generate(dataDir, name, data); return modulePath; - }; - - // the plugins global data are namespaced to avoid data conflicts: - // - by plugin name - // - by plugin id (allow using multiple instances of the same plugin) - const setGlobalData: PluginContentLoadedActions['setGlobalData'] = ( - data, - ) => { - globalData[plugin.name] = globalData[plugin.name] ?? {}; - globalData[plugin.name][pluginId] = data; - }; - - const actions: PluginContentLoadedActions = { - addRoute, - createData, - setGlobalData, - }; - - const translatedContent = - plugin.translateContent?.({content, translationFiles}) ?? content; - - await plugin.contentLoaded({ - content: translatedContent, - actions, - allContent, - }); - }, - ), + }, + setGlobalData(data) { + globalData[plugin.name] ??= {}; + globalData[plugin.name]![pluginId] = data; + }, + }; + + await plugin.contentLoaded({content, actions, allContent}); + }), ); // 4. Plugin Lifecycle - routesLoaded. - // Currently plugins run lifecycle methods in parallel and are not order-dependent. - // We could change this in future if there are plugins which need to - // run in certain order or depend on others for data. await Promise.all( - contentLoadedTranslatedPlugins.map(async (plugin) => { + loadedPlugins.map(async (plugin) => { if (!plugin.routesLoaded) { - return null; + return; } - // TODO remove this deprecated lifecycle soon - // deprecated since alpha-60 - // TODO, 1 user reported usage of this lifecycle! https://github.com/facebook/docusaurus/issues/3918 - logger.error`Plugin code=${'routesLoaded'} lifecycle is deprecated. If you think we should keep this lifecycle, please report here: path=${'https://github.com/facebook/docusaurus/issues/3918'}`; + // TODO alpha-60: remove this deprecated lifecycle soon + // 1 user reported usage of this lifecycle: https://github.com/facebook/docusaurus/issues/3918 + logger.error`Plugin code=${'routesLoaded'} lifecycle is deprecated. If you think we should keep this lifecycle, please report here: url=${'https://github.com/facebook/docusaurus/issues/3918'}`; - return plugin.routesLoaded(pluginsRouteConfigs); + await plugin.routesLoaded(pluginsRouteConfigs); }), ); @@ -221,29 +169,5 @@ export async function loadPlugins({ // routes are always placed last. sortConfig(pluginsRouteConfigs, context.siteConfig.baseUrl); - // Apply each plugin one after the other to translate the theme config - function translateThemeConfig( - untranslatedThemeConfig: ThemeConfig, - ): ThemeConfig { - return contentLoadedTranslatedPlugins.reduce( - (currentThemeConfig, plugin) => { - const translatedThemeConfigSlice = plugin.translateThemeConfig?.({ - themeConfig: currentThemeConfig, - translationFiles: plugin.translationFiles, - }); - return { - ...currentThemeConfig, - ...translatedThemeConfigSlice, - }; - }, - untranslatedThemeConfig, - ); - } - - return { - plugins: loadedPlugins, - pluginsRouteConfigs, - globalData, - themeConfigTranslated: translateThemeConfig(context.siteConfig.themeConfig), - }; + return {plugins: loadedPlugins, pluginsRouteConfigs, globalData}; } diff --git a/packages/docusaurus/src/server/plugins/init.ts b/packages/docusaurus/src/server/plugins/init.ts index 8f5ee1a41cb5..14c01f2ace20 100644 --- a/packages/docusaurus/src/server/plugins/init.ts +++ b/packages/docusaurus/src/server/plugins/init.ts @@ -6,95 +6,23 @@ */ import {createRequire} from 'module'; -import importFresh from 'import-fresh'; +import path from 'path'; import type { - DocusaurusPluginVersionInformation, - ImportedPluginModule, + PluginVersionInformation, LoadContext, PluginModule, - PluginConfig, PluginOptions, InitializedPlugin, + NormalizedPluginConfig, } from '@docusaurus/types'; import {DEFAULT_PLUGIN_ID} from '@docusaurus/utils'; -import {getPluginVersion} from '../versions'; +import {getPluginVersion} from '../siteMetadata'; import {ensureUniquePluginInstanceIds} from './pluginIds'; import { normalizePluginOptions, normalizeThemeConfig, } from '@docusaurus/utils-validation'; - -type NormalizedPluginConfig = { - plugin: PluginModule; - options: PluginOptions; - // Only available when a string is provided in config - pluginModule?: { - path: string; - module: ImportedPluginModule; - }; -}; - -function normalizePluginConfig( - pluginConfig: PluginConfig, - pluginRequire: NodeRequire, -): NormalizedPluginConfig { - // plugins: ['./plugin'] - if (typeof pluginConfig === 'string') { - const pluginModuleImport = pluginConfig; - const pluginPath = pluginRequire.resolve(pluginModuleImport); - const pluginModule = importFresh(pluginPath); - return { - plugin: pluginModule?.default ?? pluginModule, - options: {}, - pluginModule: { - path: pluginModuleImport, - module: pluginModule, - }, - }; - } - - // plugins: [function plugin() { }] - if (typeof pluginConfig === 'function') { - return { - plugin: pluginConfig, - options: {}, - }; - } - - if (Array.isArray(pluginConfig)) { - // plugins: [ - // ['./plugin',options], - // ] - if (typeof pluginConfig[0] === 'string') { - const pluginModuleImport = pluginConfig[0]; - const pluginPath = pluginRequire.resolve(pluginModuleImport); - const pluginModule = importFresh(pluginPath); - return { - plugin: pluginModule?.default ?? pluginModule, - options: pluginConfig[1] ?? {}, - pluginModule: { - path: pluginModuleImport, - module: pluginModule, - }, - }; - } - // plugins: [ - // [function plugin() { },options], - // ] - if (typeof pluginConfig[0] === 'function') { - return { - plugin: pluginConfig[0], - options: pluginConfig[1] ?? {}, - }; - } - } - - throw new Error( - `Unexpected: can't load plugin for following plugin config.\n${JSON.stringify( - pluginConfig, - )}`, - ); -} +import {loadPluginConfigs} from './configs'; function getOptionValidationFunction( normalizedPluginConfig: NormalizedPluginConfig, @@ -105,9 +33,8 @@ function getOptionValidationFunction( normalizedPluginConfig.pluginModule.module?.default?.validateOptions ?? normalizedPluginConfig.pluginModule.module?.validateOptions ); - } else { - return normalizedPluginConfig.plugin.validateOptions; } + return normalizedPluginConfig.plugin.validateOptions; } function getThemeValidationFunction( @@ -119,34 +46,33 @@ function getThemeValidationFunction( normalizedPluginConfig.pluginModule.module.default?.validateThemeConfig ?? normalizedPluginConfig.pluginModule.module.validateThemeConfig ); - } else { - return normalizedPluginConfig.plugin.validateThemeConfig; } + return normalizedPluginConfig.plugin.validateThemeConfig; } -export default async function initPlugins({ - pluginConfigs, - context, -}: { - pluginConfigs: PluginConfig[]; - context: LoadContext; -}): Promise { - // We need to resolve plugins from the perspective of the siteDir, since the siteDir's package.json - // declares the dependency on these plugins. +/** + * Runs the plugin constructors and returns their return values. It would load + * plugin configs from `plugins`, `themes`, and `presets`. + */ +export async function initPlugins( + context: LoadContext, +): Promise { + // We need to resolve plugins from the perspective of the site config, as if + // we are using `require.resolve` on those module names. const pluginRequire = createRequire(context.siteConfigPath); + const pluginConfigs = await loadPluginConfigs(context); - function doGetPluginVersion( + async function doGetPluginVersion( normalizedPluginConfig: NormalizedPluginConfig, - ): DocusaurusPluginVersionInformation { + ): Promise { // get plugin version if (normalizedPluginConfig.pluginModule?.path) { const pluginPath = pluginRequire.resolve( normalizedPluginConfig.pluginModule?.path, ); return getPluginVersion(pluginPath, context.siteDir); - } else { - return {type: 'local'}; } + return {type: 'local'}; } function doValidateThemeConfig( @@ -160,39 +86,34 @@ export default async function initPlugins({ validate: normalizeThemeConfig, themeConfig: context.siteConfig.themeConfig, }); - } else { - return context.siteConfig.themeConfig; } + return context.siteConfig.themeConfig; } function doValidatePluginOptions( normalizedPluginConfig: NormalizedPluginConfig, - ) { + ): Required { const validateOptions = getOptionValidationFunction(normalizedPluginConfig); if (validateOptions) { return validateOptions({ validate: normalizePluginOptions, options: normalizedPluginConfig.options, }); - } else { - // Important to ensure all plugins have an id - // as we don't go through the Joi schema that adds it - return { - ...normalizedPluginConfig.options, - id: normalizedPluginConfig.options.id ?? DEFAULT_PLUGIN_ID, - }; } + // Important to ensure all plugins have an id + // as we don't go through the Joi schema that adds it + return { + ...normalizedPluginConfig.options, + id: normalizedPluginConfig.options.id ?? DEFAULT_PLUGIN_ID, + }; } async function initializePlugin( - pluginConfig: PluginConfig, + normalizedPluginConfig: NormalizedPluginConfig, ): Promise { - const normalizedPluginConfig = normalizePluginConfig( - pluginConfig, - pluginRequire, + const pluginVersion: PluginVersionInformation = await doGetPluginVersion( + normalizedPluginConfig, ); - const pluginVersion: DocusaurusPluginVersionInformation = - doGetPluginVersion(normalizedPluginConfig); const pluginOptions = doValidatePluginOptions(normalizedPluginConfig); // Side-effect: merge the normalized theme config in the original one @@ -210,19 +131,13 @@ export default async function initPlugins({ ...pluginInstance, options: pluginOptions, version: pluginVersion, + path: path.dirname(normalizedPluginConfig.entryPath), }; } - const plugins: InitializedPlugin[] = ( - await Promise.all( - pluginConfigs.map((pluginConfig) => { - if (!pluginConfig) { - return null; - } - return initializePlugin(pluginConfig); - }), - ) - ).filter((item: T): item is Exclude => Boolean(item)); + const plugins: InitializedPlugin[] = await Promise.all( + pluginConfigs.map(initializePlugin), + ); ensureUniquePluginInstanceIds(plugins); diff --git a/packages/docusaurus/src/server/moduleShorthand.ts b/packages/docusaurus/src/server/plugins/moduleShorthand.ts similarity index 95% rename from packages/docusaurus/src/server/moduleShorthand.ts rename to packages/docusaurus/src/server/plugins/moduleShorthand.ts index 37a50e5027f2..df0ab5570d43 100644 --- a/packages/docusaurus/src/server/moduleShorthand.ts +++ b/packages/docusaurus/src/server/plugins/moduleShorthand.ts @@ -14,7 +14,7 @@ export function getNamePatterns( if (!moduleName.includes('/')) { return [`${moduleName}/docusaurus-${moduleType}`]; } - const [scope, packageName] = moduleName.split(/\/(.*)/); + const [scope, packageName] = moduleName.split(/\/(?.*)/); return [ `${scope}/${packageName}`, `${scope}/docusaurus-${moduleType}-${packageName}`, diff --git a/packages/docusaurus/src/server/plugins/pluginIds.ts b/packages/docusaurus/src/server/plugins/pluginIds.ts index 494dea23fabd..86e3eb5d9f97 100644 --- a/packages/docusaurus/src/server/plugins/pluginIds.ts +++ b/packages/docusaurus/src/server/plugins/pluginIds.ts @@ -5,18 +5,20 @@ * LICENSE file in the root directory of this source tree. */ -import {groupBy} from 'lodash'; +import _ from 'lodash'; import {DEFAULT_PLUGIN_ID} from '@docusaurus/utils'; import type {InitializedPlugin} from '@docusaurus/types'; -// It is forbidden to have 2 plugins of the same name sharing the same id -// this is required to support multi-instance plugins without conflict +/** + * It is forbidden to have 2 plugins of the same name sharing the same ID. + * This is required to support multi-instance plugins without conflict. + */ export function ensureUniquePluginInstanceIds( plugins: InitializedPlugin[], ): void { - const pluginsByName = groupBy(plugins, (p) => p.name); + const pluginsByName = _.groupBy(plugins, (p) => p.name); Object.entries(pluginsByName).forEach(([pluginName, pluginInstances]) => { - const pluginInstancesById = groupBy( + const pluginInstancesById = _.groupBy( pluginInstances, (p) => p.options.id ?? DEFAULT_PLUGIN_ID, ); diff --git a/packages/docusaurus/src/server/presets/index.ts b/packages/docusaurus/src/server/plugins/presets.ts similarity index 52% rename from packages/docusaurus/src/server/presets/index.ts rename to packages/docusaurus/src/server/plugins/presets.ts index 01263685a082..877150cc6703 100644 --- a/packages/docusaurus/src/server/presets/index.ts +++ b/packages/docusaurus/src/server/plugins/presets.ts @@ -10,32 +10,33 @@ import importFresh from 'import-fresh'; import type { LoadContext, PluginConfig, - PresetConfig, ImportedPresetModule, + DocusaurusConfig, } from '@docusaurus/types'; -import {resolveModuleName} from '../moduleShorthand'; +import {resolveModuleName} from './moduleShorthand'; -export default function loadPresets(context: LoadContext): { - plugins: PluginConfig[]; - themes: PluginConfig[]; -} { - // We need to resolve presets from the perspective of the siteDir, since the siteDir's package.json - // declares the dependency on these presets. +/** + * Calls preset functions, aggregates each of their return values, and returns + * the plugin and theme configs. + */ +export async function loadPresets( + context: LoadContext, +): Promise> { + // We need to resolve plugins from the perspective of the site config, as if + // we are using `require.resolve` on those module names. const presetRequire = createRequire(context.siteConfigPath); - const presets: PresetConfig[] = context.siteConfig.presets || []; - const unflatPlugins: PluginConfig[][] = []; - const unflatThemes: PluginConfig[][] = []; + const {presets} = context.siteConfig; + const plugins: PluginConfig[] = []; + const themes: PluginConfig[] = []; presets.forEach((presetItem) => { - let presetModuleImport; + let presetModuleImport: string; let presetOptions = {}; if (typeof presetItem === 'string') { presetModuleImport = presetItem; - } else if (Array.isArray(presetItem)) { - [presetModuleImport, presetOptions = {}] = presetItem; } else { - throw new Error('Invalid presets format detected in config.'); + [presetModuleImport, presetOptions] = presetItem; } const presetName = resolveModuleName( presetModuleImport, @@ -52,15 +53,12 @@ export default function loadPresets(context: LoadContext): { ); if (preset.plugins) { - unflatPlugins.push(preset.plugins); + plugins.push(...preset.plugins.filter(Boolean)); } if (preset.themes) { - unflatThemes.push(preset.themes); + themes.push(...preset.themes.filter(Boolean)); } }); - return { - plugins: unflatPlugins.flat().filter(Boolean), - themes: unflatThemes.flat().filter(Boolean), - }; + return {plugins, themes}; } diff --git a/packages/docusaurus/src/server/plugins/routeConfig.ts b/packages/docusaurus/src/server/plugins/routeConfig.ts new file mode 100644 index 000000000000..e5664dd1693f --- /dev/null +++ b/packages/docusaurus/src/server/plugins/routeConfig.ts @@ -0,0 +1,68 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import type {RouteConfig} from '@docusaurus/types'; +import { + applyTrailingSlash, + type ApplyTrailingSlashParams, +} from '@docusaurus/utils-common'; + +/** Recursively applies trailing slash config to all nested routes. */ +export function applyRouteTrailingSlash( + route: RouteConfig, + params: ApplyTrailingSlashParams, +): RouteConfig { + return { + ...route, + path: applyTrailingSlash(route.path, params), + ...(route.routes && { + routes: route.routes.map((subroute) => + applyRouteTrailingSlash(subroute, params), + ), + }), + }; +} + +export function sortConfig( + routeConfigs: RouteConfig[], + baseUrl: string = '/', +): void { + // Sort the route config. This ensures that route with nested + // routes is always placed last. + routeConfigs.sort((a, b) => { + // Root route should get placed last. + if (a.path === baseUrl && b.path !== baseUrl) { + return 1; + } + if (a.path !== baseUrl && b.path === baseUrl) { + return -1; + } + + if (a.routes && !b.routes) { + return 1; + } + if (!a.routes && b.routes) { + return -1; + } + // Higher priority get placed first. + if (a.priority || b.priority) { + const priorityA = a.priority || 0; + const priorityB = b.priority || 0; + const score = priorityB - priorityA; + + if (score !== 0) { + return score; + } + } + + return a.path.localeCompare(b.path); + }); + + routeConfigs.forEach((routeConfig) => { + routeConfig.routes?.sort((a, b) => a.path.localeCompare(b.path)); + }); +} diff --git a/packages/docusaurus/src/server/plugins/synthetic.ts b/packages/docusaurus/src/server/plugins/synthetic.ts new file mode 100644 index 000000000000..f44fad1eb3d8 --- /dev/null +++ b/packages/docusaurus/src/server/plugins/synthetic.ts @@ -0,0 +1,129 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import path from 'path'; +import admonitions from 'remark-admonitions'; +import type {RuleSetRule} from 'webpack'; +import type {HtmlTagObject, LoadedPlugin, LoadContext} from '@docusaurus/types'; + +/** + * Make a synthetic plugin to: + * - Inject site client modules + * - Inject scripts/stylesheets + */ +export function createBootstrapPlugin({ + siteDir, + siteConfig, +}: LoadContext): LoadedPlugin { + const { + stylesheets, + scripts, + clientModules: siteConfigClientModules, + } = siteConfig; + return { + name: 'docusaurus-bootstrap-plugin', + content: null, + options: { + id: 'default', + }, + version: {type: 'synthetic'}, + path: siteDir, + getClientModules() { + return siteConfigClientModules; + }, + injectHtmlTags: () => { + const stylesheetsTags = stylesheets.map((source) => + typeof source === 'string' + ? `` + : ({ + tagName: 'link', + attributes: { + rel: 'stylesheet', + ...source, + }, + } as HtmlTagObject), + ); + const scriptsTags = scripts.map((source) => + typeof source === 'string' + ? `` + : ({ + tagName: 'script', + attributes: { + ...source, + }, + } as HtmlTagObject), + ); + return { + headTags: [...stylesheetsTags, ...scriptsTags], + }; + }, + }; +} + +/** + * Configure Webpack fallback mdx loader for md/mdx files out of content-plugin + * folders. Adds a "fallback" mdx loader for mdx files that are not processed by + * content plugins. This allows to do things such as importing repo/README.md as + * a partial from another doc. Not ideal solution, but good enough for now + */ +export function createMDXFallbackPlugin({ + siteDir, + siteConfig, +}: LoadContext): LoadedPlugin { + return { + name: 'docusaurus-mdx-fallback-plugin', + content: null, + options: { + id: 'default', + }, + version: {type: 'synthetic'}, + // Synthetic, the path doesn't matter much + path: '.', + configureWebpack(config, isServer, {getJSLoader}) { + // We need the mdx fallback loader to exclude files that were already + // processed by content plugins mdx loaders. This works, but a bit + // hacky... Not sure there's a way to handle that differently in webpack + function getMDXFallbackExcludedPaths(): string[] { + const rules: RuleSetRule[] = config?.module?.rules as RuleSetRule[]; + return rules.flatMap((rule) => { + const isMDXRule = + rule.test instanceof RegExp && rule.test.test('x.mdx'); + return isMDXRule ? (rule.include as string[]) : []; + }); + } + const mdxLoaderOptions = { + staticDirs: siteConfig.staticDirectories.map((dir) => + path.resolve(siteDir, dir), + ), + siteDir, + // External MDX files are always meant to be imported as partials + isMDXPartial: () => true, + // External MDX files might have front matter, just disable the warning + isMDXPartialFrontMatterWarningDisabled: true, + remarkPlugins: [admonitions], + }; + + return { + module: { + rules: [ + { + test: /\.mdx?$/i, + exclude: getMDXFallbackExcludedPaths(), + use: [ + getJSLoader({isServer}), + { + loader: require.resolve('@docusaurus/mdx-loader'), + options: mdxLoaderOptions, + }, + ], + }, + ], + }, + }; + }, + }; +} diff --git a/packages/docusaurus/src/server/presets/__tests__/index.test.ts b/packages/docusaurus/src/server/presets/__tests__/index.test.ts deleted file mode 100644 index 823d4af4aac9..000000000000 --- a/packages/docusaurus/src/server/presets/__tests__/index.test.ts +++ /dev/null @@ -1,283 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import path from 'path'; - -import loadPresets from '../index'; -import type {LoadContext} from '@docusaurus/types'; - -describe('loadPresets', () => { - test('no presets', () => { - const context = { - siteConfigPath: __dirname, - siteConfig: {}, - } as LoadContext; - const presets = loadPresets(context); - expect(presets).toMatchInlineSnapshot(` - Object { - "plugins": Array [], - "themes": Array [], - } - `); - }); - - test('string form', () => { - const context = { - siteConfigPath: __dirname, - siteConfig: { - presets: [path.join(__dirname, '__fixtures__/preset-bar.js')], - }, - } as LoadContext; - const presets = loadPresets(context); - expect(presets).toMatchInlineSnapshot(` - Object { - "plugins": Array [ - Array [ - "@docusaurus/plugin-content-docs", - undefined, - ], - Array [ - "@docusaurus/plugin-content-blog", - undefined, - ], - ], - "themes": Array [], - } - `); - }); - - test('string form composite', () => { - const context = { - siteConfigPath: __dirname, - siteConfig: { - presets: [ - path.join(__dirname, '__fixtures__/preset-bar.js'), - path.join(__dirname, '__fixtures__/preset-foo.js'), - ], - }, - } as LoadContext; - const presets = loadPresets(context); - expect(presets).toMatchInlineSnapshot(` - Object { - "plugins": Array [ - Array [ - "@docusaurus/plugin-content-docs", - undefined, - ], - Array [ - "@docusaurus/plugin-content-blog", - undefined, - ], - Array [ - "@docusaurus/plugin-content-pages", - undefined, - ], - Array [ - "@docusaurus/plugin-sitemap", - undefined, - ], - ], - "themes": Array [], - } - `); - }); - - test('array form', () => { - const context = { - siteConfigPath: __dirname, - siteConfig: { - presets: [[path.join(__dirname, '__fixtures__/preset-bar.js')]], - }, - } as Partial; - const presets = loadPresets(context); - expect(presets).toMatchInlineSnapshot(` - Object { - "plugins": Array [ - Array [ - "@docusaurus/plugin-content-docs", - undefined, - ], - Array [ - "@docusaurus/plugin-content-blog", - undefined, - ], - ], - "themes": Array [], - } - `); - }); - - test('array form with options', () => { - const context = { - siteConfigPath: __dirname, - siteConfig: { - presets: [ - [ - path.join(__dirname, '__fixtures__/preset-bar.js'), - {docs: {path: '../'}}, - ], - ], - }, - } as Partial; - const presets = loadPresets(context); - expect(presets).toMatchInlineSnapshot(` - Object { - "plugins": Array [ - Array [ - "@docusaurus/plugin-content-docs", - Object { - "path": "../", - }, - ], - Array [ - "@docusaurus/plugin-content-blog", - undefined, - ], - ], - "themes": Array [], - } - `); - }); - - test('array form composite', () => { - const context = { - siteConfigPath: __dirname, - siteConfig: { - presets: [ - [ - path.join(__dirname, '__fixtures__/preset-bar.js'), - {docs: {path: '../'}}, - ], - [ - path.join(__dirname, '__fixtures__/preset-foo.js'), - {pages: {path: '../'}}, - ], - ], - }, - } as Partial; - const presets = loadPresets(context); - expect(presets).toMatchInlineSnapshot(` - Object { - "plugins": Array [ - Array [ - "@docusaurus/plugin-content-docs", - Object { - "path": "../", - }, - ], - Array [ - "@docusaurus/plugin-content-blog", - undefined, - ], - Array [ - "@docusaurus/plugin-content-pages", - Object { - "path": "../", - }, - ], - Array [ - "@docusaurus/plugin-sitemap", - undefined, - ], - ], - "themes": Array [], - } - `); - }); - - test('mixed form', () => { - const context = { - siteConfigPath: __dirname, - siteConfig: { - presets: [ - [ - path.join(__dirname, '__fixtures__/preset-bar.js'), - {docs: {path: '../'}}, - ], - path.join(__dirname, '__fixtures__/preset-foo.js'), - ], - }, - } as LoadContext; - const presets = loadPresets(context); - expect(presets).toMatchInlineSnapshot(` - Object { - "plugins": Array [ - Array [ - "@docusaurus/plugin-content-docs", - Object { - "path": "../", - }, - ], - Array [ - "@docusaurus/plugin-content-blog", - undefined, - ], - Array [ - "@docusaurus/plugin-content-pages", - undefined, - ], - Array [ - "@docusaurus/plugin-sitemap", - undefined, - ], - ], - "themes": Array [], - } - `); - }); - - test('mixed form with themes', () => { - const context = { - siteConfigPath: __dirname, - siteConfig: { - presets: [ - [ - path.join(__dirname, '__fixtures__/preset-bar.js'), - {docs: {path: '../'}}, - ], - path.join(__dirname, '__fixtures__/preset-foo.js'), - path.join(__dirname, '__fixtures__/preset-qux.js'), - ], - }, - } as LoadContext; - const presets = loadPresets(context); - expect(presets).toMatchInlineSnapshot(` - Object { - "plugins": Array [ - Array [ - "@docusaurus/plugin-content-docs", - Object { - "path": "../", - }, - ], - Array [ - "@docusaurus/plugin-content-blog", - undefined, - ], - Array [ - "@docusaurus/plugin-content-pages", - undefined, - ], - Array [ - "@docusaurus/plugin-sitemap", - undefined, - ], - Array [ - "@docusaurus/plugin-test", - undefined, - ], - ], - "themes": Array [ - Array [ - "@docusaurus/theme-classic", - undefined, - ], - ], - } - `); - }); -}); diff --git a/packages/docusaurus/src/server/routes.ts b/packages/docusaurus/src/server/routes.ts index 03dde9e97492..22fb44bbd7fc 100644 --- a/packages/docusaurus/src/server/routes.ts +++ b/packages/docusaurus/src/server/routes.ts @@ -6,32 +6,90 @@ */ import { - genChunkName, + docuHash, normalizeUrl, - removeSuffix, simpleHash, escapePath, + reportMessage, } from '@docusaurus/utils'; -import {has, isPlainObject, isString} from 'lodash'; -import {stringify} from 'querystring'; +import _ from 'lodash'; +import query from 'querystring'; +import {getAllFinalRoutes} from './utils'; import type { - ChunkRegistry, Module, RouteConfig, - RouteModule, + RouteModules, ChunkNames, + RouteChunkNames, + ReportingSeverity, } from '@docusaurus/types'; -type RegistryMap = { - [chunkName: string]: ChunkRegistry; +type LoadedRoutes = { + /** Serialized routes config that can be directly emitted into temp file. */ + routesConfig: string; + /** @see {ChunkNames} */ + routesChunkNames: RouteChunkNames; + /** A map from chunk name to module loaders. */ + registry: { + [chunkName: string]: {loader: string; modulePath: string}; + }; + /** + * Collect all page paths for injecting it later in the plugin lifecycle. + * This is useful for plugins like sitemaps, redirects etc... Only collects + * "actual" pages, i.e. those without subroutes, because if a route has + * subroutes, it is probably a wrapper. + */ + routesPaths: string[]; }; +/** Indents every line of `str` by one level. */ function indent(str: string) { - const spaces = ' '; - return `${spaces}${str.replace(/(\n)/g, `\n${spaces}`)}`; + return ` ${str.replace(/\n/g, `\n `)}`; +} + +const chunkNameCache = new Map(); + +/** + * Generates a unique chunk name that can be used in the chunk registry. + * + * @param modulePath A path to generate chunk name from. The actual value has no + * semantic significance. + * @param prefix A prefix to append to the chunk name, to avoid name clash. + * @param preferredName Chunk names default to `modulePath`, and this can supply + * a more human-readable name. + * @param shortId When `true`, the chunk name would only be a hash without any + * other characters. Useful for bundle size. Defaults to `true` in production. + */ +export function genChunkName( + modulePath: string, + prefix?: string, + preferredName?: string, + shortId: boolean = process.env.NODE_ENV === 'production', +): string { + let chunkName = chunkNameCache.get(modulePath); + if (!chunkName) { + if (shortId) { + chunkName = simpleHash(modulePath, 8); + } else { + let str = modulePath; + if (preferredName) { + const shortHash = simpleHash(modulePath, 3); + str = `${preferredName}${shortHash}`; + } + const name = str === '/' ? 'index' : docuHash(str); + chunkName = prefix ? `${prefix}---${name}` : name; + } + chunkNameCache.set(modulePath, chunkName); + } + return chunkName; } -const createRouteCodeString = ({ +/** + * Takes a piece of route config, and serializes it into raw JS code. The shape + * is the same as react-router's `RouteConfig`. Formatting is similar to + * `JSON.stringify` but without all the quotes. + */ +function serializeRouteConfig({ routePath, routeHash, exact, @@ -43,10 +101,10 @@ const createRouteCodeString = ({ exact?: boolean; subroutesCodeStrings?: string[]; props: {[propName: string]: unknown}; -}) => { +}) { const parts = [ `path: '${routePath}'`, - `component: ComponentCreator('${routePath}','${routeHash}')`, + `component: ComponentCreator('${routePath}', '${routeHash}')`, ]; if (exact) { @@ -56,197 +114,211 @@ const createRouteCodeString = ({ if (subroutesCodeStrings) { parts.push( `routes: [ -${indent(removeSuffix(subroutesCodeStrings.join(',\n'), ',\n'))} +${indent(subroutesCodeStrings.join(',\n'))} ]`, ); } Object.entries(props).forEach(([propName, propValue]) => { - // Figure out how to "unquote" JS attributes that don't need to be quoted - // Is this lib reliable? https://github.com/armanozak/should-quote - const shouldQuote = true; // TODO - const key = shouldQuote ? `'${propName}'` : propName; + // Inspired by https://github.com/armanozak/should-quote/blob/main/packages/should-quote/src/lib/should-quote.ts + const shouldQuote = ((key: string) => { + // Pre-sanitation to prevent injection + if (/[.,;:}/\s]/.test(key)) { + return true; + } + try { + // If this key can be used in an expression like ({a:0}).a + // eslint-disable-next-line no-eval + eval(`({${key}:0}).${key}`); + return false; + } catch { + return true; + } + })(propName); + // Escape quotes as well + const key = shouldQuote ? JSON.stringify(propName) : propName; parts.push(`${key}: ${JSON.stringify(propValue)}`); }); return `{ ${indent(parts.join(',\n'))} }`; -}; - -const NotFoundRouteCode = `{ - path: '*', - component: ComponentCreator('*') -}`; - -const RoutesImportsCode = [ - `import React from 'react';`, - `import ComponentCreator from '@docusaurus/ComponentCreator';`, -].join('\n'); - -function isModule(value: unknown): value is Module { - if (isString(value)) { - return true; - } - if (isPlainObject(value) && has(value, '__import') && has(value, 'path')) { - return true; - } - return false; } +const isModule = (value: unknown): value is Module => + typeof value === 'string' || + (typeof value === 'object' && + // eslint-disable-next-line no-underscore-dangle + !!(value as {[key: string]: unknown})?.__import); + +/** + * Takes a {@link Module} (which is nothing more than a path plus some metadata + * like query) and returns the string path it represents. + */ function getModulePath(target: Module): string { if (typeof target === 'string') { return target; } - const queryStr = target.query ? `?${stringify(target.query)}` : ''; + const queryStr = target.query ? `?${query.stringify(target.query)}` : ''; return `${target.path}${queryStr}`; } -type LoadedRoutes = { - registry: { - [chunkName: string]: ChunkRegistry; - }; - routesConfig: string; - routesChunkNames: { - [routePath: string]: ChunkNames; - }; - routesPaths: string[]; -}; +/** + * Takes a route module (which is a tree of modules), and transforms each module + * into a chunk name. It also mutates `res.registry` and registers the loaders + * for each chunk. + * + * @param routeModule One route module to be transformed. + * @param prefix Prefix passed to {@link genChunkName}. + * @param name Preferred name passed to {@link genChunkName}. + * @param res The route structures being loaded. + */ +function genChunkNames( + routeModule: RouteModules, + prefix: string, + name: string, + res: LoadedRoutes, +): ChunkNames; +function genChunkNames( + routeModule: RouteModules | RouteModules[] | Module, + prefix: string, + name: string, + res: LoadedRoutes, +): ChunkNames | ChunkNames[] | string; +function genChunkNames( + routeModule: RouteModules | RouteModules[] | Module, + prefix: string, + name: string, + res: LoadedRoutes, +): string | ChunkNames | ChunkNames[] { + if (isModule(routeModule)) { + // This is a leaf node, no need to recurse + const modulePath = getModulePath(routeModule); + const chunkName = genChunkName(modulePath, prefix, name); + res.registry[chunkName] = { + loader: `() => import(/* webpackChunkName: '${chunkName}' */ '${escapePath( + modulePath, + )}')`, + modulePath, + }; + return chunkName; + } + if (Array.isArray(routeModule)) { + return routeModule.map((val, index) => + genChunkNames(val, `${index}`, name, res), + ); + } + return _.mapValues(routeModule, (v, key) => genChunkNames(v, key, name, res)); +} -export default async function loadRoutes( +export function handleDuplicateRoutes( pluginsRouteConfigs: RouteConfig[], - baseUrl: string, -): Promise { - const registry: { - [chunkName: string]: ChunkRegistry; - } = {}; - const routesPaths: string[] = [normalizeUrl([baseUrl, '404.html'])]; - const routesChunkNames: { - [routePath: string]: ChunkNames; - } = {}; - - // This is the higher level overview of route code generation. - function generateRouteCode(routeConfig: RouteConfig): string { - const { - path: routePath, - component, - modules = {}, - routes: subroutes, - exact, - priority, - ...props - } = routeConfig; - - if (!isString(routePath) || !component) { - throw new Error( - `Invalid route config: path must be a string and component is required.\n${JSON.stringify( - routeConfig, - )}`, - ); - } - - // Collect all page paths for injecting it later in the plugin lifecycle - // This is useful for plugins like sitemaps, redirects etc... - // If a route has subroutes, it is not necessarily a valid page path (more likely to be a wrapper) - if (!subroutes) { - routesPaths.push(routePath); + onDuplicateRoutes: ReportingSeverity, +): void { + if (onDuplicateRoutes === 'ignore') { + return; + } + const allRoutes: string[] = getAllFinalRoutes(pluginsRouteConfigs).map( + (routeConfig) => routeConfig.path, + ); + const seenRoutes = new Set(); + const duplicatePaths = allRoutes.filter((route) => { + if (seenRoutes.has(route)) { + return true; } + seenRoutes.add(route); + return false; + }); + if (duplicatePaths.length > 0) { + const finalMessage = `Duplicate routes found! +${duplicatePaths + .map( + (duplicateRoute) => + `- Attempting to create page at ${duplicateRoute}, but a page already exists at this route.`, + ) + .join('\n')} +This could lead to non-deterministic routing behavior.`; + reportMessage(finalMessage, onDuplicateRoutes); + } +} - // We hash the route to generate the key, because 2 routes can conflict with - // each others if they have the same path, ex: parent=/docs, child=/docs - // see https://github.com/facebook/docusaurus/issues/2917 - const routeHash = simpleHash(JSON.stringify(routeConfig), 3); - const chunkNamesKey = `${routePath}-${routeHash}`; - routesChunkNames[chunkNamesKey] = { - ...genRouteChunkNames(registry, {component}, 'component', component), - ...genRouteChunkNames(registry, modules, 'module', routePath), - }; +/** + * This is the higher level overview of route code generation. For each route + * config node, it returns the node's serialized form, and mutates `registry`, + * `routesPaths`, and `routesChunkNames` accordingly. + */ +function genRouteCode(routeConfig: RouteConfig, res: LoadedRoutes): string { + const { + path: routePath, + component, + modules = {}, + routes: subroutes, + priority, + exact, + ...props + } = routeConfig; - return createRouteCodeString({ - routePath: routeConfig.path.replace(/'/g, "\\'"), - routeHash, - exact, - subroutesCodeStrings: subroutes?.map(generateRouteCode), - props, - }); + if (typeof routePath !== 'string' || !component) { + throw new Error( + `Invalid route config: path must be a string and component is required. +${JSON.stringify(routeConfig)}`, + ); } - const routesConfig = ` -${RoutesImportsCode} - -export default [ -${indent(`${pluginsRouteConfigs.map(generateRouteCode).join(',\n')},`)} -${indent(NotFoundRouteCode)} -]; -`; + if (!subroutes) { + res.routesPaths.push(routePath); + } - return { - registry, - routesConfig, - routesChunkNames, - routesPaths, + const routeHash = simpleHash(JSON.stringify(routeConfig), 3); + res.routesChunkNames[`${routePath}-${routeHash}`] = { + // Avoid clash with a prop called "component" + ...genChunkNames({__comp: component}, 'component', component, res), + ...genChunkNames(modules, 'module', routePath, res), }; -} - -function genRouteChunkNames( - registry: RegistryMap, - value: Module, - prefix?: string, - name?: string, -): string; -function genRouteChunkNames( - registry: RegistryMap, - value: RouteModule, - prefix?: string, - name?: string, -): ChunkNames; -function genRouteChunkNames( - registry: RegistryMap, - value: RouteModule[], - prefix?: string, - name?: string, -): ChunkNames[]; -function genRouteChunkNames( - registry: RegistryMap, - value: RouteModule | RouteModule[] | Module, - prefix?: string, - name?: string, -): ChunkNames | ChunkNames[] | string; -function genRouteChunkNames( - // TODO instead of passing a mutating the registry, return a registry slice? - registry: RegistryMap, - value: RouteModule | RouteModule[] | Module | null | undefined, - prefix?: string, - name?: string, -): null | string | ChunkNames | ChunkNames[] { - if (!value) { - return null; - } + return serializeRouteConfig({ + routePath: routePath.replace(/'/g, "\\'"), + routeHash, + subroutesCodeStrings: subroutes?.map((r) => genRouteCode(r, res)), + exact, + props, + }); +} - if (Array.isArray(value)) { - return value.map((val, index) => - genRouteChunkNames(registry, val, `${index}`, name), - ); - } +/** + * Routes are prepared into three temp files: + * + * - `routesConfig`, the route config passed to react-router. This file is kept + * minimal, because it can't be code-splitted. + * - `routesChunkNames`, a mapping from route paths (hashed) to code-splitted + * chunk names. + * - `registry`, a mapping from chunk names to options for react-loadable. + */ +export async function loadRoutes( + routeConfigs: RouteConfig[], + baseUrl: string, + onDuplicateRoutes: ReportingSeverity, +): Promise { + handleDuplicateRoutes(routeConfigs, onDuplicateRoutes); + const res: LoadedRoutes = { + // To be written by `genRouteCode` + routesConfig: '', + routesChunkNames: {}, + registry: {}, + routesPaths: [normalizeUrl([baseUrl, '404.html'])], + }; - if (isModule(value)) { - const modulePath = getModulePath(value); - const chunkName = genChunkName(modulePath, prefix, name); - const loader = `() => import(/* webpackChunkName: '${chunkName}' */ '${escapePath( - modulePath, - )}')`; + res.routesConfig = `import React from 'react'; +import ComponentCreator from '@docusaurus/ComponentCreator'; - registry[chunkName] = { - loader, - modulePath, - }; - return chunkName; - } +export default [ +${indent(`${routeConfigs.map((r) => genRouteCode(r, res)).join(',\n')},`)} + { + path: '*', + component: ComponentCreator('*'), + }, +]; +`; - const newValue: ChunkNames = {}; - Object.keys(value).forEach((key) => { - newValue[key] = genRouteChunkNames(registry, value[key], key, name); - }); - return newValue; + return res; } diff --git a/packages/docusaurus/src/server/siteMetadata.ts b/packages/docusaurus/src/server/siteMetadata.ts new file mode 100644 index 000000000000..199dbc034a6b --- /dev/null +++ b/packages/docusaurus/src/server/siteMetadata.ts @@ -0,0 +1,116 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import type { + LoadedPlugin, + PluginVersionInformation, + SiteMetadata, +} from '@docusaurus/types'; +import fs from 'fs-extra'; +import path from 'path'; +import logger from '@docusaurus/logger'; + +async function getPackageJsonVersion( + packageJsonPath: string, +): Promise { + if (await fs.pathExists(packageJsonPath)) { + // eslint-disable-next-line @typescript-eslint/no-var-requires, import/no-dynamic-require, global-require + return require(packageJsonPath).version; + } + return undefined; +} + +async function getPackageJsonName( + packageJsonPath: string, +): Promise { + // eslint-disable-next-line @typescript-eslint/no-var-requires, import/no-dynamic-require, global-require + return require(packageJsonPath).name; +} + +export async function getPluginVersion( + pluginPath: string, + siteDir: string, +): Promise { + let potentialPluginPackageJsonDirectory = path.dirname(pluginPath); + while (potentialPluginPackageJsonDirectory !== '/') { + const packageJsonPath = path.join( + potentialPluginPackageJsonDirectory, + 'package.json', + ); + if ( + (await fs.pathExists(packageJsonPath)) && + (await fs.lstat(packageJsonPath)).isFile() + ) { + if (potentialPluginPackageJsonDirectory === siteDir) { + // If the plugin belongs to the same docusaurus project, we classify it + // as local plugin. + return {type: 'project'}; + } + return { + type: 'package', + name: await getPackageJsonName(packageJsonPath), + version: await getPackageJsonVersion(packageJsonPath), + }; + } + potentialPluginPackageJsonDirectory = path.dirname( + potentialPluginPackageJsonDirectory, + ); + } + // In the case where a plugin is a path where no parent directory contains + // package.json, we can only classify it as local. Could happen if one puts a + // script in the parent directory of the site. + return {type: 'local'}; +} + +/** + * We want all `@docusaurus/*` packages to have the exact same version! + * @see https://github.com/facebook/docusaurus/issues/3371 + * @see https://github.com/facebook/docusaurus/pull/3386 + */ +function checkDocusaurusPackagesVersion(siteMetadata: SiteMetadata) { + const {docusaurusVersion} = siteMetadata; + Object.entries(siteMetadata.pluginVersions).forEach( + ([plugin, versionInfo]) => { + if ( + versionInfo.type === 'package' && + versionInfo.name?.startsWith('@docusaurus/') && + versionInfo.version && + versionInfo.version !== docusaurusVersion + ) { + // should we throw instead? + // It still could work with different versions + logger.error`Invalid name=${plugin} version number=${versionInfo.version}. +All official @docusaurus/* packages should have the exact same version as @docusaurus/core (number=${docusaurusVersion}). +Maybe you want to check, or regenerate your yarn.lock or package-lock.json file?`; + } + }, + ); +} + +export async function loadSiteMetadata({ + plugins, + siteDir, +}: { + plugins: LoadedPlugin[]; + siteDir: string; +}): Promise { + const siteMetadata: SiteMetadata = { + docusaurusVersion: (await getPackageJsonVersion( + path.join(__dirname, '../../package.json'), + ))!, + siteVersion: await getPackageJsonVersion( + path.join(siteDir, 'package.json'), + ), + pluginVersions: Object.fromEntries( + plugins + .filter(({version: {type}}) => type !== 'synthetic') + .map(({name, version}) => [name, version]), + ), + }; + checkDocusaurusPackagesVersion(siteMetadata); + return siteMetadata; +} diff --git a/packages/docusaurus/src/server/themes/__tests__/index.test.ts b/packages/docusaurus/src/server/themes/__tests__/index.test.ts deleted file mode 100644 index 157ec169d3b6..000000000000 --- a/packages/docusaurus/src/server/themes/__tests__/index.test.ts +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import path from 'path'; -import {loadThemeAliases} from '../index'; - -describe('loadThemeAliases', () => { - test('next alias can override the previous alias', () => { - const fixtures = path.join(__dirname, '__fixtures__'); - const theme1Path = path.join(fixtures, 'theme-1'); - const theme2Path = path.join(fixtures, 'theme-2'); - - const alias = loadThemeAliases([theme1Path, theme2Path], []); - - // Testing entries, because order matters! - expect(Object.entries(alias)).toEqual( - Object.entries({ - '@theme-init/Layout': path.join(theme1Path, 'Layout.js'), - - '@theme-original/Footer': path.join(theme1Path, 'Footer/index.js'), - '@theme-original/Layout': path.join(theme2Path, 'Layout/index.js'), - '@theme-original/Navbar': path.join(theme2Path, 'Navbar.js'), - '@theme-original/NavbarItem/NestedNavbarItem': path.join( - theme2Path, - 'NavbarItem/NestedNavbarItem/index.js', - ), - '@theme-original/NavbarItem/SiblingNavbarItem': path.join( - theme2Path, - 'NavbarItem/SiblingNavbarItem.js', - ), - '@theme-original/NavbarItem/zzz': path.join( - theme2Path, - 'NavbarItem/zzz.js', - ), - '@theme-original/NavbarItem': path.join( - theme2Path, - 'NavbarItem/index.js', - ), - - '@theme/Footer': path.join(theme1Path, 'Footer/index.js'), - '@theme/Layout': path.join(theme2Path, 'Layout/index.js'), - '@theme/Navbar': path.join(theme2Path, 'Navbar.js'), - '@theme/NavbarItem/NestedNavbarItem': path.join( - theme2Path, - 'NavbarItem/NestedNavbarItem/index.js', - ), - '@theme/NavbarItem/SiblingNavbarItem': path.join( - theme2Path, - 'NavbarItem/SiblingNavbarItem.js', - ), - '@theme/NavbarItem/zzz': path.join(theme2Path, 'NavbarItem/zzz.js'), - '@theme/NavbarItem': path.join(theme2Path, 'NavbarItem/index.js'), - }), - ); - expect(alias).not.toEqual({}); - }); -}); diff --git a/packages/docusaurus/src/server/themes/alias.ts b/packages/docusaurus/src/server/themes/alias.ts deleted file mode 100644 index ec6508c04664..000000000000 --- a/packages/docusaurus/src/server/themes/alias.ts +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import fs from 'fs-extra'; -import path from 'path'; -import {fileToPath, posixPath, normalizeUrl, Globby} from '@docusaurus/utils'; -import type {ThemeAliases} from '@docusaurus/types'; -import {sortBy} from 'lodash'; - -// Order of Webpack aliases is important because one alias can shadow another -// This ensure @theme/NavbarItem alias is after @theme/NavbarItem/LocaleDropdown -// See https://github.com/facebook/docusaurus/pull/3922 -// See https://github.com/facebook/docusaurus/issues/5382 -export function sortAliases(aliases: ThemeAliases): ThemeAliases { - // Alphabetical order by default - const entries = sortBy(Object.entries(aliases), ([alias]) => alias); - // @theme/NavbarItem should be after @theme/NavbarItem/LocaleDropdown - entries.sort(([alias1], [alias2]) => - alias1.includes(`${alias2}/`) ? -1 : 0, - ); - return Object.fromEntries(entries); -} - -// TODO make async -export default function themeAlias( - themePath: string, - addOriginalAlias: boolean, -): ThemeAliases { - if (!fs.pathExistsSync(themePath)) { - return {}; - } - - const themeComponentFiles = Globby.sync(['**/*.{js,jsx,ts,tsx}'], { - cwd: themePath, - }); - - const aliases: ThemeAliases = {}; - - themeComponentFiles.forEach((relativeSource) => { - const filePath = path.join(themePath, relativeSource); - const fileName = fileToPath(relativeSource); - - const aliasName = posixPath( - normalizeUrl(['@theme', fileName]).replace(/\/$/, ''), - ); - aliases[aliasName] = filePath; - - if (addOriginalAlias) { - // For swizzled components to access the original. - const originalAliasName = posixPath( - normalizeUrl(['@theme-original', fileName]).replace(/\/$/, ''), - ); - aliases[originalAliasName] = filePath; - } - }); - - return sortAliases(aliases); -} diff --git a/packages/docusaurus/src/server/themes/index.ts b/packages/docusaurus/src/server/themes/index.ts deleted file mode 100644 index 387e3792cc8b..000000000000 --- a/packages/docusaurus/src/server/themes/index.ts +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import type {ThemeAliases, LoadedPlugin} from '@docusaurus/types'; -import path from 'path'; -import {THEME_PATH} from '@docusaurus/utils'; -import themeAlias, {sortAliases} from './alias'; - -const ThemeFallbackDir = path.resolve(__dirname, '../../client/theme-fallback'); - -export function loadThemeAliases( - themePaths: string[], - userThemePaths: string[], -): ThemeAliases { - const aliases: ThemeAliases = {}; - - themePaths.forEach((themePath) => { - const themeAliases = themeAlias(themePath, true); - Object.keys(themeAliases).forEach((aliasKey) => { - // If this alias shadows a previous one, use @theme-init to preserve the initial one. - // @theme-init is only applied once: to the initial theme that provided this component - if (aliasKey in aliases) { - const componentName = aliasKey.substring(aliasKey.indexOf('/') + 1); - const initAlias = `@theme-init/${componentName}`; - if (!(initAlias in aliases)) { - aliases[initAlias] = aliases[aliasKey]; - } - } - aliases[aliasKey] = themeAliases[aliasKey]; - }); - }); - - userThemePaths.forEach((themePath) => { - const userThemeAliases = themeAlias(themePath, false); - Object.assign(aliases, userThemeAliases); - }); - - return sortAliases(aliases); -} - -export function loadPluginsThemeAliases({ - siteDir, - plugins, -}: { - siteDir: string; - plugins: LoadedPlugin[]; -}): ThemeAliases { - const pluginThemes: string[] = plugins - .map((plugin) => (plugin.getThemePath ? plugin.getThemePath() : undefined)) - .filter((x): x is string => Boolean(x)); - const userTheme = path.resolve(siteDir, THEME_PATH); - return loadThemeAliases([ThemeFallbackDir, ...pluginThemes], [userTheme]); -} diff --git a/packages/docusaurus/src/server/translations/__tests__/translations.test.ts b/packages/docusaurus/src/server/translations/__tests__/translations.test.ts index acb1be2fe8fc..b4dda430760b 100644 --- a/packages/docusaurus/src/server/translations/__tests__/translations.test.ts +++ b/packages/docusaurus/src/server/translations/__tests__/translations.test.ts @@ -5,11 +5,11 @@ * LICENSE file in the root directory of this source tree. */ +import {jest} from '@jest/globals'; import { - ensureTranslationFileContent, - writeTranslationFileContent, writePluginTranslations, - readTranslationFileContent, + writeCodeTranslations, + readCodeTranslationFileContent, type WriteTranslationsOptions, localizePluginTranslationFile, getPluginsDefaultCodeTranslationMessages, @@ -34,129 +34,93 @@ async function createTmpSiteDir() { async function createTmpTranslationFile( content: TranslationFileContent | null, ) { - const filePath = await tmp.tmpName({ - prefix: 'jest-createTmpTranslationFile', - postfix: '.json', - }); + const siteDir = await createTmpSiteDir(); + const filePath = path.join(siteDir, 'i18n/en/code.json'); // null means we don't want a file, just a filename if (content !== null) { - await fs.writeFile(filePath, JSON.stringify(content, null, 2)); + await fs.outputFile(filePath, JSON.stringify(content, null, 2)); } return { - filePath, - readFile: async () => JSON.parse(await fs.readFile(filePath, 'utf8')), + siteDir, + readFile() { + return fs.readJSON(filePath); + }, }; } -describe('ensureTranslationFileContent', () => { - test('should pass valid translation file content', () => { - ensureTranslationFileContent({}); - ensureTranslationFileContent({key1: {message: ''}}); - ensureTranslationFileContent({key1: {message: 'abc'}}); - ensureTranslationFileContent({key1: {message: 'abc', description: 'desc'}}); - ensureTranslationFileContent({ - key1: {message: 'abc', description: 'desc'}, - key2: {message: 'def', description: 'desc'}, - }); - }); - - test('should fail for invalid translation file content', () => { - expect(() => - ensureTranslationFileContent(null), - ).toThrowErrorMatchingInlineSnapshot( - `"\\"value\\" must be of type object"`, - ); - expect(() => - ensureTranslationFileContent(undefined), - ).toThrowErrorMatchingInlineSnapshot(`"\\"value\\" is required"`); - expect(() => - ensureTranslationFileContent('HEY'), - ).toThrowErrorMatchingInlineSnapshot( - `"\\"value\\" must be of type object"`, - ); - expect(() => - ensureTranslationFileContent(42), - ).toThrowErrorMatchingInlineSnapshot( - `"\\"value\\" must be of type object"`, - ); - expect(() => - ensureTranslationFileContent({key: {description: 'no message'}}), - ).toThrowErrorMatchingInlineSnapshot(`"\\"key.message\\" is required"`); - expect(() => - ensureTranslationFileContent({key: {message: 42}}), - ).toThrowErrorMatchingInlineSnapshot( - `"\\"key.message\\" must be a string"`, - ); - expect(() => - ensureTranslationFileContent({ - key: {message: 'Message', description: 42}, - }), - ).toThrowErrorMatchingInlineSnapshot( - `"\\"key.description\\" must be a string"`, - ); +describe('writeCodeTranslations', () => { + const consoleInfoMock = jest + .spyOn(console, 'info') + .mockImplementation(() => {}); + beforeEach(() => { + consoleInfoMock.mockClear(); }); -}); -describe('writeTranslationFileContent', () => { - test('should create new translation file', async () => { - const {filePath, readFile} = await createTmpTranslationFile(null); - - await writeTranslationFileContent({ - filePath, - content: { + it('creates new translation file', async () => { + const {siteDir, readFile} = await createTmpTranslationFile(null); + await writeCodeTranslations( + {siteDir, locale: 'en'}, + { key1: {message: 'key1 message'}, key2: {message: 'key2 message'}, key3: {message: 'key3 message'}, }, - }); + {}, + ); await expect(readFile()).resolves.toEqual({ key1: {message: 'key1 message'}, key2: {message: 'key2 message'}, key3: {message: 'key3 message'}, }); + expect(consoleInfoMock).toBeCalledWith( + expect.stringMatching(/3.* translations will be written/), + ); }); - test('should create new translation file with prefix', async () => { - const {filePath, readFile} = await createTmpTranslationFile(null); - - await writeTranslationFileContent({ - filePath, - content: { + it('creates new translation file with prefix', async () => { + const {siteDir, readFile} = await createTmpTranslationFile(null); + await writeCodeTranslations( + {siteDir, locale: 'en'}, + { key1: {message: 'key1 message'}, key2: {message: 'key2 message'}, key3: {message: 'key3 message'}, }, - options: { + { messagePrefix: 'PREFIX ', }, - }); + ); await expect(readFile()).resolves.toEqual({ key1: {message: 'PREFIX key1 message'}, key2: {message: 'PREFIX key2 message'}, key3: {message: 'PREFIX key3 message'}, }); + expect(consoleInfoMock).toBeCalledWith( + expect.stringMatching(/3.* translations will be written/), + ); }); - test('should append missing translations', async () => { - const {filePath, readFile} = await createTmpTranslationFile({ + it('appends missing translations', async () => { + const {siteDir, readFile} = await createTmpTranslationFile({ key1: {message: 'key1 message'}, key2: {message: 'key2 message'}, key3: {message: 'key3 message'}, }); - await writeTranslationFileContent({ - filePath, - content: { + await writeCodeTranslations( + {siteDir, locale: 'en'}, + { key1: {message: 'key1 message new'}, key2: {message: 'key2 message new'}, key3: {message: 'key3 message new'}, key4: {message: 'key4 message new'}, }, - }); + {}, + ); await expect(readFile()).resolves.toEqual({ key1: {message: 'key1 message'}, @@ -164,117 +128,147 @@ describe('writeTranslationFileContent', () => { key3: {message: 'key3 message'}, key4: {message: 'key4 message new'}, }); + expect(consoleInfoMock).toBeCalledWith( + expect.stringMatching(/4.* translations will be written/), + ); }); - test('should append missing translations with prefix', async () => { - const {filePath, readFile} = await createTmpTranslationFile({ + it('appends missing.* translations with prefix', async () => { + const {siteDir, readFile} = await createTmpTranslationFile({ key1: {message: 'key1 message'}, }); - await writeTranslationFileContent({ - filePath, - content: { + await writeCodeTranslations( + {siteDir, locale: 'en'}, + { key1: {message: 'key1 message new'}, key2: {message: 'key2 message new'}, }, - options: { + { messagePrefix: 'PREFIX ', }, - }); + ); await expect(readFile()).resolves.toEqual({ key1: {message: 'key1 message'}, key2: {message: 'PREFIX key2 message new'}, }); + expect(consoleInfoMock).toBeCalledWith( + expect.stringMatching(/2.* translations will be written/), + ); }); - test('should override missing translations', async () => { - const {filePath, readFile} = await createTmpTranslationFile({ + it('overrides missing translations', async () => { + const {siteDir, readFile} = await createTmpTranslationFile({ key1: {message: 'key1 message'}, }); - await writeTranslationFileContent({ - filePath, - content: { + await writeCodeTranslations( + {siteDir, locale: 'en'}, + { key1: {message: 'key1 message new'}, key2: {message: 'key2 message new'}, }, - options: { + { override: true, }, - }); + ); await expect(readFile()).resolves.toEqual({ key1: {message: 'key1 message new'}, key2: {message: 'key2 message new'}, }); + expect(consoleInfoMock).toBeCalledWith( + expect.stringMatching(/2.* translations will be written/), + ); }); - test('should override missing translations with prefix', async () => { - const {filePath, readFile} = await createTmpTranslationFile({ + it('overrides missing translations with prefix', async () => { + const {siteDir, readFile} = await createTmpTranslationFile({ key1: {message: 'key1 message'}, }); - await writeTranslationFileContent({ - filePath, - content: { + await writeCodeTranslations( + {siteDir, locale: 'en'}, + { key1: {message: 'key1 message new'}, key2: {message: 'key2 message new'}, }, - options: { + { override: true, messagePrefix: 'PREFIX ', }, - }); + ); await expect(readFile()).resolves.toEqual({ key1: {message: 'PREFIX key1 message new'}, key2: {message: 'PREFIX key2 message new'}, }); + expect(consoleInfoMock).toBeCalledWith( + expect.stringMatching(/2.* translations will be written/), + ); }); - test('should always override message description', async () => { - const {filePath, readFile} = await createTmpTranslationFile({ + it('always overrides message description', async () => { + const {siteDir, readFile} = await createTmpTranslationFile({ key1: {message: 'key1 message', description: 'key1 desc'}, key2: {message: 'key2 message', description: 'key2 desc'}, key3: {message: 'key3 message', description: undefined}, }); - await writeTranslationFileContent({ - filePath, - content: { + await writeCodeTranslations( + {siteDir, locale: 'en'}, + { key1: {message: 'key1 message new', description: undefined}, key2: {message: 'key2 message new', description: 'key2 desc new'}, key3: {message: 'key3 message new', description: 'key3 desc new'}, }, - }); + {}, + ); await expect(readFile()).resolves.toEqual({ key1: {message: 'key1 message', description: undefined}, key2: {message: 'key2 message', description: 'key2 desc new'}, key3: {message: 'key3 message', description: 'key3 desc new'}, }); + expect(consoleInfoMock).toBeCalledWith( + expect.stringMatching(/3.* translations will be written/), + ); }); - test('should throw for invalid content', async () => { - const {filePath} = await createTmpTranslationFile( + it('does not create empty translation files', async () => { + const {siteDir, readFile} = await createTmpTranslationFile(null); + + await writeCodeTranslations({siteDir, locale: 'en'}, {}, {}); + + await expect(readFile()).rejects.toThrowError( + /ENOENT: no such file or directory, open /, + ); + expect(consoleInfoMock).toBeCalledTimes(0); + }); + + it('throws for invalid content', async () => { + const {siteDir} = await createTmpTranslationFile( // @ts-expect-error: bad content on purpose {bad: 'content'}, ); - await expect( - writeTranslationFileContent({ - filePath, - content: { + await expect(() => + writeCodeTranslations( + {siteDir, locale: 'en'}, + { key1: {message: 'key1 message'}, }, - }), - ).rejects.toThrowError(/Invalid translation file at/); + {}, + ), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"\\"bad\\" must be of type object"`, + ); }); }); describe('writePluginTranslations', () => { - test('should write plugin translations', async () => { + it('writes plugin translations', async () => { const siteDir = await createTmpSiteDir(); const filePath = path.join( @@ -305,14 +299,14 @@ describe('writePluginTranslations', () => { }, }); - await expect(readTranslationFileContent(filePath)).resolves.toEqual({ + await expect(fs.readJSON(filePath)).resolves.toEqual({ key1: {message: 'key1 message'}, key2: {message: 'key2 message'}, key3: {message: 'key3 message'}, }); }); - test('should write plugin translations consecutively with different options', async () => { + it('writes plugin translations consecutively with different options', async () => { const siteDir = await createTmpSiteDir(); const filePath = path.join( @@ -345,16 +339,12 @@ describe('writePluginTranslations', () => { }); } - await expect(readTranslationFileContent(filePath)).resolves.toEqual( - undefined, - ); - await doWritePluginTranslations({ key1: {message: 'key1 message', description: 'key1 desc'}, key2: {message: 'key2 message', description: 'key2 desc'}, key3: {message: 'key3 message', description: 'key3 desc'}, }); - await expect(readTranslationFileContent(filePath)).resolves.toEqual({ + await expect(fs.readJSON(filePath)).resolves.toEqual({ key1: {message: 'key1 message', description: 'key1 desc'}, key2: {message: 'key2 message', description: 'key2 desc'}, key3: {message: 'key3 message', description: 'key3 desc'}, @@ -367,7 +357,7 @@ describe('writePluginTranslations', () => { }, {messagePrefix: 'PREFIX '}, ); - await expect(readTranslationFileContent(filePath)).resolves.toEqual({ + await expect(fs.readJSON(filePath)).resolves.toEqual({ key1: {message: 'key1 message', description: 'key1 desc'}, key2: {message: 'key2 message', description: 'key2 desc'}, key3: {message: 'key3 message', description: undefined}, @@ -383,17 +373,43 @@ describe('writePluginTranslations', () => { }, {messagePrefix: 'PREFIX ', override: true}, ); - await expect(readTranslationFileContent(filePath)).resolves.toEqual({ + await expect(fs.readJSON(filePath)).resolves.toEqual({ key1: {message: 'PREFIX key1 message 3', description: 'key1 desc'}, key2: {message: 'PREFIX key2 message 3', description: 'key2 desc'}, key3: {message: 'PREFIX key3 message 3', description: 'key3 desc'}, key4: {message: 'PREFIX key4 message 3', description: 'key4 desc'}, }); }); + + it('throws with explicit extension', async () => { + const siteDir = await createTmpSiteDir(); + + await expect(() => + writePluginTranslations({ + siteDir, + locale: 'fr', + translationFile: { + path: 'my/translation/file.json', + content: {}, + }, + + plugin: { + name: 'my-plugin-name', + options: { + id: 'my-plugin-id', + }, + }, + + options: {}, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Translation file path at \\"my/translation/file.json\\" does not need to end with \\".json\\", we add the extension automatically."`, + ); + }); }); describe('localizePluginTranslationFile', () => { - test('not localize if localized file does not exist', async () => { + it('does not localize if localized file does not exist', async () => { const siteDir = await createTmpSiteDir(); const translationFile: TranslationFile = { @@ -419,22 +435,22 @@ describe('localizePluginTranslationFile', () => { expect(localizedTranslationFile).toEqual(translationFile); }); - test('not localize if localized file does not exist 2', async () => { + it('normalizes partially localized translation files', async () => { const siteDir = await createTmpSiteDir(); - await writeTranslationFileContent({ - filePath: path.join( + await fs.outputJSON( + path.join( siteDir, 'i18n', 'fr', 'my-plugin-name', 'my/translation/file.json', ), - content: { + { key2: {message: 'key2 message localized'}, key4: {message: 'key4 message localized'}, }, - }); + ); const translationFile: TranslationFile = { path: 'my/translation/file', @@ -459,8 +475,9 @@ describe('localizePluginTranslationFile', () => { expect(localizedTranslationFile).toEqual({ path: translationFile.path, content: { - // We only append/override localized messages, but never delete the data of the unlocalized translation file - // This ensures that all required keys are present when trying to read the translations files + // We only append/override localized messages, but never delete the data + // of the unlocalized translation file. This ensures that all required + // keys are present when trying to read the translations files key1: {message: 'key1 message'}, key2: {message: 'key2 message localized'}, key3: {message: 'key3 message'}, @@ -470,6 +487,68 @@ describe('localizePluginTranslationFile', () => { }); }); +describe('readCodeTranslationFileContent', () => { + async function testReadTranslation(val: TranslationFileContent) { + const {siteDir} = await createTmpTranslationFile(val); + return readCodeTranslationFileContent({siteDir, locale: 'en'}); + } + + it("returns undefined if file does't exist", async () => { + await expect( + readCodeTranslationFileContent({siteDir: 'foo', locale: 'en'}), + ).resolves.toBeUndefined(); + }); + + it('passes valid translation file content', async () => { + await expect(testReadTranslation({})).resolves.toEqual({}); + await expect(testReadTranslation({key1: {message: ''}})).resolves.toEqual({ + key1: {message: ''}, + }); + await expect( + testReadTranslation({key1: {message: 'abc', description: 'desc'}}), + ).resolves.toEqual({key1: {message: 'abc', description: 'desc'}}); + await expect( + testReadTranslation({ + key1: {message: 'abc', description: 'desc'}, + key2: {message: 'def', description: 'desc'}, + }), + ).resolves.toEqual({ + key1: {message: 'abc', description: 'desc'}, + key2: {message: 'def', description: 'desc'}, + }); + }); + + it('fails for invalid translation file content', async () => { + await expect(() => + testReadTranslation('HEY'), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"\\"value\\" must be of type object"`, + ); + await expect(() => + testReadTranslation(42), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"\\"value\\" must be of type object"`, + ); + await expect(() => + testReadTranslation({key: {description: 'no message'}}), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"\\"key.message\\" is required"`, + ); + await expect(() => + testReadTranslation({key: {message: 42}}), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"\\"key.message\\" must be a string"`, + ); + await expect(() => + testReadTranslation({ + key: {message: 'Message', description: 42}, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"\\"key.description\\" must be a string"`, + ); + }); +}); + describe('getPluginsDefaultCodeTranslationMessages', () => { function createTestPlugin( fn: InitializedPlugin['getDefaultCodeTranslationMessages'], @@ -477,21 +556,21 @@ describe('getPluginsDefaultCodeTranslationMessages', () => { return {getDefaultCodeTranslationMessages: fn} as InitializedPlugin; } - test('for empty plugins', async () => { + it('works for empty plugins', async () => { const plugins: InitializedPlugin[] = []; await expect( getPluginsDefaultCodeTranslationMessages(plugins), ).resolves.toEqual({}); }); - test('for 1 plugin without lifecycle', async () => { + it('works for 1 plugin without lifecycle', async () => { const plugins: InitializedPlugin[] = [createTestPlugin(undefined)]; await expect( getPluginsDefaultCodeTranslationMessages(plugins), ).resolves.toEqual({}); }); - test('for 1 plugin with lifecycle', async () => { + it('works for 1 plugin with lifecycle', async () => { const plugins: InitializedPlugin[] = [ createTestPlugin(async () => ({ a: '1', @@ -506,7 +585,7 @@ describe('getPluginsDefaultCodeTranslationMessages', () => { }); }); - test('for 2 plugins with lifecycles', async () => { + it('works for 2 plugins with lifecycles', async () => { const plugins: InitializedPlugin[] = [ createTestPlugin(async () => ({ a: '1', @@ -527,7 +606,7 @@ describe('getPluginsDefaultCodeTranslationMessages', () => { }); }); - test('for realistic use-case', async () => { + it('works for realistic use-case', async () => { const plugins: InitializedPlugin[] = [ createTestPlugin(undefined), createTestPlugin(async () => ({ @@ -557,22 +636,24 @@ describe('getPluginsDefaultCodeTranslationMessages', () => { }); describe('applyDefaultCodeTranslations', () => { - const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(); + const consoleWarnMock = jest + .spyOn(console, 'warn') + .mockImplementation(() => {}); beforeEach(() => { - consoleSpy.mockClear(); + consoleWarnMock.mockClear(); }); - test('for no code and message', () => { + it('works for no code and message', () => { expect( applyDefaultCodeTranslations({ extractedCodeTranslations: {}, defaultCodeMessages: {}, }), ).toEqual({}); - expect(consoleSpy).toHaveBeenCalledTimes(0); + expect(consoleWarnMock).toHaveBeenCalledTimes(0); }); - test('for code and message', () => { + it('works for code and message', () => { expect( applyDefaultCodeTranslations({ extractedCodeTranslations: { @@ -591,10 +672,10 @@ describe('applyDefaultCodeTranslations', () => { description: 'description', }, }); - expect(consoleSpy).toHaveBeenCalledTimes(0); + expect(consoleWarnMock).toHaveBeenCalledTimes(0); }); - test('for code and message mismatch', () => { + it('works for code and message mismatch', () => { expect( applyDefaultCodeTranslations({ extractedCodeTranslations: { @@ -613,11 +694,11 @@ describe('applyDefaultCodeTranslations', () => { description: 'description', }, }); - expect(consoleSpy).toHaveBeenCalledTimes(1); - expect(consoleSpy.mock.calls[0][0]).toMatch(/unknownId/); + expect(consoleWarnMock).toHaveBeenCalledTimes(1); + expect(consoleWarnMock.mock.calls[0][0]).toMatch(/unknownId/); }); - test('for realistic scenario', () => { + it('works for realistic scenario', () => { expect( applyDefaultCodeTranslations({ extractedCodeTranslations: { @@ -655,8 +736,8 @@ describe('applyDefaultCodeTranslations', () => { description: 'description 3', }, }); - expect(consoleSpy).toHaveBeenCalledTimes(1); - expect(consoleSpy.mock.calls[0][0]).toMatch(/idUnknown1/); - expect(consoleSpy.mock.calls[0][0]).toMatch(/idUnknown2/); + expect(consoleWarnMock).toHaveBeenCalledTimes(1); + expect(consoleWarnMock.mock.calls[0][0]).toMatch(/idUnknown1/); + expect(consoleWarnMock.mock.calls[0][0]).toMatch(/idUnknown2/); }); }); diff --git a/packages/docusaurus/src/server/translations/__tests__/translationsExtractor.test.ts b/packages/docusaurus/src/server/translations/__tests__/translationsExtractor.test.ts index a6e113411261..5b80021d7043 100644 --- a/packages/docusaurus/src/server/translations/__tests__/translationsExtractor.test.ts +++ b/packages/docusaurus/src/server/translations/__tests__/translationsExtractor.test.ts @@ -5,6 +5,7 @@ * LICENSE file in the root directory of this source tree. */ +import {jest} from '@jest/globals'; import fs from 'fs-extra'; import tmp from 'tmp-promise'; import { @@ -46,8 +47,8 @@ async function createTmpSourceCodeFile({ }; } -describe('extractSourceCodeTranslations', () => { - test('throw for bad source code', async () => { +describe('extractSourceCodeFileTranslations', () => { + it('throws for bad source code', async () => { const {sourceCodeFilePath} = await createTmpSourceCodeFile({ extension: 'js', content: ` @@ -57,14 +58,20 @@ const default => { `, }); + const errorMock = jest.spyOn(console, 'error').mockImplementation(() => {}); + await expect( extractSourceCodeFileTranslations(sourceCodeFilePath, TestBabelOptions), - ).rejects.toThrowError( - /Error while attempting to extract Docusaurus translations from source code file at path/, + ).rejects.toThrow(); + + expect(errorMock).toBeCalledWith( + expect.stringMatching( + /Error while attempting to extract Docusaurus translations from source code file at/, + ), ); }); - test('extract nothing from untranslated source code', async () => { + it('extracts nothing from untranslated source code', async () => { const {sourceCodeFilePath} = await createTmpSourceCodeFile({ extension: 'js', content: ` @@ -84,7 +91,7 @@ const unrelated = 42; }); }); - test('extract from a translate() functions calls', async () => { + it('extracts from a translate() functions calls', async () => { const {sourceCodeFilePath} = await createTmpSourceCodeFile({ extension: 'js', content: ` @@ -117,7 +124,7 @@ export default function MyComponent() { }); }); - test('extract from a components', async () => { + it('extracts from a components', async () => { const {sourceCodeFilePath} = await createTmpSourceCodeFile({ extension: 'js', content: ` @@ -152,7 +159,7 @@ export default function MyComponent() { }); }); - test('extract statically evaluable content', async () => { + it('extracts statically evaluable content', async () => { const {sourceCodeFilePath} = await createTmpSourceCodeFile({ extension: 'js', content: ` @@ -213,7 +220,7 @@ export default function MyComponent() { }); }); - test('extract from TypeScript file', async () => { + it('extracts from TypeScript file', async () => { const {sourceCodeFilePath} = await createTmpSourceCodeFile({ extension: 'tsx', content: ` @@ -225,6 +232,7 @@ export default function MyComponent(props: ComponentProps) { return (
      +
      ); } @@ -240,12 +248,16 @@ export default function MyComponent(props: ComponentProps) { sourceCodeFilePath, translations: { codeId: {message: 'code message', description: 'code description'}, + 'code message 2': { + message: 'code message 2', + description: 'code description 2', + }, }, warnings: [], }); }); - test('do not extract from functions that is not docusaurus provided', async () => { + it('does not extract from functions that is not docusaurus provided', async () => { const {sourceCodeFilePath} = await createTmpSourceCodeFile({ extension: 'js', content: ` @@ -270,7 +282,7 @@ export default function somethingElse() { }); }); - test('do not extract from functions that is internal', async () => { + it('does not extract from functions that is internal', async () => { const {sourceCodeFilePath} = await createTmpSourceCodeFile({ extension: 'js', content: ` @@ -297,7 +309,7 @@ export default function somethingElse() { }); }); - test('recognize aliased imports', async () => { + it('recognizes aliased imports', async () => { const {sourceCodeFilePath} = await createTmpSourceCodeFile({ extension: 'js', content: ` @@ -347,7 +359,7 @@ export default function () { }); }); - test('recognize aliased imports as string literal', async () => { + it('recognizes aliased imports as string literal', async () => { const {sourceCodeFilePath} = await createTmpSourceCodeFile({ extension: 'js', content: ` @@ -381,7 +393,7 @@ export default function () { }); }); - test('warn about id if no children', async () => { + it('warns about id if no children', async () => { const {sourceCodeFilePath} = await createTmpSourceCodeFile({ extension: 'js', content: ` @@ -412,7 +424,7 @@ Full code: `, }); }); - test('warn about dynamic id', async () => { + it('warns about dynamic id', async () => { const {sourceCodeFilePath} = await createTmpSourceCodeFile({ extension: 'js', content: ` @@ -448,7 +460,7 @@ Full code: foo`, }); }); - test('warn about dynamic children', async () => { + it('warns about dynamic children', async () => { const {sourceCodeFilePath} = await createTmpSourceCodeFile({ extension: 'js', content: ` @@ -478,7 +490,7 @@ Full code: hhh`, }); }); - test('warn about dynamic translate argument', async () => { + it('warns about dynamic translate argument', async () => { const {sourceCodeFilePath} = await createTmpSourceCodeFile({ extension: 'js', content: ` @@ -506,7 +518,7 @@ Full code: translate(foo)`, }); }); - test('warn about too many arguments', async () => { + it('warns about too many arguments', async () => { const {sourceCodeFilePath} = await createTmpSourceCodeFile({ extension: 'js', content: ` @@ -538,7 +550,7 @@ Full code: translate({ }); describe('extractSiteSourceCodeTranslations', () => { - test('should extract translation from all plugins source code', async () => { + it('extracts translation from all plugins source code', async () => { const siteDir = await createTmpDir(); const siteComponentFile1 = path.join( @@ -546,8 +558,7 @@ describe('extractSiteSourceCodeTranslations', () => { SRC_DIR_NAME, 'site-component-1.jsx', ); - await fs.ensureDir(path.dirname(siteComponentFile1)); - await fs.writeFile( + await fs.outputFile( siteComponentFile1, ` import Translate from '@docusaurus/Translate'; @@ -580,8 +591,7 @@ export default function MySiteComponent1() { const plugin1Dir = await createTmpDir(); const plugin1File1 = path.join(plugin1Dir, 'subpath', 'file1.jsx'); - await fs.ensureDir(path.dirname(plugin1File1)); - await fs.writeFile( + await fs.outputFile( plugin1File1, ` import {translate} from '@docusaurus/Translate'; @@ -600,8 +610,7 @@ export default function MyComponent() { `, ); const plugin1File2 = path.join(plugin1Dir, 'src', 'theme', 'file2.jsx'); - await fs.ensureDir(path.dirname(plugin1File2)); - await fs.writeFile( + await fs.outputFile( plugin1File2, ` import {translate} from '@docusaurus/Translate'; @@ -617,9 +626,8 @@ export default function MyComponent() { ); // This one should not be found! On purpose! - const plugin1File3 = path.join(plugin1Dir, 'unscannedFolder', 'file3.jsx'); - await fs.ensureDir(path.dirname(plugin1File3)); - await fs.writeFile( + const plugin1File3 = path.join(plugin1Dir, 'ignoredFolder', 'file3.jsx'); + await fs.outputFile( plugin1File3, ` import {translate} from '@docusaurus/Translate'; @@ -633,12 +641,31 @@ export default function MyComponent() { } `, ); + + const plugin1File4 = path.join(plugin1Dir, 'src/theme/file4.jsx'); + // Contains some invalid translations... + await fs.outputFile( + plugin1File4, + ` +import {translate} from '@docusaurus/Translate'; + +export default function MyComponent() { + return ( +
      + +
      + ); +} +`, + ); + const consoleWarnMock = jest + .spyOn(console, 'warn') + .mockImplementation(() => {}); const plugin1 = createTestPlugin(plugin1Dir); const plugin2Dir = await createTmpDir(); const plugin2File = path.join(plugin1Dir, 'subpath', 'file.tsx'); - await fs.ensureDir(path.dirname(plugin2File)); - await fs.writeFile( + await fs.outputFile( plugin2File, ` import Translate, {translate} from '@docusaurus/Translate'; @@ -662,7 +689,11 @@ export default function MyComponent(props: Props) { ); const plugin2 = createTestPlugin(plugin2Dir); - const plugins = [plugin1, plugin2]; + const plugins = [ + plugin1, + plugin2, + {name: 'dummy', options: {}, version: {type: 'synthetic'}} as const, + ]; const translations = await extractSiteSourceCodeTranslations( siteDir, plugins, @@ -690,5 +721,8 @@ export default function MyComponent(props: Props) { message: 'plugin2 message 2', }, }); + expect(consoleWarnMock.mock.calls[0][0]).toMatch( + /.*\[WARNING\].* Translation extraction warnings for file .*src.theme.file4\.jsx.*\n.*- translate\(\) first arg should be a statically evaluable object\./, + ); }); }); diff --git a/packages/docusaurus/src/server/translations/translations.ts b/packages/docusaurus/src/server/translations/translations.ts index f06c55cb6e7e..4d0bcb24740f 100644 --- a/packages/docusaurus/src/server/translations/translations.ts +++ b/packages/docusaurus/src/server/translations/translations.ts @@ -7,16 +7,21 @@ import path from 'path'; import fs from 'fs-extra'; -import {mapValues, difference} from 'lodash'; +import _ from 'lodash'; +import { + getPluginI18nPath, + toMessageRelativeFilePath, + I18N_DIR_NAME, + CODE_TRANSLATIONS_FILE_NAME, +} from '@docusaurus/utils'; +import {Joi} from '@docusaurus/utils-validation'; +import logger from '@docusaurus/logger'; import type { TranslationFileContent, TranslationFile, - TranslationMessage, + CodeTranslations, InitializedPlugin, } from '@docusaurus/types'; -import {getPluginI18nPath, toMessageRelativeFilePath} from '@docusaurus/utils'; -import {Joi} from '@docusaurus/utils-validation'; -import logger from '@docusaurus/logger'; export type WriteTranslationsOptions = { override?: boolean; @@ -38,7 +43,7 @@ const TranslationFileContentSchema = Joi.object() ) .required(); -export function ensureTranslationFileContent( +function ensureTranslationFileContent( content: unknown, ): asserts content is TranslationFileContent { Joi.attempt(content, TranslationFileContentSchema, { @@ -48,7 +53,7 @@ export function ensureTranslationFileContent( }); } -export async function readTranslationFileContent( +async function readTranslationFileContent( filePath: string, ): Promise { if (await fs.pathExists(filePath)) { @@ -56,10 +61,9 @@ export async function readTranslationFileContent( const content = JSON.parse(await fs.readFile(filePath, 'utf8')); ensureTranslationFileContent(content); return content; - } catch (e) { - throw new Error( - `Invalid translation file at ${filePath}.\n${(e as Error).message}`, - ); + } catch (err) { + logger.error`Invalid translation file at path=${filePath}.`; + throw err; } } return undefined; @@ -75,7 +79,7 @@ function mergeTranslationFileContent({ options: WriteTranslationsOptions; }): TranslationFileContent { // Apply messagePrefix to all messages - const newContentTransformed = mapValues(newContent, (value) => ({ + const newContentTransformed = _.mapValues(newContent, (value) => ({ ...value, message: `${options.messagePrefix ?? ''}${value.message}`, })); @@ -86,7 +90,7 @@ function mergeTranslationFileContent({ Object.entries(newContentTransformed).forEach( ([key, {message, description}]) => { result[key] = { - // If the messages already exist, we don't override them (unless requested) + // If messages already exist, we don't override them (unless requested) message: options.override ? message : existingContent[key]?.message ?? message, @@ -98,7 +102,7 @@ function mergeTranslationFileContent({ return result; } -export async function writeTranslationFileContent({ +async function writeTranslationFileContent({ filePath, content: newContent, options = {}, @@ -110,7 +114,7 @@ export async function writeTranslationFileContent({ const existingContent = await readTranslationFileContent(filePath); // Warn about potential legacy keys - const unknownKeys = difference( + const unknownKeys = _.difference( Object.keys(existingContent ?? {}), Object.keys(newContent), ); @@ -132,25 +136,25 @@ Maybe you should remove them? ${unknownKeys}`; } translations will be written at path=${toMessageRelativeFilePath( filePath, )}.`; - await fs.ensureDir(path.dirname(filePath)); - await fs.writeFile(filePath, `${JSON.stringify(mergedContent, null, 2)}\n`); + await fs.outputFile( + filePath, + `${JSON.stringify(mergedContent, null, 2)}\n`, + ); } } // should we make this configurable? -export function getTranslationsDirPath(context: TranslationContext): string { - return path.resolve(path.join(context.siteDir, `i18n`)); -} export function getTranslationsLocaleDirPath( context: TranslationContext, ): string { - return path.join(getTranslationsDirPath(context), context.locale); + return path.join(context.siteDir, I18N_DIR_NAME, context.locale); } -export function getCodeTranslationsFilePath( - context: TranslationContext, -): string { - return path.join(getTranslationsLocaleDirPath(context), 'code.json'); +function getCodeTranslationsFilePath(context: TranslationContext): string { + return path.join( + getTranslationsLocaleDirPath(context), + CODE_TRANSLATIONS_FILE_NAME, + ); } export async function readCodeTranslationFileContent( @@ -252,14 +256,13 @@ export async function localizePluginTranslationFile({ ...localizedContent, }, }; - } else { - return translationFile; } + return translationFile; } export async function getPluginsDefaultCodeTranslationMessages( plugins: InitializedPlugin[], -): Promise> { +): Promise { const pluginsMessages = await Promise.all( plugins.map((plugin) => plugin.getDefaultCodeTranslationMessages?.() ?? {}), ); @@ -274,10 +277,10 @@ export function applyDefaultCodeTranslations({ extractedCodeTranslations, defaultCodeMessages, }: { - extractedCodeTranslations: Record; - defaultCodeMessages: Record; -}): Record { - const unusedDefaultCodeMessages = difference( + extractedCodeTranslations: TranslationFileContent; + defaultCodeMessages: CodeTranslations; +}): TranslationFileContent { + const unusedDefaultCodeMessages = _.difference( Object.keys(defaultCodeMessages), Object.keys(extractedCodeTranslations), ); @@ -286,7 +289,7 @@ export function applyDefaultCodeTranslations({ Please report this Docusaurus issue. name=${unusedDefaultCodeMessages}`; } - return mapValues( + return _.mapValues( extractedCodeTranslations, (messageTranslation, messageId) => ({ ...messageTranslation, diff --git a/packages/docusaurus/src/server/translations/translationsExtractor.ts b/packages/docusaurus/src/server/translations/translationsExtractor.ts index ec808f79b9d5..73a1a0cbfe7c 100644 --- a/packages/docusaurus/src/server/translations/translationsExtractor.ts +++ b/packages/docusaurus/src/server/translations/translationsExtractor.ts @@ -18,7 +18,6 @@ import { import type { InitializedPlugin, TranslationFileContent, - TranslationMessage, } from '@docusaurus/types'; import nodePath from 'path'; import {SRC_DIR_NAME} from '@docusaurus/utils'; @@ -45,8 +44,9 @@ function getSiteSourceCodeFilePaths(siteDir: string): string[] { function getPluginSourceCodeFilePaths(plugin: InitializedPlugin): string[] { // The getPathsToWatch() generally returns the js/jsx/ts/tsx/md/mdx file paths - // We can use this method as well to know which folders we should try to extract translations from - // Hacky/implicit, but do we want to introduce a new lifecycle method just for that??? + // We can use this method as well to know which folders we should try to + // extract translations from. Hacky/implicit, but do we want to introduce a + // new lifecycle method just for that??? const codePaths: string[] = plugin.getPathsToWatch?.() ?? []; // We also include theme code @@ -55,7 +55,7 @@ function getPluginSourceCodeFilePaths(plugin: InitializedPlugin): string[] { codePaths.push(themePath); } - return codePaths; + return codePaths.map((p) => nodePath.resolve(plugin.path, p)); } export async function globSourceCodeFilePaths( @@ -72,8 +72,9 @@ async function getSourceCodeFilePaths( const sitePaths = getSiteSourceCodeFilePaths(siteDir); // The getPathsToWatch() generally returns the js/jsx/ts/tsx/md/mdx file paths - // We can use this method as well to know which folders we should try to extract translations from - // Hacky/implicit, but do we want to introduce a new lifecycle method for that??? + // We can use this method as well to know which folders we should try to + // extract translations from. Hacky/implicit, but do we want to introduce a + // new lifecycle method for that??? const pluginsPaths = plugins.flatMap(getPluginSourceCodeFilePaths); const allPaths = [...sitePaths, ...pluginsPaths]; @@ -87,7 +88,8 @@ export async function extractSiteSourceCodeTranslations( babelOptions: TransformOptions, extraSourceCodeFilePaths: string[] = [], ): Promise { - // Should we warn here if the same translation "key" is found in multiple source code files? + // Should we warn here if the same translation "key" is found in multiple + // source code files? function toTranslationFileContent( sourceCodeFileTranslations: SourceCodeFileTranslations[], ): TranslationFileContent { @@ -127,7 +129,7 @@ function logSourceCodeFileTranslationsWarnings( type SourceCodeFileTranslations = { sourceCodeFilePath: string; - translations: Record; + translations: TranslationFileContent; warnings: string[]; }; @@ -152,8 +154,9 @@ export async function extractSourceCodeFileTranslations( const ast = parse(code, { ...babelOptions, ast: true, - // filename is important, because babel does not process the same files according to their js/ts extensions - // see see https://twitter.com/NicoloRibaudo/status/1321130735605002243 + // filename is important, because babel does not process the same files + // according to their js/ts extensions. + // See https://twitter.com/NicoloRibaudo/status/1321130735605002243 filename: sourceCodeFilePath, }) as Node; @@ -162,11 +165,9 @@ export async function extractSourceCodeFileTranslations( sourceCodeFilePath, ); return translations; - } catch (e) { - if (e instanceof Error) { - e.message = `Error while attempting to extract Docusaurus translations from source code file at path=${sourceCodeFilePath}\n${e.message}`; - } - throw e; + } catch (err) { + logger.error`Error while attempting to extract Docusaurus translations from source code file at path=${sourceCodeFilePath}.`; + throw err; } } @@ -187,7 +188,7 @@ function extractSourceCodeAstTranslations( Full code: ${generate(node).code}`; } - const translations: Record = {}; + const translations: TranslationFileContent = {}; const warnings: string[] = []; let translateComponentName: string | undefined; let translateFunctionName: string | undefined; @@ -260,14 +261,13 @@ Full code: ${generate(node).code}`; typeof attributeValueEvaluated.value === 'string' ) { return attributeValueEvaluated.value; - } else { - warnings.push( - ` prop=${propName} should be a statically evaluable object. + } + warnings.push( + ` prop=${propName} should be a statically evaluable object. Example: Message Dynamically constructed values are not allowed, because they prevent translations to be extracted. ${sourceWarningPart(path.node)}`, - ); - } + ); } return undefined; @@ -275,7 +275,7 @@ ${sourceWarningPart(path.node)}`, const id = evaluateJSXProp('id'); const description = evaluateJSXProp('description'); - let message; + let message: string; const childrenPath = path.get('children'); // Handle empty content @@ -286,7 +286,7 @@ Example: ${sourceWarningPart(path.node)}`); } else { translations[id] = { - message: message ?? id, + message: id, ...(description && {description}), }; } @@ -296,8 +296,9 @@ ${sourceWarningPart(path.node)}`); // Handle single non-empty content const singleChildren = childrenPath - // Remove empty/useless text nodes that might be around our translation! - // Makes the translation system more reliable to JSX formatting issues + // Remove empty/useless text nodes that might be around our + // translation! Makes the translation system more reliable to JSX + // formatting issues .filter( (children) => !( @@ -306,10 +307,9 @@ ${sourceWarningPart(path.node)}`); ), ) .pop(); - const isJSXText = singleChildren && singleChildren.isJSXText(); + const isJSXText = singleChildren?.isJSXText(); const isJSXExpressionContainer = - singleChildren && - singleChildren.isJSXExpressionContainer() && + singleChildren?.isJSXExpressionContainer() && (singleChildren.get('expression') as NodePath).evaluate().confident; if (isJSXText || isJSXExpressionContainer) { @@ -338,9 +338,9 @@ ${sourceWarningPart(path.node)}`, const args = path.get('arguments'); if (args.length === 1 || args.length === 2) { - const firstArgPath = args[0]; + const firstArgPath = args[0]!; - // evaluation allows translate("x" + "y"); to be considered as translate("xy"); + // translate("x" + "y"); => translate("xy"); const firstArgEvaluated = firstArgPath.evaluate(); if ( diff --git a/packages/docusaurus/src/server/utils.ts b/packages/docusaurus/src/server/utils.ts index 37a3f160d6da..e4e22df7d9da 100644 --- a/packages/docusaurus/src/server/utils.ts +++ b/packages/docusaurus/src/server/utils.ts @@ -6,7 +6,7 @@ */ import type {RouteConfig} from '@docusaurus/types'; -import nodePath from 'path'; +import path from 'path'; import {posixPath, Globby} from '@docusaurus/utils'; // Recursively get the final routes (routes with no subroutes) @@ -26,7 +26,7 @@ export async function safeGlobby( // Required for Windows support, as paths using \ should not be used by globby // (also using the windows hard drive prefix like c: is not a good idea) const globPaths = patterns.map((dirPath) => - posixPath(nodePath.relative(process.cwd(), dirPath)), + posixPath(path.relative(process.cwd(), dirPath)), ); return Globby(globPaths, options); diff --git a/packages/docusaurus/src/server/versions/__tests/index.test.ts b/packages/docusaurus/src/server/versions/__tests/index.test.ts deleted file mode 100644 index e7ba75d5f6f7..000000000000 --- a/packages/docusaurus/src/server/versions/__tests/index.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import {getPluginVersion} from '..'; -import path from 'path'; - -describe('getPluginVersion', () => { - it('Can detect external packages plugins versions of correctly.', () => { - expect( - getPluginVersion( - path.join(__dirname, '..', '__fixtures__', 'dummy-plugin.js'), - // Make the plugin appear external. - path.join(__dirname, '..', '..', '..', '..', '..', '..', 'website'), - ), - ).toEqual({type: 'package', version: 'random-version'}); - }); - - it('Can detect project plugins versions correctly.', () => { - expect( - getPluginVersion( - path.join(__dirname, '..', '__fixtures__', 'dummy-plugin.js'), - // Make the plugin appear project local. - path.join(__dirname, '..', '__fixtures__'), - ), - ).toEqual({type: 'project'}); - }); - - it('Can detect local packages versions correctly.', () => { - expect(getPluginVersion('/', '/')).toEqual({type: 'local'}); - }); -}); diff --git a/packages/docusaurus/src/server/versions/index.ts b/packages/docusaurus/src/server/versions/index.ts deleted file mode 100644 index bf499d569ce5..000000000000 --- a/packages/docusaurus/src/server/versions/index.ts +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import type {DocusaurusPluginVersionInformation} from '@docusaurus/types'; -import {existsSync, lstatSync} from 'fs-extra'; -import {dirname, join} from 'path'; - -export function getPackageJsonVersion( - packageJsonPath: string, -): string | undefined { - if (existsSync(packageJsonPath)) { - // eslint-disable-next-line @typescript-eslint/no-var-requires, import/no-dynamic-require, global-require - const {version} = require(packageJsonPath); - return typeof version === 'string' ? version : undefined; - } - return undefined; -} - -export function getPackageJsonName( - packageJsonPath: string, -): string | undefined { - if (existsSync(packageJsonPath)) { - // eslint-disable-next-line @typescript-eslint/no-var-requires, import/no-dynamic-require, global-require - const {name} = require(packageJsonPath); - return typeof name === 'string' ? name : undefined; - } - return undefined; -} - -export function getPluginVersion( - pluginPath: string, - siteDir: string, -): DocusaurusPluginVersionInformation { - let potentialPluginPackageJsonDirectory = dirname(pluginPath); - while (potentialPluginPackageJsonDirectory !== '/') { - const packageJsonPath = join( - potentialPluginPackageJsonDirectory, - 'package.json', - ); - if (existsSync(packageJsonPath) && lstatSync(packageJsonPath).isFile()) { - if (potentialPluginPackageJsonDirectory === siteDir) { - // If the plugin belongs to the same docusaurus project, we classify it as local plugin. - return {type: 'project'}; - } - return { - type: 'package', - name: getPackageJsonName(packageJsonPath), - version: getPackageJsonVersion(packageJsonPath), - }; - } - potentialPluginPackageJsonDirectory = dirname( - potentialPluginPackageJsonDirectory, - ); - } - // In rare cases where a plugin is a path where no parent directory contains package.json, we can only classify it as local. - return {type: 'local'}; -} diff --git a/packages/docusaurus/src/webpack/__tests__/__fixtures__/host.crt b/packages/docusaurus/src/webpack/__tests__/__fixtures__/host.crt new file mode 100644 index 000000000000..81a7dc73bb76 --- /dev/null +++ b/packages/docusaurus/src/webpack/__tests__/__fixtures__/host.crt @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIID8zCCAtugAwIBAgIUK1U7Oje+GjLlzxNryMDUT72qJZ0wDQYJKoZIhvcNAQEL +BQAwgYgxCzAJBgNVBAYTAkNOMREwDwYDVQQIDAhTaGFuZ2hhaTERMA8GA1UEBwwI +U2hhbmdoYWkxGDAWBgNVBAoMD0NvbXB1dGVyaXphdGlvbjESMBAGA1UEAwwJSm9z +aC1DZW5hMSUwIwYJKoZIhvcNAQkBFhZzaWRhY2hlbjIwMDNAZ21haWwuY29tMB4X +DTIyMDMxMjE0MzI0N1oXDTIzMDMxMjE0MzI0N1owgYgxCzAJBgNVBAYTAkNOMREw +DwYDVQQIDAhTaGFuZ2hhaTERMA8GA1UEBwwIU2hhbmdoYWkxGDAWBgNVBAoMD0Nv +bXB1dGVyaXphdGlvbjESMBAGA1UEAwwJSm9zaC1DZW5hMSUwIwYJKoZIhvcNAQkB +FhZzaWRhY2hlbjIwMDNAZ21haWwuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEA7Cq2QW6rcZAm6MMo97aqkFi9dkXx97fW6vPEt2bgS9O6E+M/wXBI +q1Dh3ud8sGP+CiEWa+7uIEwX9pRGyQo0Lkr7qZWSbsDh+RmdkiKUCiIUUTBopBjM +jo7XF9KBM609GYoGlKYxv4adPbOMJcK/9VdJPz3NprIA1PHEqInJNnuKMMjBMhNu +1MZ7JwING/LYBOJ/Mve08XKAcyDdWBVPe2TOfcKhEmtBTKhnOuUicuAdVtDkN34Z +e4ZlifLo7wlQU7NNh7YDOYZz3JXB5QotuqtWkUgfpMSCWG90p4P4LExLzS+2sb7O +C/jO0qYcKjaKAKjrA9IIyClF6VP1yFRZywIDAQABo1MwUTAdBgNVHQ4EFgQUNy2X ++cLPh17QdR6raPKeoKLIm2QwHwYDVR0jBBgwFoAUNy2X+cLPh17QdR6raPKeoKLI +m2QwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAzvyP1QxKR8Ik +k7v3dzRl1gKdu6BtRL1zoFXeOjFOCVX9ORxcpCJItuTM4kEbJLhC0uFxn+zQ/Urs +JAc56gic4fCIcxlTNPr4VtAEUQKhfGG7XTRs8Cl2Rm7E7FwNiGjdLuiPI+G+ZZbl +TYmB5ILGzvI8LAOii17s5wFX84PehZ9gYgcgEvVBaU7lWF3WakR53Msf2bHkjk/r +NfaINeBltOwijhzb8pWf0XG2z4olJjg1qTOgr1gNseyTwMAFwFmeXQAoidoZfKya +DD+hY1/IgiUXi2pdmO+sMHtRBG5JdOi2cjSOcTx1xkWyb60PpW4uxKhduQPAiZRO +266P7J962Q== +-----END CERTIFICATE----- diff --git a/packages/docusaurus/src/webpack/__tests__/__fixtures__/host.key b/packages/docusaurus/src/webpack/__tests__/__fixtures__/host.key new file mode 100644 index 000000000000..1b47cc35e4c0 --- /dev/null +++ b/packages/docusaurus/src/webpack/__tests__/__fixtures__/host.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA7Cq2QW6rcZAm6MMo97aqkFi9dkXx97fW6vPEt2bgS9O6E+M/ +wXBIq1Dh3ud8sGP+CiEWa+7uIEwX9pRGyQo0Lkr7qZWSbsDh+RmdkiKUCiIUUTBo +pBjMjo7XF9KBM609GYoGlKYxv4adPbOMJcK/9VdJPz3NprIA1PHEqInJNnuKMMjB +MhNu1MZ7JwING/LYBOJ/Mve08XKAcyDdWBVPe2TOfcKhEmtBTKhnOuUicuAdVtDk +N34Ze4ZlifLo7wlQU7NNh7YDOYZz3JXB5QotuqtWkUgfpMSCWG90p4P4LExLzS+2 +sb7OC/jO0qYcKjaKAKjrA9IIyClF6VP1yFRZywIDAQABAoIBAHiHR+LW/2qC3ki2 +qWba8+udTnxZMCdzzJy2cjQDrf8k/Hd/6B7qFjxQmCXx0GIZdiJnRpEpLKCRFT3D +6Ohba8wgepXO/x/FEs7VsuRM/264e9P/t7ff7C3pWn8O8N+Vz3QETF17ADK2GfPO +eX0gCmXE+V3sRdOITwJerTYys904bo5CQsDQQENpcuYbZU2IYt9dw9XrTexaFwP1 +3ssOXCwpaW4kS95a6WQlwCqNTq49zqf3VGA3QG3JEdPPWhG+jEG2L4RxSosvo4wt +MYFqeXcS5sz7WOH1gtleGL2i6WKYuLl7Bo/CLokn1tgrXjGvNpeBFvZucC+L246f +e7iG+gkCgYEA+CcISFav/uwKNv3Sdp87kVpBAno8cZTiYvB15zAGaXuLyI/OuJNh +lcJBhtZSN94T/mgj+gXDafjmRr4i7Q4Pu+KG95JTk1FfWv/974NxbRNrrp+4PFKb +wxcM1cHuqq88mUPUX+k0eKPqDcuY6vHBPAV4ji1Wl+VXpREDvhKgAEUCgYEA86Kl +xnOf3TWbEaQRJx2mMnRYLyrEEPqEMgHWlzXdWl2E9LJDGGmOEbZLv6uNcx1uWJVP +AaoitmQNTl+rSsJY0TwqooX5zvT8po9MXUt8FvButJyYUOJZFTuLtLxFJqAzFipz +SaiYTrEBC76uqe/87AVm0wCdJN4ajcptyibaus8CgYEAnXSm3L+kjKxZDuufT4VZ +1rDd7ySAldFSlFTfewIOD4BFAc297YAWu1+3FEeJg8l2BkcuDMb7Z5J3Cww6PRBf +C2iBGzXNsfw/9Q3ZotBUeFGKUhMmY6BHFVLa4gdb2RG38cgISZM/qAzZxkcZkHo1 +klAmXpCGEXuEUUiqh0BqJcECgYEAv42Gt0QbUeoetL0BO3blP9AXsWX3Z73/h+3I +EXUpRy42JcmuVRhQuf5RCi7QdMyUAJPL3WwuBKcfixpO6+VnvYKHpuadZSlbJ32N +NeDufH6nG9vvKdD852O80OohmF/mKqxPnn8u2Nf0EY7ndvcYLV2F3aoi42S5Dfg1 +X/YyjSMCgYAg2fEisapxje98KZ4TPvOffJRF5PRG4H6UBQvxaWw9oUjVkGM6t10U +D6uOCYPkb+l3wBFTNAfScr22EnpW33Q5JOAfHBeE1oEoWGdMgp1C1V9ZQTIkjXyj +YE+lrsTFVoyY+dnLcZ4U7syVkzINk10GaAKjGXD0gtrqC+cQy8z1XQ== +-----END RSA PRIVATE KEY----- diff --git a/packages/docusaurus/src/webpack/__tests__/__fixtures__/invalid.crt b/packages/docusaurus/src/webpack/__tests__/__fixtures__/invalid.crt new file mode 100644 index 000000000000..bc56c4d89448 --- /dev/null +++ b/packages/docusaurus/src/webpack/__tests__/__fixtures__/invalid.crt @@ -0,0 +1 @@ +Foo diff --git a/packages/docusaurus/src/webpack/__tests__/__fixtures__/invalid.key b/packages/docusaurus/src/webpack/__tests__/__fixtures__/invalid.key new file mode 100644 index 000000000000..bc56c4d89448 --- /dev/null +++ b/packages/docusaurus/src/webpack/__tests__/__fixtures__/invalid.key @@ -0,0 +1 @@ +Foo diff --git a/packages/docusaurus/src/webpack/__tests__/__snapshots__/base.test.ts.snap b/packages/docusaurus/src/webpack/__tests__/__snapshots__/base.test.ts.snap index 623fa86a83c6..6fdffc625968 100644 --- a/packages/docusaurus/src/webpack/__tests__/__snapshots__/base.test.ts.snap +++ b/packages/docusaurus/src/webpack/__tests__/__snapshots__/base.test.ts.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`base webpack config should create webpack aliases 1`] = ` -Object { +exports[`base webpack config creates webpack aliases 1`] = ` +{ "@docusaurus/BrowserOnly": "../../../../client/exports/BrowserOnly.tsx", "@docusaurus/ComponentCreator": "../../../../client/exports/ComponentCreator.tsx", "@docusaurus/ErrorBoundary": "../../../../client/exports/ErrorBoundary.tsx", @@ -11,9 +11,7 @@ Object { "@docusaurus/Link": "../../../../client/exports/Link.tsx", "@docusaurus/Noop": "../../../../client/exports/Noop.ts", "@docusaurus/Translate": "../../../../client/exports/Translate.tsx", - "@docusaurus/browserContext": "../../../../client/exports/browserContext.tsx", "@docusaurus/constants": "../../../../client/exports/constants.ts", - "@docusaurus/docusaurusContext": "../../../../client/exports/docusaurusContext.tsx", "@docusaurus/isInternalUrl": "../../../../client/exports/isInternalUrl.ts", "@docusaurus/renderRoutes": "../../../../client/exports/renderRoutes.ts", "@docusaurus/router": "../../../../client/exports/router.ts", @@ -21,52 +19,31 @@ Object { "@docusaurus/useDocusaurusContext": "../../../../client/exports/useDocusaurusContext.ts", "@docusaurus/useGlobalData": "../../../../client/exports/useGlobalData.ts", "@docusaurus/useIsBrowser": "../../../../client/exports/useIsBrowser.ts", + "@docusaurus/useRouteContext": "../../../../client/exports/useRouteContext.tsx", "@generated": "../../../../../../..", "@site": "", "@theme-init/PluginThemeComponentEnhanced": "pluginThemeFolder/PluginThemeComponentEnhanced.js", - "@theme-original/Error": "../../../../client/theme-fallback/Error/index.js", - "@theme-original/Layout": "../../../../client/theme-fallback/Layout/index.js", - "@theme-original/Loading": "../../../../client/theme-fallback/Loading/index.js", - "@theme-original/NotFound": "../../../../client/theme-fallback/NotFound/index.js", + "@theme-original/Error": "../../../../client/theme-fallback/Error/index.tsx", + "@theme-original/Layout": "../../../../client/theme-fallback/Layout/index.tsx", + "@theme-original/Loading": "../../../../client/theme-fallback/Loading/index.tsx", + "@theme-original/NotFound": "../../../../client/theme-fallback/NotFound/index.tsx", "@theme-original/PluginThemeComponent1": "pluginThemeFolder/PluginThemeComponent1.js", "@theme-original/PluginThemeComponentEnhanced": "secondPluginThemeFolder/PluginThemeComponentEnhanced.js", "@theme-original/PluginThemeComponentOverriddenByUser": "pluginThemeFolder/PluginThemeComponentOverriddenByUser.js", - "@theme-original/Root": "../../../../client/theme-fallback/Root/index.js", + "@theme-original/Root": "../../../../client/theme-fallback/Root/index.tsx", + "@theme-original/SiteMetadata": "../../../../client/theme-fallback/SiteMetadata/index.tsx", "@theme-original/subfolder/PluginThemeComponent2": "pluginThemeFolder/subfolder/PluginThemeComponent2.js", - "@theme/Error": "../../../../client/theme-fallback/Error/index.js", - "@theme/Layout": "../../../../client/theme-fallback/Layout/index.js", - "@theme/Loading": "../../../../client/theme-fallback/Loading/index.js", - "@theme/NotFound": "../../../../client/theme-fallback/NotFound/index.js", + "@theme/Error": "../../../../client/theme-fallback/Error/index.tsx", + "@theme/Layout": "../../../../client/theme-fallback/Layout/index.tsx", + "@theme/Loading": "../../../../client/theme-fallback/Loading/index.tsx", + "@theme/NotFound": "../../../../client/theme-fallback/NotFound/index.tsx", "@theme/PluginThemeComponent1": "pluginThemeFolder/PluginThemeComponent1.js", "@theme/PluginThemeComponentEnhanced": "src/theme/PluginThemeComponentEnhanced.js", "@theme/PluginThemeComponentOverriddenByUser": "src/theme/PluginThemeComponentOverriddenByUser.js", - "@theme/Root": "../../../../client/theme-fallback/Root/index.js", + "@theme/Root": "../../../../client/theme-fallback/Root/index.tsx", + "@theme/SiteMetadata": "../../../../client/theme-fallback/SiteMetadata/index.tsx", "@theme/UserThemeComponent1": "src/theme/UserThemeComponent1.js", "@theme/subfolder/PluginThemeComponent2": "pluginThemeFolder/subfolder/PluginThemeComponent2.js", "@theme/subfolder/UserThemeComponent2": "src/theme/subfolder/UserThemeComponent2.js", } `; - -exports[`getDocusaurusAliases() return appropriate webpack aliases 1`] = ` -Object { - "@docusaurus/BrowserOnly": "../../client/exports/BrowserOnly.tsx", - "@docusaurus/ComponentCreator": "../../client/exports/ComponentCreator.tsx", - "@docusaurus/ErrorBoundary": "../../client/exports/ErrorBoundary.tsx", - "@docusaurus/ExecutionEnvironment": "../../client/exports/ExecutionEnvironment.ts", - "@docusaurus/Head": "../../client/exports/Head.tsx", - "@docusaurus/Interpolate": "../../client/exports/Interpolate.tsx", - "@docusaurus/Link": "../../client/exports/Link.tsx", - "@docusaurus/Noop": "../../client/exports/Noop.ts", - "@docusaurus/Translate": "../../client/exports/Translate.tsx", - "@docusaurus/browserContext": "../../client/exports/browserContext.tsx", - "@docusaurus/constants": "../../client/exports/constants.ts", - "@docusaurus/docusaurusContext": "../../client/exports/docusaurusContext.tsx", - "@docusaurus/isInternalUrl": "../../client/exports/isInternalUrl.ts", - "@docusaurus/renderRoutes": "../../client/exports/renderRoutes.ts", - "@docusaurus/router": "../../client/exports/router.ts", - "@docusaurus/useBaseUrl": "../../client/exports/useBaseUrl.ts", - "@docusaurus/useDocusaurusContext": "../../client/exports/useDocusaurusContext.ts", - "@docusaurus/useGlobalData": "../../client/exports/useGlobalData.ts", - "@docusaurus/useIsBrowser": "../../client/exports/useIsBrowser.ts", -} -`; diff --git a/packages/docusaurus/src/webpack/__tests__/base.test.ts b/packages/docusaurus/src/webpack/__tests__/base.test.ts index 95d34dc70cb0..d3aff2ab5456 100644 --- a/packages/docusaurus/src/webpack/__tests__/base.test.ts +++ b/packages/docusaurus/src/webpack/__tests__/base.test.ts @@ -5,22 +5,17 @@ * LICENSE file in the root directory of this source tree. */ +import {jest} from '@jest/globals'; import path from 'path'; -import { - excludeJS, - clientDir, - getDocusaurusAliases, - createBaseConfig, -} from '../base'; -// TODO seems to be a bug with how TS does star exports +import {excludeJS, clientDir, createBaseConfig} from '../base'; import * as utils from '@docusaurus/utils/lib/webpackUtils'; import {posixPath} from '@docusaurus/utils'; -import {mapValues} from 'lodash'; +import _ from 'lodash'; import type {Props, ThemeAliases} from '@docusaurus/types'; describe('babel transpilation exclude logic', () => { - test('always transpile client dir files', () => { + it('always transpiles client dir files', () => { const clientFiles = [ 'App.js', 'clientEntry.js', @@ -28,57 +23,46 @@ describe('babel transpilation exclude logic', () => { path.join('exports', 'Link.js'), ]; clientFiles.forEach((file) => { - expect(excludeJS(path.join(clientDir, file))).toEqual(false); + expect(excludeJS(path.join(clientDir, file))).toBe(false); }); }); - test('always transpile non node_module files', () => { + it('always transpiles non node_module files', () => { const moduleFiles = [ '/pages/user/App.jsx', '/website/src/components/foo.js', '/src/theme/SearchBar/index.js', ]; moduleFiles.forEach((file) => { - expect(excludeJS(file)).toEqual(false); + expect(excludeJS(file)).toBe(false); }); }); - test('transpile docusaurus npm packages even in node_modules', () => { + it('transpiles docusaurus npm packages even in node_modules', () => { const moduleFiles = [ '/website/node_modules/docusaurus-theme-search/theme/Navbar/index.js', 'node_modules/@docusaurus/theme-classic/theme/Layout.js', '/docusaurus/website/node_modules/@docusaurus/theme-search-algolia/theme/SearchBar.js', ]; moduleFiles.forEach((file) => { - expect(excludeJS(file)).toEqual(false); + expect(excludeJS(file)).toBe(false); }); }); - test('does not transpile node_modules', () => { + it('does not transpile node_modules', () => { const moduleFiles = [ 'node_modules/react-toggle.js', '/website/node_modules/react-trend/index.js', '/docusaurus/website/node_modules/react-super.js', '/docusaurus/website/node_modules/@docusaurus/core/node_modules/core-js/modules/_descriptors.js', - 'node_modules/docusaurus-theme-classic/node_modules/react-daypicker/index.js', + 'node_modules/docusaurus-theme-classic/node_modules/react-slick/index.js', ]; moduleFiles.forEach((file) => { - expect(excludeJS(file)).toEqual(true); + expect(excludeJS(file)).toBe(true); }); }); }); -describe('getDocusaurusAliases()', () => { - test('return appropriate webpack aliases', () => { - // using relative paths makes tests work everywhere - const relativeDocusaurusAliases = mapValues( - getDocusaurusAliases(), - (aliasValue) => posixPath(path.relative(__dirname, aliasValue)), - ); - expect(relativeDocusaurusAliases).toMatchSnapshot(); - }); -}); - describe('base webpack config', () => { const props: Props = { outDir: '', @@ -121,25 +105,25 @@ describe('base webpack config', () => { jest.restoreAllMocks(); }); - test('should create webpack aliases', () => { + it('creates webpack aliases', async () => { // @ts-expect-error: Docusaurus webpack alias is always an object const aliases: ThemeAliases = - createBaseConfig(props, true).resolve?.alias ?? {}; + (await createBaseConfig(props, true)).resolve?.alias ?? {}; // Make aliases relative so that test work on all computers - const relativeAliases = mapValues(aliases, (a) => + const relativeAliases = _.mapValues(aliases, (a) => posixPath(path.relative(props.siteDir, a)), ); expect(relativeAliases).toMatchSnapshot(); }); - test('should use svg rule', () => { + it('uses svg rule', async () => { const fileLoaderUtils = utils.getFileLoaderUtils(); const mockSvg = jest.spyOn(fileLoaderUtils.rules, 'svg'); jest .spyOn(utils, 'getFileLoaderUtils') .mockImplementation(() => fileLoaderUtils); - createBaseConfig(props, false, false); + await createBaseConfig(props, false, false); expect(mockSvg).toBeCalled(); }); }); diff --git a/packages/docusaurus/src/webpack/__tests__/client.test.ts b/packages/docusaurus/src/webpack/__tests__/client.test.ts index 5e93c1e73992..87c825eb9802 100644 --- a/packages/docusaurus/src/webpack/__tests__/client.test.ts +++ b/packages/docusaurus/src/webpack/__tests__/client.test.ts @@ -5,25 +5,23 @@ * LICENSE file in the root directory of this source tree. */ -import {validate} from 'webpack'; +import webpack from 'webpack'; import createClientConfig from '../client'; -import loadSetup from '../../server/loadSetup'; +import loadSetup from '../../server/__tests__/testUtils'; describe('webpack dev config', () => { - test('simple', async () => { - console.log = jest.fn(); + it('simple', async () => { const props = await loadSetup('simple'); - const config = createClientConfig(props); - const errors = validate(config); + const config = await createClientConfig(props); + const errors = webpack.validate(config); expect(errors).toBeUndefined(); }); - test('custom', async () => { - console.log = jest.fn(); + it('custom', async () => { const props = await loadSetup('custom'); - const config = createClientConfig(props); - const errors = validate(config); + const config = await createClientConfig(props); + const errors = webpack.validate(config); expect(errors).toBeUndefined(); }); }); diff --git a/packages/docusaurus/src/webpack/__tests__/server.test.ts b/packages/docusaurus/src/webpack/__tests__/server.test.ts index 75f95fd56aa9..b722f46f7dd0 100644 --- a/packages/docusaurus/src/webpack/__tests__/server.test.ts +++ b/packages/docusaurus/src/webpack/__tests__/server.test.ts @@ -5,25 +5,26 @@ * LICENSE file in the root directory of this source tree. */ -import {validate} from 'webpack'; +import {jest} from '@jest/globals'; +import webpack from 'webpack'; import createServerConfig from '../server'; -import loadSetup from '../../server/loadSetup'; +import loadSetup from '../../server/__tests__/testUtils'; describe('webpack production config', () => { - test('simple', async () => { - console.log = jest.fn(); + it('simple', async () => { + jest.spyOn(console, 'log').mockImplementation(() => {}); const props = await loadSetup('simple'); - const config = createServerConfig({props}); - const errors = validate(config); + const config = await createServerConfig({props}); + const errors = webpack.validate(config); expect(errors).toBeUndefined(); }); - test('custom', async () => { - console.log = jest.fn(); + it('custom', async () => { + jest.spyOn(console, 'log').mockImplementation(() => {}); const props = await loadSetup('custom'); - const config = createServerConfig({props}); - const errors = validate(config); + const config = await createServerConfig({props}); + const errors = webpack.validate(config); expect(errors).toBeUndefined(); }); }); diff --git a/packages/docusaurus/src/webpack/__tests__/utils.test.ts b/packages/docusaurus/src/webpack/__tests__/utils.test.ts index e6fdb6d3297f..1a388ed4e4f8 100644 --- a/packages/docusaurus/src/webpack/__tests__/utils.test.ts +++ b/packages/docusaurus/src/webpack/__tests__/utils.test.ts @@ -5,21 +5,19 @@ * LICENSE file in the root directory of this source tree. */ -import {validate, type Configuration, type RuleSetRule} from 'webpack'; +import webpack, {type Configuration, type RuleSetRule} from 'webpack'; import path from 'path'; import { getCustomizableJSLoader, applyConfigureWebpack, applyConfigurePostCss, + getHttpsConfig, } from '../utils'; -import type { - ConfigureWebpackFn, - ConfigureWebpackFnMergeStrategy, -} from '@docusaurus/types'; +import type {Plugin} from '@docusaurus/types'; describe('customize JS loader', () => { - test('getCustomizableJSLoader defaults to babel loader', () => { + it('getCustomizableJSLoader defaults to babel loader', () => { expect(getCustomizableJSLoader()({isServer: true}).loader).toBe( require.resolve('babel-loader'), ); @@ -28,7 +26,7 @@ describe('customize JS loader', () => { ); }); - test('getCustomizableJSLoader accepts loaders with preset', () => { + it('getCustomizableJSLoader accepts loaders with preset', () => { expect(getCustomizableJSLoader('babel')({isServer: true}).loader).toBe( require.resolve('babel-loader'), ); @@ -37,7 +35,7 @@ describe('customize JS loader', () => { ); }); - test('getCustomizableJSLoader allows customization', () => { + it('getCustomizableJSLoader allows customization', () => { const customJSLoader = (isServer: boolean): RuleSetRule => ({ loader: 'my-fast-js-loader', options: String(isServer), @@ -53,7 +51,7 @@ describe('customize JS loader', () => { }); describe('extending generated webpack config', () => { - test('direct mutation on generated webpack config object', async () => { + it('direct mutation on generated webpack config object', async () => { // fake generated webpack config let config: Configuration = { output: { @@ -62,7 +60,7 @@ describe('extending generated webpack config', () => { }, }; - const configureWebpack: ConfigureWebpackFn = ( + const configureWebpack: Plugin['configureWebpack'] = ( generatedConfig, isServer, ) => { @@ -73,7 +71,7 @@ describe('extending generated webpack config', () => { filename: 'new.bundle.js', }; } - return {}; + // Implicitly returning undefined to test null-safety }; config = applyConfigureWebpack(configureWebpack, config, false, undefined, { @@ -86,11 +84,11 @@ describe('extending generated webpack config', () => { filename: 'new.bundle.js', }, }); - const errors = validate(config); + const errors = webpack.validate(config); expect(errors).toBeUndefined(); }); - test('webpack-merge with user webpack config object', async () => { + it('webpack-merge with user webpack config object', async () => { let config: Configuration = { output: { path: __dirname, @@ -98,7 +96,7 @@ describe('extending generated webpack config', () => { }, }; - const configureWebpack: ConfigureWebpackFn = () => ({ + const configureWebpack: Plugin['configureWebpack'] = () => ({ entry: 'entry.js', output: { path: path.join(__dirname, 'dist'), @@ -116,20 +114,20 @@ describe('extending generated webpack config', () => { filename: 'new.bundle.js', }, }); - const errors = validate(config); + const errors = webpack.validate(config); expect(errors).toBeUndefined(); }); - test('webpack-merge with custom strategy', async () => { + it('webpack-merge with custom strategy', async () => { const config: Configuration = { module: { rules: [{use: 'xxx'}, {use: 'yyy'}], }, }; - const createConfigureWebpack: ( - mergeStrategy?: ConfigureWebpackFnMergeStrategy, - ) => ConfigureWebpackFn = (mergeStrategy) => () => ({ + const createConfigureWebpack: (mergeStrategy?: { + [key: string]: 'prepend' | 'append'; + }) => Plugin['configureWebpack'] = (mergeStrategy) => () => ({ module: { rules: [{use: 'zzz'}], }, @@ -178,7 +176,7 @@ describe('extending generated webpack config', () => { }); describe('extending PostCSS', () => { - test('user plugin should be appended in PostCSS loader', () => { + it('user plugin should be appended in PostCSS loader', () => { let webpackConfig: Configuration = { output: { path: __dirname, @@ -268,7 +266,7 @@ describe('extending PostCSS', () => { // @ts-expect-error: relax type const postCssLoader1 = webpackConfig.module?.rules[0].use[2]; - expect(postCssLoader1.loader).toEqual('postcss-loader-1'); + expect(postCssLoader1.loader).toBe('postcss-loader-1'); const pluginNames1 = postCssLoader1.options.postcssOptions.plugins.map( (p: unknown) => p[0], @@ -283,7 +281,7 @@ describe('extending PostCSS', () => { // @ts-expect-error: relax type const postCssLoader2 = webpackConfig.module?.rules[1].use[0]; - expect(postCssLoader2.loader).toEqual('postcss-loader-2'); + expect(postCssLoader2.loader).toBe('postcss-loader-2'); const pluginNames2 = postCssLoader2.options.postcssOptions.plugins.map( (p: unknown) => p[0], @@ -297,3 +295,65 @@ describe('extending PostCSS', () => { ]); }); }); + +describe('getHttpsConfig', () => { + const originalEnv = process.env; + + beforeEach(() => { + jest.resetModules(); + process.env = {...originalEnv}; + }); + + afterAll(() => { + process.env = originalEnv; + }); + + it('returns true for HTTPS not env', async () => { + await expect(getHttpsConfig()).resolves.toBe(false); + }); + + it('returns true for HTTPS in env', async () => { + process.env.HTTPS = 'true'; + await expect(getHttpsConfig()).resolves.toBe(true); + }); + + it('returns custom certs if they are in env', async () => { + process.env.HTTPS = 'true'; + process.env.SSL_CRT_FILE = path.join(__dirname, '__fixtures__/host.crt'); + process.env.SSL_KEY_FILE = path.join(__dirname, '__fixtures__/host.key'); + await expect(getHttpsConfig()).resolves.toEqual({ + key: expect.any(Buffer), + cert: expect.any(Buffer), + }); + }); + + it("throws if file doesn't exist", async () => { + process.env.HTTPS = 'true'; + process.env.SSL_CRT_FILE = path.join( + __dirname, + '__fixtures__/nonexistent.crt', + ); + process.env.SSL_KEY_FILE = path.join(__dirname, '__fixtures__/host.key'); + await expect(getHttpsConfig()).rejects.toThrowErrorMatchingInlineSnapshot( + `"You specified SSL_CRT_FILE in your env, but the file \\"/packages/docusaurus/src/webpack/__tests__/__fixtures__/nonexistent.crt\\" can't be found."`, + ); + }); + + it('throws for invalid key', async () => { + process.env.HTTPS = 'true'; + process.env.SSL_CRT_FILE = path.join(__dirname, '__fixtures__/host.crt'); + process.env.SSL_KEY_FILE = path.join(__dirname, '__fixtures__/invalid.key'); + await expect(getHttpsConfig()).rejects.toThrowError( + /The certificate key .*[/\\]__fixtures__[/\\]invalid\.key is invalid/, + ); + }); + + it('throws for invalid cert', async () => { + process.env.HTTPS = 'true'; + process.env.SSL_CRT_FILE = path.join(__dirname, '__fixtures__/invalid.crt'); + process.env.SSL_KEY_FILE = path.join(__dirname, '__fixtures__/host.key'); + await expect(getHttpsConfig()).rejects.toThrowError( + /The certificate .*[/\\]__fixtures__[/\\]invalid\.crt is invalid/, + ); + }); +}); diff --git a/packages/docusaurus/src/server/themes/__tests__/__fixtures__/theme-1/Footer/index.js b/packages/docusaurus/src/webpack/aliases/__tests__/__fixtures__/theme-1/Footer/index.js similarity index 100% rename from packages/docusaurus/src/server/themes/__tests__/__fixtures__/theme-1/Footer/index.js rename to packages/docusaurus/src/webpack/aliases/__tests__/__fixtures__/theme-1/Footer/index.js diff --git a/packages/docusaurus/src/server/themes/__tests__/__fixtures__/theme-1/Layout.js b/packages/docusaurus/src/webpack/aliases/__tests__/__fixtures__/theme-1/Layout.js similarity index 100% rename from packages/docusaurus/src/server/themes/__tests__/__fixtures__/theme-1/Layout.js rename to packages/docusaurus/src/webpack/aliases/__tests__/__fixtures__/theme-1/Layout.js diff --git a/packages/docusaurus/src/server/themes/__tests__/__fixtures__/theme-2/Layout/index.js b/packages/docusaurus/src/webpack/aliases/__tests__/__fixtures__/theme-2/Layout/index.js similarity index 100% rename from packages/docusaurus/src/server/themes/__tests__/__fixtures__/theme-2/Layout/index.js rename to packages/docusaurus/src/webpack/aliases/__tests__/__fixtures__/theme-2/Layout/index.js diff --git a/packages/docusaurus/src/server/themes/__tests__/__fixtures__/theme-2/Navbar.js b/packages/docusaurus/src/webpack/aliases/__tests__/__fixtures__/theme-2/Navbar.js similarity index 100% rename from packages/docusaurus/src/server/themes/__tests__/__fixtures__/theme-2/Navbar.js rename to packages/docusaurus/src/webpack/aliases/__tests__/__fixtures__/theme-2/Navbar.js diff --git a/packages/docusaurus/src/server/themes/__tests__/__fixtures__/theme-2/NavbarItem/zzz.js b/packages/docusaurus/src/webpack/aliases/__tests__/__fixtures__/theme-2/NavbarItem/NestedNavbarItem/index.js similarity index 100% rename from packages/docusaurus/src/server/themes/__tests__/__fixtures__/theme-2/NavbarItem/zzz.js rename to packages/docusaurus/src/webpack/aliases/__tests__/__fixtures__/theme-2/NavbarItem/NestedNavbarItem/index.js diff --git a/packages/docusaurus/src/webpack/aliases/__tests__/__fixtures__/theme-2/NavbarItem/SiblingNavbarItem.js b/packages/docusaurus/src/webpack/aliases/__tests__/__fixtures__/theme-2/NavbarItem/SiblingNavbarItem.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/docusaurus/src/webpack/aliases/__tests__/__fixtures__/theme-2/NavbarItem/index.js b/packages/docusaurus/src/webpack/aliases/__tests__/__fixtures__/theme-2/NavbarItem/index.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/docusaurus/src/webpack/aliases/__tests__/__fixtures__/theme-2/NavbarItem/zzz.js b/packages/docusaurus/src/webpack/aliases/__tests__/__fixtures__/theme-2/NavbarItem/zzz.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/docusaurus/src/webpack/aliases/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus/src/webpack/aliases/__tests__/__snapshots__/index.test.ts.snap new file mode 100644 index 000000000000..38a22a306e14 --- /dev/null +++ b/packages/docusaurus/src/webpack/aliases/__tests__/__snapshots__/index.test.ts.snap @@ -0,0 +1,129 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`getDocusaurusAliases returns appropriate webpack aliases 1`] = ` +{ + "@docusaurus/BrowserOnly": "/packages/docusaurus/src/client/exports/BrowserOnly.tsx", + "@docusaurus/ComponentCreator": "/packages/docusaurus/src/client/exports/ComponentCreator.tsx", + "@docusaurus/ErrorBoundary": "/packages/docusaurus/src/client/exports/ErrorBoundary.tsx", + "@docusaurus/ExecutionEnvironment": "/packages/docusaurus/src/client/exports/ExecutionEnvironment.ts", + "@docusaurus/Head": "/packages/docusaurus/src/client/exports/Head.tsx", + "@docusaurus/Interpolate": "/packages/docusaurus/src/client/exports/Interpolate.tsx", + "@docusaurus/Link": "/packages/docusaurus/src/client/exports/Link.tsx", + "@docusaurus/Noop": "/packages/docusaurus/src/client/exports/Noop.ts", + "@docusaurus/Translate": "/packages/docusaurus/src/client/exports/Translate.tsx", + "@docusaurus/constants": "/packages/docusaurus/src/client/exports/constants.ts", + "@docusaurus/isInternalUrl": "/packages/docusaurus/src/client/exports/isInternalUrl.ts", + "@docusaurus/renderRoutes": "/packages/docusaurus/src/client/exports/renderRoutes.ts", + "@docusaurus/router": "/packages/docusaurus/src/client/exports/router.ts", + "@docusaurus/useBaseUrl": "/packages/docusaurus/src/client/exports/useBaseUrl.ts", + "@docusaurus/useDocusaurusContext": "/packages/docusaurus/src/client/exports/useDocusaurusContext.ts", + "@docusaurus/useGlobalData": "/packages/docusaurus/src/client/exports/useGlobalData.ts", + "@docusaurus/useIsBrowser": "/packages/docusaurus/src/client/exports/useIsBrowser.ts", + "@docusaurus/useRouteContext": "/packages/docusaurus/src/client/exports/useRouteContext.tsx", +} +`; + +exports[`loadThemeAliases next alias can override the previous alias 1`] = ` +[ + [ + "@theme-init/Layout", + "/packages/docusaurus/src/client/theme-fallback/Layout/index.tsx", + ], + [ + "@theme-original/Error", + "/packages/docusaurus/src/client/theme-fallback/Error/index.tsx", + ], + [ + "@theme-original/Footer", + "/packages/docusaurus/src/webpack/aliases/__tests__/__fixtures__/theme-1/Footer/index.js", + ], + [ + "@theme-original/Layout", + "/packages/docusaurus/src/webpack/aliases/__tests__/__fixtures__/theme-2/Layout/index.js", + ], + [ + "@theme-original/Loading", + "/packages/docusaurus/src/client/theme-fallback/Loading/index.tsx", + ], + [ + "@theme-original/Navbar", + "/packages/docusaurus/src/webpack/aliases/__tests__/__fixtures__/theme-2/Navbar.js", + ], + [ + "@theme-original/NavbarItem/NestedNavbarItem", + "/packages/docusaurus/src/webpack/aliases/__tests__/__fixtures__/theme-2/NavbarItem/NestedNavbarItem/index.js", + ], + [ + "@theme-original/NavbarItem/SiblingNavbarItem", + "/packages/docusaurus/src/webpack/aliases/__tests__/__fixtures__/theme-2/NavbarItem/SiblingNavbarItem.js", + ], + [ + "@theme-original/NavbarItem/zzz", + "/packages/docusaurus/src/webpack/aliases/__tests__/__fixtures__/theme-2/NavbarItem/zzz.js", + ], + [ + "@theme-original/NavbarItem", + "/packages/docusaurus/src/webpack/aliases/__tests__/__fixtures__/theme-2/NavbarItem/index.js", + ], + [ + "@theme-original/NotFound", + "/packages/docusaurus/src/client/theme-fallback/NotFound/index.tsx", + ], + [ + "@theme-original/Root", + "/packages/docusaurus/src/client/theme-fallback/Root/index.tsx", + ], + [ + "@theme-original/SiteMetadata", + "/packages/docusaurus/src/client/theme-fallback/SiteMetadata/index.tsx", + ], + [ + "@theme/Error", + "/packages/docusaurus/src/client/theme-fallback/Error/index.tsx", + ], + [ + "@theme/Footer", + "/packages/docusaurus/src/webpack/aliases/__tests__/__fixtures__/theme-1/Footer/index.js", + ], + [ + "@theme/Layout", + "/packages/docusaurus/src/webpack/aliases/__tests__/__fixtures__/theme-2/Layout/index.js", + ], + [ + "@theme/Loading", + "/packages/docusaurus/src/client/theme-fallback/Loading/index.tsx", + ], + [ + "@theme/Navbar", + "/packages/docusaurus/src/webpack/aliases/__tests__/__fixtures__/theme-2/Navbar.js", + ], + [ + "@theme/NavbarItem/NestedNavbarItem", + "/packages/docusaurus/src/webpack/aliases/__tests__/__fixtures__/theme-2/NavbarItem/NestedNavbarItem/index.js", + ], + [ + "@theme/NavbarItem/SiblingNavbarItem", + "/packages/docusaurus/src/webpack/aliases/__tests__/__fixtures__/theme-2/NavbarItem/SiblingNavbarItem.js", + ], + [ + "@theme/NavbarItem/zzz", + "/packages/docusaurus/src/webpack/aliases/__tests__/__fixtures__/theme-2/NavbarItem/zzz.js", + ], + [ + "@theme/NavbarItem", + "/packages/docusaurus/src/webpack/aliases/__tests__/__fixtures__/theme-2/NavbarItem/index.js", + ], + [ + "@theme/NotFound", + "/packages/docusaurus/src/client/theme-fallback/NotFound/index.tsx", + ], + [ + "@theme/Root", + "/packages/docusaurus/src/client/theme-fallback/Root/index.tsx", + ], + [ + "@theme/SiteMetadata", + "/packages/docusaurus/src/client/theme-fallback/SiteMetadata/index.tsx", + ], +] +`; diff --git a/packages/docusaurus/src/server/themes/__tests__/alias.test.ts b/packages/docusaurus/src/webpack/aliases/__tests__/index.test.ts similarity index 57% rename from packages/docusaurus/src/server/themes/__tests__/alias.test.ts rename to packages/docusaurus/src/webpack/aliases/__tests__/index.test.ts index d64e3e5b3980..b2154a41f5db 100644 --- a/packages/docusaurus/src/server/themes/__tests__/alias.test.ts +++ b/packages/docusaurus/src/webpack/aliases/__tests__/index.test.ts @@ -5,15 +5,64 @@ * LICENSE file in the root directory of this source tree. */ -import path from 'path'; import fs from 'fs-extra'; -import themeAlias from '../alias'; +import path from 'path'; +import { + loadThemeAliases, + loadDocusaurusAliases, + sortAliases, + createAliasesForTheme, +} from '../index'; + +describe('sortAliases', () => { + // https://github.com/facebook/docusaurus/issues/6878 + // Not sure if the risk actually happens, but still made tests to ensure that + // behavior is consistent + it('sorts reliably', () => { + expect( + Object.values( + sortAliases({ + '@a/b': 'b', + '@a/b/c': 'c', + '@a/b/c/d': 'd', + }), + ), + ).toEqual(['d', 'c', 'b']); + expect( + Object.values( + sortAliases({ + '@a/b': 'b', + '@a/b/c/d': 'd', + '@a/b/c': 'c', + }), + ), + ).toEqual(['d', 'c', 'b']); + expect( + Object.values( + sortAliases({ + '@a/b/c/d': 'd', + '@a/b/c': 'c', + '@a/b': 'b', + }), + ), + ).toEqual(['d', 'c', 'b']); + expect( + Object.values( + sortAliases({ + '@a/b/c': 'c', + '@a/b': 'b', + '@a/b/c/d': 'd', + }), + ), + ).toEqual(['d', 'c', 'b']); + }); +}); -describe('themeAlias', () => { - test('valid themePath 1 with components', () => { +describe('createAliasesForTheme', () => { + it('creates aliases for themePath 1 with components', async () => { const fixtures = path.join(__dirname, '__fixtures__'); const themePath = path.join(fixtures, 'theme-1'); - const alias = themeAlias(themePath, true); + const alias = await createAliasesForTheme(themePath, true); // Testing entries, because order matters! expect(Object.entries(alias)).toEqual( Object.entries({ @@ -26,10 +75,10 @@ describe('themeAlias', () => { expect(alias).not.toEqual({}); }); - test('valid themePath 1 with components without original', () => { + it('creates aliases for themePath 1 with components without original', async () => { const fixtures = path.join(__dirname, '__fixtures__'); const themePath = path.join(fixtures, 'theme-1'); - const alias = themeAlias(themePath, false); + const alias = await createAliasesForTheme(themePath, false); // Testing entries, because order matters! expect(Object.entries(alias)).toEqual( Object.entries({ @@ -40,10 +89,10 @@ describe('themeAlias', () => { expect(alias).not.toEqual({}); }); - test('valid themePath 2 with components', () => { + it('creates aliases for themePath 2 with components', async () => { const fixtures = path.join(__dirname, '__fixtures__'); const themePath = path.join(fixtures, 'theme-2'); - const alias = themeAlias(themePath, true); + const alias = await createAliasesForTheme(themePath, true); // Testing entries, because order matters! expect(Object.entries(alias)).toEqual( Object.entries({ @@ -83,10 +132,10 @@ describe('themeAlias', () => { expect(alias).not.toEqual({}); }); - test('valid themePath 2 with components without original', () => { + it('creates aliases for themePath 2 with components without original', async () => { const fixtures = path.join(__dirname, '__fixtures__'); const themePath = path.join(fixtures, 'theme-2'); - const alias = themeAlias(themePath, false); + const alias = await createAliasesForTheme(themePath, false); // Testing entries, because order matters! expect(Object.entries(alias)).toEqual( Object.entries({ @@ -107,26 +156,51 @@ describe('themeAlias', () => { expect(alias).not.toEqual({}); }); - test('valid themePath with no components', () => { + it('creates themePath with no components', async () => { const fixtures = path.join(__dirname, '__fixtures__'); const themePath = path.join(fixtures, 'empty-theme'); - fs.ensureDirSync(themePath); - const alias = themeAlias(themePath, true); + await fs.ensureDir(themePath); + const alias = await createAliasesForTheme(themePath, true); expect(alias).toEqual({}); }); - test('valid themePath with no components without original', () => { + it('creates themePath with no components without original', async () => { const fixtures = path.join(__dirname, '__fixtures__'); const themePath = path.join(fixtures, 'empty-theme'); - fs.ensureDirSync(themePath); - const alias = themeAlias(themePath, false); + await fs.ensureDir(themePath); + const alias = await createAliasesForTheme(themePath, false); expect(alias).toEqual({}); }); - test('invalid themePath that does not exist', () => { + it('creates nothing for invalid themePath that does not exist', async () => { const fixtures = path.join(__dirname, '__fixtures__'); const themePath = path.join(fixtures, '__noExist__'); - const alias = themeAlias(themePath, true); + const alias = await createAliasesForTheme(themePath, true); expect(alias).toEqual({}); }); }); + +describe('getDocusaurusAliases', () => { + it('returns appropriate webpack aliases', async () => { + await expect(loadDocusaurusAliases()).resolves.toMatchSnapshot(); + }); +}); + +describe('loadThemeAliases', () => { + it('next alias can override the previous alias', async () => { + const fixtures = path.join(__dirname, '__fixtures__'); + const theme1Path = path.join(fixtures, 'theme-1'); + const theme2Path = path.join(fixtures, 'theme-2'); + + const alias = await loadThemeAliases({ + siteDir: fixtures, + plugins: [ + {getThemePath: () => theme1Path}, + {getThemePath: () => theme2Path}, + ], + }); + + // Testing entries, because order matters! + expect(Object.entries(alias)).toMatchSnapshot(); + }); +}); diff --git a/packages/docusaurus/src/webpack/aliases/index.ts b/packages/docusaurus/src/webpack/aliases/index.ts new file mode 100644 index 000000000000..984876571c29 --- /dev/null +++ b/packages/docusaurus/src/webpack/aliases/index.ts @@ -0,0 +1,147 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import fs from 'fs-extra'; +import path from 'path'; +import { + THEME_PATH, + fileToPath, + posixPath, + normalizeUrl, + Globby, +} from '@docusaurus/utils'; +import _ from 'lodash'; +import type {ThemeAliases, LoadedPlugin} from '@docusaurus/types'; + +const ThemeFallbackDir = path.join(__dirname, '../../client/theme-fallback'); + +/** + * Order of Webpack aliases is important because one alias can shadow another. + * This ensures `@theme/NavbarItem` alias is after + * `@theme/NavbarItem/LocaleDropdown`. + * + * @see https://github.com/facebook/docusaurus/pull/3922 + * @see https://github.com/facebook/docusaurus/issues/5382 + */ +export function sortAliases(aliases: ThemeAliases): ThemeAliases { + // Alphabetical order by default + const entries = _.sortBy(Object.entries(aliases), ([alias]) => alias); + // @theme/NavbarItem should be after @theme/NavbarItem/LocaleDropdown + entries.sort(([alias1], [alias2]) => + // eslint-disable-next-line no-nested-ternary + alias1.includes(`${alias2}/`) ? -1 : alias2.includes(`${alias1}/`) ? 1 : 0, + ); + return Object.fromEntries(entries); +} + +export async function createAliasesForTheme( + themePath: string, + addOriginalAlias: boolean, +): Promise { + if (!(await fs.pathExists(themePath))) { + return {}; + } + + const themeComponentFiles = await Globby(['**/*.{js,jsx,ts,tsx}'], { + cwd: themePath, + }); + + const aliases: ThemeAliases = {}; + + themeComponentFiles.forEach((relativeSource) => { + const filePath = path.join(themePath, relativeSource); + const fileName = fileToPath(relativeSource); + + const aliasName = posixPath( + normalizeUrl(['@theme', fileName]).replace(/\/$/, ''), + ); + aliases[aliasName] = filePath; + + if (addOriginalAlias) { + // For swizzled components to access the original. + const originalAliasName = posixPath( + normalizeUrl(['@theme-original', fileName]).replace(/\/$/, ''), + ); + aliases[originalAliasName] = filePath; + } + }); + + return sortAliases(aliases); +} + +async function createThemeAliases( + themePaths: string[], + userThemePaths: string[], +): Promise { + const aliases: ThemeAliases = {}; + + for (const themePath of themePaths) { + const themeAliases = await createAliasesForTheme(themePath, true); + Object.entries(themeAliases).forEach(([aliasKey, alias]) => { + // If this alias shadows a previous one, use @theme-init to preserve the + // initial one. @theme-init is only applied once: to the initial theme + // that provided this component + if (aliasKey in aliases) { + const componentName = aliasKey.substring(aliasKey.indexOf('/') + 1); + const initAlias = `@theme-init/${componentName}`; + if (!(initAlias in aliases)) { + aliases[initAlias] = aliases[aliasKey]!; + } + } + aliases[aliasKey] = alias; + }); + } + + for (const themePath of userThemePaths) { + const userThemeAliases = await createAliasesForTheme(themePath, false); + Object.assign(aliases, userThemeAliases); + } + + return sortAliases(aliases); +} + +export function loadThemeAliases({ + siteDir, + plugins, +}: { + siteDir: string; + plugins: LoadedPlugin[]; +}): Promise { + const pluginThemes: string[] = plugins + .map( + (plugin) => + plugin.getThemePath && path.resolve(plugin.path, plugin.getThemePath()), + ) + .filter((x): x is string => Boolean(x)); + const userTheme = path.resolve(siteDir, THEME_PATH); + return createThemeAliases([ThemeFallbackDir, ...pluginThemes], [userTheme]); +} + +/** + * Note: a `@docusaurus` alias would also catch `@docusaurus/theme-common`, so + * instead of naively aliasing this to `client/exports`, we use fine-grained + * aliases instead. + */ +export async function loadDocusaurusAliases(): Promise { + const dirPath = path.resolve(__dirname, '../../client/exports'); + const extensions = ['.js', '.ts', '.tsx']; + + const aliases: ThemeAliases = {}; + + (await fs.readdir(dirPath)) + .filter((fileName) => extensions.includes(path.extname(fileName))) + .forEach((fileName) => { + const fileNameWithoutExtension = path.basename( + fileName, + path.extname(fileName), + ); + const aliasName = `@docusaurus/${fileNameWithoutExtension}`; + aliases[aliasName] = path.resolve(dirPath, fileName); + }); + + return aliases; +} diff --git a/packages/docusaurus/src/webpack/base.ts b/packages/docusaurus/src/webpack/base.ts index 79fd9237d313..2c59b7d6a873 100644 --- a/packages/docusaurus/src/webpack/base.ts +++ b/packages/docusaurus/src/webpack/base.ts @@ -16,11 +16,11 @@ import { getCustomBabelConfigFilePath, getMinimizer, } from './utils'; -import {loadPluginsThemeAliases} from '../server/themes'; +import {loadThemeAliases, loadDocusaurusAliases} from './aliases'; import {md5Hash, getFileLoaderUtils} from '@docusaurus/utils'; -const CSS_REGEX = /\.css$/; -const CSS_MODULE_REGEX = /\.module\.css$/; +const CSS_REGEX = /\.css$/i; +const CSS_MODULE_REGEX = /\.module\.css$/i; export const clientDir = path.join(__dirname, '..', 'client'); const LibrariesToTranspile = [ @@ -39,36 +39,16 @@ export function excludeJS(modulePath: string): boolean { // Don't transpile node_modules except any docusaurus npm package return ( /node_modules/.test(modulePath) && - !/(docusaurus)((?!node_modules).)*\.jsx?$/.test(modulePath) && + !/docusaurus(?:(?!node_modules).)*\.jsx?$/.test(modulePath) && !LibrariesToTranspileRegex.test(modulePath) ); } -export function getDocusaurusAliases(): Record { - const dirPath = path.resolve(__dirname, '../client/exports'); - const extensions = ['.js', '.ts', '.tsx']; - - const aliases: Record = {}; - - fs.readdirSync(dirPath) - .filter((fileName) => extensions.includes(path.extname(fileName))) - .forEach((fileName) => { - const fileNameWithoutExtension = path.basename( - fileName, - path.extname(fileName), - ); - const aliasName = `@docusaurus/${fileNameWithoutExtension}`; - aliases[aliasName] = path.resolve(dirPath, fileName); - }); - - return aliases; -} - -export function createBaseConfig( +export async function createBaseConfig( props: Props, isServer: boolean, minify: boolean = true, -): Configuration { +): Promise { const { outDir, siteDir, @@ -90,7 +70,7 @@ export function createBaseConfig( const name = isServer ? 'server' : 'client'; const mode = isProd ? 'production' : 'development'; - const themeAliases = loadPluginsThemeAliases({siteDir, plugins}); + const themeAliases = await loadThemeAliases({siteDir, plugins}); return { mode, @@ -104,16 +84,19 @@ export function createBaseConfig( // When version string changes, cache is evicted version: [ siteMetadata.docusaurusVersion, - // Webpack does not evict the cache correctly on alias/swizzle change, so we force eviction. + // Webpack does not evict the cache correctly on alias/swizzle change, + // so we force eviction. // See https://github.com/webpack/webpack/issues/13627 md5Hash(JSON.stringify(themeAliases)), ].join('-'), - // When one of those modules/dependencies change (including transitive deps), cache is invalidated + // When one of those modules/dependencies change (including transitive + // deps), cache is invalidated buildDependencies: { config: [ __filename, path.join(__dirname, isServer ? 'server.js' : 'client.js'), - // Docusaurus config changes can affect MDX/JSX compilation, so we'd rather evict the cache. + // Docusaurus config changes can affect MDX/JSX compilation, so we'd + // rather evict the cache. // See https://github.com/questdb/questdb.io/issues/493 siteConfigPath, ], @@ -151,21 +134,18 @@ export function createBaseConfig( alias: { '@site': siteDir, '@generated': generatedFilesDir, - - // Note: a @docusaurus alias would also catch @docusaurus/theme-common, - // so we use fine-grained aliases instead - // '@docusaurus': path.resolve(__dirname, '../client/exports'), - ...getDocusaurusAliases(), + ...(await loadDocusaurusAliases()), ...themeAliases, }, - // This allows you to set a fallback for where Webpack should look for modules. - // We want `@docusaurus/core` own dependencies/`node_modules` to "win" if there is conflict - // Example: if there is core-js@3 in user's own node_modules, but core depends on - // core-js@2, we should use core-js@2. + // This allows you to set a fallback for where Webpack should look for + // modules. We want `@docusaurus/core` own dependencies/`node_modules` to + // "win" if there is conflict. Example: if there is core-js@3 in user's + // own node_modules, but core depends on core-js@2, we should use + // core-js@2. modules: [ path.resolve(__dirname, '..', '..', 'node_modules'), 'node_modules', - path.resolve(fs.realpathSync(process.cwd()), 'node_modules'), + path.resolve(await fs.realpath(process.cwd()), 'node_modules'), ], }, resolveLoader: { @@ -173,7 +153,8 @@ export function createBaseConfig( }, optimization: { removeAvailableModules: false, - // Only minimize client bundle in production because server bundle is only used for static site generation + // Only minimize client bundle in production because server bundle is only + // used for static site generation minimize: minimizeEnabled, minimizer: minimizeEnabled ? getMinimizer(useSimpleCssMinifier) @@ -181,7 +162,9 @@ export function createBaseConfig( splitChunks: isServer ? false : { - // Since the chunk name includes all origin chunk names it's recommended for production builds with long term caching to NOT include [name] in the filenames + // Since the chunk name includes all origin chunk names it's + // recommended for production builds with long term caching to NOT + // include [name] in the filenames name: false, cacheGroups: { // disable the built-in cacheGroups @@ -213,12 +196,12 @@ export function createBaseConfig( fileLoaderUtils.rules.svg(), fileLoaderUtils.rules.otherAssets(), { - test: /\.(j|t)sx?$/, + test: /\.[jt]sx?$/i, exclude: excludeJS, use: [ getCustomizableJSLoader(siteConfig.webpack?.jsLoader)({ isServer, - babelOptions: getCustomBabelConfigFilePath(siteDir), + babelOptions: await getCustomBabelConfigFilePath(siteDir), }), ], }, @@ -255,8 +238,9 @@ export function createBaseConfig( chunkFilename: isProd ? 'assets/css/[name].[contenthash:8].css' : '[name].css', - // remove css order warnings if css imports are not sorted alphabetically - // see https://github.com/webpack-contrib/mini-css-extract-plugin/pull/422 for more reasoning + // remove css order warnings if css imports are not sorted + // alphabetically. See https://github.com/webpack-contrib/mini-css-extract-plugin/pull/422 + // for more reasoning ignoreOrder: true, }), ], diff --git a/packages/docusaurus/src/webpack/client.ts b/packages/docusaurus/src/webpack/client.ts index a22ecbdfb44e..ffe0a704eb95 100644 --- a/packages/docusaurus/src/webpack/client.ts +++ b/packages/docusaurus/src/webpack/client.ts @@ -15,15 +15,17 @@ import {createBaseConfig} from './base'; import ChunkAssetPlugin from './plugins/ChunkAssetPlugin'; import LogPlugin from './plugins/LogPlugin'; -export default function createClientConfig( +export default async function createClientConfig( props: Props, minify: boolean = true, -): Configuration { +): Promise { const isBuilding = process.argv[2] === 'build'; - const config = createBaseConfig(props, false, minify); + const config = await createBaseConfig(props, false, minify); const clientConfig = merge(config, { - // target: 'browserslist', // useless, disabled on purpose (errors on existing sites with no browserslist cfg) + // useless, disabled on purpose (errors on existing sites with no + // browserslist config) + // target: 'browserslist', entry: path.resolve(__dirname, '../client/clientEntry.js'), optimization: { // Keep the runtime chunk separated to enable long term caching @@ -39,7 +41,8 @@ export default function createClientConfig( ], }); - // When building include the plugin to force terminate building if errors happened in the client bundle. + // When building, include the plugin to force terminate building if errors + // happened in the client bundle. if (isBuilding) { clientConfig.plugins?.push({ apply: (compiler) => { diff --git a/packages/docusaurus/src/webpack/plugins/ChunkAssetPlugin.ts b/packages/docusaurus/src/webpack/plugins/ChunkAssetPlugin.ts index 6db248532b1b..b1eaf0f527eb 100644 --- a/packages/docusaurus/src/webpack/plugins/ChunkAssetPlugin.ts +++ b/packages/docusaurus/src/webpack/plugins/ChunkAssetPlugin.ts @@ -5,45 +5,51 @@ * LICENSE file in the root directory of this source tree. */ -import {Template, type Compiler} from 'webpack'; +import webpack, {type Compiler} from 'webpack'; const pluginName = 'chunk-asset-plugin'; -class ChunkAssetPlugin { +/** + * We modify webpack runtime to add an extra function called + * "__webpack_require__.gca" that will allow us to get the corresponding chunk + * asset for a webpack chunk. Pass it the chunkName or chunkId you want to load. + * For example: if you have a chunk named "my-chunk-name" that will map to + * "/publicPath/0a84b5e7.c8e35c7a.js" as its corresponding output path + * __webpack_require__.gca("my-chunk-name") will return + * "/publicPath/0a84b5e7.c8e35c7a.js" + * + * "gca" stands for "get chunk asset" + */ +export default class ChunkAssetPlugin { apply(compiler: Compiler): void { compiler.hooks.thisCompilation.tap(pluginName, ({mainTemplate}) => { - /* We modify webpack runtime to add an extra function called "__webpack_require__.gca" - that will allow us to get the corresponding chunk asset for a webpack chunk. - Pass it the chunkName or chunkId you want to load. - For example: if you have a chunk named "my-chunk-name" that will map to "/publicPath/0a84b5e7.c8e35c7a.js" as its corresponding output path - __webpack_require__.gca("my-chunk-name") will return "/publicPath/0a84b5e7.c8e35c7a.js" - "gca" stands for "get chunk asset" - */ mainTemplate.hooks.requireExtensions.tap(pluginName, (source, chunk) => { const chunkIdToName = chunk.getChunkMaps(false).name; - const chunkNameToId = Object.create(null); - Object.keys(chunkIdToName).forEach((chunkId) => { - const chunkName = chunkIdToName[chunkId]; - chunkNameToId[chunkName] = chunkId; - }); + const chunkNameToId = Object.fromEntries( + Object.entries(chunkIdToName).map(([chunkId, chunkName]) => [ + chunkName, + chunkId, + ]), + ); const buf = [source]; buf.push('// function to get chunk asset'); buf.push( // If chunkName is passed, we convert it to chunk asset url // .p => public path url ("/" or "/baseUrl/") - // .u(chunkId) => chunk asset url ("assets/js/x63b64xd.contentHash.js") - // not sure where this is documented, but this link was helpful: https://programmer.help/blogs/5d68849083e1a.html + // .u(chunkId) => + // chunk asset url ("assets/js/x63b64xd.contentHash.js") + // not sure where this is documented, but this link was helpful: + // https://programmer.help/blogs/5d68849083e1a.html // - // Note: __webpack_require__.gca() is called in docusaurus.ts for prefetching + // Note: __webpack_require__.gca() is called in docusaurus.ts for + // prefetching // Note: we previously used jsonpScriptSrc (Webpack 4) `__webpack_require__.gca = function(chunkId) { chunkId = ${JSON.stringify( chunkNameToId, )}[chunkId]||chunkId; return __webpack_require__.p + __webpack_require__.u(chunkId); };`, ); - return Template.asString(buf); + return webpack.Template.asString(buf); }); }); } } - -export default ChunkAssetPlugin; diff --git a/packages/docusaurus/src/webpack/plugins/CleanWebpackPlugin.ts b/packages/docusaurus/src/webpack/plugins/CleanWebpackPlugin.ts index 1818d843ef2e..e6bae7173dea 100644 --- a/packages/docusaurus/src/webpack/plugins/CleanWebpackPlugin.ts +++ b/packages/docusaurus/src/webpack/plugins/CleanWebpackPlugin.ts @@ -14,8 +14,8 @@ * 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: - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -67,7 +67,7 @@ export interface Options { cleanOnceBeforeBuildPatterns?: string[]; } -class CleanWebpackPlugin { +export default class CleanWebpackPlugin { private readonly verbose: boolean; private readonly cleanStaleWebpackAssets: boolean; private readonly protectWebpackAssets: boolean; @@ -144,7 +144,8 @@ class CleanWebpackPlugin { * * Only happens once. * - * Warning: It is recommended to initially clean your build directory outside of webpack to minimize unexpected behavior. + * Warning: It is recommended to initially clean your build directory outside + * of webpack to minimize unexpected behavior. */ handleInitial(): void { if (this.initialClean) { @@ -175,7 +176,7 @@ class CleanWebpackPlugin { stats.toJson({ all: false, assets: true, - }).assets || []; + }).assets ?? []; const assets = statsAssets.map((asset: {name: string}) => asset.name); /** @@ -232,10 +233,10 @@ class CleanWebpackPlugin { console.warn(`clean-webpack-plugin: removed ${filename}`); }); } - } catch (error) { + } catch (err) { const needsForce = /Cannot delete files\/folders outside the current working directory\./.test( - (error as Error).message, + (err as Error).message, ); if (needsForce) { @@ -245,9 +246,7 @@ class CleanWebpackPlugin { throw new Error(message); } - throw error; + throw err; } } } - -export default CleanWebpackPlugin; diff --git a/packages/docusaurus/src/webpack/plugins/LogPlugin.ts b/packages/docusaurus/src/webpack/plugins/LogPlugin.ts index 512fbf6872e6..e8e79b008057 100644 --- a/packages/docusaurus/src/webpack/plugins/LogPlugin.ts +++ b/packages/docusaurus/src/webpack/plugins/LogPlugin.ts @@ -14,7 +14,7 @@ function showError(arr: string[]) { } export default class LogPlugin extends WebpackBar { - apply(compiler: Compiler): void { + override apply(compiler: Compiler): void { super.apply(compiler); // TODO can't this be done in compile(configs) alongside the warnings??? diff --git a/packages/docusaurus/src/webpack/server.ts b/packages/docusaurus/src/webpack/server.ts index 56b8b43ff2ff..3adc362a41da 100644 --- a/packages/docusaurus/src/webpack/server.ts +++ b/packages/docusaurus/src/webpack/server.ts @@ -18,13 +18,13 @@ import {NODE_MAJOR_VERSION, NODE_MINOR_VERSION} from '@docusaurus/utils'; // Forked for Docusaurus: https://github.com/slorber/static-site-generator-webpack-plugin import StaticSiteGeneratorPlugin from '@slorber/static-site-generator-webpack-plugin'; -export default function createServerConfig({ +export default async function createServerConfig({ props, onLinksCollected = () => {}, }: { props: Props; onLinksCollected?: (staticPagePath: string, links: string[]) => void; -}): Configuration { +}): Promise { const { baseUrl, routesPaths, @@ -35,9 +35,9 @@ export default function createServerConfig({ ssrTemplate, siteConfig: {noIndex, trailingSlash}, } = props; - const config = createBaseConfig(props, true); + const config = await createBaseConfig(props, true); - const routesLocation: Record = {}; + const routesLocation: {[filePath: string]: string} = {}; // Array of paths to be rendered. Relative to output directory const ssgPaths = routesPaths.map((str) => { const ssgPath = @@ -79,9 +79,10 @@ export default function createServerConfig({ paths: ssgPaths, preferFoldersOutput: trailingSlash, - // When using "new URL('file.js', import.meta.url)", Webpack will emit __filename, and this plugin will throw - // not sure the __filename value has any importance for this plugin, just using an empty string to avoid the error - // See https://github.com/facebook/docusaurus/issues/4922 + // When using "new URL('file.js', import.meta.url)", Webpack will emit + // __filename, and this plugin will throw. not sure the __filename value + // has any importance for this plugin, just using an empty string to + // avoid the error. See https://github.com/facebook/docusaurus/issues/4922 globals: {__filename: ''}, }), diff --git a/packages/docusaurus/src/webpack/utils.ts b/packages/docusaurus/src/webpack/utils.ts index 91f6327b0277..85345c201b0a 100644 --- a/packages/docusaurus/src/webpack/utils.ts +++ b/packages/docusaurus/src/webpack/utils.ts @@ -25,13 +25,11 @@ import crypto from 'crypto'; import logger from '@docusaurus/logger'; import type {TransformOptions} from '@babel/core'; import type { - ConfigureWebpackFn, - ConfigurePostCssFn, + Plugin, PostCssOptions, ConfigureWebpackUtils, } from '@docusaurus/types'; import {BABEL_CONFIG_FILE_NAME} from '@docusaurus/utils'; -import {memoize} from 'lodash'; // Utility method to get style loaders export function getStyleLoaders( @@ -101,14 +99,14 @@ export function getStyleLoaders( ]; } -export function getCustomBabelConfigFilePath( +export async function getCustomBabelConfigFilePath( siteDir: string, -): string | undefined { +): Promise { const customBabelConfigurationPath = path.join( siteDir, BABEL_CONFIG_FILE_NAME, ); - return fs.existsSync(customBabelConfigurationPath) + return (await fs.pathExists(customBabelConfigurationPath)) ? customBabelConfigurationPath : undefined; } @@ -126,16 +124,13 @@ export function getBabelOptions({ configFile: babelOptions, caller: {name: isServer ? 'server' : 'client'}, }; - } else { - return Object.assign( - babelOptions ?? {presets: [require.resolve('../babel/preset')]}, - { - babelrc: false, - configFile: false, - caller: {name: isServer ? 'server' : 'client'}, - }, - ); } + return { + ...(babelOptions ?? {presets: [require.resolve('../babel/preset')]}), + babelrc: false, + configFile: false, + caller: {name: isServer ? 'server' : 'client'}, + }; } // Name is generic on purpose @@ -166,27 +161,6 @@ export const getCustomizableJSLoader = ? getDefaultBabelLoader({isServer, babelOptions}) : jsLoader(isServer); -// TODO remove this before end of 2021? -const warnBabelLoaderOnce = memoize(() => { - logger.warn`Docusaurus plans to support multiple JS loader strategies (Babel, esbuild...): code=${'getBabelLoader(isServer)'} is now deprecated in favor of code=${'getJSLoader(isServer)'}.`; -}); -const getBabelLoaderDeprecated = function getBabelLoaderDeprecated( - isServer: boolean, - babelOptions?: TransformOptions | string, -) { - warnBabelLoaderOnce(); - return getDefaultBabelLoader({isServer, babelOptions}); -}; - -// TODO remove this before end of 2021 ? -const warnCacheLoaderOnce = memoize(() => { - logger.warn`Docusaurus uses Webpack 5 and code=${'getCacheLoader()'} usage is now deprecated.`; -}); -function getCacheLoaderDeprecated() { - warnCacheLoaderOnce(); - return null; -} - /** * Helper function to modify webpack config * @param configureWebpack a webpack config or a function to modify config @@ -197,7 +171,7 @@ function getCacheLoaderDeprecated() { * @returns final/ modified webpack config */ export function applyConfigureWebpack( - configureWebpack: ConfigureWebpackFn, + configureWebpack: NonNullable, config: Configuration, isServer: boolean, jsLoader: 'babel' | ((isServer: boolean) => RuleSetRule) | undefined, @@ -207,16 +181,10 @@ export function applyConfigureWebpack( const utils: ConfigureWebpackUtils = { getStyleLoaders, getJSLoader: getCustomizableJSLoader(jsLoader), - getBabelLoader: getBabelLoaderDeprecated, - getCacheLoader: getCacheLoaderDeprecated, }; if (typeof configureWebpack === 'function') { - const {mergeStrategy, ...res} = configureWebpack( - config, - isServer, - utils, - content, - ); + const {mergeStrategy, ...res} = + configureWebpack(config, isServer, utils, content) ?? {}; if (res && typeof res === 'object') { const customizeRules = mergeStrategy ?? {}; return mergeWithCustomize({ @@ -229,7 +197,7 @@ export function applyConfigureWebpack( } export function applyConfigurePostCss( - configurePostCss: NonNullable, + configurePostCss: NonNullable, config: Configuration, ): Configuration { type LocalPostCSSLoader = unknown & { @@ -263,15 +231,20 @@ export function applyConfigurePostCss( return config; } +declare global { + interface Error { + /** @see https://webpack.js.org/api/node/#error-handling */ + details: unknown; + } +} + export function compile(config: Configuration[]): Promise { return new Promise((resolve, reject) => { const compiler = webpack(config); compiler.run((err, stats) => { if (err) { logger.error(err.stack || err); - // @ts-expect-error: see https://webpack.js.org/api/node/#error-handling if (err.details) { - // @ts-expect-error: see https://webpack.js.org/api/node/#error-handling logger.error(err.details); } reject(err); @@ -336,19 +309,21 @@ ${err}`, } // Read file and throw an error if it doesn't exist -function readEnvFile(file: string, type: string) { - if (!fs.existsSync(file)) { +async function readEnvFile(file: string, type: string) { + if (!(await fs.pathExists(file))) { throw new Error( `You specified ${type} in your env, but the file "${file}" can't be found.`, ); } - return fs.readFileSync(file); + return fs.readFile(file); } -const appDirectory = fs.realpathSync(process.cwd()); // Get the https config // Return cert files if provided in env, otherwise just true or false -export function getHttpsConfig(): boolean | {cert: Buffer; key: Buffer} { +export async function getHttpsConfig(): Promise< + boolean | {cert: Buffer; key: Buffer} +> { + const appDirectory = await fs.realpath(process.cwd()); const {SSL_CRT_FILE, SSL_KEY_FILE, HTTPS} = process.env; const isHttps = HTTPS === 'true'; @@ -356,8 +331,8 @@ export function getHttpsConfig(): boolean | {cert: Buffer; key: Buffer} { const crtFile = path.resolve(appDirectory, SSL_CRT_FILE); const keyFile = path.resolve(appDirectory, SSL_KEY_FILE); const config = { - cert: readEnvFile(crtFile, 'SSL_CRT_FILE'), - key: readEnvFile(keyFile, 'SSL_KEY_FILE'), + cert: await readEnvFile(crtFile, 'SSL_CRT_FILE'), + key: await readEnvFile(keyFile, 'SSL_KEY_FILE'), }; validateKeyAndCerts({...config, keyFile, crtFile}); @@ -405,8 +380,8 @@ export function getMinimizer( output: { ecma: 5, comments: false, - // Turned on because emoji and regex is not minified properly using default - // https://github.com/facebook/create-react-app/issues/2488 + // Turned on because emoji and regex is not minified properly using + // default. See https://github.com/facebook/create-react-app/issues/2488 ascii_only: true, }, }, diff --git a/packages/docusaurus/tsconfig.client.json b/packages/docusaurus/tsconfig.client.json index 5a33d4b528f6..66613cf7c22d 100644 --- a/packages/docusaurus/tsconfig.client.json +++ b/packages/docusaurus/tsconfig.client.json @@ -7,7 +7,7 @@ "tsBuildInfoFile": "./lib/client/.tsbuildinfo", "rootDir": "src/client", "outDir": "lib/client", - "jsx": "react" + "jsx": "react-native" }, "include": ["src/client", "src/deps.d.ts"] } diff --git a/packages/docusaurus/tsconfig.json b/packages/docusaurus/tsconfig.json index af81dee45121..e467cc195592 100644 --- a/packages/docusaurus/tsconfig.json +++ b/packages/docusaurus/tsconfig.json @@ -1,13 +1,10 @@ +// For editor typechecking; includes bin { - "extends": "../../tsconfig.json", + "extends": "./tsconfig.server.json", "compilerOptions": { - "incremental": true, - "tsBuildInfoFile": "./lib/.tsbuildinfo", - "rootDir": "src", - "outDir": "lib", - "jsx": "react", - "allowJs": true + "noEmit": true, + "rootDir": ".", + "module": "esnext" }, - "include": ["src"], - "exclude": ["**/__tests__/**/*", "src/client"] + "include": ["src", "bin"] } diff --git a/packages/docusaurus/tsconfig.server.json b/packages/docusaurus/tsconfig.server.json new file mode 100644 index 000000000000..cabe532baa26 --- /dev/null +++ b/packages/docusaurus/tsconfig.server.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "incremental": true, + "tsBuildInfoFile": "./lib/.tsbuildinfo", + "rootDir": "src", + "outDir": "lib", + "allowJs": true + }, + "include": ["src"], + "exclude": ["**/__tests__/**/*", "src/client"] +} diff --git a/packages/lqip-loader/README.md b/packages/lqip-loader/README.md index 98be380602f2..458ff3654d49 100644 --- a/packages/lqip-loader/README.md +++ b/packages/lqip-loader/README.md @@ -10,7 +10,7 @@ npm install --save-dev @docusaurus/lqip-loader ## Example -Generating Base64 & dominant colours palette for a jpeg image imported in your JS bundle: +Generating Base64 for a jpeg image imported in your JS bundle: > The large image file will be emitted & only 400byte of Base64 (if set to true in the loader options) will be bundled. @@ -25,9 +25,7 @@ Generating Base64 & dominant colours palette for a jpeg image imported in your J loader: '@docusaurus/lqip-loader', options: { path: '/media', // your image going to be in media folder in the output dir - name: '[name].[ext]', // you can use [hash].[ext] too if you wish, - base64: true, // default: true, gives the base64 encoded image - palette: true // default: false, gives the dominant colours palette + name: '[name].[ext]', // you can use [contenthash].[ext] too if you wish, } } ] @@ -35,13 +33,7 @@ Generating Base64 & dominant colours palette for a jpeg image imported in your J // OPTION B: Chained with your own url-loader or file-loader test: /\.(png|jpe?g)$/, loaders: [ - { - loader: '@docusaurus/lqip-loader', - options: { - base64: true, - palette: false - } - }, + '@docusaurus/lqip-loader', { loader: 'url-loader', options: { @@ -60,9 +52,6 @@ import banner from './images/banner.jpg'; console.log(banner.preSrc); // outputs: "data:image/jpeg;base64,/9j/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhY.... -// the object will have palette property, array will be sorted from most dominant colour to the least -console.log(banner.palette); // [ '#628792', '#bed4d5', '#5d4340', '#ba454d', '#c5dce4', '#551f24' ] - console.log(banner.src); // that's the original image URL to load later! ``` @@ -78,8 +67,6 @@ img { More history about the issue can be [found here](https://bugs.chromium.org/p/chromium/issues/detail?id=771110#c3) and [here](https://groups.google.com/a/chromium.org/forum/#!topic/blink-dev/6L_3ZZeuA0M). -Alternatively, you can fill the container with a really cheap colour or gradient from the amazing palette we provide. - ## Credits This package has been imported from [`@endiliey/lqip-loader`](https://github.com/endiliey/lqip-loader) which was a fork of the original [`lqip-loader`](https://github.com/zouhir/lqip-loader) created exclusively for Docusaurus. diff --git a/packages/lqip-loader/package.json b/packages/lqip-loader/package.json index 15836d455082..7c524b8bf7ba 100644 --- a/packages/lqip-loader/package.json +++ b/packages/lqip-loader/package.json @@ -1,6 +1,6 @@ { "name": "@docusaurus/lqip-loader", - "version": "2.0.0-beta.14", + "version": "2.0.0-beta.18", "description": "Low Quality Image Placeholders (LQIP) loader for webpack.", "main": "lib/index.js", "publishConfig": { @@ -17,16 +17,16 @@ }, "license": "MIT", "dependencies": { + "@docusaurus/logger": "2.0.0-beta.18", "file-loader": "^6.2.0", - "lodash": "^4.17.20", - "node-vibrant": "^3.1.5", - "sharp": "^0.29.1", + "lodash": "^4.17.21", + "sharp": "^0.30.3", "tslib": "^2.3.1" }, "engines": { "node": ">=14" }, "devDependencies": { - "@types/sharp": "^0.29.2" + "@types/sharp": "^0.30.0" } } diff --git a/packages/lqip-loader/src/__tests__/index.test.ts b/packages/lqip-loader/src/__tests__/index.test.ts deleted file mode 100644 index a46bd0665efd..000000000000 --- a/packages/lqip-loader/src/__tests__/index.test.ts +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import path from 'path'; -import Vibrant from 'node-vibrant'; -import type {Palette} from 'node-vibrant/lib/color'; - -import {toPalette, toBase64} from '../utils'; -import * as lqip from '../lqip'; - -describe('lqip-loader', () => { - describe('toBase64', () => { - test('should return a properly formatted Base64 image string', () => { - const expected = 'data:image/jpeg;base64,aGVsbG8gd29ybGQ='; - const mockedMimeType = 'image/jpeg'; - const mockedBase64Data = Buffer.from('hello world'); - expect(toBase64(mockedMimeType, mockedBase64Data)).toEqual(expected); - }); - }); - - describe('toPalette', () => { - let correctTestSwatch: Palette = {}; - let testSwatchWithNull: Palette & {Vibrant?: null} = {}; - - beforeAll(() => { - const imgPath = path.join(__dirname, '__fixtures__', 'endi.jpg'); - const vibrant = new Vibrant(imgPath, {}); - - return vibrant.getPalette().then((palette) => { - correctTestSwatch = {...palette}; - testSwatchWithNull = {...palette, Vibrant: null}; - }); - }); - - it('should return 6 hex colours sorted by popularity', () => { - expect(toPalette(correctTestSwatch)).toHaveLength(6); - }); - - it('should return 5 hex colours with no errors if a palette was incomplete', () => { - expect(toPalette(testSwatchWithNull)).toHaveLength(5); - }); - }); - - describe('lqip library', () => { - const imgPath = path.join(__dirname, '__fixtures__', 'endi.jpg'); - const invalidPath = path.join(__dirname, '__fixtures__', 'docusaurus.svg'); - - it('should reject unknown or unsupported file format', async () => { - await expect(lqip.base64(invalidPath)).rejects.toBeTruthy(); - }); - - it('should generate a valid base64', async () => { - const expectedBase64 = 'data:image/jpeg;base64,/9j/2wBDA'; - await expect(lqip.base64(imgPath)).resolves.toContain(expectedBase64); - }); - - it('should generate a valid color palette', async () => { - const imgPalette = await lqip.palette(imgPath); - expect(imgPalette).toHaveLength(6); - expect(imgPalette).toContain('#578ca1'); - }); - }); -}); diff --git a/packages/lqip-loader/src/__tests__/lqip.test.ts b/packages/lqip-loader/src/__tests__/lqip.test.ts new file mode 100644 index 000000000000..a433f0f0459c --- /dev/null +++ b/packages/lqip-loader/src/__tests__/lqip.test.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import path from 'path'; +import {base64} from '../lqip'; + +const imgPath = path.join(__dirname, '__fixtures__', 'endi.jpg'); +const invalidPath = path.join(__dirname, '__fixtures__', 'docusaurus.svg'); + +describe('base64', () => { + it('rejects unknown or unsupported file format', async () => { + await expect(base64(invalidPath)).rejects.toThrow( + /Error: Input file is missing or uses unsupported image format, lqip v.*/, + ); + }); + + it('generates a valid base64', async () => { + const expectedBase64 = 'data:image/jpeg;base64,/9j/2wBDA'; + await expect(base64(imgPath)).resolves.toContain(expectedBase64); + }); +}); diff --git a/packages/lqip-loader/src/index.ts b/packages/lqip-loader/src/index.ts index 7f20e3b2408b..112851a34c01 100644 --- a/packages/lqip-loader/src/index.ts +++ b/packages/lqip-loader/src/index.ts @@ -13,7 +13,7 @@ type Options = { palette: boolean; }; -async function lqipLoader( +export default async function lqipLoader( this: LoaderContext, contentBuffer: Buffer, ): Promise { @@ -22,62 +22,39 @@ async function lqipLoader( } const callback = this.async(); const imgPath = this.resourcePath; - - const config = this.getOptions() || {}; - config.base64 = 'base64' in config ? config.base64 : true; - config.palette = 'palette' in config ? config.palette : false; - let content = contentBuffer.toString('utf8'); const contentIsUrlExport = - /^(?:export default|module.exports =) "data:(.*)base64,(.*)/.test(content); - const contentIsFileExport = /^(?:export default|module.exports =) (.*)/.test( + /^(?:export default|module.exports =) "data:.*base64,.*/.test(content); + const contentIsFileExport = /^(?:export default|module.exports =) .*/.test( content, ); let source = ''; - const SOURCE_CHUNK = 1; if (contentIsUrlExport) { - source = content.match(/^(?:export default|module.exports =) (.*)/)![ - SOURCE_CHUNK - ]; + source = content.match( + /^(?:export default|module.exports =) (?.*)/, + )!.groups!.source!; } else { if (!contentIsFileExport) { // eslint-disable-next-line global-require, @typescript-eslint/no-var-requires const fileLoader = require('file-loader'); content = fileLoader.call(this, contentBuffer); } - source = content.match(/^(?:export default|module.exports =) (.*);/)![ - SOURCE_CHUNK - ]; + source = content.match( + /^(?:export default|module.exports =) (?.*);/, + )!.groups!.source!; } - const outputPromises: [Promise | null, Promise | null] = [ - config.base64 === true ? lqip.base64(imgPath) : null, - // color palette generation is set to false by default - // since it is little bit slower than base64 generation - config.palette === true ? lqip.palette(imgPath) : null, - ]; - try { - const data = await Promise.all(outputPromises); - if (data) { - const [preSrc, palette] = data; - const finalObject = JSON.stringify({src: 'STUB', preSrc, palette}); - const result = `module.exports = ${finalObject.replace( - '"STUB"', - source, - )};`; - callback(null, result); - } else { - callback(new Error('ERROR'), undefined); - } - } catch (error) { - console.error(error); + const preSrc = await lqip.base64(imgPath); + const finalObject = JSON.stringify({src: 'STUB', preSrc}); + const result = `module.exports = ${finalObject.replace('"STUB"', source)};`; + callback(null, result); + } catch (err) { + console.error(err); callback(new Error('ERROR'), undefined); } } lqipLoader.raw = true; - -export default lqipLoader; diff --git a/packages/lqip-loader/src/lqip.ts b/packages/lqip-loader/src/lqip.ts index 7387cdf6cc4f..f6ab095a1868 100644 --- a/packages/lqip-loader/src/lqip.ts +++ b/packages/lqip-loader/src/lqip.ts @@ -5,48 +5,42 @@ * LICENSE file in the root directory of this source tree. */ -import Vibrant from 'node-vibrant'; +import logger from '@docusaurus/logger'; import path from 'path'; import sharp from 'sharp'; -import {toPalette, toBase64} from './utils'; // eslint-disable-next-line @typescript-eslint/no-var-requires const {version} = require('../package.json'); const ERROR_EXT = `Error: Input file is missing or uses unsupported image format, lqip v${version}`; -const SUPPORTED_MIMES: Record = { +const SUPPORTED_MIMES: {[ext: string]: string} = { jpeg: 'image/jpeg', jpg: 'image/jpeg', png: 'image/png', }; -async function base64(file: string): Promise { - let extension = path.extname(file) || ''; +/** + * it returns a Base64 image string with required formatting + * to work on the web ( or in CSS url('..')) + */ +const toBase64 = (extMimeType: string, data: Buffer): string => + `data:${extMimeType};base64,${data.toString('base64')}`; + +export async function base64(file: string): Promise { + let extension = path.extname(file); extension = extension.split('.').pop()!; + const mime = SUPPORTED_MIMES[extension]; - if (!SUPPORTED_MIMES[extension]) { + if (!mime) { throw new Error(ERROR_EXT); } - const data = await sharp(file).resize(10).toBuffer(); - if (data) { - return toBase64(SUPPORTED_MIMES[extension], data); - } - throw new Error('Unhandled promise rejection in base64 promise'); -} - -async function palette(file: string): Promise { - const vibrant = new Vibrant(file, {}); - const pal = await vibrant.getPalette(); - if (pal) { - return toPalette(pal); + try { + const data = await sharp(file).resize(10).toBuffer(); + return toBase64(mime, data); + } catch (err) { + logger.error`Generation of base64 failed for image path=${file}.`; + throw err; } - throw new Error(`Unhandled promise rejection in colorPalette ${pal}`); } - -process.on('unhandledRejection', (up) => { - throw up; -}); - -export {base64, palette}; diff --git a/packages/lqip-loader/src/utils.ts b/packages/lqip-loader/src/utils.ts deleted file mode 100644 index 558ddb71a7a8..000000000000 --- a/packages/lqip-loader/src/utils.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import {sortBy} from 'lodash'; -import type {Palette} from 'node-vibrant/lib/color'; - -/** - * it returns a Base64 image string with required formatting - * to work on the web ( or in CSS url('..')) - */ -const toBase64 = (extMimeType: string, data: Buffer): string => - `data:${extMimeType};base64,${data.toString('base64')}`; - -/** - * takes a color swatch object, converts it to an array & returns - * only hex color - */ -const toPalette = (swatch: Palette): string[] => { - let palette = Object.keys(swatch).reduce((result, key) => { - if (swatch[key] !== null) { - result.push({ - popularity: swatch[key]!.getPopulation(), - hex: swatch[key]!.getHex(), - }); - } - return result; - }, [] as {popularity: number; hex: string}[]); - palette = sortBy(palette, ['popularity']); - return palette.map((color) => color.hex).reverse(); -}; - -export {toBase64, toPalette}; diff --git a/packages/stylelint-copyright/__tests__/index.test.js b/packages/stylelint-copyright/__tests__/index.test.js index 4ba700850657..20de7dbae8f2 100644 --- a/packages/stylelint-copyright/__tests__/index.test.js +++ b/packages/stylelint-copyright/__tests__/index.test.js @@ -13,19 +13,20 @@ const rule = require('..'); const {ruleName, messages} = rule; function getOutputCss(output) { + // eslint-disable-next-line no-underscore-dangle const result = output.results[0]._postcssResult; return result.root.toString(result.opts.syntax); } function testStylelintRule(config, tests) { - describe(tests.ruleName, () => { + describe(`${tests.ruleName}`, () => { const checkTestCaseContent = (testCase) => testCase.description || testCase.code || 'no description'; - if (tests.accept && tests.accept.length) { + if (tests.accept?.length) { describe('accept cases', () => { tests.accept.forEach((testCase) => { - test(checkTestCaseContent(testCase), async () => { + it(`${checkTestCaseContent(testCase)}`, async () => { const options = { code: testCase.code, config, @@ -39,16 +40,16 @@ function testStylelintRule(config, tests) { } const fixedOutput = await stylelint.lint({...options, fix: true}); const fixedCode = getOutputCss(fixedOutput); - expect(fixedCode).toBe(testCase.fixed); + expect(fixedCode).toBe(testCase.code); }); }); }); } - if (tests.reject && tests.reject.length) { + if (tests.reject?.length) { describe('reject cases', () => { tests.reject.forEach((testCase) => { - test(checkTestCaseContent(testCase), async () => { + it(`${checkTestCaseContent(testCase)}`, async () => { const options = { code: testCase.code, config, @@ -112,22 +113,63 @@ testStylelintRule( }, { ruleName, - fix: false, + fix: true, accept: [ { code: ` /** * Copyright */ +.foo {}`, + }, + { + code: `/** + * Copyright + */ - .foo {}`, +.foo {}`, + }, + { + code: `/** + * Copyright + */ +.foo {}`, }, ], reject: [ + { + code: `.foo {}`, + fixed: `/** + * Copyright + */ +.foo {}`, + message: messages.rejected, + line: 1, + column: 1, + }, { code: ` - /** -* copyright +.foo {}`, + fixed: `/** + * Copyright + */ +.foo {}`, + message: messages.rejected, + line: 1, + column: 1, + }, + { + code: `/** +* Copyright +*/ + +.foo {}`, + fixed: `/** + * Copyright + */ + +/** +* Copyright */ .foo {}`, @@ -136,18 +178,37 @@ testStylelintRule( column: 1, }, { - code: ` + code: `/** + * Copyleft + */ + +.foo {}`, + fixed: `/** + * Copyright + */ + /** * Copyleft */ - .foo {}`, +.foo {}`, message: messages.rejected, line: 1, column: 1, }, { - code: ` + code: `/** + * Copyleft + */ + +/** + * Copyright + */ + .foo {}`, + fixed: `/** + * Copyright + */ + /** * Copyleft */ diff --git a/packages/stylelint-copyright/index.js b/packages/stylelint-copyright/index.js index adf34dd2fd7d..fdf2ab794ba9 100644 --- a/packages/stylelint-copyright/index.js +++ b/packages/stylelint-copyright/index.js @@ -15,7 +15,7 @@ const messages = stylelint.utils.ruleMessages(ruleName, { const plugin = stylelint.createPlugin( ruleName, (primaryOption, secondaryOption, context) => (root, result) => { - const validOptions = stylelint.utils.validateOptions( + stylelint.utils.validateOptions( result, ruleName, { @@ -28,10 +28,6 @@ const plugin = stylelint.createPlugin( }, ); - if (!validOptions) { - return; - } - if ( root.first && root.first.type === 'comment' && diff --git a/packages/stylelint-copyright/package.json b/packages/stylelint-copyright/package.json index ded739db8a63..8c669225c399 100644 --- a/packages/stylelint-copyright/package.json +++ b/packages/stylelint-copyright/package.json @@ -1,16 +1,15 @@ { "name": "stylelint-copyright", - "version": "2.0.0-beta.14", + "version": "2.0.0-beta.18", "description": "Stylelint plugin to check CSS files for a copyright header.", "main": "index.js", "license": "MIT", - "private": true, "repository": { "type": "git", "url": "https://github.com/facebook/docusaurus.git", "directory": "packages/stylelint-copyright" }, "dependencies": { - "stylelint": "^14.3.0" + "stylelint": "^14.6.1" } } diff --git a/project-words.txt b/project-words.txt index 121100fa1f17..1c730dc2c574 100644 --- a/project-words.txt +++ b/project-words.txt @@ -5,6 +5,7 @@ alexey algoliasearch anonymized anshul +août apfs apos appinstalled @@ -12,6 +13,7 @@ applanga architecting astro atrule +autoconverted autogen autogenerating backport @@ -38,7 +40,10 @@ chedeau cheng clément clsx +codegen +codeql codesandbox +codespaces contravariance corejs crawlable @@ -48,10 +53,14 @@ customizability daishi datagit datas +dbaeumer +décembre dedup deduplicated déja deps +devcontainers +devspace devto dmitry docgen @@ -63,6 +72,7 @@ docz doesn dogfood dogfooding +dojocat dyte easyops endi @@ -70,12 +80,14 @@ endilie endiliey entrypoints errnametoolong +esbenp esbuild eslintcache evaluable externalwaiting failfast fbid +février fienny flac formik @@ -89,7 +101,11 @@ globby goss goyal gtag +hahaha +hardcoding héctor +héllô +heuristical hideable hola horiz @@ -104,8 +120,10 @@ intelli interpolatable jakepartusch jamstack +janvier javadoc jmarcey +joshcena jscodeshift jssdk kaszubowski @@ -132,6 +150,7 @@ marocchino massoud mathjax mdast +mdxa mdxast mdxhast metadatum @@ -169,6 +188,7 @@ opensearch opensearchdescription optimizt optind +orta overrideable pageview palenight @@ -181,6 +201,7 @@ peaceiris philpl photoshop picocolors +picomatch pluggable plushie pnpm @@ -211,6 +232,7 @@ quddus quddús quotify ramón +reactjs rearchitecture recrawl redirections @@ -227,22 +249,29 @@ roadmap rocketvalidator rtcts rtlcss -sàáâãäåçèéêëìíîïðòóôõöùúûüýÿ scaleway searchbar sebastien sébastien sebastienlorber sensical +serializers setaf +setext sida simen slorber +sluggified +sluggifies +sluggify +spâce stackblitz stackblitzrc +strikethrough strikethroughs stylelint stylelintrc +subdir sublabel sublicensable sublist @@ -250,30 +279,41 @@ subpage subroute subroutes subsetting +subsubcategory +subsubfolder +subsubsection +subsubsubfolder sucipto supabase svgr swizzlable teik templating +thanos toolset toplevel transifex transpiles +treeified +treeifies treeify treosh triaging typecheck +typechecks +typedoc typesense unflat unist unlocalized +unmatch unnormalized unoptimized unprefixed unswizzle unversioned upvotes +urlset userland vannicatte vercel @@ -287,6 +327,7 @@ vjeux waivable wcag webfactory +webp webpackbar wolcott writeups diff --git a/tsconfig.json b/tsconfig.json index 61e2b2df3107..cb10e8a5ccbc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,42 +1,48 @@ { "compilerOptions": { - "target": "ES2019", + /* Emit */ + "target": "ES2020", "module": "commonjs", "lib": ["ESNext", "DOM"], "declaration": true, "declarationMap": false, "jsx": "react", + "importHelpers": true, + "noEmitHelpers": true, /* Strict Type-Checking Options */ + "allowUnreachableCode": false, + // Too hard to turn on + "exactOptionalPropertyTypes": false, + "noFallthroughCasesInSwitch": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + // `process.env` is usually accessed as property + "noPropertyAccessFromIndexSignature": false, + "noUncheckedIndexedAccess": true, + /* strict family */ "strict": true, - "strictNullChecks": true, - "strictFunctionTypes": true, + "alwaysStrict": true, + "noImplicitAny": true, + "noImplicitThis": true, "strictBindCallApply": true, + "strictFunctionTypes": true, + "strictNullChecks": true, "strictPropertyInitialization": true, - "noImplicitThis": true, - "alwaysStrict": true, - - /* Additional Checks */ - "noUnusedLocals": false, // ensured by eslint, should not block compilation - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, - - /* Disabled on purpose (handled by ESLint, should not block compilation) */ + "useUnknownInCatchVariables": true, + /* Handled by ESLint */ + "noUnusedLocals": false, "noUnusedParameters": false, + "importsNotUsedAsValues": "remove", - /* Module Resolution Options */ + /* Module Resolution */ "moduleResolution": "node", + "resolveJsonModule": true, "allowSyntheticDefaultImports": true, "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, "isolatedModules": true, - - /* Advanced Options */ - "resolveJsonModule": true, - "skipLibCheck": true, // @types/webpack and webpack/types.d.ts are not the same thing - - /* Use tslib */ - "importHelpers": true, - "noEmitHelpers": true + "skipLibCheck": true // @types/webpack and webpack/types.d.ts are not the same thing }, "exclude": ["node_modules", "**/__tests__/**/*", "**/lib/**/*"] } diff --git a/website/_dogfooding/_blog tests/2021-08-21-blog-post-toc-tests.mdx b/website/_dogfooding/_blog tests/2021-08-21-blog-post-toc-tests.mdx index 2245ed3dd1b9..4264d1a8d667 100644 --- a/website/_dogfooding/_blog tests/2021-08-21-blog-post-toc-tests.mdx +++ b/website/_dogfooding/_blog tests/2021-08-21-blog-post-toc-tests.mdx @@ -4,6 +4,7 @@ authors: - slorber toc_min_heading_level: 2 toc_max_heading_level: 4 +tags: [paginated-tag] --- diff --git a/website/_dogfooding/_blog tests/2021-08-22-no-author.md b/website/_dogfooding/_blog tests/2021-08-22-no-author.md index 497129fed070..9a04406bf877 100644 --- a/website/_dogfooding/_blog tests/2021-08-22-no-author.md +++ b/website/_dogfooding/_blog tests/2021-08-22-no-author.md @@ -1,3 +1,7 @@ +--- +tags: [paginated-tag] +--- + # Hmmm! This is a blog post from an anonymous author! diff --git a/website/_dogfooding/_blog tests/2021-08-23-multiple-authors.md b/website/_dogfooding/_blog tests/2021-08-23-multiple-authors.md index 81f0e3ab05f5..0c7c39795551 100644 --- a/website/_dogfooding/_blog tests/2021-08-23-multiple-authors.md +++ b/website/_dogfooding/_blog tests/2021-08-23-multiple-authors.md @@ -8,6 +8,7 @@ tags: [ blog, docusaurus, + paginated-tag, long, long-long, long-long-long, diff --git a/website/_dogfooding/_blog tests/2021-09-13-dup-title.md b/website/_dogfooding/_blog tests/2021-09-13-dup-title.md index 82d4aea512d5..721fff002142 100644 --- a/website/_dogfooding/_blog tests/2021-09-13-dup-title.md +++ b/website/_dogfooding/_blog tests/2021-09-13-dup-title.md @@ -1,3 +1,15 @@ +--- +tags: [paginated-tag] +authors: + - name: Josh-Cena1 + - name: Josh-Cena2 + image_url: https://github.com/Josh-Cena.png + - name: Josh-Cena3 + url: https://github.com/Josh-Cena + - name: Josh-Cena4 + email: sidechen2003@gmail.com +--- + # Post with duplicate title See https://github.com/facebook/docusaurus/issues/6059. This one and [2021-11-13-dup-title.md](./2021-11-13-dup-title.md) should both show up. diff --git a/website/_dogfooding/_blog tests/2021-10-07-blog-post-mdx-feed-tests.mdx b/website/_dogfooding/_blog tests/2021-10-07-blog-post-mdx-feed-tests.mdx index 005d1b52e81b..a171eefe3f95 100644 --- a/website/_dogfooding/_blog tests/2021-10-07-blog-post-mdx-feed-tests.mdx +++ b/website/_dogfooding/_blog tests/2021-10-07-blog-post-mdx-feed-tests.mdx @@ -2,7 +2,15 @@ title: Blog post MDX Feed tests authors: - slorber -tags: [blog, docusaurus, long-long, long-long-long, long-long-long-long] +tags: + [ + paginated-tag, + blog, + docusaurus, + long-long, + long-long-long, + long-long-long-long, + ] hide_reading_time: true --- diff --git a/website/_dogfooding/_blog tests/2021-10-08-blog-post-mdx-require-feed-tests.mdx b/website/_dogfooding/_blog tests/2021-10-08-blog-post-mdx-require-feed-tests.mdx index 29c0cd60fa84..1485ef081753 100644 --- a/website/_dogfooding/_blog tests/2021-10-08-blog-post-mdx-require-feed-tests.mdx +++ b/website/_dogfooding/_blog tests/2021-10-08-blog-post-mdx-require-feed-tests.mdx @@ -2,7 +2,15 @@ title: Blog post MDX require Feed tests authors: - slorber -tags: [blog, docusaurus, long-long, long-long-long, long-long-long-long] +tags: + [ + paginated-tag, + blog, + docusaurus, + long-long, + long-long-long, + long-long-long-long, + ] --- Some MDX tests, mostly to test how the RSS feed render those diff --git a/website/_dogfooding/_blog tests/2021-11-13-dup-title.md b/website/_dogfooding/_blog tests/2021-11-13-dup-title.md index 69d9b04f662b..e5bf266ef200 100644 --- a/website/_dogfooding/_blog tests/2021-11-13-dup-title.md +++ b/website/_dogfooding/_blog tests/2021-11-13-dup-title.md @@ -1,3 +1,7 @@ +--- +tags: [paginated-tag] +--- + # Post with duplicate title I hope I'm still here diff --git a/website/_dogfooding/_blog tests/2022-01-20-image-only-authors.md b/website/_dogfooding/_blog tests/2022-01-20-image-only-authors.md index 6969846124a9..762f9db4b488 100644 --- a/website/_dogfooding/_blog tests/2022-01-20-image-only-authors.md +++ b/website/_dogfooding/_blog tests/2022-01-20-image-only-authors.md @@ -40,6 +40,7 @@ authors: url: https://github.com/anshulrgoyal - image_url: https://github.com/italicize.png url: https://github.com/italicize +tags: [paginated-tag] --- # Image-only authors diff --git a/website/_dogfooding/_blog tests/demo/2020-08-03-second-blog-intro.md b/website/_dogfooding/_blog tests/demo/2020-08-03-second-blog-intro.md index 391c91f49658..c21273c78a17 100644 --- a/website/_dogfooding/_blog tests/demo/2020-08-03-second-blog-intro.md +++ b/website/_dogfooding/_blog tests/demo/2020-08-03-second-blog-intro.md @@ -1,7 +1,7 @@ --- title: Using twice the blog plugin authors: [slorber] -tags: [blog, docusaurus] +tags: [paginated-tag, blog, docusaurus] --- Did you know you can use multiple instances of the same plugin? diff --git a/website/_dogfooding/_docs tests/tests/Case-Sentitive-Doc.md b/website/_dogfooding/_docs tests/tests/Case-Sentitive-Doc.md new file mode 100644 index 000000000000..58de34b32e24 --- /dev/null +++ b/website/_dogfooding/_docs tests/tests/Case-Sentitive-Doc.md @@ -0,0 +1,5 @@ +# Case-Sensitive doc + +This doc has uppercase and lowercase chars in its filename, and thus in its path / slug. + +It should still work fine if the doc is server from a lowercase/uppercase path. diff --git a/website/_dogfooding/_docs tests/tests/category-links/no-index-doc/_category_.json b/website/_dogfooding/_docs tests/tests/category-links/no-index-doc/_category_.json new file mode 100644 index 000000000000..b69769463655 --- /dev/null +++ b/website/_dogfooding/_docs tests/tests/category-links/no-index-doc/_category_.json @@ -0,0 +1,3 @@ +{ + "link": null +} diff --git a/website/_dogfooding/_docs tests/tests/category-links/no-index-doc/index.md b/website/_dogfooding/_docs tests/tests/category-links/no-index-doc/index.md new file mode 100644 index 000000000000..c24cb177cdf7 --- /dev/null +++ b/website/_dogfooding/_docs tests/tests/category-links/no-index-doc/index.md @@ -0,0 +1,3 @@ +# Index + +This file (`index.md`) is supposed to be a category index, but it isn't because we have set `link: null` in the `_category_.json`. diff --git a/website/_dogfooding/_docs tests/tests/category-links/no-index-doc/sample-doc.md b/website/_dogfooding/_docs tests/tests/category-links/no-index-doc/sample-doc.md new file mode 100644 index 000000000000..15fb7937c350 --- /dev/null +++ b/website/_dogfooding/_docs tests/tests/category-links/no-index-doc/sample-doc.md @@ -0,0 +1,3 @@ +# Sample doc + +Lorem Ipsum diff --git a/website/_dogfooding/_docs tests/tests/category-links/no-subdoc/index.md b/website/_dogfooding/_docs tests/tests/category-links/no-subdoc/index.md new file mode 100644 index 000000000000..1f02e8af26e7 --- /dev/null +++ b/website/_dogfooding/_docs tests/tests/category-links/no-subdoc/index.md @@ -0,0 +1,3 @@ +# No sub-docs + +The only doc of this category is the index page. It should show up as a regular doc link. diff --git a/website/_dogfooding/_docs tests/tests/custom-props/_category_.json b/website/_dogfooding/_docs tests/tests/custom-props/_category_.json new file mode 100644 index 000000000000..844af9fb2bcf --- /dev/null +++ b/website/_dogfooding/_docs tests/tests/custom-props/_category_.json @@ -0,0 +1,3 @@ +{ + "label": "Custom Props" +} diff --git a/website/_dogfooding/_docs tests/tests/custom-props/doc-with-custom-props.md b/website/_dogfooding/_docs tests/tests/custom-props/doc-with-custom-props.md new file mode 100644 index 000000000000..486859a3557b --- /dev/null +++ b/website/_dogfooding/_docs tests/tests/custom-props/doc-with-custom-props.md @@ -0,0 +1,10 @@ +--- +sidebar_custom_props: + prop: custom + number: 1 + boolean: true +--- + +# Doc with Custom Props + +This doc has custom props. diff --git a/website/_dogfooding/_docs tests/tests/custom-props/doc-without-custom-props.md b/website/_dogfooding/_docs tests/tests/custom-props/doc-without-custom-props.md new file mode 100644 index 000000000000..2fc07a1e0a4b --- /dev/null +++ b/website/_dogfooding/_docs tests/tests/custom-props/doc-without-custom-props.md @@ -0,0 +1,3 @@ +# Doc Without Custom Props + +This doc doesn't have custom props. diff --git a/website/_dogfooding/_docs tests/tests/custom-props/index.md b/website/_dogfooding/_docs tests/tests/custom-props/index.md new file mode 100644 index 000000000000..5204dd5cccf5 --- /dev/null +++ b/website/_dogfooding/_docs tests/tests/custom-props/index.md @@ -0,0 +1,22 @@ +# Custom Props + +```mdx-code-block +import {useCurrentSidebarCategory} from '@docusaurus/theme-common'; + +export const DocPropsList = ({items}) => ( + + + + + + {items.map((item, index) => ( + + + + + ))} +
      Doc PageCustom Props
      {item.label}{JSON.stringify(item.customProps)}
      +); + + +``` diff --git a/website/_dogfooding/_docs tests/tests/img-tests.mdx b/website/_dogfooding/_docs tests/tests/img-tests.mdx index 885500b6a32e..bddaf876340e 100644 --- a/website/_dogfooding/_docs tests/tests/img-tests.mdx +++ b/website/_dogfooding/_docs tests/tests/img-tests.mdx @@ -1,3 +1,7 @@ +--- +image: ./img/oss_logo.png +--- + # Image tests import Image from '@theme/IdealImage'; @@ -6,6 +10,8 @@ import docusaurusImport from '@site/static/img/docusaurus.png'; export const docusaurusRequire = require('@site/static/img/docusaurus.png'); +![URL encoded image](./img/oss_logo%20%282%29.png) + ## Regular images diff --git a/website/_dogfooding/_docs tests/tests/img/oss_logo (2).png b/website/_dogfooding/_docs tests/tests/img/oss_logo (2).png new file mode 100644 index 000000000000..81923fc56250 Binary files /dev/null and b/website/_dogfooding/_docs tests/tests/img/oss_logo (2).png differ diff --git a/website/_dogfooding/_docs tests/tests/img/oss_logo.png b/website/_dogfooding/_docs tests/tests/img/oss_logo.png new file mode 100644 index 000000000000..81923fc56250 Binary files /dev/null and b/website/_dogfooding/_docs tests/tests/img/oss_logo.png differ diff --git a/website/_dogfooding/_pages tests/code-block-tests.mdx b/website/_dogfooding/_pages tests/code-block-tests.mdx index 6cf9ee67f629..4a0355144caf 100644 --- a/website/_dogfooding/_pages tests/code-block-tests.mdx +++ b/website/_dogfooding/_pages tests/code-block-tests.mdx @@ -3,6 +3,14 @@ import BrowserWindow from '@site/src/components/BrowserWindow'; # Code block tests +```java +class HelloWorld { + public static void main(String args[]) { + System.out.println("Hello, World"); + } +} +``` + See: - https://github.com/facebook/docusaurus/pull/1584 diff --git a/website/_dogfooding/_pages tests/index.md b/website/_dogfooding/_pages tests/index.md index 62180473501b..cf94300fb891 100644 --- a/website/_dogfooding/_pages tests/index.md +++ b/website/_dogfooding/_pages tests/index.md @@ -21,6 +21,7 @@ import Readme from "../README.md" ### Other tests - [Code block tests](/tests/pages/code-block-tests) +- [Link tests](/tests/pages/link-tests) - [Error boundary tests](/tests/pages/error-boundary-tests) - [Hydration tests](/tests/pages/hydration-tests) - [Asset linking tests](/tests/pages/markdown-tests) diff --git a/website/_dogfooding/_pages tests/link-tests.tsx b/website/_dogfooding/_pages tests/link-tests.tsx new file mode 100644 index 000000000000..d0afa2ef4f0a --- /dev/null +++ b/website/_dogfooding/_pages tests/link-tests.tsx @@ -0,0 +1,30 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, {useRef} from 'react'; +import Layout from '@theme/Layout'; +import Link from '@docusaurus/Link'; + +export default function LinkTest(): JSX.Element { + const anchorRef = useRef(null); + return ( + +
      + + A little link + + +
      +
      + ); +} diff --git a/website/_dogfooding/_pages tests/markdownPageTests.md b/website/_dogfooding/_pages tests/markdownPageTests.md index 2f7ecdcb8f78..0f1ba8ec8004 100644 --- a/website/_dogfooding/_pages tests/markdownPageTests.md +++ b/website/_dogfooding/_pages tests/markdownPageTests.md @@ -102,7 +102,7 @@ import MyComponentSource from '!!raw-loader!@site/src/pages/examples/\_myCompone ```jsx live function Demo() { - React.useEffect(() => console.log('mount'), []); + useEffect(() => console.log('mount'), []); return null; } ``` diff --git a/website/_dogfooding/docs-tests-sidebars.js b/website/_dogfooding/docs-tests-sidebars.js index 7e8a83ea49d8..804301319943 100644 --- a/website/_dogfooding/docs-tests-sidebars.js +++ b/website/_dogfooding/docs-tests-sidebars.js @@ -40,6 +40,15 @@ const sidebars = { collapsible: false, items: ['index', 'more-test'], }, + { + type: 'category', + label: 'Another category with index', + collapsible: false, + link: { + type: 'generated-index', + }, + items: ['more-test'], + }, { type: 'category', label: 'Huge sidebar category', @@ -71,6 +80,35 @@ const sidebars = { }, ], }, + { + type: 'category', + label: 'HTML items tests', + collapsed: false, + collapsible: false, + items: [ + // title + { + type: 'html', + value: 'Some Text', + defaultStyle: true, + }, + // Divider + { + type: 'html', + value: + '', + }, + // Image + { + type: 'html', + defaultStyle: true, + value: ` + Powered by + Docusaurus Logo + `, + }, + ], + }, ], anotherSidebar: ['dummy'], }; @@ -82,7 +120,13 @@ function generateHugeSidebarItems() { function generateRecursive(maxLevel, currentLevel = 0) { if (currentLevel === maxLevel) { - return []; + return [ + { + type: 'link', + href: '/', + label: `Link (level ${currentLevel + 1})`, + }, + ]; } const linkItems = [...Array(linksCount).keys()].map((index) => ({ diff --git a/website/_dogfooding/dogfooding.config.js b/website/_dogfooding/dogfooding.config.js index 3d087da41171..5d7cea7670ee 100644 --- a/website/_dogfooding/dogfooding.config.js +++ b/website/_dogfooding/dogfooding.config.js @@ -7,6 +7,18 @@ const fs = require('fs'); +/** @type {import('@docusaurus/types').PluginConfig[]} */ +const dogfoodingThemeInstances = [ + /** @type {import('@docusaurus/types').PluginModule} */ + function swizzleThemeTests() { + return { + name: 'swizzle-theme-tests', + getThemePath: () => './_swizzle_theme_tests/src/theme', + }; + }, +]; +exports.dogfoodingThemeInstances = dogfoodingThemeInstances; + /** @type {import('@docusaurus/types').PluginConfig[]} */ const dogfoodingPluginInstances = [ [ @@ -19,6 +31,7 @@ const dogfoodingPluginInstances = [ // Using a symlinked folder as source, test for use-case https://github.com/facebook/docusaurus/issues/3272 // The target folder uses a _ prefix to test against an edge case regarding MDX partials: https://github.com/facebook/docusaurus/discussions/5181#discussioncomment-1018079 + // eslint-disable-next-line no-restricted-properties path: fs.realpathSync('_dogfooding/docs-tests-symlink'), showLastUpdateTime: true, sidebarItemsGenerator(args) { diff --git a/website/_dogfooding/dogfooding.css b/website/_dogfooding/dogfooding.css new file mode 100644 index 000000000000..be6cf970b6f9 --- /dev/null +++ b/website/_dogfooding/dogfooding.css @@ -0,0 +1,22 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +html.plugin-docs.plugin-id-docs-tests .red > a { + color: red; +} + +html.plugin-docs.plugin-id-docs-tests .navbar { + border-bottom: solid thin cyan; +} + +html.plugin-blog.plugin-id-blog-tests .navbar { + border-bottom: solid thin lime; +} + +html.plugin-pages.plugin-id-pages-tests .navbar { + border-bottom: solid thin yellow; +} diff --git a/website/_dogfooding/testSwizzleThemeClassic.mjs b/website/_dogfooding/testSwizzleThemeClassic.mjs new file mode 100644 index 000000000000..61f1162b9848 --- /dev/null +++ b/website/_dogfooding/testSwizzleThemeClassic.mjs @@ -0,0 +1,156 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import path from 'path'; +import fs from 'fs-extra'; +import {fileURLToPath} from 'url'; +import logger from '@docusaurus/logger'; + +import ClassicTheme from '@docusaurus/theme-classic'; + +// Unsafe imports +import {readComponentNames} from '@docusaurus/core/lib/commands/swizzle/components.js'; +import {normalizeSwizzleConfig} from '@docusaurus/core/lib/commands/swizzle/config.js'; +import {wrap, eject} from '@docusaurus/core/lib/commands/swizzle/actions.js'; + +const swizzleConfig = normalizeSwizzleConfig(ClassicTheme.getSwizzleConfig()); + +const action = process.env.SWIZZLE_ACTION ?? 'eject'; +const typescript = process.env.SWIZZLE_TYPESCRIPT === 'true'; + +const dirname = path.dirname(fileURLToPath(import.meta.url)); + +const classicThemePathBase = path.join( + dirname, + '../../packages/docusaurus-theme-classic', +); + +const themePath = swizzleConfig + ? path.join(classicThemePathBase, 'src/theme') + : path.join(classicThemePathBase, 'lib-next/theme'); + +const toPath = path.join(dirname, '_swizzle_theme_tests'); + +console.log('\n'); +console.log('Swizzle test script'); +console.log('Args', { + action, + typescript, + dirname, + themePath, + toPath, + swizzleConfig, +}); +console.log('\n'); + +await fs.remove(toPath); + +let componentNames = await readComponentNames(themePath); + +const componentsNotFound = Object.keys(swizzleConfig.components).filter( + (componentName) => !componentNames.includes(componentName), +); +if (componentsNotFound.length > 0) { + logger.error( + `${ + componentsNotFound.length + } components exist in the swizzle config but do not exist in practice. +Please double-check or clean up these components from the config: +- ${componentsNotFound.join('\n- ')} +`, + ); + process.exit(1); +} + +// TODO temp workaround: non-comps should be forbidden to wrap +if (action === 'wrap') { + const WrapBlacklist = [ + 'Layout', // due to theme-fallback? + ]; + + componentNames = componentNames.filter((componentName) => { + const blacklisted = WrapBlacklist.includes(componentName); + if (!WrapBlacklist) { + logger.warn(`${componentName} is blacklisted and will not be wrapped`); + } + return !blacklisted; + }); +} + +function getActionStatus(componentName) { + const actionStatus = + swizzleConfig.components[componentName]?.actions[action] ?? 'unsafe'; + if (!actionStatus) { + throw new Error( + `Unexpected: missing action ${action} for ${componentName}`, + ); + } + return actionStatus; +} + +for (const componentName of componentNames) { + const executeAction = () => { + const baseParams = { + action, + siteDir: toPath, + themePath, + componentName, + }; + switch (action) { + case 'wrap': + return wrap({ + ...baseParams, + importType: 'init', // For these tests, "theme-original" imports are causing an expected infinite loop + typescript, + }); + case 'eject': + return eject(baseParams); + default: + throw new Error(`Unknown action: ${action}`); + } + }; + + const actionStatus = getActionStatus(componentName); + + if (actionStatus === 'forbidden') { + logger.warn( + `${componentName} is marked as forbidden for action ${action} => skipping`, + ); + continue; + } + + const result = await executeAction(); + + const safetyLog = + actionStatus === 'unsafe' ? logger.red('unsafe') : logger.green('safe'); + + console.log( + `${componentName} ${action} (${safetyLog}) => ${ + result.createdFiles.length + } file${result.createdFiles.length > 1 ? 's' : ''} written`, + ); +} + +logger.success(` +End of the Swizzle test script. +Now try to build the site and see if it works! +`); + +const componentsWithMissingConfigs = componentNames.filter( + (componentName) => !swizzleConfig.components[componentName], +); + +// TODO require theme exhaustive config, fail fast? +// (at least for our classic theme?) +// TODO provide util so that theme authors can also check exhaustiveness? +if (componentsWithMissingConfigs.length > 0) { + logger.warn( + `${componentsWithMissingConfigs.length} components have no swizzle config. +Sample: ${componentsWithMissingConfigs.slice(0, 5).join(', ')} ... +`, + ); +} diff --git a/website/blog/2022-01-24-docusaurus-2021-recap/index.md b/website/blog/2022-01-24-docusaurus-2021-recap/index.md index 91a286e5f7c0..7059860326ab 100644 --- a/website/blog/2022-01-24-docusaurus-2021-recap/index.md +++ b/website/blog/2022-01-24-docusaurus-2021-recap/index.md @@ -116,7 +116,7 @@ With the official release of Docusaurus 2.0, we are confident to see much more n We'd like to express our gratitude to [all the contributors in 2021](https://github.com/facebook/docusaurus/graphs/contributors?from=2021-01-01&to=2022-01-01&type=c), including: -- The core team: [Alexey Pyltsyn](https://github.com/lex111), [Sébastien Lorber](https://github.com/slorber), [Joshua Chen](https://github.com/Josh-Cena), and [Yangshun Tay](https://github/yangshun) for moderating the community, publicizing Docusaurus, triaging issues, and implementing new features +- The core team: [Alexey Pyltsyn](https://github.com/lex111), [Sébastien Lorber](https://github.com/slorber), [Joshua Chen](https://github.com/Josh-Cena), and [Yangshun Tay](https://github.com/yangshun) for moderating the community, publicizing Docusaurus, triaging issues, and implementing new features - [Joel Marcey](https://github.com/JoelMarcey) for creating Docusaurus and supporting its development all along - The Algolia team for helping Docusaurus users [migrate to the new DocSearch](../2021-11-21-algolia-docsearch-migration/index.md) and answering search-related questions - All the active community members for making valuable code contributions, improving our documentation, and answering questions on Discord diff --git a/website/blog/authors.yml b/website/blog/authors.yml index 42540c8100d0..b1beb5f8ec67 100644 --- a/website/blog/authors.yml +++ b/website/blog/authors.yml @@ -3,6 +3,7 @@ JMarcey: title: Technical Lead & Developer Advocate at Facebook url: http://twitter.com/JoelMarcey image_url: https://github.com/JoelMarcey.png + email: jimarcey@gmail.com twitter: JoelMarcey slorber: @@ -11,6 +12,7 @@ slorber: url: https://sebastienlorber.com image_url: https://github.com/slorber.png twitter: sebastienlorber + email: lorber.sebastien@gmail.com yangshun: name: Yangshun Tay @@ -18,15 +20,18 @@ yangshun: url: https://github.com/yangshun image_url: https://github.com/yangshun.png twitter: yangshunz + email: tay.yang.shun@gmail.com lex111: name: Alexey Pyltsyn title: Open-source enthusiast url: https://github.com/lex111 image_url: https://github.com/lex111.png + email: lex@php.net Josh-Cena: name: Joshua Chen title: Working hard on Docusaurus url: https://joshcena.com/ image_url: https://github.com/josh-cena.png + email: sidachen2003@gmail.com diff --git a/website/community/2-resources.md b/website/community/2-resources.md index 37176ac4adc1..1490a1b8de40 100644 --- a/website/community/2-resources.md +++ b/website/community/2-resources.md @@ -46,6 +46,7 @@ See the showcase - [mdx-mermaid](https://github.com/sjwall/mdx-mermaid) - A Docusaurus v2 compatible MDX plugin for displaying [Mermaid](https://mermaid-js.github.io/mermaid) diagrams - [redocusaurus](https://github.com/rohit-gohri/redocusaurus) - A Docusaurus preset for integrating OpenAPI documentation into your docs with [Redoc](https://github.com/redocly/redoc) - [plugin-image-zoom](https://github.com/flexanalytics/plugin-image-zoom) - An Image Zoom plugin for Docusaurus 2 +- [docusaurus-plugin-typedoc](https://github.com/tgreyuk/typedoc-plugin-markdown/tree/master/packages/docusaurus-plugin-typedoc) - A Docusaurus 2 plugin to build documentation with [TypeDoc](https://typedoc.org/) ## Enterprise usage {#enterprise-usage} diff --git a/website/community/5-changelog.md b/website/community/5-changelog.md deleted file mode 100644 index 468dbaefb653..000000000000 --- a/website/community/5-changelog.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -title: Docusaurus 2 Changelog -hide_title: true -sidebar_label: Changelog ---- - -```mdx-code-block -import Changelog, {toc as ChangelogTOC} from "@site/../CHANGELOG.md" - - - -export const toc = ChangelogTOC; -``` diff --git a/website/delayCrowdin.mjs b/website/delayCrowdin.mjs index 31063fc49f93..d212e0e7c9a0 100644 --- a/website/delayCrowdin.mjs +++ b/website/delayCrowdin.mjs @@ -8,8 +8,8 @@ /* We delay a bit the i18n staging deployment Because sometimes, prod + i18n-staging call this script at the exact same time -And then both try to dl the translations at the same time, and then we have a 409 error -This delay makes sure prod starts to dl the translations in priority +And then both try to dl the translations at the same time, and then we have a +409 error. This delay makes sure prod starts to dl the translations in priority Used in conjunction with waitForCrowdin.js (which is not enough) */ diff --git a/website/docs/_partials/swizzleWarning.mdx b/website/docs/_partials/swizzleWarning.mdx deleted file mode 100644 index e2aec479dd28..000000000000 --- a/website/docs/_partials/swizzleWarning.mdx +++ /dev/null @@ -1,5 +0,0 @@ -:::caution - -We discourage swizzling of components during the Docusaurus 2 beta phase. The theme components APIs are likely to evolve and have breaking changes. If possible, stick with the default appearance for now. - -::: diff --git a/website/docs/advanced/client.md b/website/docs/advanced/client.md new file mode 100644 index 000000000000..ef4c4d220c60 --- /dev/null +++ b/website/docs/advanced/client.md @@ -0,0 +1,121 @@ +--- +description: How the Docusaurus client is structured +--- + +# Client architecture + +## Theme aliases {#theme-aliases} + +A theme works by exporting a set of components, e.g. `Navbar`, `Layout`, `Footer`, to render the data passed down from plugins. Docusaurus and users use these components by importing them using the `@theme` webpack alias: + +```js +import Navbar from '@theme/Navbar'; +``` + +The alias `@theme` can refer to a few directories, in the following priority: + +1. A user's `website/src/theme` directory, which is a special directory that has the higher precedence. +2. A Docusaurus theme package's `theme` directory. +3. Fallback components provided by Docusaurus core (usually not needed). + +This is called a _layered architecture_: a higher-priority layer providing the component would shadow a lower-priority layer, making swizzling possible. Given the following structure: + +``` +website +├── node_modules +│ └── @docusaurus/theme-classic +│ └── theme +│ └── Navbar.js +└── src + └── theme + └── Navbar.js +``` + +`website/src/theme/Navbar.js` takes precedence whenever `@theme/Navbar` is imported. This behavior is called component swizzling. If you are familiar with Objective C where a function's implementation can be swapped during runtime, it's the exact same concept here with changing the target `@theme/Navbar` is pointing to! + +We already talked about how the "userland theme" in `src/theme` can re-use a theme component through the [`@theme-original`](#wrapping) alias. One theme package can also wrap a component from another theme, by importing the component from the initial theme, using the `@theme-init` import. + +Here's an example of using this feature to enhance the default theme `CodeBlock` component with a `react-live` playground feature. + +```js +import InitialCodeBlock from '@theme-init/CodeBlock'; +import React from 'react'; + +export default function CodeBlock(props) { + return props.live ? ( + + ) : ( + + ); +} +``` + +Check the code of `@docusaurus/theme-live-codeblock` for details. + +:::caution + +Unless you want to publish a re-usable "theme enhancer" (like `@docusaurus/theme-live-codeblock`), you likely don't need `@theme-init`. + +::: + +It can be quite hard to wrap your mind around these aliases. Let's imagine the following case with a super convoluted setup with three themes/plugins and the site itself all trying to define the same component. Internally, Docusaurus loads these themes as a "stack". + +```text ++-------------------------------------------------+ +| `website/src/theme/CodeBlock.js` | <-- `@theme/CodeBlock` always points to the top ++-------------------------------------------------+ +| `theme-live-codeblock/theme/CodeBlock/index.js` | <-- `@theme-original/CodeBlock` points to the topmost non-swizzled component ++-------------------------------------------------+ +| `plugin-awesome-codeblock/theme/CodeBlock.js` | ++-------------------------------------------------+ +| `theme-classic/theme/CodeBlock/index.js` | <-- `@theme-init/CodeBlock` always points to the bottom ++-------------------------------------------------+ +``` + +The components in this "stack" are pushed in the order of `preset plugins > preset themes > plugins > themes > site`, so the swizzled component in `website/src/theme` always comes out on top because it's loaded last. + +`@theme/*` always points to the topmost component—when `CodeBlock` is swizzled, all other components requesting `@theme/CodeBlock` receive the swizzled version. + +`@theme-original/*` always points to the topmost non-swizzled component. That's why you can import `@theme-original/CodeBlock` in the swizzled component—it points to the next one in the "component stack", a theme-provided one. Plugin authors should not try to use this because your component could be the topmost component and cause a self-import. + +`@theme-init/*` always points to the bottommost component—usually, this comes from the theme or plugin that first provides this component. Individual plugins / themes trying to enhance code block can safely use `@theme-init/CodeBlock` to get its basic version. Site creators should generally not use this because you likely want to enhance the _topmost_ instead of the _bottommost_ component. It's also possible that the `@theme-init/CodeBlock` alias does not exist at all—Docusaurus only creates it when it points to a different one from `@theme-original/CodeBlock`, i.e. when it's provided by more than one theme. We don't waste aliases! + +## Client modules {#client-modules} + +Client modules are part of your site's bundle, just like theme components. However, they are usually side-effect-ful. Client modules are anything that can be `import`ed by Webpack—CSS, JS, etc. JS scripts usually work on the global context, like registering event listeners, creating global variables... + +These modules are imported globally before React even renders the initial UI. + +```js title="App.tsx" +// How it works under the hood +import '@generated/client-modules'; +``` + +Plugins and sites can both declare client modules, through [`getClientModules`](../api/plugin-methods/lifecycle-apis.md#getClientModules) and [`siteConfig.clientModules`](../api/docusaurus.config.js.md#clientModules), respectively. + +Client modules are called during server-side rendering as well, so remember to check the [execution environment](./ssg.md#escape-hatches) before accessing client-side globals. + +```js title="mySiteGlobalJs.js" +import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment'; + +if (ExecutionEnvironment.canUseDOM) { + // As soon as the site loads in the browser, register a global event listener + window.addEventListener('keydown', (e) => { + if (e.code === 'Period') { + location.assign(location.href.replace('.com', '.dev')); + } + }); +} +``` + +CSS stylesheets imported as client modules are [global](../styling-layout.md#global-styles). + +```css title="mySiteGlobalCss.css" +/* This stylesheet is global. */ +.globalSelector { + color: red; +} +``` + + + diff --git a/website/docs/advanced/plugins.md b/website/docs/advanced/plugins.md index c309f4b53637..3e6e9f969f46 100644 --- a/website/docs/advanced/plugins.md +++ b/website/docs/advanced/plugins.md @@ -80,11 +80,11 @@ Plugins come as several types: You can access them on the client side with `useDocusaurusContext().siteMetadata.pluginVersions`. -## Plugin design +## Plugin design {#plugin-design} Docusaurus' implementation of the plugins system provides us with a convenient way to hook into the website's lifecycle to modify what goes on during development/build, which involves (but is not limited to) extending the webpack config, modifying the data loaded, and creating new components to be used in a page. -### Theme design +### Theme design {#theme-design} When plugins have loaded their content, the data is made available to the client side through actions like [`createData` + `addRoute`](../api/plugin-methods/lifecycle-apis.md#addRoute) or [`setGlobalData`](../api/plugin-methods/lifecycle-apis.md#setGlobalData). This data has to be _serialized_ to plain strings, because [plugins and themes run in different environments](./architecture.md). Once the data arrives on the client side, the rest becomes familiar to React developers: data is passed along components, components are bundled with Webpack, and rendered to the window through `ReactDOM.render`... diff --git a/website/docs/advanced/routing.md b/website/docs/advanced/routing.md index 09bc837490fd..9aca36f249bb 100644 --- a/website/docs/advanced/routing.md +++ b/website/docs/advanced/routing.md @@ -13,7 +13,7 @@ import BrowserWindow from '@site/src/components/BrowserWindow'; Docusaurus' routing system follows single-page application conventions: one route, one component. In this section, we will begin by talking about routing within the three content plugins (docs, blog, and pages), and then go beyond to talk about the underlying routing system. -## Routing in content plugins +## Routing in content plugins {#routing-in-content-plugins} Every content plugin provides a `routeBasePath` option. It defines where the plugins append their routes to. By default, the docs plugin puts its routes under `/docs`; the blog plugin, `/blog`; and the pages plugin, `/`. You can think about the route structure like this: @@ -25,13 +25,13 @@ Changing `routeBasePath` can effectively alter your site's route structure. For Next, let's look at how the three plugins structure their own "boxes of subroutes". -### Pages routing +### Pages routing {#pages-routing} Pages routing are straightforward: the file paths directly map to URLs, without any other way to customize. See the [pages docs](../guides/creating-pages.md#routing) for more information. The component used for Markdown pages is `@theme/MDXPage`. React pages are directly used as the route's component. -### Blog routing +### Blog routing {#blog-routing} The blog creates the following routes: @@ -52,7 +52,7 @@ The blog creates the following routes: - The route is customizable through the `archiveBasePath` option. - The component is `@theme/BlogArchivePage`. -### Docs routing +### Docs routing {#docs-routing} The docs is the only plugin that creates **nested routes**. At the top, it registers [**version paths**](../guides/docs/versioning.md): `/`, `/next`, `/2.0.0-beta.13`... which provide the version context, including the layout and sidebar. This ensures that when switching between individual docs, the sidebar's state is preserved, and that you can switch between versions through the navbar dropdown while staying on the same doc. The component used is `@theme/DocPage`. @@ -69,12 +69,14 @@ The individual docs are rendered in the remaining space after the navbar, footer The doc's `slug` front matter customizes the last part of the route, but the base route is always defined by the plugin's `routeBasePath` and the version's `path`. -### File paths and URL paths +### File paths and URL paths {#file-paths-and-url-paths} Throughout the documentation, we always try to be unambiguous about whether we are talking about file paths or URL paths. Content plugins usually map file paths directly to URL paths, for example, `./docs/advanced/routing.md` will become `/docs/advanced/routing`. However, with `slug`, you can make URLs totally decoupled from the file structure. When writing links in Markdown, you could either mean a _file path_, or a _URL path_, which Docusaurus would use several heuristics to determine. +- If the path has a `@site` prefix, it is _always_ an asset file path. +- If the path has an `http(s)://` prefix, it is _always_ a URL path. - If the path doesn't have an extension, it is a URL path. For example, a link `[page](../plugins)` on a page with URL `/docs/advanced/routing` will link to `/docs/plugins`. Docusaurus will only detect broken links when building your site (when it knows the full route structure), but will make no assumptions about the existence of a file. It is exactly equivalent to writing `page` in a JSX file. - If the path has an `.md(x)` extension, Docusaurus would try to resolve that Markdown file to a URL, and replace the file path with a URL path. - If the path has any other extension, Docusaurus would treat it as [an asset](../guides/markdown-features/markdown-features-assets.mdx) and bundle it. @@ -126,7 +128,7 @@ The following directory structure may help you visualize this file -> URL mappin So much about content plugins. Let's take one step back and talk about how routing works in a Docusaurus app in general. -## Routes become HTML files +## Routes become HTML files {#routes-become-html-files} Because Docusaurus is a server-side rendering framework, all routes generated will be server-side rendered into static HTML files. If you are familiar with the behavior of HTTP servers like [Apache2](https://httpd.apache.org/docs/trunk/getting-started.html), you will understand how this is done: when the browser sends a request to the route `/docs/advanced/routing`, the server interprets that as request for the HTML file `/docs/advanced/routing/index.html`, and returns that. @@ -200,7 +202,7 @@ For example, the emitted HTML would contain links like ` ``` -## Escaping from SPA redirects +## Escaping from SPA redirects {#escaping-from-spa-redirects} Docusaurus builds a [single-page application](https://developer.mozilla.org/en-US/docs/Glossary/SPA), where route transitions are done through the `history.push()` method of React router. This operation is done on the client side. However, the prerequisite for a route transition to happen this way is that the target URL is known to our router. Otherwise, the router catches this path and displays a 404 page instead. diff --git a/website/docs/advanced/ssg.md b/website/docs/advanced/ssg.md index 6c478e67e5cf..b5b4e81f9f58 100644 --- a/website/docs/advanced/ssg.md +++ b/website/docs/advanced/ssg.md @@ -92,7 +92,7 @@ export default function expensiveComp() { -## Understanding SSR +## Understanding SSR {#understanding-ssr} React is not just a dynamic UI runtime—it's also a templating engine. Because Docusaurus sites mostly contain static contents, it should be able to work without any JavaScript (which React runs in), but only plain HTML/CSS. And that's what server-side rendering offers: statically rendering your React code into HTML, without any dynamic content. An HTML file has no concept of client state (it's purely markup), hence it shouldn't rely on browser APIs. @@ -100,7 +100,7 @@ These HTML files are the first to arrive at the user's browser screen when a URL In CSR-only apps, all DOM elements are generated on client side with React, and the HTML file only ever contains one root element for React to mount DOM to; in SSR, React is already facing a fully built HTML page, and it only needs to correlate the DOM elements with the virtual DOM in its model. This step is called "hydration". After React has hydrated the static markup, the app starts to work as any normal React app. -## Escape hatches +## Escape hatches {#escape-hatches} If you want to render any dynamic content on your screen that relies on the browser API to be functional at all, for example: @@ -122,7 +122,7 @@ You can read more about this pitfall in [The Perils of Rehydration](https://www. We provide several more reliable ways to escape SSR. -### `` +### `` {#browseronly} If you need to render some component in browser only (for example, because the component relies on browser specifics to be functional at all), one common approach is to wrap your component with [``](../docusaurus-core.md#browseronly) to make sure it's invisible during SSR and only rendered in CSR. @@ -163,7 +163,7 @@ function MyComponent() { While you may expect that `BrowserOnly` hides away the children during server-side rendering, it actually can't. When the React renderer tries to render this JSX tree, it does see the `{window.location.href}` variable as a node of this tree and tries to render it, although it's actually not used! Using a function ensures that we only let the renderer see the browser-only component when it's needed. -### `useIsBrowser` +### `useIsBrowser` {#useisbrowser} You can also use the `useIsBrowser()` hook to test if the component is currently in a browser environment. It returns `false` in SSR and `true` is CSR, after first client render. Use this hook if you only need to perform certain conditional operations on client-side, but not render an entirely different UI. @@ -177,7 +177,7 @@ function MyComponent() { } ``` -### `useEffect` +### `useEffect` {#useeffect} Lastly, you can put your logic in `useEffect()` to delay its execution until after first CSR. This is most appropriate if you are only performing side-effects but don't _get_ data from the client state. @@ -191,7 +191,7 @@ function MyComponent() { } ``` -### `ExecutionEnvironment` +### `ExecutionEnvironment` {#executionenvironment} The [`ExecutionEnvironment`](../docusaurus-core.md#executionenvironment) namespace contains several values, and `canUseDOM` is an effective way to detect browser environment. diff --git a/website/docs/advanced/swizzling.md b/website/docs/advanced/swizzling.md deleted file mode 100644 index 4163bb6e632b..000000000000 --- a/website/docs/advanced/swizzling.md +++ /dev/null @@ -1,195 +0,0 @@ ---- -description: Customize your site's appearance through creating your own theme components ---- - -# Swizzling - -In this section, we will introduce how customization of layout is done in Docusaurus. - -> Déja vu...? - -This section is similar to [Styling and Layout](../styling-layout.md), but this time, we are going to write more code and go deeper into the internals instead of playing with stylesheets. We will talk about a central concept in Docusaurus customization: **swizzling**, from how to swizzle, to how it works under the hood. - -We know you are busy, so we will start with the "how" before going into the "why". - -## Swizzling - -```mdx-code-block -import SwizzleWarning from "../_partials/swizzleWarning.mdx" - - -``` - -Docusaurus Themes' components are designed to be replaceable. The replacing is called "swizzle". In Objective C, method swizzling is the process of changing the implementation of an existing selector (method). **In the context of a website, component swizzling means providing an alternative component that takes precedence over the component provided by the theme.** (To gain a deeper understanding of this, you have to understand [how theme components are resolved](#theme-aliases)). To help you get started, we created a command called `docusaurus swizzle`. - -### Ejecting theme components - -To eject a component provided by the theme, run the following command in your doc site: - -```bash npm2yarn -npm run swizzle [theme name] [component name] -``` - -As an example, to swizzle the `