diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 00000000..fc51a6b3 Binary files /dev/null and b/.DS_Store differ diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 00000000..77bb1f24 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,22 @@ +module.exports = { + env: { + es6: true, + node: true, + 'jest/globals': true + }, + extends: [ + 'standard' + ], + globals: { + Atomics: 'readonly', + SharedArrayBuffer: 'readonly' + }, + parserOptions: { + ecmaVersion: 2018 + }, + plugins: [ + 'jest' + ], + rules: { + } +} diff --git a/.github/.dependabot.yml b/.github/.dependabot.yml index 6f39ced4..88cb6099 100644 --- a/.github/.dependabot.yml +++ b/.github/.dependabot.yml @@ -3,11 +3,11 @@ version: 2 updates: - - package-ecosystem: npm - directory: / + - package-ecosystem: "npm" + directory: "/" schedule: - interval: monthly + interval: "monthly" allow: - - dependency-name: '@storyblok/region-helper' + - dependency-name: "@storyblok/region-helper" reviewers: - - storyblok/plugins-team + - "storyblok/plugins-team" \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index b357b006..95e87e1b 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -7,6 +7,7 @@ assignees: '' --- + **Current behavior:** diff --git a/.github/assets/breakpoints.png b/.github/assets/breakpoints.png deleted file mode 100644 index 318d3d90..00000000 Binary files a/.github/assets/breakpoints.png and /dev/null differ diff --git a/.github/assets/debug-vscode.png b/.github/assets/debug-vscode.png deleted file mode 100644 index ec6c0290..00000000 Binary files a/.github/assets/debug-vscode.png and /dev/null differ diff --git a/.github/assets/repo-banner.png b/.github/assets/repo-banner.png deleted file mode 100644 index 66514980..00000000 Binary files a/.github/assets/repo-banner.png and /dev/null differ diff --git a/.github/workflows/lint-pr.yml b/.github/workflows/lint-pr.yml deleted file mode 100644 index 4298282b..00000000 --- a/.github/workflows/lint-pr.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: Lint PR - -on: - pull_request_target: - types: - - opened - - edited - - synchronize - -jobs: - main: - name: Validate PR title - runs-on: ubuntu-latest - steps: - - uses: amannn/action-semantic-pull-request@v5 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml deleted file mode 100644 index 209f90d2..00000000 --- a/.github/workflows/lint.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Run linters -on: [push] - -env: - PNPM_CACHE_FOLDER: .pnpm-store - -jobs: - lint: - name: Lint - runs-on: ubuntu-22.04 - strategy: - matrix: - node-version: [20] - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Setup pnpm - uses: pnpm/action-setup@v4 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - cache: pnpm - - name: Install dependencies - run: pnpm install - - name: Run Lint - run: pnpm run lint diff --git a/.github/workflows/pkg.pr.new.yml b/.github/workflows/pkg.pr.new.yml index 6edf26ff..48554102 100644 --- a/.github/workflows/pkg.pr.new.yml +++ b/.github/workflows/pkg.pr.new.yml @@ -1,13 +1,12 @@ name: Publish Any Commit on: push: - tags: - - '!**' branches: - '**' + tags: + - '!**' env: - PNPM_CACHE_FOLDER: .pnpm-store HUSKY: 0 # Bypass husky commit hook for CI permissions: {} @@ -26,13 +25,13 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - run: npm i -g --force corepack && corepack enable + - run: corepack enable - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - cache: pnpm + cache: yarn - name: Install dependencies - run: pnpm install + run: yarn install - name: Build - run: pnpm build - - run: pnpx pkg-pr-new publish --compact --pnpm + run: yarn build + - run: npx pkg-pr-new publish --compact \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..c24e65e1 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,25 @@ +name: Release + +on: + push: + branches: + - main + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 20.11 + cache: 'yarn' + - name: Install dependencies + run: yarn + - name: Build + run: yarn build + - name: Release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_PUBLISH }} + run: npx semantic-release diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index af7e52a1..00000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Run Tests -on: [push] - -env: - PNPM_CACHE_FOLDER: .pnpm-store - -jobs: - test: - name: Test - runs-on: ubuntu-22.04 - strategy: - matrix: - node-version: [20] - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Setup pnpm - uses: pnpm/action-setup@v4 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - cache: pnpm - - name: Install dependencies - run: pnpm install - - name: Run Test - run: pnpm run test diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml new file mode 100644 index 00000000..3f1d4592 --- /dev/null +++ b/.github/workflows/unit-tests.yml @@ -0,0 +1,22 @@ +name: 'Unit Tests Production' + +on: + pull_request: + branches: + - 'main' + +jobs: + unit-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 18 + cache: 'yarn' + - name: Install dependencies + run: yarn install --frozen-lockfile + - name: Clear jest cache + run: yarn test:unit --clearCache + - name: Run unit tests + run: yarn test:unit --silent --ci \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7cf061e4..1d7923cd 100644 --- a/.gitignore +++ b/.gitignore @@ -111,10 +111,4 @@ dist # CLI generated files components.*.json presets.*.json -storyblok-component-types.d.ts - -# storyblok folder -.storyblok/ - -# DS_Store -.DS_Store +storyblok-component-types.d.ts \ No newline at end of file diff --git a/.release-it.json b/.release-it.json deleted file mode 100644 index f1c9ed85..00000000 --- a/.release-it.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "git": { - "commitMessage": "chore: release v${version}" - }, - "github": { - "release": true, - "releaseName": "v${version}" - }, - "hooks": { - "before:init": ["pnpm run lint", "pnpm test:ci"], - "after:bump": "pnpm run build", - "after:git:release": "echo After git push, before github release", - "after:release": "echo Successfully released ${name} v${version} to ${repo.repository}." - } -} diff --git a/.vscode/launch.json b/.vscode/launch.json index 8bf6af8e..00d4fb04 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,334 +4,31 @@ { "type": "node", "request": "launch", - "name": "Debug Vitest Tests", - "program": "${workspaceFolder}/node_modules/vitest/vitest.mjs", - "args": ["run"], - "autoAttachChildProcesses": true, - "smartStep": true, + "name": "Debug Jest Tests", + "runtimeArgs": [ + "--experimental-vm-modules" + ], + "args": [ + "--silent", + "--runInBand" + ], "console": "integratedTerminal", "internalConsoleOptions": "neverOpen", - "skipFiles": ["/**"] + "program": "${workspaceFolder}/node_modules/.bin/jest", + "windows": { + "program": "${workspaceFolder}\\node_modules\\jest\\bin\\jest.js" + } }, { "type": "node", "request": "launch", - "name": "Debug login", - "program": "${workspaceFolder}/dist/index.mjs", - "args": ["login"], - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "sourceMaps": true, - "outFiles": ["${workspaceFolder}/dist/**/*.js"] - }, - { - "type": "node", - "request": "launch", - "name": "Debug login by token", - "program": "${workspaceFolder}/dist/index.mjs", - "args": ["login", "--token", "1234567890"], - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "sourceMaps": true, - "outFiles": ["${workspaceFolder}/dist/**/*.js"] - }, - { - "type": "node", - "request": "launch", - "name": "Debug logout", - "program": "${workspaceFolder}/dist/index.mjs", - "args": ["logout"], - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "sourceMaps": true, - "outFiles": ["${workspaceFolder}/dist/**/*.js"] - }, - { - "type": "node", - "request": "launch", - "name": "Debug user", - "program": "${workspaceFolder}/dist/index.mjs", - "args": ["user"], - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "sourceMaps": true, - "outFiles": ["${workspaceFolder}/dist/**/*.js"] - }, - { - "type": "node", - "request": "launch", - "name": "Debug Pull languages", - "program": "${workspaceFolder}/dist/index.mjs", - "args": ["languages", "pull", "--space", "295018"], - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "sourceMaps": true, - "outFiles": ["${workspaceFolder}/dist/**/*.js"] - }, - { - "type": "node", - "request": "launch", - "name": "Debug Test", - "program": "${workspaceFolder}/dist/index.mjs", - "args": ["test"], - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "sourceMaps": true, - "outFiles": ["${workspaceFolder}/dist/**/*.js"] - }, - { - "type": "node", - "request": "launch", - "name": "Debug Pull components", - "program": "${workspaceFolder}/dist/index.mjs", - "args": ["components", "pull", "--space", "295017", "--verbose"], - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "sourceMaps": true, - "outFiles": ["${workspaceFolder}/dist/**/*.js"] - }, - { - "type": "node", - "request": "launch", - "name": "Debug Push components", - "program": "${workspaceFolder}/dist/index.mjs", - "args": ["components", "push", "--space", "295018", "--from", "295017", "--verbose"], - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "sourceMaps": true, - "outFiles": ["${workspaceFolder}/dist/**/*.js"] - }, - { - "type": "node", - "request": "launch", - "name": "Debug Push components --separate-files", - "program": "${workspaceFolder}/dist/index.mjs", - "args": ["components", "push", "--space", "295018", "--from", "295017", "--sf"], - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "sourceMaps": true, - "outFiles": ["${workspaceFolder}/dist/**/*.js"] - }, - { - "type": "node", - "request": "launch", - "name": "Debug Push components from non-existing space", - "program": "${workspaceFolder}/dist/index.mjs", - "args": ["components", "push", "--space", "295018", "--from", "non-existing"], - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "sourceMaps": true, - "outFiles": ["${workspaceFolder}/dist/**/*.js"] - }, - { - "type": "node", - "request": "launch", - "name": "Debug Push component component-tags", - "program": "${workspaceFolder}/dist/index.mjs", - "args": ["components", "push", "component-tags", "--space", "295018", "--from", "295017", "--sf", "--verbose"], - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "sourceMaps": true, - "outFiles": ["${workspaceFolder}/dist/**/*.js"] - }, - { - "type": "node", - "request": "launch", - "name": "Debug circular dependencies", - "program": "${workspaceFolder}/dist/index.mjs", - "args": ["components", "push", "component-component-whitelist", "--space", "295018", "--from", "295017", "--verbose"], - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "sourceMaps": true, - "outFiles": ["${workspaceFolder}/dist/**/*.js"] - }, - { - "type": "node", - "request": "launch", - "name": "Debug Push component non-existing", - "program": "${workspaceFolder}/dist/index.mjs", - "args": ["components", "push", "non-existing", "--space", "295018", "--from", "295017"], - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "sourceMaps": true, - "outFiles": ["${workspaceFolder}/dist/**/*.js"] - }, - { - "type": "node", - "request": "launch", - "name": "Debug Push component filtered by pattern", - "program": "${workspaceFolder}/dist/index.mjs", - "args": ["components", "push", "--space", "295018", "--from", "295017", "--filter", "component-inside-*"], - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "sourceMaps": true, - "outFiles": ["${workspaceFolder}/dist/**/*.js"] - }, - { - "type": "node", - "request": "launch", - "name": "Debug Push component with suffix", - "program": "${workspaceFolder}/dist/index.mjs", - "args": ["components", "push", "--space", "295018", "--from", "295017", "--suffix", "prod"], - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "sourceMaps": true, - "outFiles": ["${workspaceFolder}/dist/**/*.js"] - }, - { - "type": "node", - "request": "launch", - "name": "Debug Push component with suffix and separate files", - "program": "${workspaceFolder}/dist/index.mjs", - "args": ["components", "push", "--space", "295018", "--from", "295017", "--suffix", "prod", "--separate-files"], - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "sourceMaps": true, - "outFiles": ["${workspaceFolder}/dist/**/*.js"] - }, - { - "type": "node", - "request": "launch", - "name": "Debug Migrations generate", - "program": "${workspaceFolder}/dist/index.mjs", - "args": ["migrations", "generate", "--space", "295017"], - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "sourceMaps": true, - "outFiles": ["${workspaceFolder}/dist/**/*.js"] - }, - { - "type": "node", - "request": "launch", - "name": "Debug Migrations run", - "program": "${workspaceFolder}/dist/index.mjs", - "args": ["migrations", "run", "--space", "295017"], - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "sourceMaps": true, - "outFiles": ["${workspaceFolder}/dist/**/*.js"] - }, - { - "type": "node", - "request": "launch", - "name": "Debug Migrations run component-name", - "program": "${workspaceFolder}/dist/index.mjs", - "args": ["migrations", "run", "simple_component", "--space", "295017"], - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "sourceMaps": true, - "outFiles": ["${workspaceFolder}/dist/**/*.js"] - }, - { - "type": "node", - "request": "launch", - "name": "Debug Migrations run filter", - "program": "${workspaceFolder}/dist/index.mjs", - "args": ["migrations", "run", "--space", "295017", "--filter", "component-inside-*"], - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "sourceMaps": true, - "outFiles": ["${workspaceFolder}/dist/**/*.js"] - }, - { - "type": "node", - "request": "launch", - "name": "Debug Migrations run query", - "program": "${workspaceFolder}/dist/index.mjs", - "args": ["migrations", "run", "--space", "295017", "--query", "[highlighted][is]=true"], - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "sourceMaps": true, - "outFiles": ["${workspaceFolder}/dist/**/*.js"] - }, - { - "type": "node", - "request": "launch", - "name": "Debug Migrations run starts-with", - "program": "${workspaceFolder}/dist/index.mjs", - "args": ["migrations", "run", "--space", "295017", "--starts-with", "migrations/"], - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "sourceMaps": true, - "outFiles": ["${workspaceFolder}/dist/**/*.js"] - }, - { - "type": "node", - "request": "launch", - "name": "Debug Migrations generate non-existing component", - "program": "${workspaceFolder}/dist/index.mjs", - "args": ["migrations", "generate", "non-existing", "--space", "295017"], - - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "sourceMaps": true, - "outFiles": ["${workspaceFolder}/dist/**/*.js"] - }, - { - "type": "node", - "request": "launch", - "name": "Debug Generate types", - "program": "${workspaceFolder}/dist/index.mjs", - "args": ["types", "generate", "--space", "295017", "--filter", "component-inside-*"], - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "sourceMaps": true, - "outFiles": ["${workspaceFolder}/dist/**/*.js"] - }, - { - "type": "node", - "request": "launch", - "name": "Debug Generate types strict", - "program": "${workspaceFolder}/dist/index.mjs", - "args": ["types", "generate", "--space", "295017", "--strict"], - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "sourceMaps": true, - "outFiles": ["${workspaceFolder}/dist/**/*.js"] - }, - { - "type": "node", - "request": "launch", - "name": "Debug Generate types from separate files", - "program": "${workspaceFolder}/dist/index.mjs", - "args": ["types", "generate", "--space", "295017", "--separate-files"], - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "sourceMaps": true, - "outFiles": ["${workspaceFolder}/dist/**/*.js"] - }, - { - "type": "node", - "request": "launch", - "name": "Debug Generate types with suffix", - "program": "${workspaceFolder}/dist/index.mjs", - "args": ["types", "generate", "--space", "295017", "--suffix", "prod"], - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "sourceMaps": true, - "outFiles": ["${workspaceFolder}/dist/**/*.js"] - }, - { - "type": "node", - "request": "launch", - "name": "Debug Generate types with custom fields parser", - "program": "${workspaceFolder}/dist/index.mjs", - "args": ["types", "generate", "--space", "295017", "--custom-fields-parser", "./custom-fields-parser.ts"], - "cwd": "${workspaceFolder}", - "console": "integratedTerminal", - "sourceMaps": true, - "outFiles": ["${workspaceFolder}/dist/**/*.js"] - }, - { - "type": "node", - "request": "launch", - "name": "Debug Generate types with compiler options", - "program": "${workspaceFolder}/dist/index.mjs", - "args": ["types", "generate", "--space", "295017", "--compiler-options", "./compiler-options.ts"], + "name": "Debug pull-components", + "program": "${workspaceFolder}/dist/cli.mjs", + "args": ["push-components", "components.295017.json", "--space", "295018"], "cwd": "${workspaceFolder}", "console": "integratedTerminal", "sourceMaps": true, "outFiles": ["${workspaceFolder}/dist/**/*.js"] } ] -} +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 953f331b..e2839582 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,45 +1,8 @@ { - // Enable the ESlint flat config support - "eslint.experimental.useFlatConfig": true, - "eslint.format.enable": true, - - // Disable the default formatter, use eslint instead - "prettier.enable": false, "editor.formatOnSave": false, - - // Auto fix - "editor.codeActionsOnSave": { - "source.fixAll.eslint": "explicit", - "source.organizeImports": "never" + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true, }, - - // Silent the stylistic rules in you IDE, but still auto fix them - "eslint.rules.customizations": [ - { "rule": "style/*", "severity": "off" }, - { "rule": "format/*", "severity": "off" }, - { "rule": "*-indent", "severity": "off" }, - { "rule": "*-spacing", "severity": "off" }, - { "rule": "*-spaces", "severity": "off" }, - { "rule": "*-order", "severity": "off" }, - { "rule": "*-dangle", "severity": "off" }, - { "rule": "*-newline", "severity": "off" }, - { "rule": "*quotes", "severity": "off" }, - { "rule": "*semi", "severity": "off" } - ], - - // Enable eslint for all supported languages - "eslint.validate": [ - "javascript", - "javascriptreact", - "typescript", - "typescriptreact", - "vue", - "html", - "markdown", - "json", - "jsonc", - "yaml", - "toml", - "yml" - ] + "editor.defaultFormatter": "esbenp.prettier-vscode" } diff --git a/README.md b/README.md index ecd73cc7..7e2377dd 100644 --- a/README.md +++ b/README.md @@ -1,129 +1,667 @@ -
+

+

Storyblok CLI

+

A simple CLI for scaffolding Storyblok projects and fieldtypes.

+

-![Storyblok ImagoType](https://raw.githubusercontent.com/storyblok/.github/refs/heads/main/profile/public/github-banner.png) +[![npm](https://img.shields.io/npm/v/storyblok.svg)](https://www.npmjs.com/package/storyblok) +[![npm](https://img.shields.io/npm/dt/storyblok.svg)](ttps://img.shields.io/npm/dt/storyblok.svg) +[![GitHub issues](https://img.shields.io/github/issues/storyblok/storyblok-cli.svg?style=flat-square&v=1)](https://github.com/storyblok/storyblok/issues?q=is%3Aopen+is%3Aissue) +[![GitHub closed issues](https://img.shields.io/github/issues-closed/storyblok/storyblok-cli.svg?style=flat-square&v=1)](https://github.com/storyblok/storyblok-cli/issues?q=is%3Aissue+is%3Aclosed) -

Storyblok CLI

-

A powerful CLI tool to improve the DX of your Storyblok projects.

-
-
-

- - Storyblok JS Client - - - npm - - - - - - Follow @Storyblok -
- - Follow @Storyblok - -

+> [!WARNING] +> This the legacy branch of the Storyblok CLI. We recommend you migrate to the new [CLI v4](https://github.com/storyblok/storyblok-cli/tree/main) for enhanced experience and support. + +## Installation -## Features +Make sure you have Node `>= 18.0.0` installed. -- πŸ›‘οΈ **Type Safety** - Generate TypeScript type definitions for your Storyblok components, ensuring type safety in your frontend applications -- πŸ” **Authentication** - Secure login system with support for different regions and CI environments -- 🧩 **Component Management** - Pull and push component schemas, groups, presets, and internal tags between spaces -- πŸ”„ **Migration System** - Generate and run migrations to transform or update field values across your Storyblok content -- 🌐 **Language Management** - Pull and manage languages for your Storyblok space -- πŸ“ **File Organization** - Organized file structure with the `.storyblok` directory as default -- πŸ› οΈ **Customizable Paths** - Flexible path configuration for all generated files -- πŸ“ **Naming Conventions** - Consistent file naming with customizable suffixes and prefixes -- πŸ“‚ **Separate Files Support** - Option to work with individual component files or consolidated files -- πŸ‘€ **Dry Run Mode** - Preview migrations changes before applying them to your Storyblok space -- πŸ” **Filtering Capabilities** - Filter components and stories using glob patterns and Storyblok query syntax -- πŸ“Š **Verbose Logging** - Improved error handling and detailed logging options for debugging and monitoring +```sh +$ npm i storyblok -g +``` -## Pre-requisites +## Commands -- [Node.js >= 18.0.0](https://nodejs.org/en/download/) -- Storyblok account (sign up [here](https://app.storyblok.com/#!/signup?utm_source=github.com&utm_medium=readme&utm_campaign=storyblok-cli)) -- Personal access token from Storyblok (get it [here](https://app.storyblok.com/#/me/account?tab=token)) -## Documentation +### login -> [!WARNING] -> Official documentation for this package v4 is still in development. In the meantime, please refer to the internal documentation for the [v4 beta](src/README.md). +Login to the Storyblok cli + +```sh +$ storyblok login +``` +#### Login options + +##### Options for Login with email and password +* `email`: your user's email address +* `password`: your user's password + +##### Options for Login with token (Recomended to SSO user's but works with all user accounts) +* `token`: your personal access token + +**Get your personal access token** +* Go to [https://app.storyblok.com/#/me/account?tab=token](https://app.storyblok.com/#/me/account?tab=token) and click on Generate new token. + +**For Both login options you nedd to pass the region** + +* `region` (default: `eu`): the region you would like to work in. All the supported regions can be found [here](https://www.storyblok.com/faq/define-specific-region-storyblok-api). + +> [!NOTE] +> Please keep in mind that the region must match the region of your space, and also that it will be used for all future commands you may perform. + +#### Login with token flag +You can also add the token directly from the login’s command, like the example below: + +```sh +$ storyblok login --token --region eu +``` + +### logout + +Logout from the Storyblok cli + +```sh +$ storyblok logout +``` +### user + +Get the currently logged in user + +```sh +$ storyblok user +``` + + +### select + +Usage to kickstart a boilerplate, fieldtype or theme + +```sh +$ storyblok select +``` + +### pull-languages + +Download your space's languages schema as json. This command will download 1 file. + +```sh +$ storyblok pull-languages --space +``` + +#### Options + +* `space`: your space id + +### pull-components + +Download your space's components schema as json. By default this command will download 2 files: 1 for the components and 1 for the presets; But if you pass a flag `--separate-files or --sf` the command will create file for each component and presets. And also you could pass a path `--path or -p` to save your components and presets. + +It's highly recommended to use also the `--prefix-presets-names` or `-ppn` parameter if you use `--separate-files` because it will prefix the names of the individual files with the name of the component. This feature solves the issue of multiple presets from different components but with the same name, being written in the same file. In a future major version this will become the default behavior. + +If you want to resolve datasources for single/multiple option field from your Storyblok components, you can use `--resolve-datasources` or `--rd`, it will fill up the options fields with the datasource's options. + +```sh +$ storyblok pull-components --space # Will save files like components-1234.json +``` + +```sh +$ storyblok pull-components --space --separate-files --prefix-presets-names --file-name production # Will save files like feature-production.json grid-production.json +``` + +```sh +$ storyblok pull-components --space --resolve-datasources # Will resolve datasources for single/multiple option field +``` + +#### Options + +* `space`: your space id +* `separate-files`: boolean flag to save components and presets in single files instead a file with all +* `path`: the path to save your components and preset files +* `file-name`(optional): a custom filename used to generate the component and present files, default is the space id + +### push-components + +Push your components file to your/another space + +```sh +$ storyblok push-components --space --presets-source +``` + +#### Parameters + +* `source`: can be a URL or path to JSON file, the path to a json file could be to a single or multiple files separated by comma, like `./pages-1234.json,../User/components/grid-1234.json` + +Using an **URL** + +```sh +$ storyblok push-components https://raw.githubusercontent.com/storyblok/nuxtdoc/master/seed.components.json --space 67819 +``` + +Using a **path** to a single file + +```sh +$ storyblok push-components ./components.json --space 67819 +``` + +Using a **path** to a multiple files + +```sh +$ storyblok push-components ./page.json,../grid.json,./feature.json --space 67819 +``` + +#### Options + +* `space`: your space id +* `presets-source` (optional): it can be a URL or path to JSON file with the presets + +#### Examples + +Using an **URL** for `presets-source` + +```sh +$ storyblok push-components https://raw.githubusercontent.com/storyblok/nuxtdoc/master/seed.components.json --presets-source https://url-to-your-presets-file.json --space 67819 +``` + +Using a **path** to file + +```sh +$ storyblok push-components ./components.json --presets-source ./presets.json --space 67819 +``` + +### delete-component + +Delete a single component on your space. + +```sh +storyblok delete-component --space +``` + +#### Parameters +* `component`: The name or id of the component + +#### Options +* `space_id`: the space where the command should be executed. + +#### Examples + +Delete a component on your space. +```sh +storyblok delete-component 111111 --space 67819 +``` + +```sh +storyblok delete-component teaser --space 67819 +``` + +### delete-components + +Delete all components from your Space that occur in your Local JSON. +Or delete those components on your Space that do not appear in the JSON. (`--reverse`) + +```sh +storyblok delete-components --space +``` + +#### Parameters +* `source`: can be a URL or path to JSON file, the path to a json file could be to a single or multiple files separated by comma, like `./pages-1234.json,../User/components/grid-1234.json` + +Using an **URL** + +```sh +$ storyblok push-components https://raw.githubusercontent.com/storyblok/nuxtdoc/master/seed.components.json --space 67819 +``` + +Using a **path** to a single file + +```sh +$ storyblok push-components ./components.json --space 67819 +``` + +Using a **path** to a multiple files + +```sh +$ storyblok push-components ./page.json,../grid.json,./feature.json --space 67819 +``` + +#### Options +* `space_id`: the space where the command should be executed. +* `reverse`: When passed as an argument, deletes only those components on your space that do not appear in the JSON. +* `dryrun`: when passed as an argument, does not perform any changes on the given space. + +#### Examples + +Delete all components on a certain space that occur in your local JSON. +```sh +storyblok delete-components ./components.json --space 67819 +``` + +Delete only those components which do not occur in your local json from your space. +```sh +storyblok delete-components ./components.json --space 67819 --reverse +``` + +To see the result in your console output but to not perform the command on your space, use the `--dryrun` argument. +```sh +storyblok delete-components ./components.json --space 67819 --reverse --dryrun +``` + +### sync + +Sync components, folder, roles, datasources or stories between spaces + +```sh +$ storyblok sync --type --source --target +``` + +#### Options + +* `type`: describe the command type to execute. Can be: `folders`, `components`, `stories`, `datasources` or `roles`. It's possible pass multiple types separated by comma (`,`). +* `source`: the source space to use to sync +* `target`: the target space to use to sync +* `starts-with`: sync only stories that starts with the given string +* `filter`: sync stories based on the given filter. Required Options: Required options: `--keys`, `--operations`, `--values` +* `keys`: Multiple keys should be separated by comma. Example: `--keys key1,key2`, `--keys key1` +* `operations`: Operations to be used for filtering. Can be: `is`, `in`, `not_in`, `like`, `not_like`, `any_in_array`, `all_in_array`, `gt_date`, `lt_date`, `gt_int`, `lt_int`, `gt_float`, `lt_float`. Multiple operations should be separated by comma. +* `components-full-sync`: If used, the CLI will override the full component object when synching across spaces. + +#### Examples + +```sh +# Sync components from `00001` space to `00002` space +$ storyblok sync --type components --source 00001 --target 00002 + +# Sync components and stories from `00001` space to `00002` space +$ storyblok sync --type components,stories --source 00001 --target 00002 + +# Sync only stories that starts with `myStartsWithString` from `00001` space to `00002` space +$ storyblok sync --type stories --source 00001 --target 00002 --starts-with myStartsWithString -## Setup +# Sync only stories with a category field like `reference` from `00001` space to `00002` space +$ storyblok sync --type stories --source 00001 --target 00002 --filter --keys category --operations like --values reference -This package relies on [pnpm](https://pnpm.io/) to manage dependencies. For instructions on how to install pnpm, please visit [pnpm.io](https://pnpm.io/installation). +# Sync only stories with a category field like `reference` and a name field not like `demo` from `00001` space to `00002` space +$ storyblok sync --type stories --source 00001 --target 00002 --filter --keys category,name --operations like,not_like --values reference,demo -```bash -pnpm install ``` -## Build +### quickstart -```bash -pnpm build +Create a space in Storyblok and select the boilerplate to use + +```sh +$ storyblok quickstart +``` + +### generate-migration + +Create a migration file (with the name `change__.js`) inside the `migrations` folder. Check **Migrations** section to more details + +```sh +$ storyblok generate-migration --space --component --field +``` +It's important to note that the `component` and `field` parameters are required and must be spelled exactly as they are in Storyblok. You can check the exact name by looking at the `Block library` inside your space. + +#### Options + +* `space`: space where the component is +* `component`: component name. It needs to be a valid component +* `field`: name of field + +### run-migration + +Execute a specific migration file. Check **Migrations** section to more details + +```sh +$ storyblok run-migration --space --component --field --dryrun +``` + +Optionally you can provide the publish parameter to publish content after saving. Example: + +```sh +$ storyblok run-migration --publish published --space 1234 --component article --field image +``` + +#### Options + +* `space`: the space you get from the space settings area +* `component`: component name. It needs to be a valid component +* `field`: name of field +* `dryrun`: when passed as an argument, does not perform the migration +* `publish` (optional): publish the content when update + * `all`: publish all stories, even if they have not yet been published + * `published`: only publish stories that already are published and don't have unpublished changes + * `published-with-changes`: publish stories that are published and have unpublished changes +* `publish-languages` (optional): publish specific languages. You can publish more than one language at a time by separating the languages by `,` + +### rollback-migration + +The `rollback-migration` command gives the possibility to undo the changes made from the execution of the last `run-migrations` command. + +```sh +$ storyblok rollback-migration --space 1234 --component Product --field title +``` + +**Important**: The `rollback-migrations` command will only work if there where changes done with `run-migrations`. Therefore running `run-migrations` command with the `--dryrun` flag will NOT create a rollback file. + +#### options + +* `space`: the space you get from the space settings area +* `component`: component name. It needs to be a valid component +* `field`: name of field + +### spaces + + +List all spaces of the logged account + +```sh +$ storyblok spaces +``` + +### import + +This command gives you the possibility to import flat content from `.csv`, `.xml` and `.json` files coming from other systems. + +The attributes `path` and `title` are required. + +```sh +$ storyblok import --file --type --folder --delimiter --space +``` + +A xml file needs to have following format: + +``` + + + + this-is-my-title + This is my title + Lorem ipsum dolor sit amet + https://a.storyblok.com/f/51376/x/1502f01431/corporate-website.svg + press + + +``` + +A csv file needs to have following format. The first row is used to identify the attribute names: + +``` +path;title;text;image;category +this-is-my-title;This is my title;"Lorem ipsum dolor sit amet";https://a.storyblok.com/f/51376/x/1502f01431/corporate-website.svg;press +``` + +A json file need to have following format: + +```json +[ + { + "path": "this-is-my-title", + "title": "This is my title", + "text": "Lorem ipsum dolor sit amet", + "image": "https://a.storyblok.com/f/51376/x/1502f01431/corporate-website.svg", + "category": "press" + } +] +``` + +#### Options + +* `file`: name of the file +* `type`: name of the content type you want to use for the import +* `space`: id of your space +* `delimiter` (optional): delimiter of the `.cvs` files, only necessary if you are uploading a csv file (Default value is **;** ) +* `folder` (optional): id of the folder where you want to store the content in Storyblok + +### Help + +For global help + +```sh +$ storyblok --help +``` + +For command help + +```sh +$ storyblok sync --help +``` + +### Version + +For view the CLI version + +```sh +$ storyblok -V # or --version +``` + +### delete-datasources + +The delete-datasources command enables you to remove all datasources within the designated space. By utilizing the `--by-slug` option, you can filter the datasources based on their slugs, selectively deleting specific datasources. Similarly, the `--by-name` option functions in the same way, allowing you to filter and delete datasources based on their names. + +```sh +$ storyblok delete-datasources --space-id # Will delete all datasources +``` + +```sh +$ storyblok delete-datasources --space-id --by-slug global-translations # Will only delete datasources where the slug starts with global-translations +``` + +#### Options + +* `space-id`: your space id +* `by-slug`: Filter Datasources by slug +* `by-name`: Filter Datasources by name + +## Content migrations + +Content migrations are a convenient way to change fields of your content. + +To execute a migration you first need to create a migration file. This file is a pure Javascript function where the content of a specific content type or component gets passed through. + +### 1. Creating a migration file + +To create a migration file, you need to execute the `generate-migration` command: + +```sh +# creating a migration file to product component to update the price +$ storyblok generate-migration --space 00000 --component product --field price +``` + +When you run this command a file called `change_product_price.js` will be created inside a folder called `migrations`. + +The created file will have the following content: + +```js +// here, 'subtitle' is the name of the field defined when you execute the generate command +module.exports = function (block) { + // Example to change a string to boolean + // block.subtitle = !!(block.subtitle) + + // Example to transfer content from other field + // block.subtitle = block.other_field +} +``` + +In the migration function you can manipulate the block variable to add or modify existing fields of the component. + +### 2. Running the migration file + +To run the migration function you need to execute the `run-migration` command. Pass the --dryrun option to not execute the updates and only show the changes in the terminal: + +```sh +$ storyblok run-migration --space 00000 --component product --field price --dryrun +``` + +After checking the output of the dryrun you can execute the updates: + +```sh +# you can use the --dryrun option to not execute the updates +$ storyblok run-migration --space 00000 --component product --field price +``` + +### 3. Publishing the content + +You can execute the migration and, when update the content, publish it using the `--publish` and `--publish-languages` options. When you use the `publish` option, **you need to specific one of these following options**: 'all', 'published' or 'published-with-changes': + +```sh +$ storyblok run-migration --space 00000 --component product --field price --publish all ``` -### Testing +You can specify the languages to update using `--publish-languages=` or update all languages using `--publish-languages=ALL_LANGUAGES`: -To run the tests you can use the following command: +```sh +# to update only one language +$ storyblok run-migration --space 00000 --component product --field price --publish all --publish-languages=de -```bash -pnpm run test +# to update more than one language +$ storyblok run-migration --space 00000 --component product --field price --publish all --publish-languages=de,pt ``` -If you prefer a more visual experience while writing tests you can use this command powered by [vitest/ui](https://vitest.dev/guide/ui): +### 4. Rollback migrations + +Whenever you run a `run-migrations` command a json file containing all the content before the change takes place will be generated. **Important**, this just doesn't apply if you add the `--dryrun` flag. + +Remembering that, the content that will be saved is always related to the last `run-migrations` command, that is, if you run the `run-migrations` command twice changing the same component, the content will only be saved before the last update. + +### Examples -```bash -pnpm run test:ui +#### 1. Change an image field + +Let's create an example to update all occurrences of the image field in product component. In the example we replace the url from `//a.storyblok.com` to `//my-custom-domain.com`. + +First, you need to create the migration function: + +```sh +$ storyblok generate-migration --space 00000 --component product --field image ``` -You can also check the coverage with: +Then let's update the default image field: -```bash -pnpm run coverage +```js +module.exports = function (block) { + block.image = block.image.replace('a.storyblok.com', 'my-custom-domain.com') +} ``` -### Debugging +Now you can execute the migration file: -To debug the CLI you can use the `launch.json` configuration in the `.vscode` folder. You can run any command with the debugger attached. +```sh +$ storyblok run-migration --space 00000 --component product --field image --dryrun +``` -![A screenshot of the Visual Studio Code debugger](/.github/assets/debug-vscode.png) +#### 2. Transform a Markdown field into a Richtext field -Then you can set breakpoints directly to the typescript files and the debugger will handle the rest with sourcempaps. -![A screenshot of a breakpoint set in the Visual Studio Code debugger](/.github/assets/breakpoints.png) +To transform a markdown or html field into a richtext field you first need to install a converter library. -## Community +```sh +$ npm install storyblok-markdown-richtext -g +``` + +Now check the path to the global node modules folder -For help, discussion about best practices, or any other conversation that would benefit from being searchable: +```sh +$ npm root -g +``` -- [Discuss Storyblok on Github Discussions](https://github.com/storyblok/storyblok/discussions) +Generate the migration with ```storyblok generate-migration --space 00000 --component blog --field intro``` and apply the transformation: -For community support, chatting with other users, please visit: +```js +var richtextConverter = require('/usr/local/lib/node_modules/storyblok-markdown-richtext') -- [Discuss Storyblok on Discord](https://storyblok.com/join-discord) +module.exports = function (block) { + if (typeof block.intro == 'string') { + block.intro = richtextConverter.markdownToRichtext(block.intro) + } +} +``` -## Support +## Typescript +It is possible to generate Typescript type definitions for your Storyblok components. The type definitions are based on the components' JSON Schema that can be retrieved with the [pull-components](#pull-components) command. -For bugs or feature requests, please [submit an issue](https://github.com/storyblok/storyblok-cli/issues/new/choose). +### generate-typescript-typedefs + +Generate a file with the type definitions for the specified components' JSON Schemas. + +```sh +$ storyblok generate-typescript-typedefs + --sourceFilePaths + --destinationFilePath + --typeNamesPrefix + --typeNamesSuffix + --JSONSchemaToTSOptionsPath + --customFieldTypesParserPath +``` -> [!IMPORTANT] -> Please search existing issues before submitting a new one. Issues without a minimal reproducible example will be closed. [Why reproductions are Required](https://antfu.me/posts/why-reproductions-are-required). +#### Options -### I can't share my company project code +* `sourceFilePaths` (alias `source`) : Path(s) to the components JSON file(s) as comma separated values +* `destinationFilePath` (alias `target`) *optional* : Path to the Typescript file that will be generated (*default*: `storyblok-component-types.d.ts`) +* `typeNamesPrefix` (alias `titlePrefix`) *optional* : A prefix that will be prepended to all the names of the generated types +* `typeNamesSuffix` (alias `titleSuffix`) *optional* : A suffix that will be appended to all the names of the generated types (*default*: `Storyblok`) +* `JSONSchemaToTSOptionsPath` (alias `compilerOptions`) *optional* : Path to a JSON file with a list of options supported by `json-schema-to-typescript` +* `customFieldTypesParserPath` (alias `customTypeParser`) *optional* : Path to the parser file for Custom Field Types -We understand that you might not be able to share your company's project code. Please provide a minimal reproducible example that demonstrates the issue by using tools like [Stackblitz](https://stackblitz.com) or a link to a Github Repo lease make sure you include a README file with the instructions to build and run the project, important not to include any access token, password or personal information of any kind. +#### Examples + +```sh +# Generate typedefs for the components retrieved for the space `12345` via the `storyblok pull-components` command +$ storyblok generate-typescript-typedefs --sourceFilePaths ./components.12345.json + +# Generate typedefs for multiple components sources +$ storyblok generate-typescript-typedefs --sourceFilePaths ./fooComponent-12345.json,./barComponent-12345.json + +# Custom path for the typedefs file +$ storyblok generate-typescript-typedefs --sourceFilePaths ./components.12345.json --destinationFilePath ./types/my-custom-type-file.d.ts + +# Provide customized options for the JSON-schema-to-typescript lib +$ storyblok generate-typescript-typedefs --sourceFilePaths ./components.12345.json --JSONSchemaToTSOptionsPath ./PathToJSONFileWithCustomOptions.json + +# Provide a custom field types parser +$ storyblok generate-typescript-typedefs --sourceFilePaths ./components.12345.json --customFieldTypesParserPath ./customFieldTypesParser.js + +``` + +##### JSON Schema to Typescript options +This script uses the `json-schema-to-typescript` library under the hood. Values of the [JSON Schema to Typescript options](https://www.npmjs.com/package/json-schema-to-typescript#options) can be customized providing a JSON file to the `JSONSchemaToTSOptionsPath`. + +The default values used for the `storyblok generate-typescript-typedefs` command are the same defaults for the library except for two properties: +* `bannerComment` - The default value is `""` to remove noise from the generated Typedefs file +* `unknownAny` - The default value is `false` because it can help a smoother Typescript adoption on a JS project + +Example `JSONSchemaToTSOptions` JSON file to remove `additionalProperties` from the generated type definitions: + +```json +{ + "additionalProperties": false, +} +``` + +##### Custom Field Types parser +Storyblok [Custom Field Types](https://www.storyblok.com/docs/plugins/field-plugins/introduction) do not have inherent JSONSchema definitions. To overcome this issue, you can provide a path to a script exporting a parser function that should render a [JSONSchema Node](https://json-schema.org/learn/getting-started-step-by-step#define-properties) for each of your Custom Field Types. The parser function should be exported as a default export, like in the following example: +```js +export default function (key, obj) { + switch (obj.field_type) { + case 'my-custom-field-type-name': + return { + [key]: { + type: 'object', + properties: { + color: { type: 'string' } + }, + required: ['color'] + } + } + default: + return {} + } +} +``` -### I only have a question -If you have a question, please ask in the [Discuss Storyblok on Discord](https://storyblok.com/join-discord) channel. -## Contributing -If you're interested in contributing to Storyblok CLI, please read our [contributing docs](https://github.com/storyblok/.github/blob/main/contributing.md) before submitting a pull request. -## License +## You're looking for a headstart? -[License](/LICENSE) +Check out our guides for client side apps (VueJS, Angular, React, ...), static site (Jekyll, NuxtJs, ...), dynamic site examples (Node, PHP, Python, Laravel, ...) on our [Getting Started](https://www.storyblok.com/getting-started) page. diff --git a/__mocks__/axios.js b/__mocks__/axios.js new file mode 100644 index 00000000..2b2a0140 --- /dev/null +++ b/__mocks__/axios.js @@ -0,0 +1,31 @@ +const { USERS_ROUTES, EMAIL_TEST, PASSWORD_TEST, TOKEN_TEST } = require('../tests/constants') + +const isCredCorrects = (email, pass) => { + return email === EMAIL_TEST && pass === PASSWORD_TEST +} + +const axios = { + post: jest.fn((path, data) => { + const { email, password } = data || {} + + if (path === USERS_ROUTES.LOGIN && isCredCorrects(email, password)) { + return Promise.resolve({ + data: { + access_token: TOKEN_TEST + } + }) + } + + if (path === USERS_ROUTES.SIGNUP && isCredCorrects(email, password)) { + return Promise.resolve({ + data: { + access_token: TOKEN_TEST + } + }) + } + + return Promise.reject(new Error('Incorrect access')) + }) +} + +module.exports = axios diff --git a/__mocks__/fs-extra.js b/__mocks__/fs-extra.js new file mode 100644 index 00000000..6a4db47d --- /dev/null +++ b/__mocks__/fs-extra.js @@ -0,0 +1,65 @@ +import { jest } from '@jest/globals' + +const fs = jest.createMockFromModule('fs-extra') + +let mockFiles = Object.create(null) + +// used by pull-components.spec.js +const pathExists = jest.fn((key) => { + return !!mockFiles[key] +}) + +const existsSync = jest.fn((key) => { + return `${process.cwd()}/migrations/rollback` +}) + +const readdirSync = jest.fn((key) => { + return ['rollback_product_title.json', 'rollback_product_text.json'] +}) + +const readFile = jest.fn((path) => { + mockFiles = path + return Promise.resolve(JSON.stringify([ + { + id: 0, + full_slug: 'another-post', + content: { + _uid: '5647c21f-8813-4f8a-ad38-b9f74e0e7c89', + text: 'Donec tortor mauris, mollis vel pretium vitae, lacinia nec sapien. Donec erat neque, ullamcorper tincidunt iaculis sit amet, pharetra bibendum ipsum. Nunc mattis risus ac ante consequat nec pulvinar neque molestie. Etiam interdum nunc at metus lacinia non varius erat dignissim. Integer elementum, felis id facilisis vulputate, ipsum tellus venenatis dui, at blandit nibh massa in dolor. Cras a ultricies sapien. Vivamus adipiscing feugiat pharetra.', + image: 'https://a.storyblok.com/f/51376/884x750/3bff01d851/international.svg', + title: 'test', + category: 'news', + component: 'Product' + } + } + ])) +}) + +const outputFile = jest.fn((path, data, fn) => { + mockFiles[path] = data + return Promise.resolve(true) +}) + +const __clearMockFiles = () => { + mockFiles = Object.create(null) +} + +const __setMockFiles = (mock) => { + mockFiles = mock +} + +fs.pathExists = pathExists + +fs.existsSync = existsSync + +fs.readdirSync = readdirSync + +fs.readFile = readFile + +fs.outputFile = outputFile + +fs.__clearMockFiles = __clearMockFiles + +fs.__setMockFiles = __setMockFiles + +export default fs diff --git a/__mocks__/fs.cjs b/__mocks__/fs.cjs deleted file mode 100644 index abd6f2b2..00000000 --- a/__mocks__/fs.cjs +++ /dev/null @@ -1,6 +0,0 @@ -// we can also use `import`, but then -// every export should be explicitly defined - -const { fs } = require('memfs'); - -module.exports = fs; diff --git a/__mocks__/fs.js b/__mocks__/fs.js new file mode 100644 index 00000000..accdc07f --- /dev/null +++ b/__mocks__/fs.js @@ -0,0 +1,51 @@ +const fs = jest.genMockFromModule('fs') + +const mockFiles = Object.create(null) + +// used by pull-components.spec.js +const writeFile = jest.fn((key, data, _) => { + mockFiles[key] = data +}) + +// used by scaffold.spec.js +const writeFileSync = jest.fn((key, data) => { + mockFiles[key] = data +}) + +// used by push-components.spec.js +const readFileSync = jest.fn((key) => { + if (key === 'components.js') { + return JSON.stringify({ + components: [ + { + name: 'doc', + display_name: null, + created_at: '2018-04-06T12:26:58.539Z', + id: 66594, + schema: { + content: { + type: 'markdown' + }, + summary: { + type: 'textarea' + } + }, + image: null, + preview_field: null, + is_root: true, + is_nestable: false + } + ] + }) + } + + return {} +}) + +fs.writeFile = writeFile + +fs.readFileSync = readFileSync + +fs.writeFileSync = writeFileSync + +module.exports = fs diff --git a/__mocks__/fs/promises.cjs b/__mocks__/fs/promises.cjs deleted file mode 100644 index 591b5282..00000000 --- a/__mocks__/fs/promises.cjs +++ /dev/null @@ -1,6 +0,0 @@ -// we can also use `import`, but then -// every export should be explicitly defined - -const { fs } = require('memfs'); - -module.exports = fs.promises; diff --git a/__mocks__/test.netrc b/__mocks__/test.netrc deleted file mode 100644 index 1967a0c9..00000000 --- a/__mocks__/test.netrc +++ /dev/null @@ -1,4 +0,0 @@ -machine api.storyblok.com - login julio.iglesias@storyblok.com - password my_access_token - region eu \ No newline at end of file diff --git a/build.config.ts b/build.config.ts index a0618d91..514613de 100644 --- a/build.config.ts +++ b/build.config.ts @@ -1,8 +1,23 @@ -import { defineBuildConfig } from 'unbuild'; +import { defineBuildConfig } from "unbuild"; export default defineBuildConfig({ declaration: true, - entries: ['./src/index'], - failOnWarn: false, - sourcemap: true, + rollup: { + inlineDependencies: true, + resolve: { + exportConditions: ["production", "node"] as any, + }, + }, + entries: ["src/cli"], + externals: [ + "@nuxt/test-utils", + "fsevents", + "node:url", + "node:buffer", + "node:path", + "node:child_process", + "node:process", + "node:path", + "node:os", + ], }); diff --git a/compiler-options.ts b/compiler-options.ts deleted file mode 100644 index 4c28e8a7..00000000 --- a/compiler-options.ts +++ /dev/null @@ -1,5 +0,0 @@ -export default { - unknownAny: false, - additionalProperties: false, - bannerComment: 'Awiwi', -}; diff --git a/custom-fields-parser.ts b/custom-fields-parser.ts deleted file mode 100644 index cf77fa91..00000000 --- a/custom-fields-parser.ts +++ /dev/null @@ -1,16 +0,0 @@ -export default (key: string, field: any) => { - switch (field.field_type) { - case 'native-color-picker': - return { - [key]: { - properties: { - color: { type: 'string' }, - }, - required: ['color'], - type: 'object', - }, - }; - default: - return {}; - } -}; diff --git a/eslint.config.mjs b/eslint.config.mjs deleted file mode 100644 index 4e8ca0fd..00000000 --- a/eslint.config.mjs +++ /dev/null @@ -1,8 +0,0 @@ -import { storyblokLintConfig } from '@storyblok/eslint-config'; - -export default storyblokLintConfig({ - rules: { - 'no-console': 'off', - 'style/max-statements-per-line': 'off', - }, -}); diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 00000000..351f7ed4 --- /dev/null +++ b/jest.config.js @@ -0,0 +1 @@ +export default { transform: {} } diff --git a/package.json b/package.json index 88f7624c..0c45413e 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,7 @@ { "name": "storyblok", - "type": "module", - "version": "4.0.0", - "packageManager": "pnpm@10.10.0", - "description": "Storyblok CLI", - "author": "Alvaro Saburido (https://github.com/alvarosabu/)", - "license": "MIT", + "version": "3.25.0", + "description": "A simple CLI to start Storyblok from your command line.", "repository": { "type": "git", "url": "https://github.com/storyblok/storyblok-cli.git" @@ -16,54 +12,70 @@ "node", "javascript" ], - "main": "./dist/index.mjs", - "bin": { - "storyblok": "./dist/index.mjs" - }, + "main": "./dist/cli.mjs", "files": [ "dist/**" ], + "bin": { + "storyblok": "./dist/cli.mjs" + }, + "type": "module", "scripts": { "build": "unbuild", - "typecheck": "tsc --noEmit", - "release": "release-it", - "dev": "pnpm run build && node dist/index.mjs", - "lint": "eslint .", - "lint:fix": "eslint . --fix", - "test": "vitest", - "test:ci": "vitest run", - "test:ui": "vitest --ui", - "coverage": "vitest run --coverage" + "dev": "npm run build && ./dist/cli.mjs", + "lint": "eslint src/", + "lint:fix": "eslint src/ --fix", + "test:unit": "node --experimental-vm-modules ./node_modules/.bin/jest --silent", + "test:coverage": "node --experimental-vm-modules ./node_modules/.bin/jest --coverage" }, + "author": "Dominik Angerer , Alexander Feiglstorfer ", + "license": "MIT", "dependencies": { - "@inquirer/prompts": "^7.5.1", - "@storyblok/js": "^4.0.0", - "@topcli/spinner": "^2.1.2", - "chalk": "^5.4.1", - "commander": "^13.1.0", - "dotenv": "^16.5.0", - "json-schema-to-typescript": "^15.0.4", - "minimatch": "^10.0.3", - "ohash": "^2.0.11", - "pathe": "^2.0.3", - "read-package-up": "^11.0.0", - "storyblok-js-client": "^6.10.12" + "@storyblok/region-helper": "^1.0.0", + "axios": "^0.30.0", + "chalk": "^4.1.0", + "clear": "0.1.0", + "commander": "^5.1.0", + "fast-csv": "^4.3.6", + "figlet": "^1.5.0", + "form-data": "^3.0.0", + "fs-extra": "^9.0.1", + "git-clone": "^0.2.0", + "inquirer": "^7.3.2", + "json-schema-to-typescript": "^13.1.2", + "lodash": "^4.17.21", + "netrc": "0.1.4", + "on-change": "^2.0.1", + "open": "^6.0.0", + "p-series": "^2.1.0", + "path": "^0.12.7", + "simple-uuid": "^0.0.1", + "storyblok-js-client": "6.10.3", + "update-notifier": "^5.1.0", + "xml-js": "^1.6.11" + }, + "engines": { + "node": ">=18.0.0" }, "devDependencies": { - "@release-it/conventional-changelog": "10.0.0", - "@storyblok/eslint-config": "^0.3.0", - "@types/inquirer": "^9.0.8", - "@types/node": "^22.15.18", - "@vitest/coverage-v8": "^3.1.3", - "@vitest/ui": "^3.1.3", - "eslint": "^9.26.0", - "memfs": "^4.17.1", - "msw": "^2.8.2", - "release-it": "^18.1.2", - "typescript": "^5.8.3", - "unbuild": "^3.5.0", - "uuid": "^11.1.0", - "vite": "^6.3.5", - "vitest": "^3.1.3" + "concat-stream": "^2.0.0", + "eslint": "^7.2.0", + "eslint-config-standard": "^14.1.1", + "eslint-plugin-import": "^2.21.2", + "eslint-plugin-jest": "^23.18.0", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^4.2.1", + "eslint-plugin-standard": "^4.0.1", + "jest": "^29.7.0", + "typescript": "^5.3.3", + "unbuild": "^2.0.0" + }, + "release": { + "branches": [ + "main" + ] + }, + "prettier": { + "printWidth": 120 } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml deleted file mode 100644 index 44341734..00000000 --- a/pnpm-lock.yaml +++ /dev/null @@ -1,8629 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - '@inquirer/prompts': - specifier: ^7.5.1 - version: 7.5.1(@types/node@22.15.18) - '@storyblok/js': - specifier: ^4.0.0 - version: 4.0.0 - '@topcli/spinner': - specifier: ^2.1.2 - version: 2.1.2 - chalk: - specifier: ^5.4.1 - version: 5.4.1 - commander: - specifier: ^13.1.0 - version: 13.1.0 - dotenv: - specifier: ^16.5.0 - version: 16.5.0 - json-schema-to-typescript: - specifier: ^15.0.4 - version: 15.0.4 - minimatch: - specifier: ^10.0.3 - version: 10.0.3 - ohash: - specifier: ^2.0.11 - version: 2.0.11 - pathe: - specifier: ^2.0.3 - version: 2.0.3 - read-package-up: - specifier: ^11.0.0 - version: 11.0.0 - storyblok-js-client: - specifier: ^6.10.12 - version: 6.10.12 - devDependencies: - '@release-it/conventional-changelog': - specifier: 10.0.0 - version: 10.0.0(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.1.0)(release-it@18.1.2(@types/node@22.15.18)(typescript@5.8.3)) - '@storyblok/eslint-config': - specifier: ^0.3.0 - version: 0.3.0(@typescript-eslint/utils@8.32.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3))(@vue/compiler-sfc@3.5.13)(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.1.3) - '@types/inquirer': - specifier: ^9.0.8 - version: 9.0.8 - '@types/node': - specifier: ^22.15.18 - version: 22.15.18 - '@vitest/coverage-v8': - specifier: ^3.1.3 - version: 3.1.3(vitest@3.1.3) - '@vitest/ui': - specifier: ^3.1.3 - version: 3.1.3(vitest@3.1.3) - eslint: - specifier: ^9.26.0 - version: 9.26.0(jiti@2.4.2) - memfs: - specifier: ^4.17.1 - version: 4.17.1 - msw: - specifier: ^2.8.2 - version: 2.8.2(@types/node@22.15.18)(typescript@5.8.3) - release-it: - specifier: ^18.1.2 - version: 18.1.2(@types/node@22.15.18)(typescript@5.8.3) - typescript: - specifier: ^5.8.3 - version: 5.8.3 - unbuild: - specifier: ^3.5.0 - version: 3.5.0(typescript@5.8.3) - uuid: - specifier: ^11.1.0 - version: 11.1.0 - vite: - specifier: ^6.3.5 - version: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(yaml@2.7.1) - vitest: - specifier: ^3.1.3 - version: 3.1.3(@types/debug@4.1.12)(@types/node@22.15.18)(@vitest/ui@3.1.3)(jiti@2.4.2)(msw@2.8.2(@types/node@22.15.18)(typescript@5.8.3))(yaml@2.7.1) - -packages: - - '@ampproject/remapping@2.3.0': - resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} - engines: {node: '>=6.0.0'} - - '@antfu/eslint-config@3.6.2': - resolution: {integrity: sha512-cewFaIEuSSOjbIsNts8gjeMLQrrMDhZjZJHMWk+OyVGJLHRE09JiF5Yg5+XjMVYlG/7fPqeuwEehLrer+8zMfA==} - hasBin: true - peerDependencies: - '@eslint-react/eslint-plugin': ^1.5.8 - '@prettier/plugin-xml': ^3.4.1 - '@unocss/eslint-plugin': '>=0.50.0' - astro-eslint-parser: ^1.0.2 - eslint: ^9.10.0 - eslint-plugin-astro: ^1.2.0 - eslint-plugin-format: '>=0.1.0' - eslint-plugin-react-hooks: ^4.6.0 - eslint-plugin-react-refresh: ^0.4.4 - eslint-plugin-solid: ^0.14.3 - eslint-plugin-svelte: '>=2.35.1' - prettier-plugin-astro: ^0.13.0 - prettier-plugin-slidev: ^1.0.5 - svelte-eslint-parser: '>=0.37.0' - peerDependenciesMeta: - '@eslint-react/eslint-plugin': - optional: true - '@prettier/plugin-xml': - optional: true - '@unocss/eslint-plugin': - optional: true - astro-eslint-parser: - optional: true - eslint-plugin-astro: - optional: true - eslint-plugin-format: - optional: true - eslint-plugin-react-hooks: - optional: true - eslint-plugin-react-refresh: - optional: true - eslint-plugin-solid: - optional: true - eslint-plugin-svelte: - optional: true - prettier-plugin-astro: - optional: true - prettier-plugin-slidev: - optional: true - svelte-eslint-parser: - optional: true - - '@antfu/install-pkg@0.4.1': - resolution: {integrity: sha512-T7yB5QNG29afhWVkVq7XeIMBa5U/vs9mX69YqayXypPRmYzUmzwnYltplHmPtZ4HPCn+sQKeXW8I47wCbuBOjw==} - - '@antfu/utils@0.7.10': - resolution: {integrity: sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==} - - '@apidevtools/json-schema-ref-parser@11.9.3': - resolution: {integrity: sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ==} - engines: {node: '>= 16'} - - '@babel/code-frame@7.27.1': - resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-string-parser@7.27.1': - resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-identifier@7.27.1': - resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} - engines: {node: '>=6.9.0'} - - '@babel/parser@7.27.2': - resolution: {integrity: sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==} - engines: {node: '>=6.0.0'} - hasBin: true - - '@babel/types@7.27.1': - resolution: {integrity: sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==} - engines: {node: '>=6.9.0'} - - '@bcoe/v8-coverage@1.0.2': - resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} - engines: {node: '>=18'} - - '@bundled-es-modules/cookie@2.0.1': - resolution: {integrity: sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==} - - '@bundled-es-modules/statuses@1.0.1': - resolution: {integrity: sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==} - - '@bundled-es-modules/tough-cookie@0.1.6': - resolution: {integrity: sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==} - - '@clack/core@0.3.5': - resolution: {integrity: sha512-5cfhQNH+1VQ2xLQlmzXMqUoiaH0lRBq9/CLW9lTyMbuKLC3+xEK01tHVvyut++mLOn5urSHmkm6I0Lg9MaJSTQ==} - - '@clack/prompts@0.7.0': - resolution: {integrity: sha512-0MhX9/B4iL6Re04jPrttDm+BsP8y6mS7byuv0BvXgdXhbV5PdlsHt55dvNsuBCPZ7xq1oTAOOuotR9NFbQyMSA==} - bundledDependencies: - - is-unicode-supported - - '@conventional-changelog/git-client@1.0.1': - resolution: {integrity: sha512-PJEqBwAleffCMETaVm/fUgHldzBE35JFk3/9LL6NUA5EXa3qednu+UT6M7E5iBu3zIQZCULYIiZ90fBYHt6xUw==} - engines: {node: '>=18'} - peerDependencies: - conventional-commits-filter: ^5.0.0 - conventional-commits-parser: ^6.0.0 - peerDependenciesMeta: - conventional-commits-filter: - optional: true - conventional-commits-parser: - optional: true - - '@dprint/formatter@0.3.0': - resolution: {integrity: sha512-N9fxCxbaBOrDkteSOzaCqwWjso5iAe+WJPsHC021JfHNj2ThInPNEF13ORDKta3llq5D1TlclODCvOvipH7bWQ==} - - '@dprint/markdown@0.17.8': - resolution: {integrity: sha512-ukHFOg+RpG284aPdIg7iPrCYmMs3Dqy43S1ejybnwlJoFiW02b+6Bbr5cfZKFRYNP3dKGM86BqHEnMzBOyLvvA==} - - '@dprint/toml@0.6.4': - resolution: {integrity: sha512-bZXIUjxr0LIuHWshZr/5mtUkOrnh0NKVZEF6ACojW5z7zkJu7s9sV2mMXm8XQDqN4cJzdHYUYzUyEGdfciaLJA==} - - '@emnapi/core@1.4.3': - resolution: {integrity: sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==} - - '@emnapi/runtime@1.4.3': - resolution: {integrity: sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==} - - '@emnapi/wasi-threads@1.0.2': - resolution: {integrity: sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==} - - '@es-joy/jsdoccomment@0.49.0': - resolution: {integrity: sha512-xjZTSFgECpb9Ohuk5yMX5RhUEbfeQcuOp8IF60e+wyzWEF0M5xeSgqsfLtvPEX8BIyOX9saZqzuGPmZ8oWc+5Q==} - engines: {node: '>=16'} - - '@es-joy/jsdoccomment@0.50.1': - resolution: {integrity: sha512-fas3qe1hw38JJgU/0m5sDpcrbZGysBeZcMwW5Ws9brYxY64MJyWLXRZCj18keTycT1LFTrFXdSNMS+GRVaU6Hw==} - engines: {node: '>=18'} - - '@esbuild/aix-ppc64@0.25.4': - resolution: {integrity: sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - - '@esbuild/android-arm64@0.25.4': - resolution: {integrity: sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm@0.25.4': - resolution: {integrity: sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-x64@0.25.4': - resolution: {integrity: sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/darwin-arm64@0.25.4': - resolution: {integrity: sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-x64@0.25.4': - resolution: {integrity: sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/freebsd-arm64@0.25.4': - resolution: {integrity: sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.25.4': - resolution: {integrity: sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/linux-arm64@0.25.4': - resolution: {integrity: sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm@0.25.4': - resolution: {integrity: sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-ia32@0.25.4': - resolution: {integrity: sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-loong64@0.25.4': - resolution: {integrity: sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-mips64el@0.25.4': - resolution: {integrity: sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-ppc64@0.25.4': - resolution: {integrity: sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-riscv64@0.25.4': - resolution: {integrity: sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-s390x@0.25.4': - resolution: {integrity: sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-x64@0.25.4': - resolution: {integrity: sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/netbsd-arm64@0.25.4': - resolution: {integrity: sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.25.4': - resolution: {integrity: sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-arm64@0.25.4': - resolution: {integrity: sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.25.4': - resolution: {integrity: sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - - '@esbuild/sunos-x64@0.25.4': - resolution: {integrity: sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/win32-arm64@0.25.4': - resolution: {integrity: sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-ia32@0.25.4': - resolution: {integrity: sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-x64@0.25.4': - resolution: {integrity: sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - - '@eslint-community/eslint-plugin-eslint-comments@4.5.0': - resolution: {integrity: sha512-MAhuTKlr4y/CE3WYX26raZjy+I/kS2PLKSzvfmDCGrBLTFHOYwqROZdr4XwPgXwX3K9rjzMr4pSmUWGnzsUyMg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 - - '@eslint-community/eslint-utils@4.7.0': - resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - - '@eslint-community/regexpp@4.12.1': - resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - - '@eslint/compat@1.2.9': - resolution: {integrity: sha512-gCdSY54n7k+driCadyMNv8JSPzYLeDVM/ikZRtvtROBpRdFSkS8W9A82MqsaY7lZuwL0wiapgD0NT1xT0hyJsA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^9.10.0 - peerDependenciesMeta: - eslint: - optional: true - - '@eslint/config-array@0.20.0': - resolution: {integrity: sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/config-helpers@0.2.2': - resolution: {integrity: sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/core@0.10.0': - resolution: {integrity: sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/core@0.13.0': - resolution: {integrity: sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/eslintrc@3.3.1': - resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/js@9.26.0': - resolution: {integrity: sha512-I9XlJawFdSMvWjDt6wksMCrgns5ggLNfFwFvnShsleWruvXM514Qxk8V246efTw+eo9JABvVz+u3q2RiAowKxQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/markdown@6.4.0': - resolution: {integrity: sha512-J07rR8uBSNFJ9iliNINrchilpkmCihPmTVotpThUeKEn5G8aBBZnkjNBy/zovhJA5LBk1vWU9UDlhqKSc/dViQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/object-schema@2.1.6': - resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/plugin-kit@0.2.8': - resolution: {integrity: sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@humanfs/core@0.19.1': - resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} - engines: {node: '>=18.18.0'} - - '@humanfs/node@0.16.6': - resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} - engines: {node: '>=18.18.0'} - - '@humanwhocodes/module-importer@1.0.1': - resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} - engines: {node: '>=12.22'} - - '@humanwhocodes/retry@0.3.1': - resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} - engines: {node: '>=18.18'} - - '@humanwhocodes/retry@0.4.3': - resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} - engines: {node: '>=18.18'} - - '@hutson/parse-repository-url@5.0.0': - resolution: {integrity: sha512-e5+YUKENATs1JgYHMzTr2MW/NDcXGfYFAuOQU8gJgF/kEh4EqKgfGrfLI67bMD4tbhZVlkigz/9YYwWcbOFthg==} - engines: {node: '>=10.13.0'} - - '@iarna/toml@2.2.5': - resolution: {integrity: sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==} - - '@inquirer/checkbox@4.1.6': - resolution: {integrity: sha512-62u896rWCtKKE43soodq5e/QcRsA22I+7/4Ov7LESWnKRO6BVo2A1DFLDmXL9e28TB0CfHc3YtkbPm7iwajqkg==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/confirm@5.1.10': - resolution: {integrity: sha512-FxbQ9giWxUWKUk2O5XZ6PduVnH2CZ/fmMKMBkH71MHJvWr7WL5AHKevhzF1L5uYWB2P548o1RzVxrNd3dpmk6g==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/core@10.1.11': - resolution: {integrity: sha512-BXwI/MCqdtAhzNQlBEFE7CEflhPkl/BqvAuV/aK6lW3DClIfYVDWPP/kXuXHtBWC7/EEbNqd/1BGq2BGBBnuxw==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/editor@4.2.11': - resolution: {integrity: sha512-YoZr0lBnnLFPpfPSNsQ8IZyKxU47zPyVi9NLjCWtna52//M/xuL0PGPAxHxxYhdOhnvY2oBafoM+BI5w/JK7jw==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/expand@4.0.13': - resolution: {integrity: sha512-HgYNWuZLHX6q5y4hqKhwyytqAghmx35xikOGY3TcgNiElqXGPas24+UzNPOwGUZa5Dn32y25xJqVeUcGlTv+QQ==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/figures@1.0.11': - resolution: {integrity: sha512-eOg92lvrn/aRUqbxRyvpEWnrvRuTYRifixHkYVpJiygTgVSBIHDqLh0SrMQXkafvULg3ck11V7xvR+zcgvpHFw==} - engines: {node: '>=18'} - - '@inquirer/input@4.1.10': - resolution: {integrity: sha512-kV3BVne3wJ+j6reYQUZi/UN9NZGZLxgc/tfyjeK3mrx1QI7RXPxGp21IUTv+iVHcbP4ytZALF8vCHoxyNSC6qg==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/number@3.0.13': - resolution: {integrity: sha512-IrLezcg/GWKS8zpKDvnJ/YTflNJdG0qSFlUM/zNFsdi4UKW/CO+gaJpbMgQ20Q58vNKDJbEzC6IebdkprwL6ew==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/password@4.0.13': - resolution: {integrity: sha512-NN0S/SmdhakqOTJhDwOpeBEEr8VdcYsjmZHDb0rblSh2FcbXQOr+2IApP7JG4WE3sxIdKytDn4ed3XYwtHxmJQ==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/prompts@7.5.1': - resolution: {integrity: sha512-5AOrZPf2/GxZ+SDRZ5WFplCA2TAQgK3OYrXCYmJL5NaTu4ECcoWFlfUZuw7Es++6Njv7iu/8vpYJhuzxUH76Vg==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/rawlist@4.1.1': - resolution: {integrity: sha512-VBUC0jPN2oaOq8+krwpo/mf3n/UryDUkKog3zi+oIi8/e5hykvdntgHUB9nhDM78RubiyR1ldIOfm5ue+2DeaQ==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/search@3.0.13': - resolution: {integrity: sha512-9g89d2c5Izok/Gw/U7KPC3f9kfe5rA1AJ24xxNZG0st+vWekSk7tB9oE+dJv5JXd0ZSijomvW0KPMoBd8qbN4g==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/select@4.2.1': - resolution: {integrity: sha512-gt1Kd5XZm+/ddemcT3m23IP8aD8rC9drRckWoP/1f7OL46Yy2FGi8DSmNjEjQKtPl6SV96Kmjbl6p713KXJ/Jg==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/type@3.0.6': - resolution: {integrity: sha512-/mKVCtVpyBu3IDarv0G+59KC4stsD5mDsGpYh+GKs1NZT88Jh52+cuoA1AtLk2Q0r/quNl+1cSUyLRHBFeD0XA==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@isaacs/balanced-match@4.0.1': - resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} - engines: {node: 20 || >=22} - - '@isaacs/brace-expansion@5.0.0': - resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} - engines: {node: 20 || >=22} - - '@isaacs/cliui@8.0.2': - resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} - engines: {node: '>=12'} - - '@istanbuljs/schema@0.1.3': - resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} - engines: {node: '>=8'} - - '@jridgewell/gen-mapping@0.3.8': - resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} - engines: {node: '>=6.0.0'} - - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - - '@jridgewell/set-array@1.2.1': - resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} - engines: {node: '>=6.0.0'} - - '@jridgewell/sourcemap-codec@1.5.0': - resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} - - '@jridgewell/trace-mapping@0.3.25': - resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - - '@jsdevtools/ono@7.1.3': - resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} - - '@jsonjoy.com/base64@1.1.2': - resolution: {integrity: sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==} - engines: {node: '>=10.0'} - peerDependencies: - tslib: '2' - - '@jsonjoy.com/json-pack@1.2.0': - resolution: {integrity: sha512-io1zEbbYcElht3tdlqEOFxZ0dMTYrHz9iMf0gqn1pPjZFTCgM5R4R5IMA20Chb2UPYYsxjzs8CgZ7Nb5n2K2rA==} - engines: {node: '>=10.0'} - peerDependencies: - tslib: '2' - - '@jsonjoy.com/util@1.6.0': - resolution: {integrity: sha512-sw/RMbehRhN68WRtcKCpQOPfnH6lLP4GJfqzi3iYej8tnzpZUDr6UkZYJjcjjC0FWEJOJbyM3PTIwxucUmDG2A==} - engines: {node: '>=10.0'} - peerDependencies: - tslib: '2' - - '@modelcontextprotocol/sdk@1.11.2': - resolution: {integrity: sha512-H9vwztj5OAqHg9GockCQC06k1natgcxWQSRpQcPJf6i5+MWBzfKkRtxGbjQf0X2ihii0ffLZCRGbYV2f2bjNCQ==} - engines: {node: '>=18'} - - '@mswjs/interceptors@0.37.6': - resolution: {integrity: sha512-wK+5pLK5XFmgtH3aQ2YVvA3HohS3xqV/OxuVOdNx9Wpnz7VE/fnC+e1A7ln6LFYeck7gOJ/dsZV6OLplOtAJ2w==} - engines: {node: '>=18'} - - '@napi-rs/wasm-runtime@0.2.9': - resolution: {integrity: sha512-OKRBiajrrxB9ATokgEQoG87Z25c67pCpYcCwmXYX8PBftC9pBfN18gnm/fh1wurSLEKIAt+QRFLFCQISrb66Jg==} - - '@nodelib/fs.scandir@2.1.5': - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} - - '@nodelib/fs.stat@2.0.5': - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} - - '@nodelib/fs.walk@1.2.8': - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} - - '@octokit/auth-token@5.1.2': - resolution: {integrity: sha512-JcQDsBdg49Yky2w2ld20IHAlwr8d/d8N6NiOXbtuoPCqzbsiJgF633mVUw3x4mo0H5ypataQIX7SFu3yy44Mpw==} - engines: {node: '>= 18'} - - '@octokit/core@6.1.5': - resolution: {integrity: sha512-vvmsN0r7rguA+FySiCsbaTTobSftpIDIpPW81trAmsv9TGxg3YCujAxRYp/Uy8xmDgYCzzgulG62H7KYUFmeIg==} - engines: {node: '>= 18'} - - '@octokit/endpoint@10.1.4': - resolution: {integrity: sha512-OlYOlZIsfEVZm5HCSR8aSg02T2lbUWOsCQoPKfTXJwDzcHQBrVBGdGXb89dv2Kw2ToZaRtudp8O3ZIYoaOjKlA==} - engines: {node: '>= 18'} - - '@octokit/graphql@8.2.2': - resolution: {integrity: sha512-Yi8hcoqsrXGdt0yObxbebHXFOiUA+2v3n53epuOg1QUgOB6c4XzvisBNVXJSl8RYA5KrDuSL2yq9Qmqe5N0ryA==} - engines: {node: '>= 18'} - - '@octokit/openapi-types@24.2.0': - resolution: {integrity: sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==} - - '@octokit/openapi-types@25.0.0': - resolution: {integrity: sha512-FZvktFu7HfOIJf2BScLKIEYjDsw6RKc7rBJCdvCTfKsVnx2GEB/Nbzjr29DUdb7vQhlzS/j8qDzdditP0OC6aw==} - - '@octokit/plugin-paginate-rest@11.6.0': - resolution: {integrity: sha512-n5KPteiF7pWKgBIBJSk8qzoZWcUkza2O6A0za97pMGVrGfPdltxrfmfF5GucHYvHGZD8BdaZmmHGz5cX/3gdpw==} - engines: {node: '>= 18'} - peerDependencies: - '@octokit/core': '>=6' - - '@octokit/plugin-request-log@5.3.1': - resolution: {integrity: sha512-n/lNeCtq+9ofhC15xzmJCNKP2BWTv8Ih2TTy+jatNCCq/gQP/V7rK3fjIfuz0pDWDALO/o/4QY4hyOF6TQQFUw==} - engines: {node: '>= 18'} - peerDependencies: - '@octokit/core': '>=6' - - '@octokit/plugin-rest-endpoint-methods@13.5.0': - resolution: {integrity: sha512-9Pas60Iv9ejO3WlAX3maE1+38c5nqbJXV5GrncEfkndIpZrJ/WPMRd2xYDcPPEt5yzpxcjw9fWNoPhsSGzqKqw==} - engines: {node: '>= 18'} - peerDependencies: - '@octokit/core': '>=6' - - '@octokit/request-error@6.1.8': - resolution: {integrity: sha512-WEi/R0Jmq+IJKydWlKDmryPcmdYSVjL3ekaiEL1L9eo1sUnqMJ+grqmC9cjk7CA7+b2/T397tO5d8YLOH3qYpQ==} - engines: {node: '>= 18'} - - '@octokit/request@9.2.3': - resolution: {integrity: sha512-Ma+pZU8PXLOEYzsWf0cn/gY+ME57Wq8f49WTXA8FMHp2Ps9djKw//xYJ1je8Hm0pR2lU9FUGeJRWOtxq6olt4w==} - engines: {node: '>= 18'} - - '@octokit/rest@21.0.2': - resolution: {integrity: sha512-+CiLisCoyWmYicH25y1cDfCrv41kRSvTq6pPWtRroRJzhsCZWZyCqGyI8foJT5LmScADSwRAnr/xo+eewL04wQ==} - engines: {node: '>= 18'} - - '@octokit/types@13.10.0': - resolution: {integrity: sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==} - - '@octokit/types@14.0.0': - resolution: {integrity: sha512-VVmZP0lEhbo2O1pdq63gZFiGCKkm8PPp8AUOijlwPO6hojEVjspA0MWKP7E4hbvGxzFKNqKr6p0IYtOH/Wf/zA==} - - '@open-draft/deferred-promise@2.2.0': - resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} - - '@open-draft/logger@0.3.0': - resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==} - - '@open-draft/until@2.1.0': - resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} - - '@pkgjs/parseargs@0.11.0': - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} - engines: {node: '>=14'} - - '@pkgr/core@0.1.2': - resolution: {integrity: sha512-fdDH1LSGfZdTH2sxdpVMw31BanV28K/Gry0cVFxaNP77neJSkd82mM8ErPNYs9e+0O7SdHBLTDzDgwUuy18RnQ==} - engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - - '@pkgr/core@0.2.4': - resolution: {integrity: sha512-ROFF39F6ZrnzSUEmQQZUar0Jt4xVoP9WnDRdWwF4NNcXs3xBTLgBUDoOwW141y1jP+S8nahIbdxbFC7IShw9Iw==} - engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - - '@pnpm/config.env-replace@1.1.0': - resolution: {integrity: sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==} - engines: {node: '>=12.22.0'} - - '@pnpm/network.ca-file@1.0.2': - resolution: {integrity: sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==} - engines: {node: '>=12.22.0'} - - '@pnpm/npm-conf@2.3.1': - resolution: {integrity: sha512-c83qWb22rNRuB0UaVCI0uRPNRr8Z0FWnEIvT47jiHAmOIUHbBOg5XvV7pM5x+rKn9HRpjxquDbXYSXr3fAKFcw==} - engines: {node: '>=12'} - - '@polka/url@1.0.0-next.29': - resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} - - '@release-it/conventional-changelog@10.0.0': - resolution: {integrity: sha512-49qf9phGmPUIGpY2kwfgehs9en1znbPv2zdNn1WMLAH9DtHUh4m6KNSB+mLFGAMUhv24JhsA8ruYRYgluc2UJw==} - engines: {node: ^20.9.0 || >=22.0.0} - peerDependencies: - release-it: ^18.0.0 - - '@rollup/plugin-alias@5.1.1': - resolution: {integrity: sha512-PR9zDb+rOzkRb2VD+EuKB7UC41vU5DIwZ5qqCpk0KJudcWAyi8rvYOhS7+L5aZCspw1stTViLgN5v6FF1p5cgQ==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - - '@rollup/plugin-commonjs@28.0.3': - resolution: {integrity: sha512-pyltgilam1QPdn+Zd9gaCfOLcnjMEJ9gV+bTw6/r73INdvzf1ah9zLIJBm+kW7R6IUFIQ1YO+VqZtYxZNWFPEQ==} - engines: {node: '>=16.0.0 || 14 >= 14.17'} - peerDependencies: - rollup: ^2.68.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - - '@rollup/plugin-json@6.1.0': - resolution: {integrity: sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - - '@rollup/plugin-node-resolve@16.0.1': - resolution: {integrity: sha512-tk5YCxJWIG81umIvNkSod2qK5KyQW19qcBF/B78n1bjtOON6gzKoVeSzAE8yHCZEDmqkHKkxplExA8KzdJLJpA==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^2.78.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - - '@rollup/plugin-replace@6.0.2': - resolution: {integrity: sha512-7QaYCf8bqF04dOy7w/eHmJeNExxTYwvKAmlSAH/EaWWUzbT0h5sbF6bktFoX/0F/0qwng5/dWFMyf3gzaM8DsQ==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - - '@rollup/pluginutils@5.1.4': - resolution: {integrity: sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - - '@rollup/rollup-android-arm-eabi@4.40.2': - resolution: {integrity: sha512-JkdNEq+DFxZfUwxvB58tHMHBHVgX23ew41g1OQinthJ+ryhdRk67O31S7sYw8u2lTjHUPFxwar07BBt1KHp/hg==} - cpu: [arm] - os: [android] - - '@rollup/rollup-android-arm64@4.40.2': - resolution: {integrity: sha512-13unNoZ8NzUmnndhPTkWPWbX3vtHodYmy+I9kuLxN+F+l+x3LdVF7UCu8TWVMt1POHLh6oDHhnOA04n8oJZhBw==} - cpu: [arm64] - os: [android] - - '@rollup/rollup-darwin-arm64@4.40.2': - resolution: {integrity: sha512-Gzf1Hn2Aoe8VZzevHostPX23U7N5+4D36WJNHK88NZHCJr7aVMG4fadqkIf72eqVPGjGc0HJHNuUaUcxiR+N/w==} - cpu: [arm64] - os: [darwin] - - '@rollup/rollup-darwin-x64@4.40.2': - resolution: {integrity: sha512-47N4hxa01a4x6XnJoskMKTS8XZ0CZMd8YTbINbi+w03A2w4j1RTlnGHOz/P0+Bg1LaVL6ufZyNprSg+fW5nYQQ==} - cpu: [x64] - os: [darwin] - - '@rollup/rollup-freebsd-arm64@4.40.2': - resolution: {integrity: sha512-8t6aL4MD+rXSHHZUR1z19+9OFJ2rl1wGKvckN47XFRVO+QL/dUSpKA2SLRo4vMg7ELA8pzGpC+W9OEd1Z/ZqoQ==} - cpu: [arm64] - os: [freebsd] - - '@rollup/rollup-freebsd-x64@4.40.2': - resolution: {integrity: sha512-C+AyHBzfpsOEYRFjztcYUFsH4S7UsE9cDtHCtma5BK8+ydOZYgMmWg1d/4KBytQspJCld8ZIujFMAdKG1xyr4Q==} - cpu: [x64] - os: [freebsd] - - '@rollup/rollup-linux-arm-gnueabihf@4.40.2': - resolution: {integrity: sha512-de6TFZYIvJwRNjmW3+gaXiZ2DaWL5D5yGmSYzkdzjBDS3W+B9JQ48oZEsmMvemqjtAFzE16DIBLqd6IQQRuG9Q==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm-musleabihf@4.40.2': - resolution: {integrity: sha512-urjaEZubdIkacKc930hUDOfQPysezKla/O9qV+O89enqsqUmQm8Xj8O/vh0gHg4LYfv7Y7UsE3QjzLQzDYN1qg==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm64-gnu@4.40.2': - resolution: {integrity: sha512-KlE8IC0HFOC33taNt1zR8qNlBYHj31qGT1UqWqtvR/+NuCVhfufAq9fxO8BMFC22Wu0rxOwGVWxtCMvZVLmhQg==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-arm64-musl@4.40.2': - resolution: {integrity: sha512-j8CgxvfM0kbnhu4XgjnCWJQyyBOeBI1Zq91Z850aUddUmPeQvuAy6OiMdPS46gNFgy8gN1xkYyLgwLYZG3rBOg==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-loongarch64-gnu@4.40.2': - resolution: {integrity: sha512-Ybc/1qUampKuRF4tQXc7G7QY9YRyeVSykfK36Y5Qc5dmrIxwFhrOzqaVTNoZygqZ1ZieSWTibfFhQ5qK8jpWxw==} - cpu: [loong64] - os: [linux] - - '@rollup/rollup-linux-powerpc64le-gnu@4.40.2': - resolution: {integrity: sha512-3FCIrnrt03CCsZqSYAOW/k9n625pjpuMzVfeI+ZBUSDT3MVIFDSPfSUgIl9FqUftxcUXInvFah79hE1c9abD+Q==} - cpu: [ppc64] - os: [linux] - - '@rollup/rollup-linux-riscv64-gnu@4.40.2': - resolution: {integrity: sha512-QNU7BFHEvHMp2ESSY3SozIkBPaPBDTsfVNGx3Xhv+TdvWXFGOSH2NJvhD1zKAT6AyuuErJgbdvaJhYVhVqrWTg==} - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-riscv64-musl@4.40.2': - resolution: {integrity: sha512-5W6vNYkhgfh7URiXTO1E9a0cy4fSgfE4+Hl5agb/U1sa0kjOLMLC1wObxwKxecE17j0URxuTrYZZME4/VH57Hg==} - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-s390x-gnu@4.40.2': - resolution: {integrity: sha512-B7LKIz+0+p348JoAL4X/YxGx9zOx3sR+o6Hj15Y3aaApNfAshK8+mWZEf759DXfRLeL2vg5LYJBB7DdcleYCoQ==} - cpu: [s390x] - os: [linux] - - '@rollup/rollup-linux-x64-gnu@4.40.2': - resolution: {integrity: sha512-lG7Xa+BmBNwpjmVUbmyKxdQJ3Q6whHjMjzQplOs5Z+Gj7mxPtWakGHqzMqNER68G67kmCX9qX57aRsW5V0VOng==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-linux-x64-musl@4.40.2': - resolution: {integrity: sha512-tD46wKHd+KJvsmije4bUskNuvWKFcTOIM9tZ/RrmIvcXnbi0YK/cKS9FzFtAm7Oxi2EhV5N2OpfFB348vSQRXA==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-win32-arm64-msvc@4.40.2': - resolution: {integrity: sha512-Bjv/HG8RRWLNkXwQQemdsWw4Mg+IJ29LK+bJPW2SCzPKOUaMmPEppQlu/Fqk1d7+DX3V7JbFdbkh/NMmurT6Pg==} - cpu: [arm64] - os: [win32] - - '@rollup/rollup-win32-ia32-msvc@4.40.2': - resolution: {integrity: sha512-dt1llVSGEsGKvzeIO76HToiYPNPYPkmjhMHhP00T9S4rDern8P2ZWvWAQUEJ+R1UdMWJ/42i/QqJ2WV765GZcA==} - cpu: [ia32] - os: [win32] - - '@rollup/rollup-win32-x64-msvc@4.40.2': - resolution: {integrity: sha512-bwspbWB04XJpeElvsp+DCylKfF4trJDa2Y9Go8O6A7YLX2LIKGcNK/CYImJN6ZP4DcuOHB4Utl3iCbnR62DudA==} - cpu: [x64] - os: [win32] - - '@sec-ant/readable-stream@0.4.1': - resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} - - '@sindresorhus/merge-streams@2.3.0': - resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==} - engines: {node: '>=18'} - - '@sindresorhus/merge-streams@4.0.0': - resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} - engines: {node: '>=18'} - - '@storyblok/eslint-config@0.3.0': - resolution: {integrity: sha512-7JJr3K+ibx4rGAgkQBrwncCpdJ+eaBM7PfCWE2s/gmCqyvQGuGu8IRRKv9/V2u9gyv1BLQBDWmEhz7J8mwQAlA==} - peerDependencies: - eslint: '>=8.40.0' - - '@storyblok/js@4.0.0': - resolution: {integrity: sha512-D3EB9LQ9P9I9WTauwUaBkZ6J8qMVnpUAxPZRbarL7SdDKILD4CAVR9Bn+T6H5znu45J4Zq9P7OvxH+sQZPvgXw==} - - '@storyblok/richtext@3.2.0': - resolution: {integrity: sha512-koVGDv1HtiI5vymE5fzRB1VO1PNguj+RSfHI4zCy2+90pRoqZbsUCR8V2TaNl7Pg8R1oOkFZnbAxdt9Z4xoxDg==} - - '@stylistic/eslint-plugin@2.13.0': - resolution: {integrity: sha512-RnO1SaiCFHn666wNz2QfZEFxvmiNRqhzaMXHXxXXKt+MEP7aajlPxUSMIQpKAaJfverpovEYqjBOXDq6dDcaOQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: '>=8.40.0' - - '@tootallnate/quickjs-emscripten@0.23.0': - resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} - - '@topcli/spinner@2.1.2': - resolution: {integrity: sha512-vpkhANKccY+zzu6QeavBM+VO/nhCYT7HJiKZTa86uTc3RzJ+pOkt1SgGLZExAzXM8tmDHGfBwXQQaRBak42+EA==} - engines: {node: '>=16'} - - '@topcli/wcwidth@1.0.1': - resolution: {integrity: sha512-fS1k22N6DU5DpDfw61Kz0GDb3hNmJAgDcc8fNWSGBETKuuJnx+0fGSeL0w6dL1YRRjSOxWQJLiwW+byfv84EyQ==} - engines: {node: '>=14'} - - '@trysound/sax@0.2.0': - resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} - engines: {node: '>=10.13.0'} - - '@tybys/wasm-util@0.9.0': - resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==} - - '@types/cookie@0.6.0': - resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} - - '@types/debug@4.1.12': - resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} - - '@types/eslint@9.6.1': - resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} - - '@types/estree@1.0.7': - resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} - - '@types/inquirer@9.0.8': - resolution: {integrity: sha512-CgPD5kFGWsb8HJ5K7rfWlifao87m4ph8uioU7OTncJevmE/VLIqAAjfQtko578JZg7/f69K4FgqYym3gNr7DeA==} - - '@types/json-schema@7.0.15': - resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - - '@types/lodash@4.17.16': - resolution: {integrity: sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g==} - - '@types/mdast@4.0.4': - resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} - - '@types/ms@2.1.0': - resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} - - '@types/node@22.15.18': - resolution: {integrity: sha512-v1DKRfUdyW+jJhZNEI1PYy29S2YRxMV5AOO/x/SjKmW0acCIOqmbj6Haf9eHAhsPmrhlHSxEhv/1WszcLWV4cg==} - - '@types/normalize-package-data@2.4.4': - resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} - - '@types/parse-path@7.1.0': - resolution: {integrity: sha512-EULJ8LApcVEPbrfND0cRQqutIOdiIgJ1Mgrhpy755r14xMohPTEpkV/k28SJvuOs9bHRFW8x+KeDAEPiGQPB9Q==} - deprecated: This is a stub types definition. parse-path provides its own type definitions, so you do not need this installed. - - '@types/resolve@1.20.2': - resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} - - '@types/semver@7.7.0': - resolution: {integrity: sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==} - - '@types/statuses@2.0.5': - resolution: {integrity: sha512-jmIUGWrAiwu3dZpxntxieC+1n/5c3mjrImkmOSQ2NC5uP6cYO4aAZDdSmRcI5C1oiTmqlZGHC+/NmJrKogbP5A==} - - '@types/through@0.0.33': - resolution: {integrity: sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==} - - '@types/tough-cookie@4.0.5': - resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} - - '@types/unist@3.0.3': - resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} - - '@typescript-eslint/eslint-plugin@8.32.1': - resolution: {integrity: sha512-6u6Plg9nP/J1GRpe/vcjjabo6Uc5YQPAMxsgQyGC/I0RuukiG1wIe3+Vtg3IrSCVJDmqK3j8adrtzXSENRtFgg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' - - '@typescript-eslint/parser@8.32.1': - resolution: {integrity: sha512-LKMrmwCPoLhM45Z00O1ulb6jwyVr2kr3XJp+G+tSEZcbauNnScewcQwtJqXDhXeYPDEjZ8C1SjXm015CirEmGg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' - - '@typescript-eslint/scope-manager@8.32.1': - resolution: {integrity: sha512-7IsIaIDeZn7kffk7qXC3o6Z4UblZJKV3UBpkvRNpr5NSyLji7tvTcvmnMNYuYLyh26mN8W723xpo3i4MlD33vA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/type-utils@8.32.1': - resolution: {integrity: sha512-mv9YpQGA8iIsl5KyUPi+FGLm7+bA4fgXaeRcFKRDRwDMu4iwrSHeDPipwueNXhdIIZltwCJv+NkxftECbIZWfA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' - - '@typescript-eslint/types@8.32.1': - resolution: {integrity: sha512-YmybwXUJcgGqgAp6bEsgpPXEg6dcCyPyCSr0CAAueacR/CCBi25G3V8gGQ2kRzQRBNol7VQknxMs9HvVa9Rvfg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/typescript-estree@8.32.1': - resolution: {integrity: sha512-Y3AP9EIfYwBb4kWGb+simvPaqQoT5oJuzzj9m0i6FCY6SPvlomY2Ei4UEMm7+FXtlNJbor80ximyslzaQF6xhg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <5.9.0' - - '@typescript-eslint/utils@8.32.1': - resolution: {integrity: sha512-DsSFNIgLSrc89gpq1LJB7Hm1YpuhK086DRDJSNrewcGvYloWW1vZLHBTIvarKZDcAORIy/uWNx8Gad+4oMpkSA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' - - '@typescript-eslint/visitor-keys@8.32.1': - resolution: {integrity: sha512-ar0tjQfObzhSaW3C3QNmTc5ofj0hDoNQ5XWrCy6zDyabdr0TWhCkClp+rywGNj/odAFBVzzJrK4tEq5M4Hmu4w==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@unrs/resolver-binding-darwin-arm64@1.7.2': - resolution: {integrity: sha512-vxtBno4xvowwNmO/ASL0Y45TpHqmNkAaDtz4Jqb+clmcVSSl8XCG/PNFFkGsXXXS6AMjP+ja/TtNCFFa1QwLRg==} - cpu: [arm64] - os: [darwin] - - '@unrs/resolver-binding-darwin-x64@1.7.2': - resolution: {integrity: sha512-qhVa8ozu92C23Hsmv0BF4+5Dyyd5STT1FolV4whNgbY6mj3kA0qsrGPe35zNR3wAN7eFict3s4Rc2dDTPBTuFQ==} - cpu: [x64] - os: [darwin] - - '@unrs/resolver-binding-freebsd-x64@1.7.2': - resolution: {integrity: sha512-zKKdm2uMXqLFX6Ac7K5ElnnG5VIXbDlFWzg4WJ8CGUedJryM5A3cTgHuGMw1+P5ziV8CRhnSEgOnurTI4vpHpg==} - cpu: [x64] - os: [freebsd] - - '@unrs/resolver-binding-linux-arm-gnueabihf@1.7.2': - resolution: {integrity: sha512-8N1z1TbPnHH+iDS/42GJ0bMPLiGK+cUqOhNbMKtWJ4oFGzqSJk/zoXFzcQkgtI63qMcUI7wW1tq2usZQSb2jxw==} - cpu: [arm] - os: [linux] - - '@unrs/resolver-binding-linux-arm-musleabihf@1.7.2': - resolution: {integrity: sha512-tjYzI9LcAXR9MYd9rO45m1s0B/6bJNuZ6jeOxo1pq1K6OBuRMMmfyvJYval3s9FPPGmrldYA3mi4gWDlWuTFGA==} - cpu: [arm] - os: [linux] - - '@unrs/resolver-binding-linux-arm64-gnu@1.7.2': - resolution: {integrity: sha512-jon9M7DKRLGZ9VYSkFMflvNqu9hDtOCEnO2QAryFWgT6o6AXU8du56V7YqnaLKr6rAbZBWYsYpikF226v423QA==} - cpu: [arm64] - os: [linux] - - '@unrs/resolver-binding-linux-arm64-musl@1.7.2': - resolution: {integrity: sha512-c8Cg4/h+kQ63pL43wBNaVMmOjXI/X62wQmru51qjfTvI7kmCy5uHTJvK/9LrF0G8Jdx8r34d019P1DVJmhXQpA==} - cpu: [arm64] - os: [linux] - - '@unrs/resolver-binding-linux-ppc64-gnu@1.7.2': - resolution: {integrity: sha512-A+lcwRFyrjeJmv3JJvhz5NbcCkLQL6Mk16kHTNm6/aGNc4FwPHPE4DR9DwuCvCnVHvF5IAd9U4VIs/VvVir5lg==} - cpu: [ppc64] - os: [linux] - - '@unrs/resolver-binding-linux-riscv64-gnu@1.7.2': - resolution: {integrity: sha512-hQQ4TJQrSQW8JlPm7tRpXN8OCNP9ez7PajJNjRD1ZTHQAy685OYqPrKjfaMw/8LiHCt8AZ74rfUVHP9vn0N69Q==} - cpu: [riscv64] - os: [linux] - - '@unrs/resolver-binding-linux-riscv64-musl@1.7.2': - resolution: {integrity: sha512-NoAGbiqrxtY8kVooZ24i70CjLDlUFI7nDj3I9y54U94p+3kPxwd2L692YsdLa+cqQ0VoqMWoehDFp21PKRUoIQ==} - cpu: [riscv64] - os: [linux] - - '@unrs/resolver-binding-linux-s390x-gnu@1.7.2': - resolution: {integrity: sha512-KaZByo8xuQZbUhhreBTW+yUnOIHUsv04P8lKjQ5otiGoSJ17ISGYArc+4vKdLEpGaLbemGzr4ZeUbYQQsLWFjA==} - cpu: [s390x] - os: [linux] - - '@unrs/resolver-binding-linux-x64-gnu@1.7.2': - resolution: {integrity: sha512-dEidzJDubxxhUCBJ/SHSMJD/9q7JkyfBMT77Px1npl4xpg9t0POLvnWywSk66BgZS/b2Hy9Y1yFaoMTFJUe9yg==} - cpu: [x64] - os: [linux] - - '@unrs/resolver-binding-linux-x64-musl@1.7.2': - resolution: {integrity: sha512-RvP+Ux3wDjmnZDT4XWFfNBRVG0fMsc+yVzNFUqOflnDfZ9OYujv6nkh+GOr+watwrW4wdp6ASfG/e7bkDradsw==} - cpu: [x64] - os: [linux] - - '@unrs/resolver-binding-wasm32-wasi@1.7.2': - resolution: {integrity: sha512-y797JBmO9IsvXVRCKDXOxjyAE4+CcZpla2GSoBQ33TVb3ILXuFnMrbR/QQZoauBYeOFuu4w3ifWLw52sdHGz6g==} - engines: {node: '>=14.0.0'} - cpu: [wasm32] - - '@unrs/resolver-binding-win32-arm64-msvc@1.7.2': - resolution: {integrity: sha512-gtYTh4/VREVSLA+gHrfbWxaMO/00y+34htY7XpioBTy56YN2eBjkPrY1ML1Zys89X3RJDKVaogzwxlM1qU7egg==} - cpu: [arm64] - os: [win32] - - '@unrs/resolver-binding-win32-ia32-msvc@1.7.2': - resolution: {integrity: sha512-Ywv20XHvHTDRQs12jd3MY8X5C8KLjDbg/jyaal/QLKx3fAShhJyD4blEANInsjxW3P7isHx1Blt56iUDDJO3jg==} - cpu: [ia32] - os: [win32] - - '@unrs/resolver-binding-win32-x64-msvc@1.7.2': - resolution: {integrity: sha512-friS8NEQfHaDbkThxopGk+LuE5v3iY0StruifjQEt7SLbA46OnfgMO15sOTkbpJkol6RB+1l1TYPXh0sCddpvA==} - cpu: [x64] - os: [win32] - - '@vitest/coverage-v8@3.1.3': - resolution: {integrity: sha512-cj76U5gXCl3g88KSnf80kof6+6w+K4BjOflCl7t6yRJPDuCrHtVu0SgNYOUARJOL5TI8RScDbm5x4s1/P9bvpw==} - peerDependencies: - '@vitest/browser': 3.1.3 - vitest: 3.1.3 - peerDependenciesMeta: - '@vitest/browser': - optional: true - - '@vitest/eslint-plugin@1.1.44': - resolution: {integrity: sha512-m4XeohMT+Dj2RZfxnbiFR+Cv5dEC0H7C6TlxRQT7GK2556solm99kxgzJp/trKrZvanZcOFyw7aABykUTfWyrg==} - peerDependencies: - '@typescript-eslint/utils': '>= 8.24.0' - eslint: '>= 8.57.0' - typescript: '>= 5.0.0' - vitest: '*' - peerDependenciesMeta: - typescript: - optional: true - vitest: - optional: true - - '@vitest/expect@3.1.3': - resolution: {integrity: sha512-7FTQQuuLKmN1Ig/h+h/GO+44Q1IlglPlR2es4ab7Yvfx+Uk5xsv+Ykk+MEt/M2Yn/xGmzaLKxGw2lgy2bwuYqg==} - - '@vitest/mocker@3.1.3': - resolution: {integrity: sha512-PJbLjonJK82uCWHjzgBJZuR7zmAOrSvKk1QBxrennDIgtH4uK0TB1PvYmc0XBCigxxtiAVPfWtAdy4lpz8SQGQ==} - peerDependencies: - msw: ^2.4.9 - vite: ^5.0.0 || ^6.0.0 - peerDependenciesMeta: - msw: - optional: true - vite: - optional: true - - '@vitest/pretty-format@3.1.3': - resolution: {integrity: sha512-i6FDiBeJUGLDKADw2Gb01UtUNb12yyXAqC/mmRWuYl+m/U9GS7s8us5ONmGkGpUUo7/iAYzI2ePVfOZTYvUifA==} - - '@vitest/runner@3.1.3': - resolution: {integrity: sha512-Tae+ogtlNfFei5DggOsSUvkIaSuVywujMj6HzR97AHK6XK8i3BuVyIifWAm/sE3a15lF5RH9yQIrbXYuo0IFyA==} - - '@vitest/snapshot@3.1.3': - resolution: {integrity: sha512-XVa5OPNTYUsyqG9skuUkFzAeFnEzDp8hQu7kZ0N25B1+6KjGm4hWLtURyBbsIAOekfWQ7Wuz/N/XXzgYO3deWQ==} - - '@vitest/spy@3.1.3': - resolution: {integrity: sha512-x6w+ctOEmEXdWaa6TO4ilb7l9DxPR5bwEb6hILKuxfU1NqWT2mpJD9NJN7t3OTfxmVlOMrvtoFJGdgyzZ605lQ==} - - '@vitest/ui@3.1.3': - resolution: {integrity: sha512-IipSzX+8DptUdXN/GWq3hq5z18MwnpphYdOMm0WndkRGYELzfq7NDP8dMpZT7JGW1uXFrIGxOW2D0Xi++ulByg==} - peerDependencies: - vitest: 3.1.3 - - '@vitest/utils@3.1.3': - resolution: {integrity: sha512-2Ltrpht4OmHO9+c/nmHtF09HWiyWdworqnHIwjfvDyWjuwKbdkcS9AnhsDn+8E2RM4x++foD1/tNuLPVvWG1Rg==} - - '@vue/compiler-core@3.5.13': - resolution: {integrity: sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==} - - '@vue/compiler-dom@3.5.13': - resolution: {integrity: sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==} - - '@vue/compiler-sfc@3.5.13': - resolution: {integrity: sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==} - - '@vue/compiler-ssr@3.5.13': - resolution: {integrity: sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==} - - '@vue/shared@3.5.13': - resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==} - - accepts@2.0.0: - resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} - engines: {node: '>= 0.6'} - - acorn-jsx@5.3.2: - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - - acorn@8.14.1: - resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} - engines: {node: '>=0.4.0'} - hasBin: true - - add-stream@1.0.0: - resolution: {integrity: sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==} - - agent-base@7.1.3: - resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} - engines: {node: '>= 14'} - - ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - - ansi-align@3.0.1: - resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} - - ansi-escapes@4.3.2: - resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} - engines: {node: '>=8'} - - ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - - ansi-regex@6.1.0: - resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} - engines: {node: '>=12'} - - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - - ansi-styles@6.2.1: - resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} - engines: {node: '>=12'} - - are-docs-informative@0.0.2: - resolution: {integrity: sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==} - engines: {node: '>=14'} - - argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - - array-ify@1.0.0: - resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} - - assertion-error@2.0.1: - resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} - engines: {node: '>=12'} - - ast-types@0.13.4: - resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==} - engines: {node: '>=4'} - - async-retry@1.3.3: - resolution: {integrity: sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==} - - atomically@2.0.3: - resolution: {integrity: sha512-kU6FmrwZ3Lx7/7y3hPS5QnbJfaohcIul5fGqf7ok+4KklIEk9tJ0C2IQPdacSbVUWv6zVHXEBWoWd6NrVMT7Cw==} - - autoprefixer@10.4.21: - resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==} - engines: {node: ^10 || ^12 || >=14} - hasBin: true - peerDependencies: - postcss: ^8.1.0 - - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - - basic-ftp@5.0.5: - resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==} - engines: {node: '>=10.0.0'} - - before-after-hook@3.0.2: - resolution: {integrity: sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==} - - body-parser@2.2.0: - resolution: {integrity: sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==} - engines: {node: '>=18'} - - boolbase@1.0.0: - resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} - - boxen@8.0.1: - resolution: {integrity: sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==} - engines: {node: '>=18'} - - brace-expansion@1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} - - brace-expansion@2.0.1: - resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} - - braces@3.0.3: - resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} - engines: {node: '>=8'} - - browserslist@4.24.5: - resolution: {integrity: sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - - buffer-from@1.1.2: - resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - - builtin-modules@3.3.0: - resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} - engines: {node: '>=6'} - - bundle-name@4.1.0: - resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} - engines: {node: '>=18'} - - bytes@3.1.2: - resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} - engines: {node: '>= 0.8'} - - cac@6.7.14: - resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} - engines: {node: '>=8'} - - call-bind-apply-helpers@1.0.2: - resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} - engines: {node: '>= 0.4'} - - call-bound@1.0.4: - resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} - engines: {node: '>= 0.4'} - - callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - - camelcase@8.0.0: - resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==} - engines: {node: '>=16'} - - caniuse-api@3.0.0: - resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} - - caniuse-lite@1.0.30001718: - resolution: {integrity: sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==} - - ccount@2.0.1: - resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} - - chai@5.2.0: - resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==} - engines: {node: '>=12'} - - chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} - - chalk@5.4.1: - resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - - character-entities@2.0.2: - resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} - - chardet@0.7.0: - resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} - - check-error@2.1.1: - resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} - engines: {node: '>= 16'} - - ci-info@4.2.0: - resolution: {integrity: sha512-cYY9mypksY8NRqgDB1XD1RiJL338v/551niynFTGkZOO2LHuB2OmOYxDIe/ttN9AHwrqdum1360G3ald0W9kCg==} - engines: {node: '>=8'} - - citty@0.1.6: - resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} - - clean-regexp@1.0.0: - resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==} - engines: {node: '>=4'} - - cli-boxes@3.0.0: - resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} - engines: {node: '>=10'} - - cli-cursor@4.0.0: - resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - cli-cursor@5.0.0: - resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} - engines: {node: '>=18'} - - cli-spinners@2.9.2: - resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} - engines: {node: '>=6'} - - cli-width@4.1.0: - resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} - engines: {node: '>= 12'} - - cliui@8.0.1: - resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} - engines: {node: '>=12'} - - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - - colord@2.9.3: - resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} - - commander@13.1.0: - resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==} - engines: {node: '>=18'} - - commander@7.2.0: - resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} - engines: {node: '>= 10'} - - comment-parser@1.4.1: - resolution: {integrity: sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==} - engines: {node: '>= 12.0.0'} - - commondir@1.0.1: - resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} - - compare-func@2.0.0: - resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} - - concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - - concat-stream@2.0.0: - resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} - engines: {'0': node >= 6.0} - - confbox@0.1.8: - resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} - - confbox@0.2.2: - resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} - - config-chain@1.1.13: - resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} - - configstore@7.0.0: - resolution: {integrity: sha512-yk7/5PN5im4qwz0WFZW3PXnzHgPu9mX29Y8uZ3aefe2lBPC1FYttWZRcaW9fKkT0pBCJyuQ2HfbmPVaODi9jcQ==} - engines: {node: '>=18'} - - consola@3.4.2: - resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} - engines: {node: ^14.18.0 || >=16.10.0} - - content-disposition@1.0.0: - resolution: {integrity: sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==} - engines: {node: '>= 0.6'} - - content-type@1.0.5: - resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} - engines: {node: '>= 0.6'} - - conventional-changelog-angular@8.0.0: - resolution: {integrity: sha512-CLf+zr6St0wIxos4bmaKHRXWAcsCXrJU6F4VdNDrGRK3B8LDLKoX3zuMV5GhtbGkVR/LohZ6MT6im43vZLSjmA==} - engines: {node: '>=18'} - - conventional-changelog-atom@5.0.0: - resolution: {integrity: sha512-WfzCaAvSCFPkznnLgLnfacRAzjgqjLUjvf3MftfsJzQdDICqkOOpcMtdJF3wTerxSpv2IAAjX8doM3Vozqle3g==} - engines: {node: '>=18'} - - conventional-changelog-codemirror@5.0.0: - resolution: {integrity: sha512-8gsBDI5Y3vrKUCxN6Ue8xr6occZ5nsDEc4C7jO/EovFGozx8uttCAyfhRrvoUAWi2WMm3OmYs+0mPJU7kQdYWQ==} - engines: {node: '>=18'} - - conventional-changelog-conventionalcommits@8.0.0: - resolution: {integrity: sha512-eOvlTO6OcySPyyyk8pKz2dP4jjElYunj9hn9/s0OB+gapTO8zwS9UQWrZ1pmF2hFs3vw1xhonOLGcGjy/zgsuA==} - engines: {node: '>=18'} - - conventional-changelog-core@8.0.0: - resolution: {integrity: sha512-EATUx5y9xewpEe10UEGNpbSHRC6cVZgO+hXQjofMqpy+gFIrcGvH3Fl6yk2VFKh7m+ffenup2N7SZJYpyD9evw==} - engines: {node: '>=18'} - - conventional-changelog-ember@5.0.0: - resolution: {integrity: sha512-RPflVfm5s4cSO33GH/Ey26oxhiC67akcxSKL8CLRT3kQX2W3dbE19sSOM56iFqUJYEwv9mD9r6k79weWe1urfg==} - engines: {node: '>=18'} - - conventional-changelog-eslint@6.0.0: - resolution: {integrity: sha512-eiUyULWjzq+ybPjXwU6NNRflApDWlPEQEHvI8UAItYW/h22RKkMnOAtfCZxMmrcMO1OKUWtcf2MxKYMWe9zJuw==} - engines: {node: '>=18'} - - conventional-changelog-express@5.0.0: - resolution: {integrity: sha512-D8Q6WctPkQpvr2HNCCmwU5GkX22BVHM0r4EW8vN0230TSyS/d6VQJDAxGb84lbg0dFjpO22MwmsikKL++Oo/oQ==} - engines: {node: '>=18'} - - conventional-changelog-jquery@6.0.0: - resolution: {integrity: sha512-2kxmVakyehgyrho2ZHBi90v4AHswkGzHuTaoH40bmeNqUt20yEkDOSpw8HlPBfvEQBwGtbE+5HpRwzj6ac2UfA==} - engines: {node: '>=18'} - - conventional-changelog-jshint@5.0.0: - resolution: {integrity: sha512-gGNphSb/opc76n2eWaO6ma4/Wqu3tpa2w7i9WYqI6Cs2fncDSI2/ihOfMvXveeTTeld0oFvwMVNV+IYQIk3F3g==} - engines: {node: '>=18'} - - conventional-changelog-preset-loader@5.0.0: - resolution: {integrity: sha512-SetDSntXLk8Jh1NOAl1Gu5uLiCNSYenB5tm0YVeZKePRIgDW9lQImromTwLa3c/Gae298tsgOM+/CYT9XAl0NA==} - engines: {node: '>=18'} - - conventional-changelog-writer@8.0.1: - resolution: {integrity: sha512-hlqcy3xHred2gyYg/zXSMXraY2mjAYYo0msUCpK+BGyaVJMFCKWVXPIHiaacGO2GGp13kvHWXFhYmxT4QQqW3Q==} - engines: {node: '>=18'} - hasBin: true - - conventional-changelog@6.0.0: - resolution: {integrity: sha512-tuUH8H/19VjtD9Ig7l6TQRh+Z0Yt0NZ6w/cCkkyzUbGQTnUEmKfGtkC9gGfVgCfOL1Rzno5NgNF4KY8vR+Jo3w==} - engines: {node: '>=18'} - - conventional-commits-filter@5.0.0: - resolution: {integrity: sha512-tQMagCOC59EVgNZcC5zl7XqO30Wki9i9J3acbUvkaosCT6JX3EeFwJD7Qqp4MCikRnzS18WXV3BLIQ66ytu6+Q==} - engines: {node: '>=18'} - - conventional-commits-parser@6.1.0: - resolution: {integrity: sha512-5nxDo7TwKB5InYBl4ZC//1g9GRwB/F3TXOGR9hgUjMGfvSP4Vu5NkpNro2+1+TIEy1vwxApl5ircECr2ri5JIw==} - engines: {node: '>=18'} - hasBin: true - - conventional-recommended-bump@10.0.0: - resolution: {integrity: sha512-RK/fUnc2btot0oEVtrj3p2doImDSs7iiz/bftFCDzels0Qs1mxLghp+DFHMaOC0qiCI6sWzlTDyBFSYuot6pRA==} - engines: {node: '>=18'} - hasBin: true - - cookie-signature@1.2.2: - resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} - engines: {node: '>=6.6.0'} - - cookie@0.7.2: - resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} - engines: {node: '>= 0.6'} - - core-js-compat@3.42.0: - resolution: {integrity: sha512-bQasjMfyDGyaeWKBIu33lHh9qlSR0MFE/Nmc6nMjf/iU9b3rSMdAYz1Baxrv4lPdGUsTqZudHA4jIGSJy0SWZQ==} - - cors@2.8.5: - resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} - engines: {node: '>= 0.10'} - - cosmiconfig@9.0.0: - resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} - engines: {node: '>=14'} - peerDependencies: - typescript: '>=4.9.5' - peerDependenciesMeta: - typescript: - optional: true - - cross-spawn@7.0.6: - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} - engines: {node: '>= 8'} - - css-declaration-sorter@7.2.0: - resolution: {integrity: sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==} - engines: {node: ^14 || ^16 || >=18} - peerDependencies: - postcss: ^8.0.9 - - css-select@5.1.0: - resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} - - css-tree@2.2.1: - resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==} - engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} - - css-tree@2.3.1: - resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} - engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} - - css-what@6.1.0: - resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} - engines: {node: '>= 6'} - - cssesc@3.0.0: - resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} - engines: {node: '>=4'} - hasBin: true - - cssnano-preset-default@7.0.7: - resolution: {integrity: sha512-jW6CG/7PNB6MufOrlovs1TvBTEVmhY45yz+bd0h6nw3h6d+1e+/TX+0fflZ+LzvZombbT5f+KC063w9VoHeHow==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - cssnano-utils@5.0.1: - resolution: {integrity: sha512-ZIP71eQgG9JwjVZsTPSqhc6GHgEr53uJ7tK5///VfyWj6Xp2DBmixWHqJgPno+PqATzn48pL42ww9x5SSGmhZg==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - cssnano@7.0.7: - resolution: {integrity: sha512-evKu7yiDIF7oS+EIpwFlMF730ijRyLFaM2o5cTxRGJR9OKHKkc+qP443ZEVR9kZG0syaAJJCPJyfv5pbrxlSng==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - csso@5.0.5: - resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} - engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} - - data-uri-to-buffer@6.0.2: - resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==} - engines: {node: '>= 14'} - - debug@3.2.7: - resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - debug@4.4.1: - resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - decode-named-character-reference@1.1.0: - resolution: {integrity: sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w==} - - deep-eql@5.0.2: - resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} - engines: {node: '>=6'} - - deep-extend@0.6.0: - resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} - engines: {node: '>=4.0.0'} - - deep-is@0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - - deepmerge@4.3.1: - resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} - engines: {node: '>=0.10.0'} - - default-browser-id@5.0.0: - resolution: {integrity: sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==} - engines: {node: '>=18'} - - default-browser@5.2.1: - resolution: {integrity: sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==} - engines: {node: '>=18'} - - define-lazy-prop@3.0.0: - resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} - engines: {node: '>=12'} - - defu@6.1.4: - resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} - - degenerator@5.0.1: - resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==} - engines: {node: '>= 14'} - - depd@2.0.0: - resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} - engines: {node: '>= 0.8'} - - dequal@2.0.3: - resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} - engines: {node: '>=6'} - - devlop@1.1.0: - resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} - - dom-serializer@2.0.0: - resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} - - domelementtype@2.3.0: - resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} - - domhandler@5.0.3: - resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} - engines: {node: '>= 4'} - - domutils@3.2.2: - resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} - - dot-prop@5.3.0: - resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} - engines: {node: '>=8'} - - dot-prop@9.0.0: - resolution: {integrity: sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ==} - engines: {node: '>=18'} - - dotenv@16.5.0: - resolution: {integrity: sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==} - engines: {node: '>=12'} - - dunder-proto@1.0.1: - resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} - engines: {node: '>= 0.4'} - - eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - - ee-first@1.1.1: - resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - - electron-to-chromium@1.5.154: - resolution: {integrity: sha512-G4VCFAyKbp1QJ+sWdXYIRYsPGvlV5sDACfCmoMFog3rjm1syLhI41WXm/swZypwCIWIm4IFLWzHY14joWMQ5Fw==} - - emoji-regex@10.4.0: - resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==} - - emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - - emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - - encodeurl@2.0.0: - resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} - engines: {node: '>= 0.8'} - - enhanced-resolve@5.18.1: - resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==} - engines: {node: '>=10.13.0'} - - entities@4.5.0: - resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} - engines: {node: '>=0.12'} - - env-paths@2.2.1: - resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} - engines: {node: '>=6'} - - error-ex@1.3.2: - resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} - - es-define-property@1.0.1: - resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} - engines: {node: '>= 0.4'} - - es-errors@1.3.0: - resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} - engines: {node: '>= 0.4'} - - es-module-lexer@1.7.0: - resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} - - es-object-atoms@1.1.1: - resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} - engines: {node: '>= 0.4'} - - esbuild@0.25.4: - resolution: {integrity: sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==} - engines: {node: '>=18'} - hasBin: true - - escalade@3.2.0: - resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} - engines: {node: '>=6'} - - escape-goat@4.0.0: - resolution: {integrity: sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==} - engines: {node: '>=12'} - - escape-html@1.0.3: - resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} - - escape-string-regexp@1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} - engines: {node: '>=0.8.0'} - - escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - - escape-string-regexp@5.0.0: - resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} - engines: {node: '>=12'} - - escodegen@2.1.0: - resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} - engines: {node: '>=6.0'} - hasBin: true - - eslint-compat-utils@0.5.1: - resolution: {integrity: sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==} - engines: {node: '>=12'} - peerDependencies: - eslint: '>=6.0.0' - - eslint-compat-utils@0.6.5: - resolution: {integrity: sha512-vAUHYzue4YAa2hNACjB8HvUQj5yehAZgiClyFVVom9cP8z5NSFq3PwB/TtJslN2zAMgRX6FCFCjYBbQh71g5RQ==} - engines: {node: '>=12'} - peerDependencies: - eslint: '>=6.0.0' - - eslint-config-flat-gitignore@0.3.0: - resolution: {integrity: sha512-0Ndxo4qGhcewjTzw52TK06Mc00aDtHNTdeeW2JfONgDcLkRO/n/BteMRzNVpLQYxdCC/dFEilfM9fjjpGIJ9Og==} - peerDependencies: - eslint: ^9.5.0 - - eslint-flat-config-utils@0.4.0: - resolution: {integrity: sha512-kfd5kQZC+BMO0YwTol6zxjKX1zAsk8JfSAopbKjKqmENTJcew+yBejuvccAg37cvOrN0Mh+DVbeyznuNWEjt4A==} - - eslint-formatting-reporter@0.0.0: - resolution: {integrity: sha512-k9RdyTqxqN/wNYVaTk/ds5B5rA8lgoAmvceYN7bcZMBwU7TuXx5ntewJv81eF3pIL/CiJE+pJZm36llG8yhyyw==} - peerDependencies: - eslint: '>=8.40.0' - - eslint-import-resolver-node@0.3.9: - resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} - - eslint-json-compat-utils@0.2.1: - resolution: {integrity: sha512-YzEodbDyW8DX8bImKhAcCeu/L31Dd/70Bidx2Qex9OFUtgzXLqtfWL4Hr5fM/aCCB8QUZLuJur0S9k6UfgFkfg==} - engines: {node: '>=12'} - peerDependencies: - '@eslint/json': '*' - eslint: '*' - jsonc-eslint-parser: ^2.4.0 - peerDependenciesMeta: - '@eslint/json': - optional: true - - eslint-merge-processors@0.1.0: - resolution: {integrity: sha512-IvRXXtEajLeyssvW4wJcZ2etxkR9mUf4zpNwgI+m/Uac9RfXHskuJefkHUcawVzePnd6xp24enp5jfgdHzjRdQ==} - peerDependencies: - eslint: '*' - - eslint-parser-plain@0.1.1: - resolution: {integrity: sha512-KRgd6wuxH4U8kczqPp+Oyk4irThIhHWxgFgLDtpgjUGVIS3wGrJntvZW/p6hHq1T4FOwnOtCNkvAI4Kr+mQ/Hw==} - - eslint-plugin-antfu@2.7.0: - resolution: {integrity: sha512-gZM3jq3ouqaoHmUNszb1Zo2Ux7RckSvkGksjLWz9ipBYGSv1EwwBETN6AdiUXn+RpVHXTbEMPAPlXJazcA6+iA==} - peerDependencies: - eslint: '*' - - eslint-plugin-command@0.2.7: - resolution: {integrity: sha512-UXJ/1R6kdKDcHhiRqxHJ9RZ3juMR1IWQuSrnwt56qCjxt/am+5+YDt6GKs1FJPnppe6/geEYsO3CR9jc63i0xw==} - peerDependencies: - eslint: '*' - - eslint-plugin-es-x@7.8.0: - resolution: {integrity: sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - eslint: '>=8' - - eslint-plugin-format@0.1.3: - resolution: {integrity: sha512-vTmshuv1iMfmcM1HADnyhae5MBBGlJZBZyZ+ybtXCEzRe3nRhUvLX+6rAvsEfcdK6a2pqpLs/F530dXKvfQqYQ==} - peerDependencies: - eslint: ^8.40.0 || ^9.0.0 - - eslint-plugin-import-x@4.11.1: - resolution: {integrity: sha512-CiqREASJRnhwCB0NujkTdo4jU+cJAnhQrd4aCnWC1o+rYWIWakVbyuzVbnCriUUSLAnn5CoJ2ob36TEgNzejBQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - - eslint-plugin-jsdoc@50.6.17: - resolution: {integrity: sha512-hq+VQylhd12l8qjexyriDsejZhqiP33WgMTy2AmaGZ9+MrMWVqPECsM87GPxgHfQn0zw+YTuhqjUfk1f+q67aQ==} - engines: {node: '>=18'} - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 - - eslint-plugin-jsonc@2.20.0: - resolution: {integrity: sha512-FRgCn9Hzk5eKboCbVMrr9QrhM0eO4G+WKH8IFXoaeqhM/2kuWzbStJn4kkr0VWL8J5H8RYZF+Aoam1vlBaZVkw==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: '>=6.0.0' - - eslint-plugin-n@17.18.0: - resolution: {integrity: sha512-hvZ/HusueqTJ7VDLoCpjN0hx4N4+jHIWTXD4TMLHy9F23XkDagR9v+xQWRWR57yY55GPF8NnD4ox9iGTxirY8A==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: '>=8.23.0' - - eslint-plugin-no-only-tests@3.3.0: - resolution: {integrity: sha512-brcKcxGnISN2CcVhXJ/kEQlNa0MEfGRtwKtWA16SkqXHKitaKIMrfemJKLKX1YqDU5C/5JY3PvZXd5jEW04e0Q==} - engines: {node: '>=5.0.0'} - - eslint-plugin-perfectionist@3.9.1: - resolution: {integrity: sha512-9WRzf6XaAxF4Oi5t/3TqKP5zUjERhasHmLFHin2Yw6ZAp/EP/EVA2dr3BhQrrHWCm5SzTMZf0FcjDnBkO2xFkA==} - engines: {node: ^18.0.0 || >=20.0.0} - peerDependencies: - astro-eslint-parser: ^1.0.2 - eslint: '>=8.0.0' - svelte: '>=3.0.0' - svelte-eslint-parser: ^0.41.1 - vue-eslint-parser: '>=9.0.0' - peerDependenciesMeta: - astro-eslint-parser: - optional: true - svelte: - optional: true - svelte-eslint-parser: - optional: true - vue-eslint-parser: - optional: true - - eslint-plugin-regexp@2.7.0: - resolution: {integrity: sha512-U8oZI77SBtH8U3ulZ05iu0qEzIizyEDXd+BWHvyVxTOjGwcDcvy/kEpgFG4DYca2ByRLiVPFZ2GeH7j1pdvZTA==} - engines: {node: ^18 || >=20} - peerDependencies: - eslint: '>=8.44.0' - - eslint-plugin-toml@0.11.1: - resolution: {integrity: sha512-Y1WuMSzfZpeMIrmlP1nUh3kT8p96mThIq4NnHrYUhg10IKQgGfBZjAWnrg9fBqguiX4iFps/x/3Hb5TxBisfdw==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: '>=6.0.0' - - eslint-plugin-unicorn@55.0.0: - resolution: {integrity: sha512-n3AKiVpY2/uDcGrS3+QsYDkjPfaOrNrsfQxU9nt5nitd9KuvVXrfAvgCO9DYPSfap+Gqjw9EOrXIsBp5tlHZjA==} - engines: {node: '>=18.18'} - peerDependencies: - eslint: '>=8.56.0' - - eslint-plugin-unused-imports@4.1.4: - resolution: {integrity: sha512-YptD6IzQjDardkl0POxnnRBhU1OEePMV0nd6siHaRBbd+lyh6NAhFEobiznKU7kTsSsDeSD62Pe7kAM1b7dAZQ==} - peerDependencies: - '@typescript-eslint/eslint-plugin': ^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0 - eslint: ^9.0.0 || ^8.0.0 - peerDependenciesMeta: - '@typescript-eslint/eslint-plugin': - optional: true - - eslint-plugin-vue@9.33.0: - resolution: {integrity: sha512-174lJKuNsuDIlLpjeXc5E2Tss8P44uIimAfGD0b90k0NoirJqpG7stLuU9Vp/9ioTOrQdWVREc4mRd1BD+CvGw==} - engines: {node: ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 - - eslint-plugin-yml@1.18.0: - resolution: {integrity: sha512-9NtbhHRN2NJa/s3uHchO3qVVZw0vyOIvWlXWGaKCr/6l3Go62wsvJK5byiI6ZoYztDsow4GnS69BZD3GnqH3hA==} - engines: {node: ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: '>=6.0.0' - - eslint-processor-vue-blocks@0.1.2: - resolution: {integrity: sha512-PfpJ4uKHnqeL/fXUnzYkOax3aIenlwewXRX8jFinA1a2yCFnLgMuiH3xvCgvHHUlV2xJWQHbCTdiJWGwb3NqpQ==} - peerDependencies: - '@vue/compiler-sfc': ^3.3.0 - eslint: ^8.50.0 || ^9.0.0 - - eslint-scope@7.2.2: - resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - eslint-scope@8.3.0: - resolution: {integrity: sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - eslint-visitor-keys@3.4.3: - resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - eslint-visitor-keys@4.2.0: - resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - eslint@9.26.0: - resolution: {integrity: sha512-Hx0MOjPh6uK9oq9nVsATZKE/Wlbai7KFjfCuw9UHaguDW3x+HF0O5nIi3ud39TWgrTjTO5nHxmL3R1eANinWHQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - hasBin: true - peerDependencies: - jiti: '*' - peerDependenciesMeta: - jiti: - optional: true - - espree@10.3.0: - resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - espree@9.6.1: - resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - esprima@4.0.1: - resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} - engines: {node: '>=4'} - hasBin: true - - esquery@1.6.0: - resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} - engines: {node: '>=0.10'} - - esrecurse@4.3.0: - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} - engines: {node: '>=4.0'} - - estraverse@5.3.0: - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} - engines: {node: '>=4.0'} - - estree-walker@2.0.2: - resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} - - estree-walker@3.0.3: - resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - - esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} - - etag@1.8.1: - resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} - engines: {node: '>= 0.6'} - - eventsource-parser@3.0.2: - resolution: {integrity: sha512-6RxOBZ/cYgd8usLwsEl+EC09Au/9BcmCKYF2/xbml6DNczf7nv0MQb+7BA2F+li6//I+28VNlQR37XfQtcAJuA==} - engines: {node: '>=18.0.0'} - - eventsource@3.0.7: - resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} - engines: {node: '>=18.0.0'} - - execa@8.0.1: - resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} - engines: {node: '>=16.17'} - - execa@9.5.2: - resolution: {integrity: sha512-EHlpxMCpHWSAh1dgS6bVeoLAXGnJNdR93aabr4QCGbzOM73o5XmRfM/e5FUqsw3aagP8S8XEWUWFAxnRBnAF0Q==} - engines: {node: ^18.19.0 || >=20.5.0} - - expect-type@1.2.1: - resolution: {integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==} - engines: {node: '>=12.0.0'} - - express-rate-limit@7.5.0: - resolution: {integrity: sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==} - engines: {node: '>= 16'} - peerDependencies: - express: ^4.11 || 5 || ^5.0.0-beta.1 - - express@5.1.0: - resolution: {integrity: sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==} - engines: {node: '>= 18'} - - exsolve@1.0.5: - resolution: {integrity: sha512-pz5dvkYYKQ1AHVrgOzBKWeP4u4FRb3a6DNK2ucr0OoNwYIU4QWsJ+NM36LLzORT+z845MzKHHhpXiUF5nvQoJg==} - - external-editor@3.1.0: - resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} - engines: {node: '>=4'} - - fast-content-type-parse@2.0.1: - resolution: {integrity: sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==} - - fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - - fast-diff@1.3.0: - resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} - - fast-glob@3.3.3: - resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} - engines: {node: '>=8.6.0'} - - fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - - fast-levenshtein@2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - - fastq@1.19.1: - resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} - - fault@2.0.1: - resolution: {integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==} - - fdir@6.4.4: - resolution: {integrity: sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==} - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - - fflate@0.8.2: - resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} - - figures@6.1.0: - resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} - engines: {node: '>=18'} - - file-entry-cache@8.0.0: - resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} - engines: {node: '>=16.0.0'} - - fill-range@7.1.1: - resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} - engines: {node: '>=8'} - - finalhandler@2.1.0: - resolution: {integrity: sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==} - engines: {node: '>= 0.8'} - - find-up-simple@1.0.1: - resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==} - engines: {node: '>=18'} - - find-up@4.1.0: - resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} - engines: {node: '>=8'} - - find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} - - fix-dts-default-cjs-exports@1.0.1: - resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} - - flat-cache@4.0.1: - resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} - engines: {node: '>=16'} - - flatted@3.3.3: - resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} - - foreground-child@3.3.1: - resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} - engines: {node: '>=14'} - - format@0.2.2: - resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} - engines: {node: '>=0.4.x'} - - forwarded@0.2.0: - resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} - engines: {node: '>= 0.6'} - - fraction.js@4.3.7: - resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} - - fresh@2.0.0: - resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} - engines: {node: '>= 0.8'} - - fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - - get-caller-file@2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} - - get-east-asian-width@1.3.0: - resolution: {integrity: sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==} - engines: {node: '>=18'} - - get-intrinsic@1.3.0: - resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} - engines: {node: '>= 0.4'} - - get-proto@1.0.1: - resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} - engines: {node: '>= 0.4'} - - get-stream@8.0.1: - resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} - engines: {node: '>=16'} - - get-stream@9.0.1: - resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} - engines: {node: '>=18'} - - get-tsconfig@4.10.0: - resolution: {integrity: sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==} - - get-uri@6.0.4: - resolution: {integrity: sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==} - engines: {node: '>= 14'} - - git-raw-commits@5.0.0: - resolution: {integrity: sha512-I2ZXrXeOc0KrCvC7swqtIFXFN+rbjnC7b2T943tvemIOVNl+XP8YnA9UVwqFhzzLClnSA60KR/qEjLpXzs73Qg==} - engines: {node: '>=18'} - hasBin: true - - git-semver-tags@8.0.0: - resolution: {integrity: sha512-N7YRIklvPH3wYWAR2vysaqGLPRcpwQ0GKdlqTiVN5w1UmCdaeY3K8s6DMKRCh54DDdzyt/OAB6C8jgVtb7Y2Fg==} - engines: {node: '>=18'} - hasBin: true - - git-up@8.1.1: - resolution: {integrity: sha512-FDenSF3fVqBYSaJoYy1KSc2wosx0gCvKP+c+PRBht7cAaiCeQlBtfBDX9vgnNOHmdePlSFITVcn4pFfcgNvx3g==} - - git-url-parse@16.0.0: - resolution: {integrity: sha512-Y8iAF0AmCaqXc6a5GYgPQW9ESbncNLOL+CeQAJRhmWUOmnPkKpBYeWYp4mFd3LA5j53CdGDdslzX12yEBVHQQg==} - - glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - - glob-parent@6.0.2: - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} - engines: {node: '>=10.13.0'} - - glob@10.4.5: - resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} - hasBin: true - - glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported - - global-directory@4.0.1: - resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==} - engines: {node: '>=18'} - - globals@13.24.0: - resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} - engines: {node: '>=8'} - - globals@14.0.0: - resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} - engines: {node: '>=18'} - - globals@15.15.0: - resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} - engines: {node: '>=18'} - - globby@14.0.2: - resolution: {integrity: sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==} - engines: {node: '>=18'} - - gopd@1.2.0: - resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} - engines: {node: '>= 0.4'} - - graceful-fs@4.2.10: - resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} - - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - - graphemer@1.4.0: - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - - graphql@16.11.0: - resolution: {integrity: sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==} - engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} - - handlebars@4.7.8: - resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} - engines: {node: '>=0.4.7'} - hasBin: true - - has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - - has-symbols@1.1.0: - resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} - engines: {node: '>= 0.4'} - - hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} - engines: {node: '>= 0.4'} - - headers-polyfill@4.0.3: - resolution: {integrity: sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==} - - hookable@5.5.3: - resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} - - hosted-git-info@2.8.9: - resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} - - hosted-git-info@7.0.2: - resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} - engines: {node: ^16.14.0 || >=18.0.0} - - html-escaper@2.0.2: - resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} - - http-errors@2.0.0: - resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} - engines: {node: '>= 0.8'} - - http-proxy-agent@7.0.2: - resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} - engines: {node: '>= 14'} - - https-proxy-agent@7.0.6: - resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} - engines: {node: '>= 14'} - - human-signals@5.0.0: - resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} - engines: {node: '>=16.17.0'} - - human-signals@8.0.1: - resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==} - engines: {node: '>=18.18.0'} - - hyperdyperid@1.2.0: - resolution: {integrity: sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==} - engines: {node: '>=10.18'} - - iconv-lite@0.4.24: - resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} - engines: {node: '>=0.10.0'} - - iconv-lite@0.6.3: - resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} - engines: {node: '>=0.10.0'} - - ignore@5.3.2: - resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} - engines: {node: '>= 4'} - - ignore@7.0.4: - resolution: {integrity: sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A==} - engines: {node: '>= 4'} - - import-fresh@3.3.1: - resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} - engines: {node: '>=6'} - - imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} - - indent-string@4.0.0: - resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} - engines: {node: '>=8'} - - index-to-position@1.1.0: - resolution: {integrity: sha512-XPdx9Dq4t9Qk1mTMbWONJqU7boCoumEH7fRET37HX5+khDUl3J2W6PdALxhILYlIYx2amlwYcRPp28p0tSiojg==} - engines: {node: '>=18'} - - inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. - - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - - ini@1.3.8: - resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} - - ini@4.1.1: - resolution: {integrity: sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - inquirer@12.3.0: - resolution: {integrity: sha512-3NixUXq+hM8ezj2wc7wC37b32/rHq1MwNZDYdvx+d6jokOD+r+i8Q4Pkylh9tISYP114A128LCX8RKhopC5RfQ==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - - interpret@1.4.0: - resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==} - engines: {node: '>= 0.10'} - - ip-address@9.0.5: - resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} - engines: {node: '>= 12'} - - ipaddr.js@1.9.1: - resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} - engines: {node: '>= 0.10'} - - is-arrayish@0.2.1: - resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - - is-builtin-module@3.2.1: - resolution: {integrity: sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==} - engines: {node: '>=6'} - - is-core-module@2.16.1: - resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} - engines: {node: '>= 0.4'} - - is-docker@3.0.0: - resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - hasBin: true - - is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - - is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - - is-in-ci@1.0.0: - resolution: {integrity: sha512-eUuAjybVTHMYWm/U+vBO1sY/JOCgoPCXRxzdju0K+K0BiGW0SChEL1MLC0PoCIR1OlPo5YAp8HuQoUlsWEICwg==} - engines: {node: '>=18'} - hasBin: true - - is-inside-container@1.0.0: - resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} - engines: {node: '>=14.16'} - hasBin: true - - is-installed-globally@1.0.0: - resolution: {integrity: sha512-K55T22lfpQ63N4KEN57jZUAaAYqYHEe8veb/TycJRk9DdSCLLcovXz/mL6mOnhQaZsQGwPhuFopdQIlqGSEjiQ==} - engines: {node: '>=18'} - - is-interactive@2.0.0: - resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} - engines: {node: '>=12'} - - is-module@1.0.0: - resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} - - is-node-process@1.2.0: - resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} - - is-npm@6.0.0: - resolution: {integrity: sha512-JEjxbSmtPSt1c8XTkVrlujcXdKV1/tvuQ7GwKcAlyiVLeYFQ2VHat8xfrDJsIkhCdF/tZ7CiIR3sy141c6+gPQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - - is-obj@2.0.0: - resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} - engines: {node: '>=8'} - - is-path-inside@4.0.0: - resolution: {integrity: sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==} - engines: {node: '>=12'} - - is-plain-obj@4.1.0: - resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} - engines: {node: '>=12'} - - is-promise@4.0.0: - resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} - - is-reference@1.2.1: - resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} - - is-ssh@1.4.1: - resolution: {integrity: sha512-JNeu1wQsHjyHgn9NcWTaXq6zWSR6hqE0++zhfZlkFBbScNkyvxCdeV8sRkSBaeLKxmbpR21brail63ACNxJ0Tg==} - - is-stream@3.0.0: - resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - is-stream@4.0.1: - resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} - engines: {node: '>=18'} - - is-unicode-supported@1.3.0: - resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==} - engines: {node: '>=12'} - - is-unicode-supported@2.1.0: - resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} - engines: {node: '>=18'} - - is-wsl@3.1.0: - resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} - engines: {node: '>=16'} - - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - - issue-parser@7.0.1: - resolution: {integrity: sha512-3YZcUUR2Wt1WsapF+S/WiA2WmlW0cWAoPccMqne7AxEBhCdFeTPjfv/Axb8V2gyCgY3nRw+ksZ3xSUX+R47iAg==} - engines: {node: ^18.17 || >=20.6.1} - - istanbul-lib-coverage@3.2.2: - resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} - engines: {node: '>=8'} - - istanbul-lib-report@3.0.1: - resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} - engines: {node: '>=10'} - - istanbul-lib-source-maps@5.0.6: - resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} - engines: {node: '>=10'} - - istanbul-reports@3.1.7: - resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} - engines: {node: '>=8'} - - jackspeak@3.4.3: - resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - - jiti@1.21.7: - resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} - hasBin: true - - jiti@2.4.2: - resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} - hasBin: true - - js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - - js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} - hasBin: true - - jsbn@1.1.0: - resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==} - - jsdoc-type-pratt-parser@4.1.0: - resolution: {integrity: sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==} - engines: {node: '>=12.0.0'} - - jsesc@0.5.0: - resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==} - hasBin: true - - jsesc@3.1.0: - resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} - engines: {node: '>=6'} - hasBin: true - - json-buffer@3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - - json-parse-even-better-errors@2.3.1: - resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - - json-schema-to-typescript@15.0.4: - resolution: {integrity: sha512-Su9oK8DR4xCmDsLlyvadkXzX6+GGXJpbhwoLtOGArAG61dvbW4YQmSEno2y66ahpIdmLMg6YUf/QHLgiwvkrHQ==} - engines: {node: '>=16.0.0'} - hasBin: true - - json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - - json-stable-stringify-without-jsonify@1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - - jsonc-eslint-parser@2.4.0: - resolution: {integrity: sha512-WYDyuc/uFcGp6YtM2H0uKmUwieOuzeE/5YocFJLnLfclZ4inf3mRn8ZVy1s7Hxji7Jxm6Ss8gqpexD/GlKoGgg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - keyv@4.5.4: - resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - - kleur@4.1.5: - resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} - engines: {node: '>=6'} - - knitwork@1.2.0: - resolution: {integrity: sha512-xYSH7AvuQ6nXkq42x0v5S8/Iry+cfulBz/DJQzhIyESdLD7425jXsPy4vn5cCXU+HhRN2kVw51Vd1K6/By4BQg==} - - ky@1.8.1: - resolution: {integrity: sha512-7Bp3TpsE+L+TARSnnDpk3xg8Idi8RwSLdj6CMbNWoOARIrGrbuLGusV0dYwbZOm4bB3jHNxSw8Wk/ByDqJEnDw==} - engines: {node: '>=18'} - - latest-version@9.0.0: - resolution: {integrity: sha512-7W0vV3rqv5tokqkBAFV1LbR7HPOWzXQDpDgEuib/aJ1jsZZx6x3c2mBI+TJhJzOhkGeaLbCKEHXEXLfirtG2JA==} - engines: {node: '>=18'} - - levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} - - lilconfig@3.1.3: - resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} - engines: {node: '>=14'} - - lines-and-columns@1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - - local-pkg@0.5.1: - resolution: {integrity: sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==} - engines: {node: '>=14'} - - locate-path@5.0.0: - resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} - engines: {node: '>=8'} - - locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} - - lodash.capitalize@4.2.1: - resolution: {integrity: sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw==} - - lodash.escaperegexp@4.1.2: - resolution: {integrity: sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==} - - lodash.isplainobject@4.0.6: - resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} - - lodash.isstring@4.0.1: - resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} - - lodash.memoize@4.1.2: - resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} - - lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - - lodash.uniq@4.5.0: - resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} - - lodash.uniqby@4.7.0: - resolution: {integrity: sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==} - - lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - - log-symbols@6.0.0: - resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==} - engines: {node: '>=18'} - - longest-streak@3.1.0: - resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} - - loupe@3.1.3: - resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==} - - lru-cache@10.4.3: - resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - - lru-cache@7.18.3: - resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} - engines: {node: '>=12'} - - macos-release@3.3.0: - resolution: {integrity: sha512-tPJQ1HeyiU2vRruNGhZ+VleWuMQRro8iFtJxYgnS4NQe+EukKF6aGiIT+7flZhISAt2iaXBCfFGvAyif7/f8nQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - magic-string@0.30.17: - resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} - - magicast@0.3.5: - resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} - - make-dir@4.0.0: - resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} - engines: {node: '>=10'} - - markdown-table@3.0.4: - resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} - - math-intrinsics@1.1.0: - resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} - engines: {node: '>= 0.4'} - - mdast-util-find-and-replace@3.0.2: - resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} - - mdast-util-from-markdown@2.0.2: - resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} - - mdast-util-frontmatter@2.0.1: - resolution: {integrity: sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==} - - mdast-util-gfm-autolink-literal@2.0.1: - resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} - - mdast-util-gfm-footnote@2.1.0: - resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} - - mdast-util-gfm-strikethrough@2.0.0: - resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} - - mdast-util-gfm-table@2.0.0: - resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} - - mdast-util-gfm-task-list-item@2.0.0: - resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} - - mdast-util-gfm@3.1.0: - resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} - - mdast-util-phrasing@4.1.0: - resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} - - mdast-util-to-markdown@2.1.2: - resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} - - mdast-util-to-string@4.0.0: - resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} - - mdn-data@2.0.28: - resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} - - mdn-data@2.0.30: - resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} - - media-typer@1.1.0: - resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} - engines: {node: '>= 0.8'} - - memfs@4.17.1: - resolution: {integrity: sha512-thuTRd7F4m4dReCIy7vv4eNYnU6XI/tHMLSMMHLiortw/Y0QxqKtinG523U2aerzwYWGi606oBP4oMPy4+edag==} - engines: {node: '>= 4.0.0'} - - meow@13.2.0: - resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==} - engines: {node: '>=18'} - - merge-descriptors@2.0.0: - resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} - engines: {node: '>=18'} - - merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - - merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} - - micromark-core-commonmark@2.0.3: - resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} - - micromark-extension-frontmatter@2.0.0: - resolution: {integrity: sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==} - - micromark-extension-gfm-autolink-literal@2.1.0: - resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} - - micromark-extension-gfm-footnote@2.1.0: - resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} - - micromark-extension-gfm-strikethrough@2.1.0: - resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} - - micromark-extension-gfm-table@2.1.1: - resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==} - - micromark-extension-gfm-tagfilter@2.0.0: - resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} - - micromark-extension-gfm-task-list-item@2.1.0: - resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} - - micromark-extension-gfm@3.0.0: - resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} - - micromark-factory-destination@2.0.1: - resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} - - micromark-factory-label@2.0.1: - resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} - - micromark-factory-space@2.0.1: - resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} - - micromark-factory-title@2.0.1: - resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} - - micromark-factory-whitespace@2.0.1: - resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} - - micromark-util-character@2.1.1: - resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} - - micromark-util-chunked@2.0.1: - resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} - - micromark-util-classify-character@2.0.1: - resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} - - micromark-util-combine-extensions@2.0.1: - resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} - - micromark-util-decode-numeric-character-reference@2.0.2: - resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} - - micromark-util-decode-string@2.0.1: - resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} - - micromark-util-encode@2.0.1: - resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} - - micromark-util-html-tag-name@2.0.1: - resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} - - micromark-util-normalize-identifier@2.0.1: - resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} - - micromark-util-resolve-all@2.0.1: - resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} - - micromark-util-sanitize-uri@2.0.1: - resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} - - micromark-util-subtokenize@2.1.0: - resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} - - micromark-util-symbol@2.0.1: - resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} - - micromark-util-types@2.0.2: - resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} - - micromark@4.0.2: - resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} - - micromatch@4.0.8: - resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} - engines: {node: '>=8.6'} - - mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} - - mime-db@1.54.0: - resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} - engines: {node: '>= 0.6'} - - mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} - - mime-types@3.0.1: - resolution: {integrity: sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==} - engines: {node: '>= 0.6'} - - mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} - - mimic-fn@4.0.0: - resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} - engines: {node: '>=12'} - - mimic-function@5.0.1: - resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} - engines: {node: '>=18'} - - min-indent@1.0.1: - resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} - engines: {node: '>=4'} - - minimatch@10.0.3: - resolution: {integrity: sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==} - engines: {node: 20 || >=22} - - minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - - minimatch@9.0.5: - resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} - engines: {node: '>=16 || 14 >=14.17'} - - minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - - minipass@7.1.2: - resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} - engines: {node: '>=16 || 14 >=14.17'} - - mkdist@2.3.0: - resolution: {integrity: sha512-thkRk+pHdudjdZT3FJpPZ2+pncI6mGlH/B+KBVddlZj4MrFGW41sRIv1wZawZUHU8v7cttGaj+5nx8P+dG664A==} - hasBin: true - peerDependencies: - sass: ^1.85.0 - typescript: '>=5.7.3' - vue: ^3.5.13 - vue-sfc-transformer: ^0.1.1 - vue-tsc: ^1.8.27 || ^2.0.21 - peerDependenciesMeta: - sass: - optional: true - typescript: - optional: true - vue: - optional: true - vue-sfc-transformer: - optional: true - vue-tsc: - optional: true - - mlly@1.7.4: - resolution: {integrity: sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==} - - mrmime@2.0.1: - resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} - engines: {node: '>=10'} - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - msw@2.8.2: - resolution: {integrity: sha512-ugu8RBgUj6//RD0utqDDPdS+QIs36BKYkDAM6u59hcMVtFM4PM0vW4l3G1R+1uCWP2EWFUG8reT/gPXVEtx7/w==} - engines: {node: '>=18'} - hasBin: true - peerDependencies: - typescript: '>= 4.8.x' - peerDependenciesMeta: - typescript: - optional: true - - mute-stream@2.0.0: - resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} - engines: {node: ^18.17.0 || >=20.5.0} - - nanoid@3.3.11: - resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - - napi-postinstall@0.2.4: - resolution: {integrity: sha512-ZEzHJwBhZ8qQSbknHqYcdtQVr8zUgGyM/q6h6qAyhtyVMNrSgDhrC4disf03dYW0e+czXyLnZINnCTEkWy0eJg==} - engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - hasBin: true - - natural-compare-lite@1.4.0: - resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} - - natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - - negotiator@1.0.0: - resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} - engines: {node: '>= 0.6'} - - neo-async@2.6.2: - resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - - netmask@2.0.2: - resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} - engines: {node: '>= 0.4.0'} - - new-github-release-url@2.0.0: - resolution: {integrity: sha512-NHDDGYudnvRutt/VhKFlX26IotXe1w0cmkDm6JGquh5bz/bDTw0LufSmH/GxTjEdpHEO+bVKFTwdrcGa/9XlKQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - node-releases@2.0.19: - resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} - - normalize-package-data@2.5.0: - resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} - - normalize-package-data@6.0.2: - resolution: {integrity: sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==} - engines: {node: ^16.14.0 || >=18.0.0} - - normalize-range@0.1.2: - resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} - engines: {node: '>=0.10.0'} - - npm-run-path@5.3.0: - resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - npm-run-path@6.0.0: - resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} - engines: {node: '>=18'} - - nth-check@2.1.1: - resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} - - object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - - object-inspect@1.13.4: - resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} - engines: {node: '>= 0.4'} - - ohash@2.0.11: - resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} - - on-finished@2.4.1: - resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} - engines: {node: '>= 0.8'} - - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - - onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} - - onetime@6.0.0: - resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} - engines: {node: '>=12'} - - onetime@7.0.0: - resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} - engines: {node: '>=18'} - - open@10.1.0: - resolution: {integrity: sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==} - engines: {node: '>=18'} - - optionator@0.9.4: - resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} - engines: {node: '>= 0.8.0'} - - ora@8.1.1: - resolution: {integrity: sha512-YWielGi1XzG1UTvOaCFaNgEnuhZVMSHYkW/FQ7UX8O26PtlpdM84c0f7wLPlkvx2RfiQmnzd61d/MGxmpQeJPw==} - engines: {node: '>=18'} - - os-name@6.0.0: - resolution: {integrity: sha512-bv608E0UX86atYi2GMGjDe0vF/X1TJjemNS8oEW6z22YW1Rc3QykSYoGfkQbX0zZX9H0ZB6CQP/3GTf1I5hURg==} - engines: {node: '>=18'} - - os-tmpdir@1.0.2: - resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} - engines: {node: '>=0.10.0'} - - outvariant@1.4.3: - resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} - - p-limit@2.3.0: - resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} - engines: {node: '>=6'} - - p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} - - p-locate@4.1.0: - resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} - engines: {node: '>=8'} - - p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} - - p-try@2.2.0: - resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} - engines: {node: '>=6'} - - pac-proxy-agent@7.2.0: - resolution: {integrity: sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==} - engines: {node: '>= 14'} - - pac-resolver@7.0.1: - resolution: {integrity: sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==} - engines: {node: '>= 14'} - - package-json-from-dist@1.0.1: - resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - - package-json@10.0.1: - resolution: {integrity: sha512-ua1L4OgXSBdsu1FPb7F3tYH0F48a6kxvod4pLUlGY9COeJAJQNX/sNH2IiEmsxw7lqYiAwrdHMjz1FctOsyDQg==} - engines: {node: '>=18'} - - package-manager-detector@0.2.11: - resolution: {integrity: sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==} - - parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} - - parse-gitignore@2.0.0: - resolution: {integrity: sha512-RmVuCHWsfu0QPNW+mraxh/xjQVw/lhUCUru8Zni3Ctq3AoMhpDTq0OVdKS6iesd6Kqb7viCV3isAL43dciOSog==} - engines: {node: '>=14'} - - parse-imports-exports@0.2.4: - resolution: {integrity: sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==} - - parse-json@5.2.0: - resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} - engines: {node: '>=8'} - - parse-json@8.3.0: - resolution: {integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==} - engines: {node: '>=18'} - - parse-ms@4.0.0: - resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} - engines: {node: '>=18'} - - parse-path@7.1.0: - resolution: {integrity: sha512-EuCycjZtfPcjWk7KTksnJ5xPMvWGA/6i4zrLYhRG0hGvC3GPU/jGUj3Cy+ZR0v30duV3e23R95T1lE2+lsndSw==} - - parse-statements@1.0.11: - resolution: {integrity: sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==} - - parse-url@9.2.0: - resolution: {integrity: sha512-bCgsFI+GeGWPAvAiUv63ZorMeif3/U0zaXABGJbOWt5OH2KCaPHF6S+0ok4aqM9RuIPGyZdx9tR9l13PsW4AYQ==} - engines: {node: '>=14.13.0'} - - parseurl@1.3.3: - resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} - engines: {node: '>= 0.8'} - - path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} - - path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - - path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - - path-key@4.0.0: - resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} - engines: {node: '>=12'} - - path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - - path-scurry@1.11.1: - resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} - engines: {node: '>=16 || 14 >=14.18'} - - path-to-regexp@6.3.0: - resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} - - path-to-regexp@8.2.0: - resolution: {integrity: sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==} - engines: {node: '>=16'} - - path-type@5.0.0: - resolution: {integrity: sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==} - engines: {node: '>=12'} - - pathe@1.1.2: - resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} - - pathe@2.0.3: - resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - - pathval@2.0.0: - resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} - engines: {node: '>= 14.16'} - - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} - - picomatch@4.0.2: - resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} - engines: {node: '>=12'} - - pkce-challenge@5.0.0: - resolution: {integrity: sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==} - engines: {node: '>=16.20.0'} - - pkg-types@1.3.1: - resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} - - pkg-types@2.1.0: - resolution: {integrity: sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A==} - - pluralize@8.0.0: - resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} - engines: {node: '>=4'} - - postcss-calc@10.1.1: - resolution: {integrity: sha512-NYEsLHh8DgG/PRH2+G9BTuUdtf9ViS+vdoQ0YA5OQdGsfN4ztiwtDWNtBl9EKeqNMFnIu8IKZ0cLxEQ5r5KVMw==} - engines: {node: ^18.12 || ^20.9 || >=22.0} - peerDependencies: - postcss: ^8.4.38 - - postcss-colormin@7.0.3: - resolution: {integrity: sha512-xZxQcSyIVZbSsl1vjoqZAcMYYdnJsIyG8OvqShuuqf12S88qQboxxEy0ohNCOLwVPXTU+hFHvJPACRL2B5ohTA==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - postcss-convert-values@7.0.5: - resolution: {integrity: sha512-0VFhH8nElpIs3uXKnVtotDJJNX0OGYSZmdt4XfSfvOMrFw1jKfpwpZxfC4iN73CTM/MWakDEmsHQXkISYj4BXw==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - postcss-discard-comments@7.0.4: - resolution: {integrity: sha512-6tCUoql/ipWwKtVP/xYiFf1U9QgJ0PUvxN7pTcsQ8Ns3Fnwq1pU5D5s1MhT/XySeLq6GXNvn37U46Ded0TckWg==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - postcss-discard-duplicates@7.0.2: - resolution: {integrity: sha512-eTonaQvPZ/3i1ASDHOKkYwAybiM45zFIc7KXils4mQmHLqIswXD9XNOKEVxtTFnsmwYzF66u4LMgSr0abDlh5w==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - postcss-discard-empty@7.0.1: - resolution: {integrity: sha512-cFrJKZvcg/uxB6Ijr4l6qmn3pXQBna9zyrPC+sK0zjbkDUZew+6xDltSF7OeB7rAtzaaMVYSdbod+sZOCWnMOg==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - postcss-discard-overridden@7.0.1: - resolution: {integrity: sha512-7c3MMjjSZ/qYrx3uc1940GSOzN1Iqjtlqe8uoSg+qdVPYyRb0TILSqqmtlSFuE4mTDECwsm397Ya7iXGzfF7lg==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - postcss-merge-longhand@7.0.5: - resolution: {integrity: sha512-Kpu5v4Ys6QI59FxmxtNB/iHUVDn9Y9sYw66D6+SZoIk4QTz1prC4aYkhIESu+ieG1iylod1f8MILMs1Em3mmIw==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - postcss-merge-rules@7.0.5: - resolution: {integrity: sha512-ZonhuSwEaWA3+xYbOdJoEReKIBs5eDiBVLAGpYZpNFPzXZcEE5VKR7/qBEQvTZpiwjqhhqEQ+ax5O3VShBj9Wg==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - postcss-minify-font-values@7.0.1: - resolution: {integrity: sha512-2m1uiuJeTplll+tq4ENOQSzB8LRnSUChBv7oSyFLsJRtUgAAJGP6LLz0/8lkinTgxrmJSPOEhgY1bMXOQ4ZXhQ==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - postcss-minify-gradients@7.0.1: - resolution: {integrity: sha512-X9JjaysZJwlqNkJbUDgOclyG3jZEpAMOfof6PUZjPnPrePnPG62pS17CjdM32uT1Uq1jFvNSff9l7kNbmMSL2A==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - postcss-minify-params@7.0.3: - resolution: {integrity: sha512-vUKV2+f5mtjewYieanLX0xemxIp1t0W0H/D11u+kQV/MWdygOO7xPMkbK+r9P6Lhms8MgzKARF/g5OPXhb8tgg==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - postcss-minify-selectors@7.0.5: - resolution: {integrity: sha512-x2/IvofHcdIrAm9Q+p06ZD1h6FPcQ32WtCRVodJLDR+WMn8EVHI1kvLxZuGKz/9EY5nAmI6lIQIrpo4tBy5+ug==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - postcss-nested@7.0.2: - resolution: {integrity: sha512-5osppouFc0VR9/VYzYxO03VaDa3e8F23Kfd6/9qcZTUI8P58GIYlArOET2Wq0ywSl2o2PjELhYOFI4W7l5QHKw==} - engines: {node: '>=18.0'} - peerDependencies: - postcss: ^8.2.14 - - postcss-normalize-charset@7.0.1: - resolution: {integrity: sha512-sn413ofhSQHlZFae//m9FTOfkmiZ+YQXsbosqOWRiVQncU2BA3daX3n0VF3cG6rGLSFVc5Di/yns0dFfh8NFgQ==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - postcss-normalize-display-values@7.0.1: - resolution: {integrity: sha512-E5nnB26XjSYz/mGITm6JgiDpAbVuAkzXwLzRZtts19jHDUBFxZ0BkXAehy0uimrOjYJbocby4FVswA/5noOxrQ==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - postcss-normalize-positions@7.0.1: - resolution: {integrity: sha512-pB/SzrIP2l50ZIYu+yQZyMNmnAcwyYb9R1fVWPRxm4zcUFCY2ign7rcntGFuMXDdd9L2pPNUgoODDk91PzRZuQ==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - postcss-normalize-repeat-style@7.0.1: - resolution: {integrity: sha512-NsSQJ8zj8TIDiF0ig44Byo3Jk9e4gNt9x2VIlJudnQQ5DhWAHJPF4Tr1ITwyHio2BUi/I6Iv0HRO7beHYOloYQ==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - postcss-normalize-string@7.0.1: - resolution: {integrity: sha512-QByrI7hAhsoze992kpbMlJSbZ8FuCEc1OT9EFbZ6HldXNpsdpZr+YXC5di3UEv0+jeZlHbZcoCADgb7a+lPmmQ==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - postcss-normalize-timing-functions@7.0.1: - resolution: {integrity: sha512-bHifyuuSNdKKsnNJ0s8fmfLMlvsQwYVxIoUBnowIVl2ZAdrkYQNGVB4RxjfpvkMjipqvbz0u7feBZybkl/6NJg==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - postcss-normalize-unicode@7.0.3: - resolution: {integrity: sha512-EcoA29LvG3F+EpOh03iqu+tJY3uYYKzArqKJHxDhUYLa2u58aqGq16K6/AOsXD9yqLN8O6y9mmePKN5cx6krOw==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - postcss-normalize-url@7.0.1: - resolution: {integrity: sha512-sUcD2cWtyK1AOL/82Fwy1aIVm/wwj5SdZkgZ3QiUzSzQQofrbq15jWJ3BA7Z+yVRwamCjJgZJN0I9IS7c6tgeQ==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - postcss-normalize-whitespace@7.0.1: - resolution: {integrity: sha512-vsbgFHMFQrJBJKrUFJNZ2pgBeBkC2IvvoHjz1to0/0Xk7sII24T0qFOiJzG6Fu3zJoq/0yI4rKWi7WhApW+EFA==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - postcss-ordered-values@7.0.2: - resolution: {integrity: sha512-AMJjt1ECBffF7CEON/Y0rekRLS6KsePU6PRP08UqYW4UGFRnTXNrByUzYK1h8AC7UWTZdQ9O3Oq9kFIhm0SFEw==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - postcss-reduce-initial@7.0.3: - resolution: {integrity: sha512-RFvkZaqiWtGMlVjlUHpaxGqEL27lgt+Q2Ixjf83CRAzqdo+TsDyGPtJUbPx2MuYIJ+sCQc2TrOvRnhcXQfgIVA==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - postcss-reduce-transforms@7.0.1: - resolution: {integrity: sha512-MhyEbfrm+Mlp/36hvZ9mT9DaO7dbncU0CvWI8V93LRkY6IYlu38OPg3FObnuKTUxJ4qA8HpurdQOo5CyqqO76g==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - postcss-selector-parser@6.1.2: - resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} - engines: {node: '>=4'} - - postcss-selector-parser@7.1.0: - resolution: {integrity: sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==} - engines: {node: '>=4'} - - postcss-svgo@7.0.2: - resolution: {integrity: sha512-5Dzy66JlnRM6pkdOTF8+cGsB1fnERTE8Nc+Eed++fOWo1hdsBptCsbG8UuJkgtZt75bRtMJIrPeZmtfANixdFA==} - engines: {node: ^18.12.0 || ^20.9.0 || >= 18} - peerDependencies: - postcss: ^8.4.32 - - postcss-unique-selectors@7.0.4: - resolution: {integrity: sha512-pmlZjsmEAG7cHd7uK3ZiNSW6otSZ13RHuZ/4cDN/bVglS5EpF2r2oxY99SuOHa8m7AWoBCelTS3JPpzsIs8skQ==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - postcss-value-parser@4.2.0: - resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - - postcss@8.5.3: - resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} - engines: {node: ^10 || ^12 || >=14} - - prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} - - prettier-linter-helpers@1.0.0: - resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} - engines: {node: '>=6.0.0'} - - prettier@3.5.3: - resolution: {integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==} - engines: {node: '>=14'} - hasBin: true - - pretty-bytes@6.1.1: - resolution: {integrity: sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==} - engines: {node: ^14.13.1 || >=16.0.0} - - pretty-ms@9.2.0: - resolution: {integrity: sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==} - engines: {node: '>=18'} - - proto-list@1.2.4: - resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} - - protocols@2.0.2: - resolution: {integrity: sha512-hHVTzba3wboROl0/aWRRG9dMytgH6ow//STBZh43l/wQgmMhYhOFi0EHWAPtoCz9IAUymsyP0TSBHkhgMEGNnQ==} - - proxy-addr@2.0.7: - resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} - engines: {node: '>= 0.10'} - - proxy-agent@6.5.0: - resolution: {integrity: sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==} - engines: {node: '>= 14'} - - proxy-from-env@1.1.0: - resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} - - psl@1.15.0: - resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} - - punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} - - pupa@3.1.0: - resolution: {integrity: sha512-FLpr4flz5xZTSJxSeaheeMKN/EDzMdK7b8PTOC6a5PYFKTucWbdqjgqaEyH0shFiSJrVB1+Qqi4Tk19ccU6Aug==} - engines: {node: '>=12.20'} - - qs@6.14.0: - resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} - engines: {node: '>=0.6'} - - quansync@0.2.10: - resolution: {integrity: sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==} - - querystringify@2.2.0: - resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} - - queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - - range-parser@1.2.1: - resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} - engines: {node: '>= 0.6'} - - raw-body@3.0.0: - resolution: {integrity: sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==} - engines: {node: '>= 0.8'} - - rc@1.2.8: - resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} - hasBin: true - - read-package-up@11.0.0: - resolution: {integrity: sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ==} - engines: {node: '>=18'} - - read-pkg-up@7.0.1: - resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} - engines: {node: '>=8'} - - read-pkg@5.2.0: - resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} - engines: {node: '>=8'} - - read-pkg@9.0.1: - resolution: {integrity: sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==} - engines: {node: '>=18'} - - readable-stream@3.6.2: - resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} - engines: {node: '>= 6'} - - rechoir@0.6.2: - resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} - engines: {node: '>= 0.10'} - - refa@0.12.1: - resolution: {integrity: sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - - regexp-ast-analysis@0.7.1: - resolution: {integrity: sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - - regexp-tree@0.1.27: - resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==} - hasBin: true - - registry-auth-token@5.1.0: - resolution: {integrity: sha512-GdekYuwLXLxMuFTwAPg5UKGLW/UXzQrZvH/Zj791BQif5T05T0RsaLfHc9q3ZOKi7n+BoprPD9mJ0O0k4xzUlw==} - engines: {node: '>=14'} - - registry-url@6.0.1: - resolution: {integrity: sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==} - engines: {node: '>=12'} - - regjsparser@0.10.0: - resolution: {integrity: sha512-qx+xQGZVsy55CH0a1hiVwHmqjLryfh7wQyF5HO07XJ9f7dQMY/gPQHhlyDkIzJKC+x2fUCpCcUODUUUFrm7SHA==} - hasBin: true - - release-it@18.1.2: - resolution: {integrity: sha512-HOVRcicehCgoCsPFOu0iCBlEC8GDOoKS5s6ICkWmqomGEoZtRQ88D3RCsI5MciSU8vAQU+aWZW2z57NQNNb74w==} - engines: {node: ^20.9.0 || >=22.0.0} - hasBin: true - - require-directory@2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} - engines: {node: '>=0.10.0'} - - requires-port@1.0.0: - resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} - - resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - - resolve-pkg-maps@1.0.0: - resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - - resolve@1.22.10: - resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} - engines: {node: '>= 0.4'} - hasBin: true - - restore-cursor@4.0.0: - resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - restore-cursor@5.1.0: - resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} - engines: {node: '>=18'} - - retry@0.13.1: - resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} - engines: {node: '>= 4'} - - reusify@1.1.0: - resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - - rollup-plugin-dts@6.2.1: - resolution: {integrity: sha512-sR3CxYUl7i2CHa0O7bA45mCrgADyAQ0tVtGSqi3yvH28M+eg1+g5d7kQ9hLvEz5dorK3XVsH5L2jwHLQf72DzA==} - engines: {node: '>=16'} - peerDependencies: - rollup: ^3.29.4 || ^4 - typescript: ^4.5 || ^5.0 - - rollup@4.40.2: - resolution: {integrity: sha512-tfUOg6DTP4rhQ3VjOO6B4wyrJnGOX85requAXvqYTHsOgb2TFJdZ3aWpT8W2kPoypSGP7dZUyzxJ9ee4buM5Fg==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - - router@2.2.0: - resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} - engines: {node: '>= 18'} - - run-applescript@7.0.0: - resolution: {integrity: sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==} - engines: {node: '>=18'} - - run-async@3.0.0: - resolution: {integrity: sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==} - engines: {node: '>=0.12.0'} - - run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - - rxjs@7.8.2: - resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} - - safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - - safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - - scslre@0.3.0: - resolution: {integrity: sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ==} - engines: {node: ^14.0.0 || >=16.0.0} - - scule@1.3.0: - resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==} - - semver@5.7.2: - resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} - hasBin: true - - semver@7.6.3: - resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} - engines: {node: '>=10'} - hasBin: true - - semver@7.7.2: - resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} - engines: {node: '>=10'} - hasBin: true - - send@1.2.0: - resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==} - engines: {node: '>= 18'} - - serve-static@2.2.0: - resolution: {integrity: sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==} - engines: {node: '>= 18'} - - setprototypeof@1.2.0: - resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - - shelljs@0.8.5: - resolution: {integrity: sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==} - engines: {node: '>=4'} - hasBin: true - - side-channel-list@1.0.0: - resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} - engines: {node: '>= 0.4'} - - side-channel-map@1.0.1: - resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} - engines: {node: '>= 0.4'} - - side-channel-weakmap@1.0.2: - resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} - engines: {node: '>= 0.4'} - - side-channel@1.1.0: - resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} - engines: {node: '>= 0.4'} - - siginfo@2.0.0: - resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - - signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - - signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} - - sirv@3.0.1: - resolution: {integrity: sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==} - engines: {node: '>=18'} - - sisteransi@1.0.5: - resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} - - slash@5.1.0: - resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} - engines: {node: '>=14.16'} - - smart-buffer@4.2.0: - resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} - engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} - - socks-proxy-agent@8.0.5: - resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} - engines: {node: '>= 14'} - - socks@2.8.4: - resolution: {integrity: sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==} - engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} - - source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} - engines: {node: '>=0.10.0'} - - source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} - engines: {node: '>=0.10.0'} - - spdx-correct@3.2.0: - resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} - - spdx-exceptions@2.5.0: - resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} - - spdx-expression-parse@3.0.1: - resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} - - spdx-expression-parse@4.0.0: - resolution: {integrity: sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==} - - spdx-license-ids@3.0.21: - resolution: {integrity: sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==} - - sprintf-js@1.1.3: - resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} - - stable-hash@0.0.5: - resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} - - stackback@0.0.2: - resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - - statuses@2.0.1: - resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} - engines: {node: '>= 0.8'} - - std-env@3.9.0: - resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} - - stdin-discarder@0.2.2: - resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==} - engines: {node: '>=18'} - - storyblok-js-client@6.10.12: - resolution: {integrity: sha512-f5wNGnlMyBoo/9UqTY0uGXk3q8q+iaQ+Xl53/mUGvvi4ULvSEDsBJpubKjikdYSbKdOxPCOHwOo13ZDb2IbX/g==} - - storyblok-js-client@7.0.0: - resolution: {integrity: sha512-00zWW3jscL4W1kek9QCrInq1jw4xwjeM0jXuKg4sa/bCT8QuLGaGU9Wn3hat/hkvZLRFC09fV4sJRxDqwsFaKQ==} - - strict-event-emitter@0.5.1: - resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} - - string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} - - string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} - - string-width@7.2.0: - resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} - engines: {node: '>=18'} - - string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - - strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - - strip-ansi@7.1.0: - resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} - engines: {node: '>=12'} - - strip-final-newline@3.0.0: - resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} - engines: {node: '>=12'} - - strip-final-newline@4.0.0: - resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} - engines: {node: '>=18'} - - strip-indent@3.0.0: - resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} - engines: {node: '>=8'} - - strip-json-comments@2.0.1: - resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} - engines: {node: '>=0.10.0'} - - strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} - - stubborn-fs@1.2.5: - resolution: {integrity: sha512-H2N9c26eXjzL/S/K+i/RHHcFanE74dptvvjM8iwzwbVcWY/zjBbgRqF3K0DY4+OD+uTTASTBvDoxPDaPN02D7g==} - - stylehacks@7.0.5: - resolution: {integrity: sha512-5kNb7V37BNf0Q3w+1pxfa+oiNPS++/b4Jil9e/kPDgrk1zjEd6uR7SZeJiYaLYH6RRSC1XX2/37OTeU/4FvuIA==} - engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} - peerDependencies: - postcss: ^8.4.32 - - supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - - supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} - - svgo@3.3.2: - resolution: {integrity: sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==} - engines: {node: '>=14.0.0'} - hasBin: true - - synckit@0.10.3: - resolution: {integrity: sha512-R1urvuyiTaWfeCggqEvpDJwAlDVdsT9NM+IP//Tk2x7qHCkSvBk/fwFgw/TLAHzZlrAnnazMcRw0ZD8HlYFTEQ==} - engines: {node: ^14.18.0 || >=16.0.0} - - synckit@0.9.2: - resolution: {integrity: sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==} - engines: {node: ^14.18.0 || >=16.0.0} - - tapable@2.2.1: - resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} - engines: {node: '>=6'} - - test-exclude@7.0.1: - resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} - engines: {node: '>=18'} - - thingies@1.21.0: - resolution: {integrity: sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==} - engines: {node: '>=10.18'} - peerDependencies: - tslib: ^2 - - tinybench@2.9.0: - resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - - tinyexec@0.3.2: - resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} - - tinyglobby@0.2.13: - resolution: {integrity: sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==} - engines: {node: '>=12.0.0'} - - tinypool@1.0.2: - resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==} - engines: {node: ^18.0.0 || >=20.0.0} - - tinyrainbow@2.0.0: - resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} - engines: {node: '>=14.0.0'} - - tinyspy@3.0.2: - resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} - engines: {node: '>=14.0.0'} - - tmp@0.0.33: - resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} - engines: {node: '>=0.6.0'} - - to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} - - toidentifier@1.0.1: - resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} - engines: {node: '>=0.6'} - - toml-eslint-parser@0.10.0: - resolution: {integrity: sha512-khrZo4buq4qVmsGzS5yQjKe/WsFvV8fGfOjDQN0q4iy9FjRfPWRgTFrU8u1R2iu/SfWLhY9WnCi4Jhdrcbtg+g==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - totalist@3.0.1: - resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} - engines: {node: '>=6'} - - tough-cookie@4.1.4: - resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} - engines: {node: '>=6'} - - tree-dump@1.0.2: - resolution: {integrity: sha512-dpev9ABuLWdEubk+cIaI9cHwRNNDjkBBLXTwI4UCUFdQ5xXKqNXoK4FEciw/vxf+NQ7Cb7sGUyeUtORvHIdRXQ==} - engines: {node: '>=10.0'} - peerDependencies: - tslib: '2' - - ts-api-utils@2.1.0: - resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} - engines: {node: '>=18.12'} - peerDependencies: - typescript: '>=4.8.4' - - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - - type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} - - type-fest@0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} - - type-fest@0.21.3: - resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} - engines: {node: '>=10'} - - type-fest@0.6.0: - resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} - engines: {node: '>=8'} - - type-fest@0.8.1: - resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} - engines: {node: '>=8'} - - type-fest@2.19.0: - resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} - engines: {node: '>=12.20'} - - type-fest@4.41.0: - resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} - engines: {node: '>=16'} - - type-is@2.0.1: - resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} - engines: {node: '>= 0.6'} - - typedarray@0.0.6: - resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} - - typescript@5.8.3: - resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} - engines: {node: '>=14.17'} - hasBin: true - - ufo@1.6.1: - resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} - - uglify-js@3.19.3: - resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} - engines: {node: '>=0.8.0'} - hasBin: true - - unbuild@3.5.0: - resolution: {integrity: sha512-DPFttsiADnHRb/K+yJ9r9jdn6JyXlsmdT0S12VFC14DFSJD+cxBnHq+v0INmqqPVPxOoUjvJFYUVIb02rWnVeA==} - hasBin: true - peerDependencies: - typescript: ^5.7.3 - peerDependenciesMeta: - typescript: - optional: true - - undici-types@6.21.0: - resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - - undici@6.21.1: - resolution: {integrity: sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ==} - engines: {node: '>=18.17'} - - unicorn-magic@0.1.0: - resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} - engines: {node: '>=18'} - - unicorn-magic@0.3.0: - resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} - engines: {node: '>=18'} - - unist-util-is@6.0.0: - resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==} - - unist-util-stringify-position@4.0.0: - resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} - - unist-util-visit-parents@6.0.1: - resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==} - - unist-util-visit@5.0.0: - resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} - - universal-user-agent@7.0.3: - resolution: {integrity: sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==} - - universalify@0.2.0: - resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} - engines: {node: '>= 4.0.0'} - - unpipe@1.0.0: - resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} - engines: {node: '>= 0.8'} - - unrs-resolver@1.7.2: - resolution: {integrity: sha512-BBKpaylOW8KbHsu378Zky/dGh4ckT/4NW/0SHRABdqRLcQJ2dAOjDo9g97p04sWflm0kqPqpUatxReNV/dqI5A==} - - untyped@2.0.0: - resolution: {integrity: sha512-nwNCjxJTjNuLCgFr42fEak5OcLuB3ecca+9ksPFNvtfYSLpjf+iJqSIaSnIile6ZPbKYxI5k2AfXqeopGudK/g==} - hasBin: true - - update-browserslist-db@1.1.3: - resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - - update-notifier@7.3.1: - resolution: {integrity: sha512-+dwUY4L35XFYEzE+OAL3sarJdUioVovq+8f7lcIJ7wnmnYQV5UD1Y/lcwaMSyaQ6Bj3JMj1XSTjZbNLHn/19yA==} - engines: {node: '>=18'} - - uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - - url-join@5.0.0: - resolution: {integrity: sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - url-parse@1.5.10: - resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} - - util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - - uuid@11.1.0: - resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} - hasBin: true - - validate-npm-package-license@3.0.4: - resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} - - vary@1.1.2: - resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} - engines: {node: '>= 0.8'} - - vite-node@3.1.3: - resolution: {integrity: sha512-uHV4plJ2IxCl4u1up1FQRrqclylKAogbtBfOTwcuJ28xFi+89PZ57BRh+naIRvH70HPwxy5QHYzg1OrEaC7AbA==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - - vite@6.3.5: - resolution: {integrity: sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - peerDependencies: - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 - jiti: '>=1.21.0' - less: '*' - lightningcss: ^1.21.0 - sass: '*' - sass-embedded: '*' - stylus: '*' - sugarss: '*' - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - '@types/node': - optional: true - jiti: - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - - vitest@3.1.3: - resolution: {integrity: sha512-188iM4hAHQ0km23TN/adso1q5hhwKqUpv+Sd6p5sOuh6FhQnRNW3IsiIpvxqahtBabsJ2SLZgmGSpcYK4wQYJw==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - peerDependencies: - '@edge-runtime/vm': '*' - '@types/debug': ^4.1.12 - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 - '@vitest/browser': 3.1.3 - '@vitest/ui': 3.1.3 - happy-dom: '*' - jsdom: '*' - peerDependenciesMeta: - '@edge-runtime/vm': - optional: true - '@types/debug': - optional: true - '@types/node': - optional: true - '@vitest/browser': - optional: true - '@vitest/ui': - optional: true - happy-dom: - optional: true - jsdom: - optional: true - - vue-eslint-parser@9.4.3: - resolution: {integrity: sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==} - engines: {node: ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: '>=6.0.0' - - when-exit@2.1.4: - resolution: {integrity: sha512-4rnvd3A1t16PWzrBUcSDZqcAmsUIy4minDXT/CZ8F2mVDgd65i4Aalimgz1aQkRGU0iH5eT5+6Rx2TK8o443Pg==} - - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - - why-is-node-running@2.3.0: - resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} - engines: {node: '>=8'} - hasBin: true - - widest-line@5.0.0: - resolution: {integrity: sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==} - engines: {node: '>=18'} - - wildcard-match@5.1.4: - resolution: {integrity: sha512-wldeCaczs8XXq7hj+5d/F38JE2r7EXgb6WQDM84RVwxy81T/sxB5e9+uZLK9Q9oNz1mlvjut+QtvgaOQFPVq/g==} - - windows-release@6.0.1: - resolution: {integrity: sha512-MS3BzG8QK33dAyqwxfYJCJ03arkwKaddUOvvnnlFdXLudflsQF6I8yAxrLBeQk4yO8wjdH/+ax0YzxJEDrOftg==} - engines: {node: '>=18'} - - word-wrap@1.2.5: - resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} - engines: {node: '>=0.10.0'} - - wordwrap@1.0.0: - resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} - - wrap-ansi@6.2.0: - resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} - engines: {node: '>=8'} - - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} - - wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} - - wrap-ansi@9.0.0: - resolution: {integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==} - engines: {node: '>=18'} - - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - - xdg-basedir@5.1.0: - resolution: {integrity: sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==} - engines: {node: '>=12'} - - xml-name-validator@4.0.0: - resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} - engines: {node: '>=12'} - - y18n@5.0.8: - resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} - engines: {node: '>=10'} - - yaml-eslint-parser@1.3.0: - resolution: {integrity: sha512-E/+VitOorXSLiAqtTd7Yqax0/pAS3xaYMP+AUUJGOK1OZG3rhcj9fcJOM5HJ2VrP1FrStVCWr1muTfQCdj4tAA==} - engines: {node: ^14.17.0 || >=16.0.0} - - yaml@2.7.1: - resolution: {integrity: sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==} - engines: {node: '>= 14'} - hasBin: true - - yargs-parser@21.1.1: - resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} - engines: {node: '>=12'} - - yargs@17.7.2: - resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} - engines: {node: '>=12'} - - yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - - yoctocolors-cjs@2.1.2: - resolution: {integrity: sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==} - engines: {node: '>=18'} - - yoctocolors@2.1.1: - resolution: {integrity: sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==} - engines: {node: '>=18'} - - zod-to-json-schema@3.24.5: - resolution: {integrity: sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==} - peerDependencies: - zod: ^3.24.1 - - zod@3.24.4: - resolution: {integrity: sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg==} - - zwitch@2.0.4: - resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} - -snapshots: - - '@ampproject/remapping@2.3.0': - dependencies: - '@jridgewell/gen-mapping': 0.3.8 - '@jridgewell/trace-mapping': 0.3.25 - - '@antfu/eslint-config@3.6.2(@typescript-eslint/utils@8.32.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3))(@vue/compiler-sfc@3.5.13)(eslint-plugin-format@0.1.3(eslint@9.26.0(jiti@2.4.2)))(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.1.3)': - dependencies: - '@antfu/install-pkg': 0.4.1 - '@clack/prompts': 0.7.0 - '@eslint-community/eslint-plugin-eslint-comments': 4.5.0(eslint@9.26.0(jiti@2.4.2)) - '@eslint/markdown': 6.4.0 - '@stylistic/eslint-plugin': 2.13.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/eslint-plugin': 8.32.1(@typescript-eslint/parser@8.32.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/parser': 8.32.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3) - '@vitest/eslint-plugin': 1.1.44(@typescript-eslint/utils@8.32.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.1.3) - eslint: 9.26.0(jiti@2.4.2) - eslint-config-flat-gitignore: 0.3.0(eslint@9.26.0(jiti@2.4.2)) - eslint-flat-config-utils: 0.4.0 - eslint-merge-processors: 0.1.0(eslint@9.26.0(jiti@2.4.2)) - eslint-plugin-antfu: 2.7.0(eslint@9.26.0(jiti@2.4.2)) - eslint-plugin-command: 0.2.7(eslint@9.26.0(jiti@2.4.2)) - eslint-plugin-import-x: 4.11.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3) - eslint-plugin-jsdoc: 50.6.17(eslint@9.26.0(jiti@2.4.2)) - eslint-plugin-jsonc: 2.20.0(eslint@9.26.0(jiti@2.4.2)) - eslint-plugin-n: 17.18.0(eslint@9.26.0(jiti@2.4.2)) - eslint-plugin-no-only-tests: 3.3.0 - eslint-plugin-perfectionist: 3.9.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3)(vue-eslint-parser@9.4.3(eslint@9.26.0(jiti@2.4.2))) - eslint-plugin-regexp: 2.7.0(eslint@9.26.0(jiti@2.4.2)) - eslint-plugin-toml: 0.11.1(eslint@9.26.0(jiti@2.4.2)) - eslint-plugin-unicorn: 55.0.0(eslint@9.26.0(jiti@2.4.2)) - eslint-plugin-unused-imports: 4.1.4(@typescript-eslint/eslint-plugin@8.32.1(@typescript-eslint/parser@8.32.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.26.0(jiti@2.4.2)) - eslint-plugin-vue: 9.33.0(eslint@9.26.0(jiti@2.4.2)) - eslint-plugin-yml: 1.18.0(eslint@9.26.0(jiti@2.4.2)) - eslint-processor-vue-blocks: 0.1.2(@vue/compiler-sfc@3.5.13)(eslint@9.26.0(jiti@2.4.2)) - globals: 15.15.0 - jsonc-eslint-parser: 2.4.0 - local-pkg: 0.5.1 - parse-gitignore: 2.0.0 - picocolors: 1.1.1 - toml-eslint-parser: 0.10.0 - vue-eslint-parser: 9.4.3(eslint@9.26.0(jiti@2.4.2)) - yaml-eslint-parser: 1.3.0 - yargs: 17.7.2 - optionalDependencies: - eslint-plugin-format: 0.1.3(eslint@9.26.0(jiti@2.4.2)) - transitivePeerDependencies: - - '@eslint/json' - - '@typescript-eslint/utils' - - '@vue/compiler-sfc' - - supports-color - - svelte - - typescript - - vitest - - '@antfu/install-pkg@0.4.1': - dependencies: - package-manager-detector: 0.2.11 - tinyexec: 0.3.2 - - '@antfu/utils@0.7.10': {} - - '@apidevtools/json-schema-ref-parser@11.9.3': - dependencies: - '@jsdevtools/ono': 7.1.3 - '@types/json-schema': 7.0.15 - js-yaml: 4.1.0 - - '@babel/code-frame@7.27.1': - dependencies: - '@babel/helper-validator-identifier': 7.27.1 - js-tokens: 4.0.0 - picocolors: 1.1.1 - - '@babel/helper-string-parser@7.27.1': {} - - '@babel/helper-validator-identifier@7.27.1': {} - - '@babel/parser@7.27.2': - dependencies: - '@babel/types': 7.27.1 - - '@babel/types@7.27.1': - dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 - - '@bcoe/v8-coverage@1.0.2': {} - - '@bundled-es-modules/cookie@2.0.1': - dependencies: - cookie: 0.7.2 - - '@bundled-es-modules/statuses@1.0.1': - dependencies: - statuses: 2.0.1 - - '@bundled-es-modules/tough-cookie@0.1.6': - dependencies: - '@types/tough-cookie': 4.0.5 - tough-cookie: 4.1.4 - - '@clack/core@0.3.5': - dependencies: - picocolors: 1.1.1 - sisteransi: 1.0.5 - - '@clack/prompts@0.7.0': - dependencies: - '@clack/core': 0.3.5 - picocolors: 1.1.1 - sisteransi: 1.0.5 - - '@conventional-changelog/git-client@1.0.1(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.1.0)': - dependencies: - '@types/semver': 7.7.0 - semver: 7.7.2 - optionalDependencies: - conventional-commits-filter: 5.0.0 - conventional-commits-parser: 6.1.0 - - '@dprint/formatter@0.3.0': {} - - '@dprint/markdown@0.17.8': {} - - '@dprint/toml@0.6.4': {} - - '@emnapi/core@1.4.3': - dependencies: - '@emnapi/wasi-threads': 1.0.2 - tslib: 2.8.1 - optional: true - - '@emnapi/runtime@1.4.3': - dependencies: - tslib: 2.8.1 - optional: true - - '@emnapi/wasi-threads@1.0.2': - dependencies: - tslib: 2.8.1 - optional: true - - '@es-joy/jsdoccomment@0.49.0': - dependencies: - comment-parser: 1.4.1 - esquery: 1.6.0 - jsdoc-type-pratt-parser: 4.1.0 - - '@es-joy/jsdoccomment@0.50.1': - dependencies: - '@types/eslint': 9.6.1 - '@types/estree': 1.0.7 - '@typescript-eslint/types': 8.32.1 - comment-parser: 1.4.1 - esquery: 1.6.0 - jsdoc-type-pratt-parser: 4.1.0 - - '@esbuild/aix-ppc64@0.25.4': - optional: true - - '@esbuild/android-arm64@0.25.4': - optional: true - - '@esbuild/android-arm@0.25.4': - optional: true - - '@esbuild/android-x64@0.25.4': - optional: true - - '@esbuild/darwin-arm64@0.25.4': - optional: true - - '@esbuild/darwin-x64@0.25.4': - optional: true - - '@esbuild/freebsd-arm64@0.25.4': - optional: true - - '@esbuild/freebsd-x64@0.25.4': - optional: true - - '@esbuild/linux-arm64@0.25.4': - optional: true - - '@esbuild/linux-arm@0.25.4': - optional: true - - '@esbuild/linux-ia32@0.25.4': - optional: true - - '@esbuild/linux-loong64@0.25.4': - optional: true - - '@esbuild/linux-mips64el@0.25.4': - optional: true - - '@esbuild/linux-ppc64@0.25.4': - optional: true - - '@esbuild/linux-riscv64@0.25.4': - optional: true - - '@esbuild/linux-s390x@0.25.4': - optional: true - - '@esbuild/linux-x64@0.25.4': - optional: true - - '@esbuild/netbsd-arm64@0.25.4': - optional: true - - '@esbuild/netbsd-x64@0.25.4': - optional: true - - '@esbuild/openbsd-arm64@0.25.4': - optional: true - - '@esbuild/openbsd-x64@0.25.4': - optional: true - - '@esbuild/sunos-x64@0.25.4': - optional: true - - '@esbuild/win32-arm64@0.25.4': - optional: true - - '@esbuild/win32-ia32@0.25.4': - optional: true - - '@esbuild/win32-x64@0.25.4': - optional: true - - '@eslint-community/eslint-plugin-eslint-comments@4.5.0(eslint@9.26.0(jiti@2.4.2))': - dependencies: - escape-string-regexp: 4.0.0 - eslint: 9.26.0(jiti@2.4.2) - ignore: 5.3.2 - - '@eslint-community/eslint-utils@4.7.0(eslint@9.26.0(jiti@2.4.2))': - dependencies: - eslint: 9.26.0(jiti@2.4.2) - eslint-visitor-keys: 3.4.3 - - '@eslint-community/regexpp@4.12.1': {} - - '@eslint/compat@1.2.9(eslint@9.26.0(jiti@2.4.2))': - optionalDependencies: - eslint: 9.26.0(jiti@2.4.2) - - '@eslint/config-array@0.20.0': - dependencies: - '@eslint/object-schema': 2.1.6 - debug: 4.4.1 - minimatch: 3.1.2 - transitivePeerDependencies: - - supports-color - - '@eslint/config-helpers@0.2.2': {} - - '@eslint/core@0.10.0': - dependencies: - '@types/json-schema': 7.0.15 - - '@eslint/core@0.13.0': - dependencies: - '@types/json-schema': 7.0.15 - - '@eslint/eslintrc@3.3.1': - dependencies: - ajv: 6.12.6 - debug: 4.4.1 - espree: 10.3.0 - globals: 14.0.0 - ignore: 5.3.2 - import-fresh: 3.3.1 - js-yaml: 4.1.0 - minimatch: 3.1.2 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - - '@eslint/js@9.26.0': {} - - '@eslint/markdown@6.4.0': - dependencies: - '@eslint/core': 0.10.0 - '@eslint/plugin-kit': 0.2.8 - mdast-util-from-markdown: 2.0.2 - mdast-util-frontmatter: 2.0.1 - mdast-util-gfm: 3.1.0 - micromark-extension-frontmatter: 2.0.0 - micromark-extension-gfm: 3.0.0 - transitivePeerDependencies: - - supports-color - - '@eslint/object-schema@2.1.6': {} - - '@eslint/plugin-kit@0.2.8': - dependencies: - '@eslint/core': 0.13.0 - levn: 0.4.1 - - '@humanfs/core@0.19.1': {} - - '@humanfs/node@0.16.6': - dependencies: - '@humanfs/core': 0.19.1 - '@humanwhocodes/retry': 0.3.1 - - '@humanwhocodes/module-importer@1.0.1': {} - - '@humanwhocodes/retry@0.3.1': {} - - '@humanwhocodes/retry@0.4.3': {} - - '@hutson/parse-repository-url@5.0.0': {} - - '@iarna/toml@2.2.5': {} - - '@inquirer/checkbox@4.1.6(@types/node@22.15.18)': - dependencies: - '@inquirer/core': 10.1.11(@types/node@22.15.18) - '@inquirer/figures': 1.0.11 - '@inquirer/type': 3.0.6(@types/node@22.15.18) - ansi-escapes: 4.3.2 - yoctocolors-cjs: 2.1.2 - optionalDependencies: - '@types/node': 22.15.18 - - '@inquirer/confirm@5.1.10(@types/node@22.15.18)': - dependencies: - '@inquirer/core': 10.1.11(@types/node@22.15.18) - '@inquirer/type': 3.0.6(@types/node@22.15.18) - optionalDependencies: - '@types/node': 22.15.18 - - '@inquirer/core@10.1.11(@types/node@22.15.18)': - dependencies: - '@inquirer/figures': 1.0.11 - '@inquirer/type': 3.0.6(@types/node@22.15.18) - ansi-escapes: 4.3.2 - cli-width: 4.1.0 - mute-stream: 2.0.0 - signal-exit: 4.1.0 - wrap-ansi: 6.2.0 - yoctocolors-cjs: 2.1.2 - optionalDependencies: - '@types/node': 22.15.18 - - '@inquirer/editor@4.2.11(@types/node@22.15.18)': - dependencies: - '@inquirer/core': 10.1.11(@types/node@22.15.18) - '@inquirer/type': 3.0.6(@types/node@22.15.18) - external-editor: 3.1.0 - optionalDependencies: - '@types/node': 22.15.18 - - '@inquirer/expand@4.0.13(@types/node@22.15.18)': - dependencies: - '@inquirer/core': 10.1.11(@types/node@22.15.18) - '@inquirer/type': 3.0.6(@types/node@22.15.18) - yoctocolors-cjs: 2.1.2 - optionalDependencies: - '@types/node': 22.15.18 - - '@inquirer/figures@1.0.11': {} - - '@inquirer/input@4.1.10(@types/node@22.15.18)': - dependencies: - '@inquirer/core': 10.1.11(@types/node@22.15.18) - '@inquirer/type': 3.0.6(@types/node@22.15.18) - optionalDependencies: - '@types/node': 22.15.18 - - '@inquirer/number@3.0.13(@types/node@22.15.18)': - dependencies: - '@inquirer/core': 10.1.11(@types/node@22.15.18) - '@inquirer/type': 3.0.6(@types/node@22.15.18) - optionalDependencies: - '@types/node': 22.15.18 - - '@inquirer/password@4.0.13(@types/node@22.15.18)': - dependencies: - '@inquirer/core': 10.1.11(@types/node@22.15.18) - '@inquirer/type': 3.0.6(@types/node@22.15.18) - ansi-escapes: 4.3.2 - optionalDependencies: - '@types/node': 22.15.18 - - '@inquirer/prompts@7.5.1(@types/node@22.15.18)': - dependencies: - '@inquirer/checkbox': 4.1.6(@types/node@22.15.18) - '@inquirer/confirm': 5.1.10(@types/node@22.15.18) - '@inquirer/editor': 4.2.11(@types/node@22.15.18) - '@inquirer/expand': 4.0.13(@types/node@22.15.18) - '@inquirer/input': 4.1.10(@types/node@22.15.18) - '@inquirer/number': 3.0.13(@types/node@22.15.18) - '@inquirer/password': 4.0.13(@types/node@22.15.18) - '@inquirer/rawlist': 4.1.1(@types/node@22.15.18) - '@inquirer/search': 3.0.13(@types/node@22.15.18) - '@inquirer/select': 4.2.1(@types/node@22.15.18) - optionalDependencies: - '@types/node': 22.15.18 - - '@inquirer/rawlist@4.1.1(@types/node@22.15.18)': - dependencies: - '@inquirer/core': 10.1.11(@types/node@22.15.18) - '@inquirer/type': 3.0.6(@types/node@22.15.18) - yoctocolors-cjs: 2.1.2 - optionalDependencies: - '@types/node': 22.15.18 - - '@inquirer/search@3.0.13(@types/node@22.15.18)': - dependencies: - '@inquirer/core': 10.1.11(@types/node@22.15.18) - '@inquirer/figures': 1.0.11 - '@inquirer/type': 3.0.6(@types/node@22.15.18) - yoctocolors-cjs: 2.1.2 - optionalDependencies: - '@types/node': 22.15.18 - - '@inquirer/select@4.2.1(@types/node@22.15.18)': - dependencies: - '@inquirer/core': 10.1.11(@types/node@22.15.18) - '@inquirer/figures': 1.0.11 - '@inquirer/type': 3.0.6(@types/node@22.15.18) - ansi-escapes: 4.3.2 - yoctocolors-cjs: 2.1.2 - optionalDependencies: - '@types/node': 22.15.18 - - '@inquirer/type@3.0.6(@types/node@22.15.18)': - optionalDependencies: - '@types/node': 22.15.18 - - '@isaacs/balanced-match@4.0.1': {} - - '@isaacs/brace-expansion@5.0.0': - dependencies: - '@isaacs/balanced-match': 4.0.1 - - '@isaacs/cliui@8.0.2': - dependencies: - string-width: 5.1.2 - string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.0 - strip-ansi-cjs: strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: wrap-ansi@7.0.0 - - '@istanbuljs/schema@0.1.3': {} - - '@jridgewell/gen-mapping@0.3.8': - dependencies: - '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.5.0 - '@jridgewell/trace-mapping': 0.3.25 - - '@jridgewell/resolve-uri@3.1.2': {} - - '@jridgewell/set-array@1.2.1': {} - - '@jridgewell/sourcemap-codec@1.5.0': {} - - '@jridgewell/trace-mapping@0.3.25': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.0 - - '@jsdevtools/ono@7.1.3': {} - - '@jsonjoy.com/base64@1.1.2(tslib@2.8.1)': - dependencies: - tslib: 2.8.1 - - '@jsonjoy.com/json-pack@1.2.0(tslib@2.8.1)': - dependencies: - '@jsonjoy.com/base64': 1.1.2(tslib@2.8.1) - '@jsonjoy.com/util': 1.6.0(tslib@2.8.1) - hyperdyperid: 1.2.0 - thingies: 1.21.0(tslib@2.8.1) - tslib: 2.8.1 - - '@jsonjoy.com/util@1.6.0(tslib@2.8.1)': - dependencies: - tslib: 2.8.1 - - '@modelcontextprotocol/sdk@1.11.2': - dependencies: - content-type: 1.0.5 - cors: 2.8.5 - cross-spawn: 7.0.6 - eventsource: 3.0.7 - express: 5.1.0 - express-rate-limit: 7.5.0(express@5.1.0) - pkce-challenge: 5.0.0 - raw-body: 3.0.0 - zod: 3.24.4 - zod-to-json-schema: 3.24.5(zod@3.24.4) - transitivePeerDependencies: - - supports-color - - '@mswjs/interceptors@0.37.6': - dependencies: - '@open-draft/deferred-promise': 2.2.0 - '@open-draft/logger': 0.3.0 - '@open-draft/until': 2.1.0 - is-node-process: 1.2.0 - outvariant: 1.4.3 - strict-event-emitter: 0.5.1 - - '@napi-rs/wasm-runtime@0.2.9': - dependencies: - '@emnapi/core': 1.4.3 - '@emnapi/runtime': 1.4.3 - '@tybys/wasm-util': 0.9.0 - optional: true - - '@nodelib/fs.scandir@2.1.5': - dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 - - '@nodelib/fs.stat@2.0.5': {} - - '@nodelib/fs.walk@1.2.8': - dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.19.1 - - '@octokit/auth-token@5.1.2': {} - - '@octokit/core@6.1.5': - dependencies: - '@octokit/auth-token': 5.1.2 - '@octokit/graphql': 8.2.2 - '@octokit/request': 9.2.3 - '@octokit/request-error': 6.1.8 - '@octokit/types': 14.0.0 - before-after-hook: 3.0.2 - universal-user-agent: 7.0.3 - - '@octokit/endpoint@10.1.4': - dependencies: - '@octokit/types': 14.0.0 - universal-user-agent: 7.0.3 - - '@octokit/graphql@8.2.2': - dependencies: - '@octokit/request': 9.2.3 - '@octokit/types': 14.0.0 - universal-user-agent: 7.0.3 - - '@octokit/openapi-types@24.2.0': {} - - '@octokit/openapi-types@25.0.0': {} - - '@octokit/plugin-paginate-rest@11.6.0(@octokit/core@6.1.5)': - dependencies: - '@octokit/core': 6.1.5 - '@octokit/types': 13.10.0 - - '@octokit/plugin-request-log@5.3.1(@octokit/core@6.1.5)': - dependencies: - '@octokit/core': 6.1.5 - - '@octokit/plugin-rest-endpoint-methods@13.5.0(@octokit/core@6.1.5)': - dependencies: - '@octokit/core': 6.1.5 - '@octokit/types': 13.10.0 - - '@octokit/request-error@6.1.8': - dependencies: - '@octokit/types': 14.0.0 - - '@octokit/request@9.2.3': - dependencies: - '@octokit/endpoint': 10.1.4 - '@octokit/request-error': 6.1.8 - '@octokit/types': 14.0.0 - fast-content-type-parse: 2.0.1 - universal-user-agent: 7.0.3 - - '@octokit/rest@21.0.2': - dependencies: - '@octokit/core': 6.1.5 - '@octokit/plugin-paginate-rest': 11.6.0(@octokit/core@6.1.5) - '@octokit/plugin-request-log': 5.3.1(@octokit/core@6.1.5) - '@octokit/plugin-rest-endpoint-methods': 13.5.0(@octokit/core@6.1.5) - - '@octokit/types@13.10.0': - dependencies: - '@octokit/openapi-types': 24.2.0 - - '@octokit/types@14.0.0': - dependencies: - '@octokit/openapi-types': 25.0.0 - - '@open-draft/deferred-promise@2.2.0': {} - - '@open-draft/logger@0.3.0': - dependencies: - is-node-process: 1.2.0 - outvariant: 1.4.3 - - '@open-draft/until@2.1.0': {} - - '@pkgjs/parseargs@0.11.0': - optional: true - - '@pkgr/core@0.1.2': {} - - '@pkgr/core@0.2.4': {} - - '@pnpm/config.env-replace@1.1.0': {} - - '@pnpm/network.ca-file@1.0.2': - dependencies: - graceful-fs: 4.2.10 - - '@pnpm/npm-conf@2.3.1': - dependencies: - '@pnpm/config.env-replace': 1.1.0 - '@pnpm/network.ca-file': 1.0.2 - config-chain: 1.1.13 - - '@polka/url@1.0.0-next.29': {} - - '@release-it/conventional-changelog@10.0.0(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.1.0)(release-it@18.1.2(@types/node@22.15.18)(typescript@5.8.3))': - dependencies: - concat-stream: 2.0.0 - conventional-changelog: 6.0.0(conventional-commits-filter@5.0.0) - conventional-recommended-bump: 10.0.0 - git-semver-tags: 8.0.0(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.1.0) - release-it: 18.1.2(@types/node@22.15.18)(typescript@5.8.3) - semver: 7.7.2 - transitivePeerDependencies: - - conventional-commits-filter - - conventional-commits-parser - - '@rollup/plugin-alias@5.1.1(rollup@4.40.2)': - optionalDependencies: - rollup: 4.40.2 - - '@rollup/plugin-commonjs@28.0.3(rollup@4.40.2)': - dependencies: - '@rollup/pluginutils': 5.1.4(rollup@4.40.2) - commondir: 1.0.1 - estree-walker: 2.0.2 - fdir: 6.4.4(picomatch@4.0.2) - is-reference: 1.2.1 - magic-string: 0.30.17 - picomatch: 4.0.2 - optionalDependencies: - rollup: 4.40.2 - - '@rollup/plugin-json@6.1.0(rollup@4.40.2)': - dependencies: - '@rollup/pluginutils': 5.1.4(rollup@4.40.2) - optionalDependencies: - rollup: 4.40.2 - - '@rollup/plugin-node-resolve@16.0.1(rollup@4.40.2)': - dependencies: - '@rollup/pluginutils': 5.1.4(rollup@4.40.2) - '@types/resolve': 1.20.2 - deepmerge: 4.3.1 - is-module: 1.0.0 - resolve: 1.22.10 - optionalDependencies: - rollup: 4.40.2 - - '@rollup/plugin-replace@6.0.2(rollup@4.40.2)': - dependencies: - '@rollup/pluginutils': 5.1.4(rollup@4.40.2) - magic-string: 0.30.17 - optionalDependencies: - rollup: 4.40.2 - - '@rollup/pluginutils@5.1.4(rollup@4.40.2)': - dependencies: - '@types/estree': 1.0.7 - estree-walker: 2.0.2 - picomatch: 4.0.2 - optionalDependencies: - rollup: 4.40.2 - - '@rollup/rollup-android-arm-eabi@4.40.2': - optional: true - - '@rollup/rollup-android-arm64@4.40.2': - optional: true - - '@rollup/rollup-darwin-arm64@4.40.2': - optional: true - - '@rollup/rollup-darwin-x64@4.40.2': - optional: true - - '@rollup/rollup-freebsd-arm64@4.40.2': - optional: true - - '@rollup/rollup-freebsd-x64@4.40.2': - optional: true - - '@rollup/rollup-linux-arm-gnueabihf@4.40.2': - optional: true - - '@rollup/rollup-linux-arm-musleabihf@4.40.2': - optional: true - - '@rollup/rollup-linux-arm64-gnu@4.40.2': - optional: true - - '@rollup/rollup-linux-arm64-musl@4.40.2': - optional: true - - '@rollup/rollup-linux-loongarch64-gnu@4.40.2': - optional: true - - '@rollup/rollup-linux-powerpc64le-gnu@4.40.2': - optional: true - - '@rollup/rollup-linux-riscv64-gnu@4.40.2': - optional: true - - '@rollup/rollup-linux-riscv64-musl@4.40.2': - optional: true - - '@rollup/rollup-linux-s390x-gnu@4.40.2': - optional: true - - '@rollup/rollup-linux-x64-gnu@4.40.2': - optional: true - - '@rollup/rollup-linux-x64-musl@4.40.2': - optional: true - - '@rollup/rollup-win32-arm64-msvc@4.40.2': - optional: true - - '@rollup/rollup-win32-ia32-msvc@4.40.2': - optional: true - - '@rollup/rollup-win32-x64-msvc@4.40.2': - optional: true - - '@sec-ant/readable-stream@0.4.1': {} - - '@sindresorhus/merge-streams@2.3.0': {} - - '@sindresorhus/merge-streams@4.0.0': {} - - '@storyblok/eslint-config@0.3.0(@typescript-eslint/utils@8.32.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3))(@vue/compiler-sfc@3.5.13)(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.1.3)': - dependencies: - '@antfu/eslint-config': 3.6.2(@typescript-eslint/utils@8.32.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3))(@vue/compiler-sfc@3.5.13)(eslint-plugin-format@0.1.3(eslint@9.26.0(jiti@2.4.2)))(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.1.3) - eslint: 9.26.0(jiti@2.4.2) - eslint-plugin-format: 0.1.3(eslint@9.26.0(jiti@2.4.2)) - transitivePeerDependencies: - - '@eslint-react/eslint-plugin' - - '@eslint/json' - - '@prettier/plugin-xml' - - '@typescript-eslint/utils' - - '@unocss/eslint-plugin' - - '@vue/compiler-sfc' - - astro-eslint-parser - - eslint-plugin-astro - - eslint-plugin-react-hooks - - eslint-plugin-react-refresh - - eslint-plugin-solid - - eslint-plugin-svelte - - prettier-plugin-astro - - prettier-plugin-slidev - - supports-color - - svelte - - svelte-eslint-parser - - typescript - - vitest - - '@storyblok/js@4.0.0': - dependencies: - '@storyblok/richtext': 3.2.0 - storyblok-js-client: 7.0.0 - - '@storyblok/richtext@3.2.0': {} - - '@stylistic/eslint-plugin@2.13.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3)': - dependencies: - '@typescript-eslint/utils': 8.32.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3) - eslint: 9.26.0(jiti@2.4.2) - eslint-visitor-keys: 4.2.0 - espree: 10.3.0 - estraverse: 5.3.0 - picomatch: 4.0.2 - transitivePeerDependencies: - - supports-color - - typescript - - '@tootallnate/quickjs-emscripten@0.23.0': {} - - '@topcli/spinner@2.1.2': - dependencies: - '@topcli/wcwidth': 1.0.1 - ansi-regex: 6.1.0 - cli-cursor: 4.0.0 - cli-spinners: 2.9.2 - kleur: 4.1.5 - strip-ansi: 7.1.0 - - '@topcli/wcwidth@1.0.1': {} - - '@trysound/sax@0.2.0': {} - - '@tybys/wasm-util@0.9.0': - dependencies: - tslib: 2.8.1 - optional: true - - '@types/cookie@0.6.0': {} - - '@types/debug@4.1.12': - dependencies: - '@types/ms': 2.1.0 - - '@types/eslint@9.6.1': - dependencies: - '@types/estree': 1.0.7 - '@types/json-schema': 7.0.15 - - '@types/estree@1.0.7': {} - - '@types/inquirer@9.0.8': - dependencies: - '@types/through': 0.0.33 - rxjs: 7.8.2 - - '@types/json-schema@7.0.15': {} - - '@types/lodash@4.17.16': {} - - '@types/mdast@4.0.4': - dependencies: - '@types/unist': 3.0.3 - - '@types/ms@2.1.0': {} - - '@types/node@22.15.18': - dependencies: - undici-types: 6.21.0 - - '@types/normalize-package-data@2.4.4': {} - - '@types/parse-path@7.1.0': - dependencies: - parse-path: 7.1.0 - - '@types/resolve@1.20.2': {} - - '@types/semver@7.7.0': {} - - '@types/statuses@2.0.5': {} - - '@types/through@0.0.33': - dependencies: - '@types/node': 22.15.18 - - '@types/tough-cookie@4.0.5': {} - - '@types/unist@3.0.3': {} - - '@typescript-eslint/eslint-plugin@8.32.1(@typescript-eslint/parser@8.32.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3)': - dependencies: - '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.32.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/scope-manager': 8.32.1 - '@typescript-eslint/type-utils': 8.32.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/utils': 8.32.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/visitor-keys': 8.32.1 - eslint: 9.26.0(jiti@2.4.2) - graphemer: 1.4.0 - ignore: 7.0.4 - natural-compare: 1.4.0 - ts-api-utils: 2.1.0(typescript@5.8.3) - typescript: 5.8.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/parser@8.32.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3)': - dependencies: - '@typescript-eslint/scope-manager': 8.32.1 - '@typescript-eslint/types': 8.32.1 - '@typescript-eslint/typescript-estree': 8.32.1(typescript@5.8.3) - '@typescript-eslint/visitor-keys': 8.32.1 - debug: 4.4.1 - eslint: 9.26.0(jiti@2.4.2) - typescript: 5.8.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/scope-manager@8.32.1': - dependencies: - '@typescript-eslint/types': 8.32.1 - '@typescript-eslint/visitor-keys': 8.32.1 - - '@typescript-eslint/type-utils@8.32.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3)': - dependencies: - '@typescript-eslint/typescript-estree': 8.32.1(typescript@5.8.3) - '@typescript-eslint/utils': 8.32.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3) - debug: 4.4.1 - eslint: 9.26.0(jiti@2.4.2) - ts-api-utils: 2.1.0(typescript@5.8.3) - typescript: 5.8.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/types@8.32.1': {} - - '@typescript-eslint/typescript-estree@8.32.1(typescript@5.8.3)': - dependencies: - '@typescript-eslint/types': 8.32.1 - '@typescript-eslint/visitor-keys': 8.32.1 - debug: 4.4.1 - fast-glob: 3.3.3 - is-glob: 4.0.3 - minimatch: 9.0.5 - semver: 7.7.2 - ts-api-utils: 2.1.0(typescript@5.8.3) - typescript: 5.8.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/utils@8.32.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3)': - dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.26.0(jiti@2.4.2)) - '@typescript-eslint/scope-manager': 8.32.1 - '@typescript-eslint/types': 8.32.1 - '@typescript-eslint/typescript-estree': 8.32.1(typescript@5.8.3) - eslint: 9.26.0(jiti@2.4.2) - typescript: 5.8.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/visitor-keys@8.32.1': - dependencies: - '@typescript-eslint/types': 8.32.1 - eslint-visitor-keys: 4.2.0 - - '@unrs/resolver-binding-darwin-arm64@1.7.2': - optional: true - - '@unrs/resolver-binding-darwin-x64@1.7.2': - optional: true - - '@unrs/resolver-binding-freebsd-x64@1.7.2': - optional: true - - '@unrs/resolver-binding-linux-arm-gnueabihf@1.7.2': - optional: true - - '@unrs/resolver-binding-linux-arm-musleabihf@1.7.2': - optional: true - - '@unrs/resolver-binding-linux-arm64-gnu@1.7.2': - optional: true - - '@unrs/resolver-binding-linux-arm64-musl@1.7.2': - optional: true - - '@unrs/resolver-binding-linux-ppc64-gnu@1.7.2': - optional: true - - '@unrs/resolver-binding-linux-riscv64-gnu@1.7.2': - optional: true - - '@unrs/resolver-binding-linux-riscv64-musl@1.7.2': - optional: true - - '@unrs/resolver-binding-linux-s390x-gnu@1.7.2': - optional: true - - '@unrs/resolver-binding-linux-x64-gnu@1.7.2': - optional: true - - '@unrs/resolver-binding-linux-x64-musl@1.7.2': - optional: true - - '@unrs/resolver-binding-wasm32-wasi@1.7.2': - dependencies: - '@napi-rs/wasm-runtime': 0.2.9 - optional: true - - '@unrs/resolver-binding-win32-arm64-msvc@1.7.2': - optional: true - - '@unrs/resolver-binding-win32-ia32-msvc@1.7.2': - optional: true - - '@unrs/resolver-binding-win32-x64-msvc@1.7.2': - optional: true - - '@vitest/coverage-v8@3.1.3(vitest@3.1.3)': - dependencies: - '@ampproject/remapping': 2.3.0 - '@bcoe/v8-coverage': 1.0.2 - debug: 4.4.1 - istanbul-lib-coverage: 3.2.2 - istanbul-lib-report: 3.0.1 - istanbul-lib-source-maps: 5.0.6 - istanbul-reports: 3.1.7 - magic-string: 0.30.17 - magicast: 0.3.5 - std-env: 3.9.0 - test-exclude: 7.0.1 - tinyrainbow: 2.0.0 - vitest: 3.1.3(@types/debug@4.1.12)(@types/node@22.15.18)(@vitest/ui@3.1.3)(jiti@2.4.2)(msw@2.8.2(@types/node@22.15.18)(typescript@5.8.3))(yaml@2.7.1) - transitivePeerDependencies: - - supports-color - - '@vitest/eslint-plugin@1.1.44(@typescript-eslint/utils@8.32.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.1.3)': - dependencies: - '@typescript-eslint/utils': 8.32.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3) - eslint: 9.26.0(jiti@2.4.2) - optionalDependencies: - typescript: 5.8.3 - vitest: 3.1.3(@types/debug@4.1.12)(@types/node@22.15.18)(@vitest/ui@3.1.3)(jiti@2.4.2)(msw@2.8.2(@types/node@22.15.18)(typescript@5.8.3))(yaml@2.7.1) - - '@vitest/expect@3.1.3': - dependencies: - '@vitest/spy': 3.1.3 - '@vitest/utils': 3.1.3 - chai: 5.2.0 - tinyrainbow: 2.0.0 - - '@vitest/mocker@3.1.3(msw@2.8.2(@types/node@22.15.18)(typescript@5.8.3))(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(yaml@2.7.1))': - dependencies: - '@vitest/spy': 3.1.3 - estree-walker: 3.0.3 - magic-string: 0.30.17 - optionalDependencies: - msw: 2.8.2(@types/node@22.15.18)(typescript@5.8.3) - vite: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(yaml@2.7.1) - - '@vitest/pretty-format@3.1.3': - dependencies: - tinyrainbow: 2.0.0 - - '@vitest/runner@3.1.3': - dependencies: - '@vitest/utils': 3.1.3 - pathe: 2.0.3 - - '@vitest/snapshot@3.1.3': - dependencies: - '@vitest/pretty-format': 3.1.3 - magic-string: 0.30.17 - pathe: 2.0.3 - - '@vitest/spy@3.1.3': - dependencies: - tinyspy: 3.0.2 - - '@vitest/ui@3.1.3(vitest@3.1.3)': - dependencies: - '@vitest/utils': 3.1.3 - fflate: 0.8.2 - flatted: 3.3.3 - pathe: 2.0.3 - sirv: 3.0.1 - tinyglobby: 0.2.13 - tinyrainbow: 2.0.0 - vitest: 3.1.3(@types/debug@4.1.12)(@types/node@22.15.18)(@vitest/ui@3.1.3)(jiti@2.4.2)(msw@2.8.2(@types/node@22.15.18)(typescript@5.8.3))(yaml@2.7.1) - - '@vitest/utils@3.1.3': - dependencies: - '@vitest/pretty-format': 3.1.3 - loupe: 3.1.3 - tinyrainbow: 2.0.0 - - '@vue/compiler-core@3.5.13': - dependencies: - '@babel/parser': 7.27.2 - '@vue/shared': 3.5.13 - entities: 4.5.0 - estree-walker: 2.0.2 - source-map-js: 1.2.1 - - '@vue/compiler-dom@3.5.13': - dependencies: - '@vue/compiler-core': 3.5.13 - '@vue/shared': 3.5.13 - - '@vue/compiler-sfc@3.5.13': - dependencies: - '@babel/parser': 7.27.2 - '@vue/compiler-core': 3.5.13 - '@vue/compiler-dom': 3.5.13 - '@vue/compiler-ssr': 3.5.13 - '@vue/shared': 3.5.13 - estree-walker: 2.0.2 - magic-string: 0.30.17 - postcss: 8.5.3 - source-map-js: 1.2.1 - - '@vue/compiler-ssr@3.5.13': - dependencies: - '@vue/compiler-dom': 3.5.13 - '@vue/shared': 3.5.13 - - '@vue/shared@3.5.13': {} - - accepts@2.0.0: - dependencies: - mime-types: 3.0.1 - negotiator: 1.0.0 - - acorn-jsx@5.3.2(acorn@8.14.1): - dependencies: - acorn: 8.14.1 - - acorn@8.14.1: {} - - add-stream@1.0.0: {} - - agent-base@7.1.3: {} - - ajv@6.12.6: - dependencies: - fast-deep-equal: 3.1.3 - fast-json-stable-stringify: 2.1.0 - json-schema-traverse: 0.4.1 - uri-js: 4.4.1 - - ansi-align@3.0.1: - dependencies: - string-width: 4.2.3 - - ansi-escapes@4.3.2: - dependencies: - type-fest: 0.21.3 - - ansi-regex@5.0.1: {} - - ansi-regex@6.1.0: {} - - ansi-styles@4.3.0: - dependencies: - color-convert: 2.0.1 - - ansi-styles@6.2.1: {} - - are-docs-informative@0.0.2: {} - - argparse@2.0.1: {} - - array-ify@1.0.0: {} - - assertion-error@2.0.1: {} - - ast-types@0.13.4: - dependencies: - tslib: 2.8.1 - - async-retry@1.3.3: - dependencies: - retry: 0.13.1 - - atomically@2.0.3: - dependencies: - stubborn-fs: 1.2.5 - when-exit: 2.1.4 - - autoprefixer@10.4.21(postcss@8.5.3): - dependencies: - browserslist: 4.24.5 - caniuse-lite: 1.0.30001718 - fraction.js: 4.3.7 - normalize-range: 0.1.2 - picocolors: 1.1.1 - postcss: 8.5.3 - postcss-value-parser: 4.2.0 - - balanced-match@1.0.2: {} - - basic-ftp@5.0.5: {} - - before-after-hook@3.0.2: {} - - body-parser@2.2.0: - dependencies: - bytes: 3.1.2 - content-type: 1.0.5 - debug: 4.4.1 - http-errors: 2.0.0 - iconv-lite: 0.6.3 - on-finished: 2.4.1 - qs: 6.14.0 - raw-body: 3.0.0 - type-is: 2.0.1 - transitivePeerDependencies: - - supports-color - - boolbase@1.0.0: {} - - boxen@8.0.1: - dependencies: - ansi-align: 3.0.1 - camelcase: 8.0.0 - chalk: 5.4.1 - cli-boxes: 3.0.0 - string-width: 7.2.0 - type-fest: 4.41.0 - widest-line: 5.0.0 - wrap-ansi: 9.0.0 - - brace-expansion@1.1.11: - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - - brace-expansion@2.0.1: - dependencies: - balanced-match: 1.0.2 - - braces@3.0.3: - dependencies: - fill-range: 7.1.1 - - browserslist@4.24.5: - dependencies: - caniuse-lite: 1.0.30001718 - electron-to-chromium: 1.5.154 - node-releases: 2.0.19 - update-browserslist-db: 1.1.3(browserslist@4.24.5) - - buffer-from@1.1.2: {} - - builtin-modules@3.3.0: {} - - bundle-name@4.1.0: - dependencies: - run-applescript: 7.0.0 - - bytes@3.1.2: {} - - cac@6.7.14: {} - - call-bind-apply-helpers@1.0.2: - dependencies: - es-errors: 1.3.0 - function-bind: 1.1.2 - - call-bound@1.0.4: - dependencies: - call-bind-apply-helpers: 1.0.2 - get-intrinsic: 1.3.0 - - callsites@3.1.0: {} - - camelcase@8.0.0: {} - - caniuse-api@3.0.0: - dependencies: - browserslist: 4.24.5 - caniuse-lite: 1.0.30001718 - lodash.memoize: 4.1.2 - lodash.uniq: 4.5.0 - - caniuse-lite@1.0.30001718: {} - - ccount@2.0.1: {} - - chai@5.2.0: - dependencies: - assertion-error: 2.0.1 - check-error: 2.1.1 - deep-eql: 5.0.2 - loupe: 3.1.3 - pathval: 2.0.0 - - chalk@4.1.2: - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - - chalk@5.4.1: {} - - character-entities@2.0.2: {} - - chardet@0.7.0: {} - - check-error@2.1.1: {} - - ci-info@4.2.0: {} - - citty@0.1.6: - dependencies: - consola: 3.4.2 - - clean-regexp@1.0.0: - dependencies: - escape-string-regexp: 1.0.5 - - cli-boxes@3.0.0: {} - - cli-cursor@4.0.0: - dependencies: - restore-cursor: 4.0.0 - - cli-cursor@5.0.0: - dependencies: - restore-cursor: 5.1.0 - - cli-spinners@2.9.2: {} - - cli-width@4.1.0: {} - - cliui@8.0.1: - dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 7.0.0 - - color-convert@2.0.1: - dependencies: - color-name: 1.1.4 - - color-name@1.1.4: {} - - colord@2.9.3: {} - - commander@13.1.0: {} - - commander@7.2.0: {} - - comment-parser@1.4.1: {} - - commondir@1.0.1: {} - - compare-func@2.0.0: - dependencies: - array-ify: 1.0.0 - dot-prop: 5.3.0 - - concat-map@0.0.1: {} - - concat-stream@2.0.0: - dependencies: - buffer-from: 1.1.2 - inherits: 2.0.4 - readable-stream: 3.6.2 - typedarray: 0.0.6 - - confbox@0.1.8: {} - - confbox@0.2.2: {} - - config-chain@1.1.13: - dependencies: - ini: 1.3.8 - proto-list: 1.2.4 - - configstore@7.0.0: - dependencies: - atomically: 2.0.3 - dot-prop: 9.0.0 - graceful-fs: 4.2.11 - xdg-basedir: 5.1.0 - - consola@3.4.2: {} - - content-disposition@1.0.0: - dependencies: - safe-buffer: 5.2.1 - - content-type@1.0.5: {} - - conventional-changelog-angular@8.0.0: - dependencies: - compare-func: 2.0.0 - - conventional-changelog-atom@5.0.0: {} - - conventional-changelog-codemirror@5.0.0: {} - - conventional-changelog-conventionalcommits@8.0.0: - dependencies: - compare-func: 2.0.0 - - conventional-changelog-core@8.0.0(conventional-commits-filter@5.0.0): - dependencies: - '@hutson/parse-repository-url': 5.0.0 - add-stream: 1.0.0 - conventional-changelog-writer: 8.0.1 - conventional-commits-parser: 6.1.0 - git-raw-commits: 5.0.0(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.1.0) - git-semver-tags: 8.0.0(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.1.0) - hosted-git-info: 7.0.2 - normalize-package-data: 6.0.2 - read-package-up: 11.0.0 - read-pkg: 9.0.1 - transitivePeerDependencies: - - conventional-commits-filter - - conventional-changelog-ember@5.0.0: {} - - conventional-changelog-eslint@6.0.0: {} - - conventional-changelog-express@5.0.0: {} - - conventional-changelog-jquery@6.0.0: {} - - conventional-changelog-jshint@5.0.0: - dependencies: - compare-func: 2.0.0 - - conventional-changelog-preset-loader@5.0.0: {} - - conventional-changelog-writer@8.0.1: - dependencies: - conventional-commits-filter: 5.0.0 - handlebars: 4.7.8 - meow: 13.2.0 - semver: 7.7.2 - - conventional-changelog@6.0.0(conventional-commits-filter@5.0.0): - dependencies: - conventional-changelog-angular: 8.0.0 - conventional-changelog-atom: 5.0.0 - conventional-changelog-codemirror: 5.0.0 - conventional-changelog-conventionalcommits: 8.0.0 - conventional-changelog-core: 8.0.0(conventional-commits-filter@5.0.0) - conventional-changelog-ember: 5.0.0 - conventional-changelog-eslint: 6.0.0 - conventional-changelog-express: 5.0.0 - conventional-changelog-jquery: 6.0.0 - conventional-changelog-jshint: 5.0.0 - conventional-changelog-preset-loader: 5.0.0 - transitivePeerDependencies: - - conventional-commits-filter - - conventional-commits-filter@5.0.0: {} - - conventional-commits-parser@6.1.0: - dependencies: - meow: 13.2.0 - - conventional-recommended-bump@10.0.0: - dependencies: - '@conventional-changelog/git-client': 1.0.1(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.1.0) - conventional-changelog-preset-loader: 5.0.0 - conventional-commits-filter: 5.0.0 - conventional-commits-parser: 6.1.0 - meow: 13.2.0 - - cookie-signature@1.2.2: {} - - cookie@0.7.2: {} - - core-js-compat@3.42.0: - dependencies: - browserslist: 4.24.5 - - cors@2.8.5: - dependencies: - object-assign: 4.1.1 - vary: 1.1.2 - - cosmiconfig@9.0.0(typescript@5.8.3): - dependencies: - env-paths: 2.2.1 - import-fresh: 3.3.1 - js-yaml: 4.1.0 - parse-json: 5.2.0 - optionalDependencies: - typescript: 5.8.3 - - cross-spawn@7.0.6: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - - css-declaration-sorter@7.2.0(postcss@8.5.3): - dependencies: - postcss: 8.5.3 - - css-select@5.1.0: - dependencies: - boolbase: 1.0.0 - css-what: 6.1.0 - domhandler: 5.0.3 - domutils: 3.2.2 - nth-check: 2.1.1 - - css-tree@2.2.1: - dependencies: - mdn-data: 2.0.28 - source-map-js: 1.2.1 - - css-tree@2.3.1: - dependencies: - mdn-data: 2.0.30 - source-map-js: 1.2.1 - - css-what@6.1.0: {} - - cssesc@3.0.0: {} - - cssnano-preset-default@7.0.7(postcss@8.5.3): - dependencies: - browserslist: 4.24.5 - css-declaration-sorter: 7.2.0(postcss@8.5.3) - cssnano-utils: 5.0.1(postcss@8.5.3) - postcss: 8.5.3 - postcss-calc: 10.1.1(postcss@8.5.3) - postcss-colormin: 7.0.3(postcss@8.5.3) - postcss-convert-values: 7.0.5(postcss@8.5.3) - postcss-discard-comments: 7.0.4(postcss@8.5.3) - postcss-discard-duplicates: 7.0.2(postcss@8.5.3) - postcss-discard-empty: 7.0.1(postcss@8.5.3) - postcss-discard-overridden: 7.0.1(postcss@8.5.3) - postcss-merge-longhand: 7.0.5(postcss@8.5.3) - postcss-merge-rules: 7.0.5(postcss@8.5.3) - postcss-minify-font-values: 7.0.1(postcss@8.5.3) - postcss-minify-gradients: 7.0.1(postcss@8.5.3) - postcss-minify-params: 7.0.3(postcss@8.5.3) - postcss-minify-selectors: 7.0.5(postcss@8.5.3) - postcss-normalize-charset: 7.0.1(postcss@8.5.3) - postcss-normalize-display-values: 7.0.1(postcss@8.5.3) - postcss-normalize-positions: 7.0.1(postcss@8.5.3) - postcss-normalize-repeat-style: 7.0.1(postcss@8.5.3) - postcss-normalize-string: 7.0.1(postcss@8.5.3) - postcss-normalize-timing-functions: 7.0.1(postcss@8.5.3) - postcss-normalize-unicode: 7.0.3(postcss@8.5.3) - postcss-normalize-url: 7.0.1(postcss@8.5.3) - postcss-normalize-whitespace: 7.0.1(postcss@8.5.3) - postcss-ordered-values: 7.0.2(postcss@8.5.3) - postcss-reduce-initial: 7.0.3(postcss@8.5.3) - postcss-reduce-transforms: 7.0.1(postcss@8.5.3) - postcss-svgo: 7.0.2(postcss@8.5.3) - postcss-unique-selectors: 7.0.4(postcss@8.5.3) - - cssnano-utils@5.0.1(postcss@8.5.3): - dependencies: - postcss: 8.5.3 - - cssnano@7.0.7(postcss@8.5.3): - dependencies: - cssnano-preset-default: 7.0.7(postcss@8.5.3) - lilconfig: 3.1.3 - postcss: 8.5.3 - - csso@5.0.5: - dependencies: - css-tree: 2.2.1 - - data-uri-to-buffer@6.0.2: {} - - debug@3.2.7: - dependencies: - ms: 2.1.3 - - debug@4.4.1: - dependencies: - ms: 2.1.3 - - decode-named-character-reference@1.1.0: - dependencies: - character-entities: 2.0.2 - - deep-eql@5.0.2: {} - - deep-extend@0.6.0: {} - - deep-is@0.1.4: {} - - deepmerge@4.3.1: {} - - default-browser-id@5.0.0: {} - - default-browser@5.2.1: - dependencies: - bundle-name: 4.1.0 - default-browser-id: 5.0.0 - - define-lazy-prop@3.0.0: {} - - defu@6.1.4: {} - - degenerator@5.0.1: - dependencies: - ast-types: 0.13.4 - escodegen: 2.1.0 - esprima: 4.0.1 - - depd@2.0.0: {} - - dequal@2.0.3: {} - - devlop@1.1.0: - dependencies: - dequal: 2.0.3 - - dom-serializer@2.0.0: - dependencies: - domelementtype: 2.3.0 - domhandler: 5.0.3 - entities: 4.5.0 - - domelementtype@2.3.0: {} - - domhandler@5.0.3: - dependencies: - domelementtype: 2.3.0 - - domutils@3.2.2: - dependencies: - dom-serializer: 2.0.0 - domelementtype: 2.3.0 - domhandler: 5.0.3 - - dot-prop@5.3.0: - dependencies: - is-obj: 2.0.0 - - dot-prop@9.0.0: - dependencies: - type-fest: 4.41.0 - - dotenv@16.5.0: {} - - dunder-proto@1.0.1: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-errors: 1.3.0 - gopd: 1.2.0 - - eastasianwidth@0.2.0: {} - - ee-first@1.1.1: {} - - electron-to-chromium@1.5.154: {} - - emoji-regex@10.4.0: {} - - emoji-regex@8.0.0: {} - - emoji-regex@9.2.2: {} - - encodeurl@2.0.0: {} - - enhanced-resolve@5.18.1: - dependencies: - graceful-fs: 4.2.11 - tapable: 2.2.1 - - entities@4.5.0: {} - - env-paths@2.2.1: {} - - error-ex@1.3.2: - dependencies: - is-arrayish: 0.2.1 - - es-define-property@1.0.1: {} - - es-errors@1.3.0: {} - - es-module-lexer@1.7.0: {} - - es-object-atoms@1.1.1: - dependencies: - es-errors: 1.3.0 - - esbuild@0.25.4: - optionalDependencies: - '@esbuild/aix-ppc64': 0.25.4 - '@esbuild/android-arm': 0.25.4 - '@esbuild/android-arm64': 0.25.4 - '@esbuild/android-x64': 0.25.4 - '@esbuild/darwin-arm64': 0.25.4 - '@esbuild/darwin-x64': 0.25.4 - '@esbuild/freebsd-arm64': 0.25.4 - '@esbuild/freebsd-x64': 0.25.4 - '@esbuild/linux-arm': 0.25.4 - '@esbuild/linux-arm64': 0.25.4 - '@esbuild/linux-ia32': 0.25.4 - '@esbuild/linux-loong64': 0.25.4 - '@esbuild/linux-mips64el': 0.25.4 - '@esbuild/linux-ppc64': 0.25.4 - '@esbuild/linux-riscv64': 0.25.4 - '@esbuild/linux-s390x': 0.25.4 - '@esbuild/linux-x64': 0.25.4 - '@esbuild/netbsd-arm64': 0.25.4 - '@esbuild/netbsd-x64': 0.25.4 - '@esbuild/openbsd-arm64': 0.25.4 - '@esbuild/openbsd-x64': 0.25.4 - '@esbuild/sunos-x64': 0.25.4 - '@esbuild/win32-arm64': 0.25.4 - '@esbuild/win32-ia32': 0.25.4 - '@esbuild/win32-x64': 0.25.4 - - escalade@3.2.0: {} - - escape-goat@4.0.0: {} - - escape-html@1.0.3: {} - - escape-string-regexp@1.0.5: {} - - escape-string-regexp@4.0.0: {} - - escape-string-regexp@5.0.0: {} - - escodegen@2.1.0: - dependencies: - esprima: 4.0.1 - estraverse: 5.3.0 - esutils: 2.0.3 - optionalDependencies: - source-map: 0.6.1 - - eslint-compat-utils@0.5.1(eslint@9.26.0(jiti@2.4.2)): - dependencies: - eslint: 9.26.0(jiti@2.4.2) - semver: 7.7.2 - - eslint-compat-utils@0.6.5(eslint@9.26.0(jiti@2.4.2)): - dependencies: - eslint: 9.26.0(jiti@2.4.2) - semver: 7.7.2 - - eslint-config-flat-gitignore@0.3.0(eslint@9.26.0(jiti@2.4.2)): - dependencies: - '@eslint/compat': 1.2.9(eslint@9.26.0(jiti@2.4.2)) - eslint: 9.26.0(jiti@2.4.2) - find-up-simple: 1.0.1 - - eslint-flat-config-utils@0.4.0: - dependencies: - pathe: 1.1.2 - - eslint-formatting-reporter@0.0.0(eslint@9.26.0(jiti@2.4.2)): - dependencies: - eslint: 9.26.0(jiti@2.4.2) - prettier-linter-helpers: 1.0.0 - - eslint-import-resolver-node@0.3.9: - dependencies: - debug: 3.2.7 - is-core-module: 2.16.1 - resolve: 1.22.10 - transitivePeerDependencies: - - supports-color - - eslint-json-compat-utils@0.2.1(eslint@9.26.0(jiti@2.4.2))(jsonc-eslint-parser@2.4.0): - dependencies: - eslint: 9.26.0(jiti@2.4.2) - esquery: 1.6.0 - jsonc-eslint-parser: 2.4.0 - - eslint-merge-processors@0.1.0(eslint@9.26.0(jiti@2.4.2)): - dependencies: - eslint: 9.26.0(jiti@2.4.2) - - eslint-parser-plain@0.1.1: {} - - eslint-plugin-antfu@2.7.0(eslint@9.26.0(jiti@2.4.2)): - dependencies: - '@antfu/utils': 0.7.10 - eslint: 9.26.0(jiti@2.4.2) - - eslint-plugin-command@0.2.7(eslint@9.26.0(jiti@2.4.2)): - dependencies: - '@es-joy/jsdoccomment': 0.49.0 - eslint: 9.26.0(jiti@2.4.2) - - eslint-plugin-es-x@7.8.0(eslint@9.26.0(jiti@2.4.2)): - dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.26.0(jiti@2.4.2)) - '@eslint-community/regexpp': 4.12.1 - eslint: 9.26.0(jiti@2.4.2) - eslint-compat-utils: 0.5.1(eslint@9.26.0(jiti@2.4.2)) - - eslint-plugin-format@0.1.3(eslint@9.26.0(jiti@2.4.2)): - dependencies: - '@dprint/formatter': 0.3.0 - '@dprint/markdown': 0.17.8 - '@dprint/toml': 0.6.4 - eslint: 9.26.0(jiti@2.4.2) - eslint-formatting-reporter: 0.0.0(eslint@9.26.0(jiti@2.4.2)) - eslint-parser-plain: 0.1.1 - prettier: 3.5.3 - synckit: 0.9.2 - - eslint-plugin-import-x@4.11.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3): - dependencies: - '@typescript-eslint/utils': 8.32.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3) - comment-parser: 1.4.1 - debug: 4.4.1 - eslint: 9.26.0(jiti@2.4.2) - eslint-import-resolver-node: 0.3.9 - get-tsconfig: 4.10.0 - is-glob: 4.0.3 - minimatch: 10.0.3 - semver: 7.7.2 - stable-hash: 0.0.5 - tslib: 2.8.1 - unrs-resolver: 1.7.2 - transitivePeerDependencies: - - supports-color - - typescript - - eslint-plugin-jsdoc@50.6.17(eslint@9.26.0(jiti@2.4.2)): - dependencies: - '@es-joy/jsdoccomment': 0.50.1 - are-docs-informative: 0.0.2 - comment-parser: 1.4.1 - debug: 4.4.1 - escape-string-regexp: 4.0.0 - eslint: 9.26.0(jiti@2.4.2) - espree: 10.3.0 - esquery: 1.6.0 - parse-imports-exports: 0.2.4 - semver: 7.7.2 - spdx-expression-parse: 4.0.0 - transitivePeerDependencies: - - supports-color - - eslint-plugin-jsonc@2.20.0(eslint@9.26.0(jiti@2.4.2)): - dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.26.0(jiti@2.4.2)) - eslint: 9.26.0(jiti@2.4.2) - eslint-compat-utils: 0.6.5(eslint@9.26.0(jiti@2.4.2)) - eslint-json-compat-utils: 0.2.1(eslint@9.26.0(jiti@2.4.2))(jsonc-eslint-parser@2.4.0) - espree: 10.3.0 - graphemer: 1.4.0 - jsonc-eslint-parser: 2.4.0 - natural-compare: 1.4.0 - synckit: 0.10.3 - transitivePeerDependencies: - - '@eslint/json' - - eslint-plugin-n@17.18.0(eslint@9.26.0(jiti@2.4.2)): - dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.26.0(jiti@2.4.2)) - enhanced-resolve: 5.18.1 - eslint: 9.26.0(jiti@2.4.2) - eslint-plugin-es-x: 7.8.0(eslint@9.26.0(jiti@2.4.2)) - get-tsconfig: 4.10.0 - globals: 15.15.0 - ignore: 5.3.2 - minimatch: 9.0.5 - semver: 7.7.2 - - eslint-plugin-no-only-tests@3.3.0: {} - - eslint-plugin-perfectionist@3.9.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3)(vue-eslint-parser@9.4.3(eslint@9.26.0(jiti@2.4.2))): - dependencies: - '@typescript-eslint/types': 8.32.1 - '@typescript-eslint/utils': 8.32.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3) - eslint: 9.26.0(jiti@2.4.2) - minimatch: 9.0.5 - natural-compare-lite: 1.4.0 - optionalDependencies: - vue-eslint-parser: 9.4.3(eslint@9.26.0(jiti@2.4.2)) - transitivePeerDependencies: - - supports-color - - typescript - - eslint-plugin-regexp@2.7.0(eslint@9.26.0(jiti@2.4.2)): - dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.26.0(jiti@2.4.2)) - '@eslint-community/regexpp': 4.12.1 - comment-parser: 1.4.1 - eslint: 9.26.0(jiti@2.4.2) - jsdoc-type-pratt-parser: 4.1.0 - refa: 0.12.1 - regexp-ast-analysis: 0.7.1 - scslre: 0.3.0 - - eslint-plugin-toml@0.11.1(eslint@9.26.0(jiti@2.4.2)): - dependencies: - debug: 4.4.1 - eslint: 9.26.0(jiti@2.4.2) - eslint-compat-utils: 0.5.1(eslint@9.26.0(jiti@2.4.2)) - lodash: 4.17.21 - toml-eslint-parser: 0.10.0 - transitivePeerDependencies: - - supports-color - - eslint-plugin-unicorn@55.0.0(eslint@9.26.0(jiti@2.4.2)): - dependencies: - '@babel/helper-validator-identifier': 7.27.1 - '@eslint-community/eslint-utils': 4.7.0(eslint@9.26.0(jiti@2.4.2)) - ci-info: 4.2.0 - clean-regexp: 1.0.0 - core-js-compat: 3.42.0 - eslint: 9.26.0(jiti@2.4.2) - esquery: 1.6.0 - globals: 15.15.0 - indent-string: 4.0.0 - is-builtin-module: 3.2.1 - jsesc: 3.1.0 - pluralize: 8.0.0 - read-pkg-up: 7.0.1 - regexp-tree: 0.1.27 - regjsparser: 0.10.0 - semver: 7.7.2 - strip-indent: 3.0.0 - - eslint-plugin-unused-imports@4.1.4(@typescript-eslint/eslint-plugin@8.32.1(@typescript-eslint/parser@8.32.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.26.0(jiti@2.4.2)): - dependencies: - eslint: 9.26.0(jiti@2.4.2) - optionalDependencies: - '@typescript-eslint/eslint-plugin': 8.32.1(@typescript-eslint/parser@8.32.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3) - - eslint-plugin-vue@9.33.0(eslint@9.26.0(jiti@2.4.2)): - dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.26.0(jiti@2.4.2)) - eslint: 9.26.0(jiti@2.4.2) - globals: 13.24.0 - natural-compare: 1.4.0 - nth-check: 2.1.1 - postcss-selector-parser: 6.1.2 - semver: 7.7.2 - vue-eslint-parser: 9.4.3(eslint@9.26.0(jiti@2.4.2)) - xml-name-validator: 4.0.0 - transitivePeerDependencies: - - supports-color - - eslint-plugin-yml@1.18.0(eslint@9.26.0(jiti@2.4.2)): - dependencies: - debug: 4.4.1 - escape-string-regexp: 4.0.0 - eslint: 9.26.0(jiti@2.4.2) - eslint-compat-utils: 0.6.5(eslint@9.26.0(jiti@2.4.2)) - natural-compare: 1.4.0 - yaml-eslint-parser: 1.3.0 - transitivePeerDependencies: - - supports-color - - eslint-processor-vue-blocks@0.1.2(@vue/compiler-sfc@3.5.13)(eslint@9.26.0(jiti@2.4.2)): - dependencies: - '@vue/compiler-sfc': 3.5.13 - eslint: 9.26.0(jiti@2.4.2) - - eslint-scope@7.2.2: - dependencies: - esrecurse: 4.3.0 - estraverse: 5.3.0 - - eslint-scope@8.3.0: - dependencies: - esrecurse: 4.3.0 - estraverse: 5.3.0 - - eslint-visitor-keys@3.4.3: {} - - eslint-visitor-keys@4.2.0: {} - - eslint@9.26.0(jiti@2.4.2): - dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.26.0(jiti@2.4.2)) - '@eslint-community/regexpp': 4.12.1 - '@eslint/config-array': 0.20.0 - '@eslint/config-helpers': 0.2.2 - '@eslint/core': 0.13.0 - '@eslint/eslintrc': 3.3.1 - '@eslint/js': 9.26.0 - '@eslint/plugin-kit': 0.2.8 - '@humanfs/node': 0.16.6 - '@humanwhocodes/module-importer': 1.0.1 - '@humanwhocodes/retry': 0.4.3 - '@modelcontextprotocol/sdk': 1.11.2 - '@types/estree': 1.0.7 - '@types/json-schema': 7.0.15 - ajv: 6.12.6 - chalk: 4.1.2 - cross-spawn: 7.0.6 - debug: 4.4.1 - escape-string-regexp: 4.0.0 - eslint-scope: 8.3.0 - eslint-visitor-keys: 4.2.0 - espree: 10.3.0 - esquery: 1.6.0 - esutils: 2.0.3 - fast-deep-equal: 3.1.3 - file-entry-cache: 8.0.0 - find-up: 5.0.0 - glob-parent: 6.0.2 - ignore: 5.3.2 - imurmurhash: 0.1.4 - is-glob: 4.0.3 - json-stable-stringify-without-jsonify: 1.0.1 - lodash.merge: 4.6.2 - minimatch: 3.1.2 - natural-compare: 1.4.0 - optionator: 0.9.4 - zod: 3.24.4 - optionalDependencies: - jiti: 2.4.2 - transitivePeerDependencies: - - supports-color - - espree@10.3.0: - dependencies: - acorn: 8.14.1 - acorn-jsx: 5.3.2(acorn@8.14.1) - eslint-visitor-keys: 4.2.0 - - espree@9.6.1: - dependencies: - acorn: 8.14.1 - acorn-jsx: 5.3.2(acorn@8.14.1) - eslint-visitor-keys: 3.4.3 - - esprima@4.0.1: {} - - esquery@1.6.0: - dependencies: - estraverse: 5.3.0 - - esrecurse@4.3.0: - dependencies: - estraverse: 5.3.0 - - estraverse@5.3.0: {} - - estree-walker@2.0.2: {} - - estree-walker@3.0.3: - dependencies: - '@types/estree': 1.0.7 - - esutils@2.0.3: {} - - etag@1.8.1: {} - - eventsource-parser@3.0.2: {} - - eventsource@3.0.7: - dependencies: - eventsource-parser: 3.0.2 - - execa@8.0.1: - dependencies: - cross-spawn: 7.0.6 - get-stream: 8.0.1 - human-signals: 5.0.0 - is-stream: 3.0.0 - merge-stream: 2.0.0 - npm-run-path: 5.3.0 - onetime: 6.0.0 - signal-exit: 4.1.0 - strip-final-newline: 3.0.0 - - execa@9.5.2: - dependencies: - '@sindresorhus/merge-streams': 4.0.0 - cross-spawn: 7.0.6 - figures: 6.1.0 - get-stream: 9.0.1 - human-signals: 8.0.1 - is-plain-obj: 4.1.0 - is-stream: 4.0.1 - npm-run-path: 6.0.0 - pretty-ms: 9.2.0 - signal-exit: 4.1.0 - strip-final-newline: 4.0.0 - yoctocolors: 2.1.1 - - expect-type@1.2.1: {} - - express-rate-limit@7.5.0(express@5.1.0): - dependencies: - express: 5.1.0 - - express@5.1.0: - dependencies: - accepts: 2.0.0 - body-parser: 2.2.0 - content-disposition: 1.0.0 - content-type: 1.0.5 - cookie: 0.7.2 - cookie-signature: 1.2.2 - debug: 4.4.1 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - finalhandler: 2.1.0 - fresh: 2.0.0 - http-errors: 2.0.0 - merge-descriptors: 2.0.0 - mime-types: 3.0.1 - on-finished: 2.4.1 - once: 1.4.0 - parseurl: 1.3.3 - proxy-addr: 2.0.7 - qs: 6.14.0 - range-parser: 1.2.1 - router: 2.2.0 - send: 1.2.0 - serve-static: 2.2.0 - statuses: 2.0.1 - type-is: 2.0.1 - vary: 1.1.2 - transitivePeerDependencies: - - supports-color - - exsolve@1.0.5: {} - - external-editor@3.1.0: - dependencies: - chardet: 0.7.0 - iconv-lite: 0.4.24 - tmp: 0.0.33 - - fast-content-type-parse@2.0.1: {} - - fast-deep-equal@3.1.3: {} - - fast-diff@1.3.0: {} - - fast-glob@3.3.3: - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.8 - - fast-json-stable-stringify@2.1.0: {} - - fast-levenshtein@2.0.6: {} - - fastq@1.19.1: - dependencies: - reusify: 1.1.0 - - fault@2.0.1: - dependencies: - format: 0.2.2 - - fdir@6.4.4(picomatch@4.0.2): - optionalDependencies: - picomatch: 4.0.2 - - fflate@0.8.2: {} - - figures@6.1.0: - dependencies: - is-unicode-supported: 2.1.0 - - file-entry-cache@8.0.0: - dependencies: - flat-cache: 4.0.1 - - fill-range@7.1.1: - dependencies: - to-regex-range: 5.0.1 - - finalhandler@2.1.0: - dependencies: - debug: 4.4.1 - encodeurl: 2.0.0 - escape-html: 1.0.3 - on-finished: 2.4.1 - parseurl: 1.3.3 - statuses: 2.0.1 - transitivePeerDependencies: - - supports-color - - find-up-simple@1.0.1: {} - - find-up@4.1.0: - dependencies: - locate-path: 5.0.0 - path-exists: 4.0.0 - - find-up@5.0.0: - dependencies: - locate-path: 6.0.0 - path-exists: 4.0.0 - - fix-dts-default-cjs-exports@1.0.1: - dependencies: - magic-string: 0.30.17 - mlly: 1.7.4 - rollup: 4.40.2 - - flat-cache@4.0.1: - dependencies: - flatted: 3.3.3 - keyv: 4.5.4 - - flatted@3.3.3: {} - - foreground-child@3.3.1: - dependencies: - cross-spawn: 7.0.6 - signal-exit: 4.1.0 - - format@0.2.2: {} - - forwarded@0.2.0: {} - - fraction.js@4.3.7: {} - - fresh@2.0.0: {} - - fs.realpath@1.0.0: {} - - fsevents@2.3.3: - optional: true - - function-bind@1.1.2: {} - - get-caller-file@2.0.5: {} - - get-east-asian-width@1.3.0: {} - - get-intrinsic@1.3.0: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-define-property: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - function-bind: 1.1.2 - get-proto: 1.0.1 - gopd: 1.2.0 - has-symbols: 1.1.0 - hasown: 2.0.2 - math-intrinsics: 1.1.0 - - get-proto@1.0.1: - dependencies: - dunder-proto: 1.0.1 - es-object-atoms: 1.1.1 - - get-stream@8.0.1: {} - - get-stream@9.0.1: - dependencies: - '@sec-ant/readable-stream': 0.4.1 - is-stream: 4.0.1 - - get-tsconfig@4.10.0: - dependencies: - resolve-pkg-maps: 1.0.0 - - get-uri@6.0.4: - dependencies: - basic-ftp: 5.0.5 - data-uri-to-buffer: 6.0.2 - debug: 4.4.1 - transitivePeerDependencies: - - supports-color - - git-raw-commits@5.0.0(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.1.0): - dependencies: - '@conventional-changelog/git-client': 1.0.1(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.1.0) - meow: 13.2.0 - transitivePeerDependencies: - - conventional-commits-filter - - conventional-commits-parser - - git-semver-tags@8.0.0(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.1.0): - dependencies: - '@conventional-changelog/git-client': 1.0.1(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.1.0) - meow: 13.2.0 - transitivePeerDependencies: - - conventional-commits-filter - - conventional-commits-parser - - git-up@8.1.1: - dependencies: - is-ssh: 1.4.1 - parse-url: 9.2.0 - - git-url-parse@16.0.0: - dependencies: - git-up: 8.1.1 - - glob-parent@5.1.2: - dependencies: - is-glob: 4.0.3 - - glob-parent@6.0.2: - dependencies: - is-glob: 4.0.3 - - glob@10.4.5: - dependencies: - foreground-child: 3.3.1 - jackspeak: 3.4.3 - minimatch: 9.0.5 - minipass: 7.1.2 - package-json-from-dist: 1.0.1 - path-scurry: 1.11.1 - - glob@7.2.3: - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 - - global-directory@4.0.1: - dependencies: - ini: 4.1.1 - - globals@13.24.0: - dependencies: - type-fest: 0.20.2 - - globals@14.0.0: {} - - globals@15.15.0: {} - - globby@14.0.2: - dependencies: - '@sindresorhus/merge-streams': 2.3.0 - fast-glob: 3.3.3 - ignore: 5.3.2 - path-type: 5.0.0 - slash: 5.1.0 - unicorn-magic: 0.1.0 - - gopd@1.2.0: {} - - graceful-fs@4.2.10: {} - - graceful-fs@4.2.11: {} - - graphemer@1.4.0: {} - - graphql@16.11.0: {} - - handlebars@4.7.8: - dependencies: - minimist: 1.2.8 - neo-async: 2.6.2 - source-map: 0.6.1 - wordwrap: 1.0.0 - optionalDependencies: - uglify-js: 3.19.3 - - has-flag@4.0.0: {} - - has-symbols@1.1.0: {} - - hasown@2.0.2: - dependencies: - function-bind: 1.1.2 - - headers-polyfill@4.0.3: {} - - hookable@5.5.3: {} - - hosted-git-info@2.8.9: {} - - hosted-git-info@7.0.2: - dependencies: - lru-cache: 10.4.3 - - html-escaper@2.0.2: {} - - http-errors@2.0.0: - dependencies: - depd: 2.0.0 - inherits: 2.0.4 - setprototypeof: 1.2.0 - statuses: 2.0.1 - toidentifier: 1.0.1 - - http-proxy-agent@7.0.2: - dependencies: - agent-base: 7.1.3 - debug: 4.4.1 - transitivePeerDependencies: - - supports-color - - https-proxy-agent@7.0.6: - dependencies: - agent-base: 7.1.3 - debug: 4.4.1 - transitivePeerDependencies: - - supports-color - - human-signals@5.0.0: {} - - human-signals@8.0.1: {} - - hyperdyperid@1.2.0: {} - - iconv-lite@0.4.24: - dependencies: - safer-buffer: 2.1.2 - - iconv-lite@0.6.3: - dependencies: - safer-buffer: 2.1.2 - - ignore@5.3.2: {} - - ignore@7.0.4: {} - - import-fresh@3.3.1: - dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 - - imurmurhash@0.1.4: {} - - indent-string@4.0.0: {} - - index-to-position@1.1.0: {} - - inflight@1.0.6: - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - - inherits@2.0.4: {} - - ini@1.3.8: {} - - ini@4.1.1: {} - - inquirer@12.3.0(@types/node@22.15.18): - dependencies: - '@inquirer/core': 10.1.11(@types/node@22.15.18) - '@inquirer/prompts': 7.5.1(@types/node@22.15.18) - '@inquirer/type': 3.0.6(@types/node@22.15.18) - '@types/node': 22.15.18 - ansi-escapes: 4.3.2 - mute-stream: 2.0.0 - run-async: 3.0.0 - rxjs: 7.8.2 - - interpret@1.4.0: {} - - ip-address@9.0.5: - dependencies: - jsbn: 1.1.0 - sprintf-js: 1.1.3 - - ipaddr.js@1.9.1: {} - - is-arrayish@0.2.1: {} - - is-builtin-module@3.2.1: - dependencies: - builtin-modules: 3.3.0 - - is-core-module@2.16.1: - dependencies: - hasown: 2.0.2 - - is-docker@3.0.0: {} - - is-extglob@2.1.1: {} - - is-fullwidth-code-point@3.0.0: {} - - is-glob@4.0.3: - dependencies: - is-extglob: 2.1.1 - - is-in-ci@1.0.0: {} - - is-inside-container@1.0.0: - dependencies: - is-docker: 3.0.0 - - is-installed-globally@1.0.0: - dependencies: - global-directory: 4.0.1 - is-path-inside: 4.0.0 - - is-interactive@2.0.0: {} - - is-module@1.0.0: {} - - is-node-process@1.2.0: {} - - is-npm@6.0.0: {} - - is-number@7.0.0: {} - - is-obj@2.0.0: {} - - is-path-inside@4.0.0: {} - - is-plain-obj@4.1.0: {} - - is-promise@4.0.0: {} - - is-reference@1.2.1: - dependencies: - '@types/estree': 1.0.7 - - is-ssh@1.4.1: - dependencies: - protocols: 2.0.2 - - is-stream@3.0.0: {} - - is-stream@4.0.1: {} - - is-unicode-supported@1.3.0: {} - - is-unicode-supported@2.1.0: {} - - is-wsl@3.1.0: - dependencies: - is-inside-container: 1.0.0 - - isexe@2.0.0: {} - - issue-parser@7.0.1: - dependencies: - lodash.capitalize: 4.2.1 - lodash.escaperegexp: 4.1.2 - lodash.isplainobject: 4.0.6 - lodash.isstring: 4.0.1 - lodash.uniqby: 4.7.0 - - istanbul-lib-coverage@3.2.2: {} - - istanbul-lib-report@3.0.1: - dependencies: - istanbul-lib-coverage: 3.2.2 - make-dir: 4.0.0 - supports-color: 7.2.0 - - istanbul-lib-source-maps@5.0.6: - dependencies: - '@jridgewell/trace-mapping': 0.3.25 - debug: 4.4.1 - istanbul-lib-coverage: 3.2.2 - transitivePeerDependencies: - - supports-color - - istanbul-reports@3.1.7: - dependencies: - html-escaper: 2.0.2 - istanbul-lib-report: 3.0.1 - - jackspeak@3.4.3: - dependencies: - '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 - - jiti@1.21.7: {} - - jiti@2.4.2: {} - - js-tokens@4.0.0: {} - - js-yaml@4.1.0: - dependencies: - argparse: 2.0.1 - - jsbn@1.1.0: {} - - jsdoc-type-pratt-parser@4.1.0: {} - - jsesc@0.5.0: {} - - jsesc@3.1.0: {} - - json-buffer@3.0.1: {} - - json-parse-even-better-errors@2.3.1: {} - - json-schema-to-typescript@15.0.4: - dependencies: - '@apidevtools/json-schema-ref-parser': 11.9.3 - '@types/json-schema': 7.0.15 - '@types/lodash': 4.17.16 - is-glob: 4.0.3 - js-yaml: 4.1.0 - lodash: 4.17.21 - minimist: 1.2.8 - prettier: 3.5.3 - tinyglobby: 0.2.13 - - json-schema-traverse@0.4.1: {} - - json-stable-stringify-without-jsonify@1.0.1: {} - - jsonc-eslint-parser@2.4.0: - dependencies: - acorn: 8.14.1 - eslint-visitor-keys: 3.4.3 - espree: 9.6.1 - semver: 7.7.2 - - keyv@4.5.4: - dependencies: - json-buffer: 3.0.1 - - kleur@4.1.5: {} - - knitwork@1.2.0: {} - - ky@1.8.1: {} - - latest-version@9.0.0: - dependencies: - package-json: 10.0.1 - - levn@0.4.1: - dependencies: - prelude-ls: 1.2.1 - type-check: 0.4.0 - - lilconfig@3.1.3: {} - - lines-and-columns@1.2.4: {} - - local-pkg@0.5.1: - dependencies: - mlly: 1.7.4 - pkg-types: 1.3.1 - - locate-path@5.0.0: - dependencies: - p-locate: 4.1.0 - - locate-path@6.0.0: - dependencies: - p-locate: 5.0.0 - - lodash.capitalize@4.2.1: {} - - lodash.escaperegexp@4.1.2: {} - - lodash.isplainobject@4.0.6: {} - - lodash.isstring@4.0.1: {} - - lodash.memoize@4.1.2: {} - - lodash.merge@4.6.2: {} - - lodash.uniq@4.5.0: {} - - lodash.uniqby@4.7.0: {} - - lodash@4.17.21: {} - - log-symbols@6.0.0: - dependencies: - chalk: 5.4.1 - is-unicode-supported: 1.3.0 - - longest-streak@3.1.0: {} - - loupe@3.1.3: {} - - lru-cache@10.4.3: {} - - lru-cache@7.18.3: {} - - macos-release@3.3.0: {} - - magic-string@0.30.17: - dependencies: - '@jridgewell/sourcemap-codec': 1.5.0 - - magicast@0.3.5: - dependencies: - '@babel/parser': 7.27.2 - '@babel/types': 7.27.1 - source-map-js: 1.2.1 - - make-dir@4.0.0: - dependencies: - semver: 7.7.2 - - markdown-table@3.0.4: {} - - math-intrinsics@1.1.0: {} - - mdast-util-find-and-replace@3.0.2: - dependencies: - '@types/mdast': 4.0.4 - escape-string-regexp: 5.0.0 - unist-util-is: 6.0.0 - unist-util-visit-parents: 6.0.1 - - mdast-util-from-markdown@2.0.2: - dependencies: - '@types/mdast': 4.0.4 - '@types/unist': 3.0.3 - decode-named-character-reference: 1.1.0 - devlop: 1.1.0 - mdast-util-to-string: 4.0.0 - micromark: 4.0.2 - micromark-util-decode-numeric-character-reference: 2.0.2 - micromark-util-decode-string: 2.0.1 - micromark-util-normalize-identifier: 2.0.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - unist-util-stringify-position: 4.0.0 - transitivePeerDependencies: - - supports-color - - mdast-util-frontmatter@2.0.1: - dependencies: - '@types/mdast': 4.0.4 - devlop: 1.1.0 - escape-string-regexp: 5.0.0 - mdast-util-from-markdown: 2.0.2 - mdast-util-to-markdown: 2.1.2 - micromark-extension-frontmatter: 2.0.0 - transitivePeerDependencies: - - supports-color - - mdast-util-gfm-autolink-literal@2.0.1: - dependencies: - '@types/mdast': 4.0.4 - ccount: 2.0.1 - devlop: 1.1.0 - mdast-util-find-and-replace: 3.0.2 - micromark-util-character: 2.1.1 - - mdast-util-gfm-footnote@2.1.0: - dependencies: - '@types/mdast': 4.0.4 - devlop: 1.1.0 - mdast-util-from-markdown: 2.0.2 - mdast-util-to-markdown: 2.1.2 - micromark-util-normalize-identifier: 2.0.1 - transitivePeerDependencies: - - supports-color - - mdast-util-gfm-strikethrough@2.0.0: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-from-markdown: 2.0.2 - mdast-util-to-markdown: 2.1.2 - transitivePeerDependencies: - - supports-color - - mdast-util-gfm-table@2.0.0: - dependencies: - '@types/mdast': 4.0.4 - devlop: 1.1.0 - markdown-table: 3.0.4 - mdast-util-from-markdown: 2.0.2 - mdast-util-to-markdown: 2.1.2 - transitivePeerDependencies: - - supports-color - - mdast-util-gfm-task-list-item@2.0.0: - dependencies: - '@types/mdast': 4.0.4 - devlop: 1.1.0 - mdast-util-from-markdown: 2.0.2 - mdast-util-to-markdown: 2.1.2 - transitivePeerDependencies: - - supports-color - - mdast-util-gfm@3.1.0: - dependencies: - mdast-util-from-markdown: 2.0.2 - mdast-util-gfm-autolink-literal: 2.0.1 - mdast-util-gfm-footnote: 2.1.0 - mdast-util-gfm-strikethrough: 2.0.0 - mdast-util-gfm-table: 2.0.0 - mdast-util-gfm-task-list-item: 2.0.0 - mdast-util-to-markdown: 2.1.2 - transitivePeerDependencies: - - supports-color - - mdast-util-phrasing@4.1.0: - dependencies: - '@types/mdast': 4.0.4 - unist-util-is: 6.0.0 - - mdast-util-to-markdown@2.1.2: - dependencies: - '@types/mdast': 4.0.4 - '@types/unist': 3.0.3 - longest-streak: 3.1.0 - mdast-util-phrasing: 4.1.0 - mdast-util-to-string: 4.0.0 - micromark-util-classify-character: 2.0.1 - micromark-util-decode-string: 2.0.1 - unist-util-visit: 5.0.0 - zwitch: 2.0.4 - - mdast-util-to-string@4.0.0: - dependencies: - '@types/mdast': 4.0.4 - - mdn-data@2.0.28: {} - - mdn-data@2.0.30: {} - - media-typer@1.1.0: {} - - memfs@4.17.1: - dependencies: - '@jsonjoy.com/json-pack': 1.2.0(tslib@2.8.1) - '@jsonjoy.com/util': 1.6.0(tslib@2.8.1) - tree-dump: 1.0.2(tslib@2.8.1) - tslib: 2.8.1 - - meow@13.2.0: {} - - merge-descriptors@2.0.0: {} - - merge-stream@2.0.0: {} - - merge2@1.4.1: {} - - micromark-core-commonmark@2.0.3: - dependencies: - decode-named-character-reference: 1.1.0 - devlop: 1.1.0 - micromark-factory-destination: 2.0.1 - micromark-factory-label: 2.0.1 - micromark-factory-space: 2.0.1 - micromark-factory-title: 2.0.1 - micromark-factory-whitespace: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-chunked: 2.0.1 - micromark-util-classify-character: 2.0.1 - micromark-util-html-tag-name: 2.0.1 - micromark-util-normalize-identifier: 2.0.1 - micromark-util-resolve-all: 2.0.1 - micromark-util-subtokenize: 2.1.0 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-frontmatter@2.0.0: - dependencies: - fault: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-gfm-autolink-literal@2.1.0: - dependencies: - micromark-util-character: 2.1.1 - micromark-util-sanitize-uri: 2.0.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-gfm-footnote@2.1.0: - dependencies: - devlop: 1.1.0 - micromark-core-commonmark: 2.0.3 - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-normalize-identifier: 2.0.1 - micromark-util-sanitize-uri: 2.0.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-gfm-strikethrough@2.1.0: - dependencies: - devlop: 1.1.0 - micromark-util-chunked: 2.0.1 - micromark-util-classify-character: 2.0.1 - micromark-util-resolve-all: 2.0.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-gfm-table@2.1.1: - dependencies: - devlop: 1.1.0 - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-gfm-tagfilter@2.0.0: - dependencies: - micromark-util-types: 2.0.2 - - micromark-extension-gfm-task-list-item@2.1.0: - dependencies: - devlop: 1.1.0 - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-gfm@3.0.0: - dependencies: - micromark-extension-gfm-autolink-literal: 2.1.0 - micromark-extension-gfm-footnote: 2.1.0 - micromark-extension-gfm-strikethrough: 2.1.0 - micromark-extension-gfm-table: 2.1.1 - micromark-extension-gfm-tagfilter: 2.0.0 - micromark-extension-gfm-task-list-item: 2.1.0 - micromark-util-combine-extensions: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-factory-destination@2.0.1: - dependencies: - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-factory-label@2.0.1: - dependencies: - devlop: 1.1.0 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-factory-space@2.0.1: - dependencies: - micromark-util-character: 2.1.1 - micromark-util-types: 2.0.2 - - micromark-factory-title@2.0.1: - dependencies: - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-factory-whitespace@2.0.1: - dependencies: - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-util-character@2.1.1: - dependencies: - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-util-chunked@2.0.1: - dependencies: - micromark-util-symbol: 2.0.1 - - micromark-util-classify-character@2.0.1: - dependencies: - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-util-combine-extensions@2.0.1: - dependencies: - micromark-util-chunked: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-util-decode-numeric-character-reference@2.0.2: - dependencies: - micromark-util-symbol: 2.0.1 - - micromark-util-decode-string@2.0.1: - dependencies: - decode-named-character-reference: 1.1.0 - micromark-util-character: 2.1.1 - micromark-util-decode-numeric-character-reference: 2.0.2 - micromark-util-symbol: 2.0.1 - - micromark-util-encode@2.0.1: {} - - micromark-util-html-tag-name@2.0.1: {} - - micromark-util-normalize-identifier@2.0.1: - dependencies: - micromark-util-symbol: 2.0.1 - - micromark-util-resolve-all@2.0.1: - dependencies: - micromark-util-types: 2.0.2 - - micromark-util-sanitize-uri@2.0.1: - dependencies: - micromark-util-character: 2.1.1 - micromark-util-encode: 2.0.1 - micromark-util-symbol: 2.0.1 - - micromark-util-subtokenize@2.1.0: - dependencies: - devlop: 1.1.0 - micromark-util-chunked: 2.0.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-util-symbol@2.0.1: {} - - micromark-util-types@2.0.2: {} - - micromark@4.0.2: - dependencies: - '@types/debug': 4.1.12 - debug: 4.4.1 - decode-named-character-reference: 1.1.0 - devlop: 1.1.0 - micromark-core-commonmark: 2.0.3 - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-chunked: 2.0.1 - micromark-util-combine-extensions: 2.0.1 - micromark-util-decode-numeric-character-reference: 2.0.2 - micromark-util-encode: 2.0.1 - micromark-util-normalize-identifier: 2.0.1 - micromark-util-resolve-all: 2.0.1 - micromark-util-sanitize-uri: 2.0.1 - micromark-util-subtokenize: 2.1.0 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - transitivePeerDependencies: - - supports-color - - micromatch@4.0.8: - dependencies: - braces: 3.0.3 - picomatch: 2.3.1 - - mime-db@1.52.0: {} - - mime-db@1.54.0: {} - - mime-types@2.1.35: - dependencies: - mime-db: 1.52.0 - - mime-types@3.0.1: - dependencies: - mime-db: 1.54.0 - - mimic-fn@2.1.0: {} - - mimic-fn@4.0.0: {} - - mimic-function@5.0.1: {} - - min-indent@1.0.1: {} - - minimatch@10.0.3: - dependencies: - '@isaacs/brace-expansion': 5.0.0 - - minimatch@3.1.2: - dependencies: - brace-expansion: 1.1.11 - - minimatch@9.0.5: - dependencies: - brace-expansion: 2.0.1 - - minimist@1.2.8: {} - - minipass@7.1.2: {} - - mkdist@2.3.0(typescript@5.8.3): - dependencies: - autoprefixer: 10.4.21(postcss@8.5.3) - citty: 0.1.6 - cssnano: 7.0.7(postcss@8.5.3) - defu: 6.1.4 - esbuild: 0.25.4 - jiti: 1.21.7 - mlly: 1.7.4 - pathe: 2.0.3 - pkg-types: 2.1.0 - postcss: 8.5.3 - postcss-nested: 7.0.2(postcss@8.5.3) - semver: 7.7.2 - tinyglobby: 0.2.13 - optionalDependencies: - typescript: 5.8.3 - - mlly@1.7.4: - dependencies: - acorn: 8.14.1 - pathe: 2.0.3 - pkg-types: 1.3.1 - ufo: 1.6.1 - - mrmime@2.0.1: {} - - ms@2.1.3: {} - - msw@2.8.2(@types/node@22.15.18)(typescript@5.8.3): - dependencies: - '@bundled-es-modules/cookie': 2.0.1 - '@bundled-es-modules/statuses': 1.0.1 - '@bundled-es-modules/tough-cookie': 0.1.6 - '@inquirer/confirm': 5.1.10(@types/node@22.15.18) - '@mswjs/interceptors': 0.37.6 - '@open-draft/deferred-promise': 2.2.0 - '@open-draft/until': 2.1.0 - '@types/cookie': 0.6.0 - '@types/statuses': 2.0.5 - graphql: 16.11.0 - headers-polyfill: 4.0.3 - is-node-process: 1.2.0 - outvariant: 1.4.3 - path-to-regexp: 6.3.0 - picocolors: 1.1.1 - strict-event-emitter: 0.5.1 - type-fest: 4.41.0 - yargs: 17.7.2 - optionalDependencies: - typescript: 5.8.3 - transitivePeerDependencies: - - '@types/node' - - mute-stream@2.0.0: {} - - nanoid@3.3.11: {} - - napi-postinstall@0.2.4: {} - - natural-compare-lite@1.4.0: {} - - natural-compare@1.4.0: {} - - negotiator@1.0.0: {} - - neo-async@2.6.2: {} - - netmask@2.0.2: {} - - new-github-release-url@2.0.0: - dependencies: - type-fest: 2.19.0 - - node-releases@2.0.19: {} - - normalize-package-data@2.5.0: - dependencies: - hosted-git-info: 2.8.9 - resolve: 1.22.10 - semver: 5.7.2 - validate-npm-package-license: 3.0.4 - - normalize-package-data@6.0.2: - dependencies: - hosted-git-info: 7.0.2 - semver: 7.7.2 - validate-npm-package-license: 3.0.4 - - normalize-range@0.1.2: {} - - npm-run-path@5.3.0: - dependencies: - path-key: 4.0.0 - - npm-run-path@6.0.0: - dependencies: - path-key: 4.0.0 - unicorn-magic: 0.3.0 - - nth-check@2.1.1: - dependencies: - boolbase: 1.0.0 - - object-assign@4.1.1: {} - - object-inspect@1.13.4: {} - - ohash@2.0.11: {} - - on-finished@2.4.1: - dependencies: - ee-first: 1.1.1 - - once@1.4.0: - dependencies: - wrappy: 1.0.2 - - onetime@5.1.2: - dependencies: - mimic-fn: 2.1.0 - - onetime@6.0.0: - dependencies: - mimic-fn: 4.0.0 - - onetime@7.0.0: - dependencies: - mimic-function: 5.0.1 - - open@10.1.0: - dependencies: - default-browser: 5.2.1 - define-lazy-prop: 3.0.0 - is-inside-container: 1.0.0 - is-wsl: 3.1.0 - - optionator@0.9.4: - dependencies: - deep-is: 0.1.4 - fast-levenshtein: 2.0.6 - levn: 0.4.1 - prelude-ls: 1.2.1 - type-check: 0.4.0 - word-wrap: 1.2.5 - - ora@8.1.1: - dependencies: - chalk: 5.4.1 - cli-cursor: 5.0.0 - cli-spinners: 2.9.2 - is-interactive: 2.0.0 - is-unicode-supported: 2.1.0 - log-symbols: 6.0.0 - stdin-discarder: 0.2.2 - string-width: 7.2.0 - strip-ansi: 7.1.0 - - os-name@6.0.0: - dependencies: - macos-release: 3.3.0 - windows-release: 6.0.1 - - os-tmpdir@1.0.2: {} - - outvariant@1.4.3: {} - - p-limit@2.3.0: - dependencies: - p-try: 2.2.0 - - p-limit@3.1.0: - dependencies: - yocto-queue: 0.1.0 - - p-locate@4.1.0: - dependencies: - p-limit: 2.3.0 - - p-locate@5.0.0: - dependencies: - p-limit: 3.1.0 - - p-try@2.2.0: {} - - pac-proxy-agent@7.2.0: - dependencies: - '@tootallnate/quickjs-emscripten': 0.23.0 - agent-base: 7.1.3 - debug: 4.4.1 - get-uri: 6.0.4 - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6 - pac-resolver: 7.0.1 - socks-proxy-agent: 8.0.5 - transitivePeerDependencies: - - supports-color - - pac-resolver@7.0.1: - dependencies: - degenerator: 5.0.1 - netmask: 2.0.2 - - package-json-from-dist@1.0.1: {} - - package-json@10.0.1: - dependencies: - ky: 1.8.1 - registry-auth-token: 5.1.0 - registry-url: 6.0.1 - semver: 7.6.3 - - package-manager-detector@0.2.11: - dependencies: - quansync: 0.2.10 - - parent-module@1.0.1: - dependencies: - callsites: 3.1.0 - - parse-gitignore@2.0.0: {} - - parse-imports-exports@0.2.4: - dependencies: - parse-statements: 1.0.11 - - parse-json@5.2.0: - dependencies: - '@babel/code-frame': 7.27.1 - error-ex: 1.3.2 - json-parse-even-better-errors: 2.3.1 - lines-and-columns: 1.2.4 - - parse-json@8.3.0: - dependencies: - '@babel/code-frame': 7.27.1 - index-to-position: 1.1.0 - type-fest: 4.41.0 - - parse-ms@4.0.0: {} - - parse-path@7.1.0: - dependencies: - protocols: 2.0.2 - - parse-statements@1.0.11: {} - - parse-url@9.2.0: - dependencies: - '@types/parse-path': 7.1.0 - parse-path: 7.1.0 - - parseurl@1.3.3: {} - - path-exists@4.0.0: {} - - path-is-absolute@1.0.1: {} - - path-key@3.1.1: {} - - path-key@4.0.0: {} - - path-parse@1.0.7: {} - - path-scurry@1.11.1: - dependencies: - lru-cache: 10.4.3 - minipass: 7.1.2 - - path-to-regexp@6.3.0: {} - - path-to-regexp@8.2.0: {} - - path-type@5.0.0: {} - - pathe@1.1.2: {} - - pathe@2.0.3: {} - - pathval@2.0.0: {} - - picocolors@1.1.1: {} - - picomatch@2.3.1: {} - - picomatch@4.0.2: {} - - pkce-challenge@5.0.0: {} - - pkg-types@1.3.1: - dependencies: - confbox: 0.1.8 - mlly: 1.7.4 - pathe: 2.0.3 - - pkg-types@2.1.0: - dependencies: - confbox: 0.2.2 - exsolve: 1.0.5 - pathe: 2.0.3 - - pluralize@8.0.0: {} - - postcss-calc@10.1.1(postcss@8.5.3): - dependencies: - postcss: 8.5.3 - postcss-selector-parser: 7.1.0 - postcss-value-parser: 4.2.0 - - postcss-colormin@7.0.3(postcss@8.5.3): - dependencies: - browserslist: 4.24.5 - caniuse-api: 3.0.0 - colord: 2.9.3 - postcss: 8.5.3 - postcss-value-parser: 4.2.0 - - postcss-convert-values@7.0.5(postcss@8.5.3): - dependencies: - browserslist: 4.24.5 - postcss: 8.5.3 - postcss-value-parser: 4.2.0 - - postcss-discard-comments@7.0.4(postcss@8.5.3): - dependencies: - postcss: 8.5.3 - postcss-selector-parser: 7.1.0 - - postcss-discard-duplicates@7.0.2(postcss@8.5.3): - dependencies: - postcss: 8.5.3 - - postcss-discard-empty@7.0.1(postcss@8.5.3): - dependencies: - postcss: 8.5.3 - - postcss-discard-overridden@7.0.1(postcss@8.5.3): - dependencies: - postcss: 8.5.3 - - postcss-merge-longhand@7.0.5(postcss@8.5.3): - dependencies: - postcss: 8.5.3 - postcss-value-parser: 4.2.0 - stylehacks: 7.0.5(postcss@8.5.3) - - postcss-merge-rules@7.0.5(postcss@8.5.3): - dependencies: - browserslist: 4.24.5 - caniuse-api: 3.0.0 - cssnano-utils: 5.0.1(postcss@8.5.3) - postcss: 8.5.3 - postcss-selector-parser: 7.1.0 - - postcss-minify-font-values@7.0.1(postcss@8.5.3): - dependencies: - postcss: 8.5.3 - postcss-value-parser: 4.2.0 - - postcss-minify-gradients@7.0.1(postcss@8.5.3): - dependencies: - colord: 2.9.3 - cssnano-utils: 5.0.1(postcss@8.5.3) - postcss: 8.5.3 - postcss-value-parser: 4.2.0 - - postcss-minify-params@7.0.3(postcss@8.5.3): - dependencies: - browserslist: 4.24.5 - cssnano-utils: 5.0.1(postcss@8.5.3) - postcss: 8.5.3 - postcss-value-parser: 4.2.0 - - postcss-minify-selectors@7.0.5(postcss@8.5.3): - dependencies: - cssesc: 3.0.0 - postcss: 8.5.3 - postcss-selector-parser: 7.1.0 - - postcss-nested@7.0.2(postcss@8.5.3): - dependencies: - postcss: 8.5.3 - postcss-selector-parser: 7.1.0 - - postcss-normalize-charset@7.0.1(postcss@8.5.3): - dependencies: - postcss: 8.5.3 - - postcss-normalize-display-values@7.0.1(postcss@8.5.3): - dependencies: - postcss: 8.5.3 - postcss-value-parser: 4.2.0 - - postcss-normalize-positions@7.0.1(postcss@8.5.3): - dependencies: - postcss: 8.5.3 - postcss-value-parser: 4.2.0 - - postcss-normalize-repeat-style@7.0.1(postcss@8.5.3): - dependencies: - postcss: 8.5.3 - postcss-value-parser: 4.2.0 - - postcss-normalize-string@7.0.1(postcss@8.5.3): - dependencies: - postcss: 8.5.3 - postcss-value-parser: 4.2.0 - - postcss-normalize-timing-functions@7.0.1(postcss@8.5.3): - dependencies: - postcss: 8.5.3 - postcss-value-parser: 4.2.0 - - postcss-normalize-unicode@7.0.3(postcss@8.5.3): - dependencies: - browserslist: 4.24.5 - postcss: 8.5.3 - postcss-value-parser: 4.2.0 - - postcss-normalize-url@7.0.1(postcss@8.5.3): - dependencies: - postcss: 8.5.3 - postcss-value-parser: 4.2.0 - - postcss-normalize-whitespace@7.0.1(postcss@8.5.3): - dependencies: - postcss: 8.5.3 - postcss-value-parser: 4.2.0 - - postcss-ordered-values@7.0.2(postcss@8.5.3): - dependencies: - cssnano-utils: 5.0.1(postcss@8.5.3) - postcss: 8.5.3 - postcss-value-parser: 4.2.0 - - postcss-reduce-initial@7.0.3(postcss@8.5.3): - dependencies: - browserslist: 4.24.5 - caniuse-api: 3.0.0 - postcss: 8.5.3 - - postcss-reduce-transforms@7.0.1(postcss@8.5.3): - dependencies: - postcss: 8.5.3 - postcss-value-parser: 4.2.0 - - postcss-selector-parser@6.1.2: - dependencies: - cssesc: 3.0.0 - util-deprecate: 1.0.2 - - postcss-selector-parser@7.1.0: - dependencies: - cssesc: 3.0.0 - util-deprecate: 1.0.2 - - postcss-svgo@7.0.2(postcss@8.5.3): - dependencies: - postcss: 8.5.3 - postcss-value-parser: 4.2.0 - svgo: 3.3.2 - - postcss-unique-selectors@7.0.4(postcss@8.5.3): - dependencies: - postcss: 8.5.3 - postcss-selector-parser: 7.1.0 - - postcss-value-parser@4.2.0: {} - - postcss@8.5.3: - dependencies: - nanoid: 3.3.11 - picocolors: 1.1.1 - source-map-js: 1.2.1 - - prelude-ls@1.2.1: {} - - prettier-linter-helpers@1.0.0: - dependencies: - fast-diff: 1.3.0 - - prettier@3.5.3: {} - - pretty-bytes@6.1.1: {} - - pretty-ms@9.2.0: - dependencies: - parse-ms: 4.0.0 - - proto-list@1.2.4: {} - - protocols@2.0.2: {} - - proxy-addr@2.0.7: - dependencies: - forwarded: 0.2.0 - ipaddr.js: 1.9.1 - - proxy-agent@6.5.0: - dependencies: - agent-base: 7.1.3 - debug: 4.4.1 - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6 - lru-cache: 7.18.3 - pac-proxy-agent: 7.2.0 - proxy-from-env: 1.1.0 - socks-proxy-agent: 8.0.5 - transitivePeerDependencies: - - supports-color - - proxy-from-env@1.1.0: {} - - psl@1.15.0: - dependencies: - punycode: 2.3.1 - - punycode@2.3.1: {} - - pupa@3.1.0: - dependencies: - escape-goat: 4.0.0 - - qs@6.14.0: - dependencies: - side-channel: 1.1.0 - - quansync@0.2.10: {} - - querystringify@2.2.0: {} - - queue-microtask@1.2.3: {} - - range-parser@1.2.1: {} - - raw-body@3.0.0: - dependencies: - bytes: 3.1.2 - http-errors: 2.0.0 - iconv-lite: 0.6.3 - unpipe: 1.0.0 - - rc@1.2.8: - dependencies: - deep-extend: 0.6.0 - ini: 1.3.8 - minimist: 1.2.8 - strip-json-comments: 2.0.1 - - read-package-up@11.0.0: - dependencies: - find-up-simple: 1.0.1 - read-pkg: 9.0.1 - type-fest: 4.41.0 - - read-pkg-up@7.0.1: - dependencies: - find-up: 4.1.0 - read-pkg: 5.2.0 - type-fest: 0.8.1 - - read-pkg@5.2.0: - dependencies: - '@types/normalize-package-data': 2.4.4 - normalize-package-data: 2.5.0 - parse-json: 5.2.0 - type-fest: 0.6.0 - - read-pkg@9.0.1: - dependencies: - '@types/normalize-package-data': 2.4.4 - normalize-package-data: 6.0.2 - parse-json: 8.3.0 - type-fest: 4.41.0 - unicorn-magic: 0.1.0 - - readable-stream@3.6.2: - dependencies: - inherits: 2.0.4 - string_decoder: 1.3.0 - util-deprecate: 1.0.2 - - rechoir@0.6.2: - dependencies: - resolve: 1.22.10 - - refa@0.12.1: - dependencies: - '@eslint-community/regexpp': 4.12.1 - - regexp-ast-analysis@0.7.1: - dependencies: - '@eslint-community/regexpp': 4.12.1 - refa: 0.12.1 - - regexp-tree@0.1.27: {} - - registry-auth-token@5.1.0: - dependencies: - '@pnpm/npm-conf': 2.3.1 - - registry-url@6.0.1: - dependencies: - rc: 1.2.8 - - regjsparser@0.10.0: - dependencies: - jsesc: 0.5.0 - - release-it@18.1.2(@types/node@22.15.18)(typescript@5.8.3): - dependencies: - '@iarna/toml': 2.2.5 - '@octokit/rest': 21.0.2 - async-retry: 1.3.3 - chalk: 5.4.1 - ci-info: 4.2.0 - cosmiconfig: 9.0.0(typescript@5.8.3) - execa: 9.5.2 - git-url-parse: 16.0.0 - globby: 14.0.2 - inquirer: 12.3.0(@types/node@22.15.18) - issue-parser: 7.0.1 - lodash: 4.17.21 - mime-types: 2.1.35 - new-github-release-url: 2.0.0 - open: 10.1.0 - ora: 8.1.1 - os-name: 6.0.0 - proxy-agent: 6.5.0 - semver: 7.6.3 - shelljs: 0.8.5 - undici: 6.21.1 - update-notifier: 7.3.1 - url-join: 5.0.0 - wildcard-match: 5.1.4 - yargs-parser: 21.1.1 - transitivePeerDependencies: - - '@types/node' - - supports-color - - typescript - - require-directory@2.1.1: {} - - requires-port@1.0.0: {} - - resolve-from@4.0.0: {} - - resolve-pkg-maps@1.0.0: {} - - resolve@1.22.10: - dependencies: - is-core-module: 2.16.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - - restore-cursor@4.0.0: - dependencies: - onetime: 5.1.2 - signal-exit: 3.0.7 - - restore-cursor@5.1.0: - dependencies: - onetime: 7.0.0 - signal-exit: 4.1.0 - - retry@0.13.1: {} - - reusify@1.1.0: {} - - rollup-plugin-dts@6.2.1(rollup@4.40.2)(typescript@5.8.3): - dependencies: - magic-string: 0.30.17 - rollup: 4.40.2 - typescript: 5.8.3 - optionalDependencies: - '@babel/code-frame': 7.27.1 - - rollup@4.40.2: - dependencies: - '@types/estree': 1.0.7 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.40.2 - '@rollup/rollup-android-arm64': 4.40.2 - '@rollup/rollup-darwin-arm64': 4.40.2 - '@rollup/rollup-darwin-x64': 4.40.2 - '@rollup/rollup-freebsd-arm64': 4.40.2 - '@rollup/rollup-freebsd-x64': 4.40.2 - '@rollup/rollup-linux-arm-gnueabihf': 4.40.2 - '@rollup/rollup-linux-arm-musleabihf': 4.40.2 - '@rollup/rollup-linux-arm64-gnu': 4.40.2 - '@rollup/rollup-linux-arm64-musl': 4.40.2 - '@rollup/rollup-linux-loongarch64-gnu': 4.40.2 - '@rollup/rollup-linux-powerpc64le-gnu': 4.40.2 - '@rollup/rollup-linux-riscv64-gnu': 4.40.2 - '@rollup/rollup-linux-riscv64-musl': 4.40.2 - '@rollup/rollup-linux-s390x-gnu': 4.40.2 - '@rollup/rollup-linux-x64-gnu': 4.40.2 - '@rollup/rollup-linux-x64-musl': 4.40.2 - '@rollup/rollup-win32-arm64-msvc': 4.40.2 - '@rollup/rollup-win32-ia32-msvc': 4.40.2 - '@rollup/rollup-win32-x64-msvc': 4.40.2 - fsevents: 2.3.3 - - router@2.2.0: - dependencies: - debug: 4.4.1 - depd: 2.0.0 - is-promise: 4.0.0 - parseurl: 1.3.3 - path-to-regexp: 8.2.0 - transitivePeerDependencies: - - supports-color - - run-applescript@7.0.0: {} - - run-async@3.0.0: {} - - run-parallel@1.2.0: - dependencies: - queue-microtask: 1.2.3 - - rxjs@7.8.2: - dependencies: - tslib: 2.8.1 - - safe-buffer@5.2.1: {} - - safer-buffer@2.1.2: {} - - scslre@0.3.0: - dependencies: - '@eslint-community/regexpp': 4.12.1 - refa: 0.12.1 - regexp-ast-analysis: 0.7.1 - - scule@1.3.0: {} - - semver@5.7.2: {} - - semver@7.6.3: {} - - semver@7.7.2: {} - - send@1.2.0: - dependencies: - debug: 4.4.1 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - fresh: 2.0.0 - http-errors: 2.0.0 - mime-types: 3.0.1 - ms: 2.1.3 - on-finished: 2.4.1 - range-parser: 1.2.1 - statuses: 2.0.1 - transitivePeerDependencies: - - supports-color - - serve-static@2.2.0: - dependencies: - encodeurl: 2.0.0 - escape-html: 1.0.3 - parseurl: 1.3.3 - send: 1.2.0 - transitivePeerDependencies: - - supports-color - - setprototypeof@1.2.0: {} - - shebang-command@2.0.0: - dependencies: - shebang-regex: 3.0.0 - - shebang-regex@3.0.0: {} - - shelljs@0.8.5: - dependencies: - glob: 7.2.3 - interpret: 1.4.0 - rechoir: 0.6.2 - - side-channel-list@1.0.0: - dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - - side-channel-map@1.0.1: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 - - side-channel-weakmap@1.0.2: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 - side-channel-map: 1.0.1 - - side-channel@1.1.0: - dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - side-channel-list: 1.0.0 - side-channel-map: 1.0.1 - side-channel-weakmap: 1.0.2 - - siginfo@2.0.0: {} - - signal-exit@3.0.7: {} - - signal-exit@4.1.0: {} - - sirv@3.0.1: - dependencies: - '@polka/url': 1.0.0-next.29 - mrmime: 2.0.1 - totalist: 3.0.1 - - sisteransi@1.0.5: {} - - slash@5.1.0: {} - - smart-buffer@4.2.0: {} - - socks-proxy-agent@8.0.5: - dependencies: - agent-base: 7.1.3 - debug: 4.4.1 - socks: 2.8.4 - transitivePeerDependencies: - - supports-color - - socks@2.8.4: - dependencies: - ip-address: 9.0.5 - smart-buffer: 4.2.0 - - source-map-js@1.2.1: {} - - source-map@0.6.1: {} - - spdx-correct@3.2.0: - dependencies: - spdx-expression-parse: 3.0.1 - spdx-license-ids: 3.0.21 - - spdx-exceptions@2.5.0: {} - - spdx-expression-parse@3.0.1: - dependencies: - spdx-exceptions: 2.5.0 - spdx-license-ids: 3.0.21 - - spdx-expression-parse@4.0.0: - dependencies: - spdx-exceptions: 2.5.0 - spdx-license-ids: 3.0.21 - - spdx-license-ids@3.0.21: {} - - sprintf-js@1.1.3: {} - - stable-hash@0.0.5: {} - - stackback@0.0.2: {} - - statuses@2.0.1: {} - - std-env@3.9.0: {} - - stdin-discarder@0.2.2: {} - - storyblok-js-client@6.10.12: {} - - storyblok-js-client@7.0.0: {} - - strict-event-emitter@0.5.1: {} - - string-width@4.2.3: - dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 - - string-width@5.1.2: - dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 9.2.2 - strip-ansi: 7.1.0 - - string-width@7.2.0: - dependencies: - emoji-regex: 10.4.0 - get-east-asian-width: 1.3.0 - strip-ansi: 7.1.0 - - string_decoder@1.3.0: - dependencies: - safe-buffer: 5.2.1 - - strip-ansi@6.0.1: - dependencies: - ansi-regex: 5.0.1 - - strip-ansi@7.1.0: - dependencies: - ansi-regex: 6.1.0 - - strip-final-newline@3.0.0: {} - - strip-final-newline@4.0.0: {} - - strip-indent@3.0.0: - dependencies: - min-indent: 1.0.1 - - strip-json-comments@2.0.1: {} - - strip-json-comments@3.1.1: {} - - stubborn-fs@1.2.5: {} - - stylehacks@7.0.5(postcss@8.5.3): - dependencies: - browserslist: 4.24.5 - postcss: 8.5.3 - postcss-selector-parser: 7.1.0 - - supports-color@7.2.0: - dependencies: - has-flag: 4.0.0 - - supports-preserve-symlinks-flag@1.0.0: {} - - svgo@3.3.2: - dependencies: - '@trysound/sax': 0.2.0 - commander: 7.2.0 - css-select: 5.1.0 - css-tree: 2.3.1 - css-what: 6.1.0 - csso: 5.0.5 - picocolors: 1.1.1 - - synckit@0.10.3: - dependencies: - '@pkgr/core': 0.2.4 - tslib: 2.8.1 - - synckit@0.9.2: - dependencies: - '@pkgr/core': 0.1.2 - tslib: 2.8.1 - - tapable@2.2.1: {} - - test-exclude@7.0.1: - dependencies: - '@istanbuljs/schema': 0.1.3 - glob: 10.4.5 - minimatch: 9.0.5 - - thingies@1.21.0(tslib@2.8.1): - dependencies: - tslib: 2.8.1 - - tinybench@2.9.0: {} - - tinyexec@0.3.2: {} - - tinyglobby@0.2.13: - dependencies: - fdir: 6.4.4(picomatch@4.0.2) - picomatch: 4.0.2 - - tinypool@1.0.2: {} - - tinyrainbow@2.0.0: {} - - tinyspy@3.0.2: {} - - tmp@0.0.33: - dependencies: - os-tmpdir: 1.0.2 - - to-regex-range@5.0.1: - dependencies: - is-number: 7.0.0 - - toidentifier@1.0.1: {} - - toml-eslint-parser@0.10.0: - dependencies: - eslint-visitor-keys: 3.4.3 - - totalist@3.0.1: {} - - tough-cookie@4.1.4: - dependencies: - psl: 1.15.0 - punycode: 2.3.1 - universalify: 0.2.0 - url-parse: 1.5.10 - - tree-dump@1.0.2(tslib@2.8.1): - dependencies: - tslib: 2.8.1 - - ts-api-utils@2.1.0(typescript@5.8.3): - dependencies: - typescript: 5.8.3 - - tslib@2.8.1: {} - - type-check@0.4.0: - dependencies: - prelude-ls: 1.2.1 - - type-fest@0.20.2: {} - - type-fest@0.21.3: {} - - type-fest@0.6.0: {} - - type-fest@0.8.1: {} - - type-fest@2.19.0: {} - - type-fest@4.41.0: {} - - type-is@2.0.1: - dependencies: - content-type: 1.0.5 - media-typer: 1.1.0 - mime-types: 3.0.1 - - typedarray@0.0.6: {} - - typescript@5.8.3: {} - - ufo@1.6.1: {} - - uglify-js@3.19.3: - optional: true - - unbuild@3.5.0(typescript@5.8.3): - dependencies: - '@rollup/plugin-alias': 5.1.1(rollup@4.40.2) - '@rollup/plugin-commonjs': 28.0.3(rollup@4.40.2) - '@rollup/plugin-json': 6.1.0(rollup@4.40.2) - '@rollup/plugin-node-resolve': 16.0.1(rollup@4.40.2) - '@rollup/plugin-replace': 6.0.2(rollup@4.40.2) - '@rollup/pluginutils': 5.1.4(rollup@4.40.2) - citty: 0.1.6 - consola: 3.4.2 - defu: 6.1.4 - esbuild: 0.25.4 - fix-dts-default-cjs-exports: 1.0.1 - hookable: 5.5.3 - jiti: 2.4.2 - magic-string: 0.30.17 - mkdist: 2.3.0(typescript@5.8.3) - mlly: 1.7.4 - pathe: 2.0.3 - pkg-types: 2.1.0 - pretty-bytes: 6.1.1 - rollup: 4.40.2 - rollup-plugin-dts: 6.2.1(rollup@4.40.2)(typescript@5.8.3) - scule: 1.3.0 - tinyglobby: 0.2.13 - untyped: 2.0.0 - optionalDependencies: - typescript: 5.8.3 - transitivePeerDependencies: - - sass - - vue - - vue-sfc-transformer - - vue-tsc - - undici-types@6.21.0: {} - - undici@6.21.1: {} - - unicorn-magic@0.1.0: {} - - unicorn-magic@0.3.0: {} - - unist-util-is@6.0.0: - dependencies: - '@types/unist': 3.0.3 - - unist-util-stringify-position@4.0.0: - dependencies: - '@types/unist': 3.0.3 - - unist-util-visit-parents@6.0.1: - dependencies: - '@types/unist': 3.0.3 - unist-util-is: 6.0.0 - - unist-util-visit@5.0.0: - dependencies: - '@types/unist': 3.0.3 - unist-util-is: 6.0.0 - unist-util-visit-parents: 6.0.1 - - universal-user-agent@7.0.3: {} - - universalify@0.2.0: {} - - unpipe@1.0.0: {} - - unrs-resolver@1.7.2: - dependencies: - napi-postinstall: 0.2.4 - optionalDependencies: - '@unrs/resolver-binding-darwin-arm64': 1.7.2 - '@unrs/resolver-binding-darwin-x64': 1.7.2 - '@unrs/resolver-binding-freebsd-x64': 1.7.2 - '@unrs/resolver-binding-linux-arm-gnueabihf': 1.7.2 - '@unrs/resolver-binding-linux-arm-musleabihf': 1.7.2 - '@unrs/resolver-binding-linux-arm64-gnu': 1.7.2 - '@unrs/resolver-binding-linux-arm64-musl': 1.7.2 - '@unrs/resolver-binding-linux-ppc64-gnu': 1.7.2 - '@unrs/resolver-binding-linux-riscv64-gnu': 1.7.2 - '@unrs/resolver-binding-linux-riscv64-musl': 1.7.2 - '@unrs/resolver-binding-linux-s390x-gnu': 1.7.2 - '@unrs/resolver-binding-linux-x64-gnu': 1.7.2 - '@unrs/resolver-binding-linux-x64-musl': 1.7.2 - '@unrs/resolver-binding-wasm32-wasi': 1.7.2 - '@unrs/resolver-binding-win32-arm64-msvc': 1.7.2 - '@unrs/resolver-binding-win32-ia32-msvc': 1.7.2 - '@unrs/resolver-binding-win32-x64-msvc': 1.7.2 - - untyped@2.0.0: - dependencies: - citty: 0.1.6 - defu: 6.1.4 - jiti: 2.4.2 - knitwork: 1.2.0 - scule: 1.3.0 - - update-browserslist-db@1.1.3(browserslist@4.24.5): - dependencies: - browserslist: 4.24.5 - escalade: 3.2.0 - picocolors: 1.1.1 - - update-notifier@7.3.1: - dependencies: - boxen: 8.0.1 - chalk: 5.4.1 - configstore: 7.0.0 - is-in-ci: 1.0.0 - is-installed-globally: 1.0.0 - is-npm: 6.0.0 - latest-version: 9.0.0 - pupa: 3.1.0 - semver: 7.6.3 - xdg-basedir: 5.1.0 - - uri-js@4.4.1: - dependencies: - punycode: 2.3.1 - - url-join@5.0.0: {} - - url-parse@1.5.10: - dependencies: - querystringify: 2.2.0 - requires-port: 1.0.0 - - util-deprecate@1.0.2: {} - - uuid@11.1.0: {} - - validate-npm-package-license@3.0.4: - dependencies: - spdx-correct: 3.2.0 - spdx-expression-parse: 3.0.1 - - vary@1.1.2: {} - - vite-node@3.1.3(@types/node@22.15.18)(jiti@2.4.2)(yaml@2.7.1): - dependencies: - cac: 6.7.14 - debug: 4.4.1 - es-module-lexer: 1.7.0 - pathe: 2.0.3 - vite: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(yaml@2.7.1) - transitivePeerDependencies: - - '@types/node' - - jiti - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - - vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(yaml@2.7.1): - dependencies: - esbuild: 0.25.4 - fdir: 6.4.4(picomatch@4.0.2) - picomatch: 4.0.2 - postcss: 8.5.3 - rollup: 4.40.2 - tinyglobby: 0.2.13 - optionalDependencies: - '@types/node': 22.15.18 - fsevents: 2.3.3 - jiti: 2.4.2 - yaml: 2.7.1 - - vitest@3.1.3(@types/debug@4.1.12)(@types/node@22.15.18)(@vitest/ui@3.1.3)(jiti@2.4.2)(msw@2.8.2(@types/node@22.15.18)(typescript@5.8.3))(yaml@2.7.1): - dependencies: - '@vitest/expect': 3.1.3 - '@vitest/mocker': 3.1.3(msw@2.8.2(@types/node@22.15.18)(typescript@5.8.3))(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(yaml@2.7.1)) - '@vitest/pretty-format': 3.1.3 - '@vitest/runner': 3.1.3 - '@vitest/snapshot': 3.1.3 - '@vitest/spy': 3.1.3 - '@vitest/utils': 3.1.3 - chai: 5.2.0 - debug: 4.4.1 - expect-type: 1.2.1 - magic-string: 0.30.17 - pathe: 2.0.3 - std-env: 3.9.0 - tinybench: 2.9.0 - tinyexec: 0.3.2 - tinyglobby: 0.2.13 - tinypool: 1.0.2 - tinyrainbow: 2.0.0 - vite: 6.3.5(@types/node@22.15.18)(jiti@2.4.2)(yaml@2.7.1) - vite-node: 3.1.3(@types/node@22.15.18)(jiti@2.4.2)(yaml@2.7.1) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/debug': 4.1.12 - '@types/node': 22.15.18 - '@vitest/ui': 3.1.3(vitest@3.1.3) - transitivePeerDependencies: - - jiti - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - - vue-eslint-parser@9.4.3(eslint@9.26.0(jiti@2.4.2)): - dependencies: - debug: 4.4.1 - eslint: 9.26.0(jiti@2.4.2) - eslint-scope: 7.2.2 - eslint-visitor-keys: 3.4.3 - espree: 9.6.1 - esquery: 1.6.0 - lodash: 4.17.21 - semver: 7.7.2 - transitivePeerDependencies: - - supports-color - - when-exit@2.1.4: {} - - which@2.0.2: - dependencies: - isexe: 2.0.0 - - why-is-node-running@2.3.0: - dependencies: - siginfo: 2.0.0 - stackback: 0.0.2 - - widest-line@5.0.0: - dependencies: - string-width: 7.2.0 - - wildcard-match@5.1.4: {} - - windows-release@6.0.1: - dependencies: - execa: 8.0.1 - - word-wrap@1.2.5: {} - - wordwrap@1.0.0: {} - - wrap-ansi@6.2.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - - wrap-ansi@7.0.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - - wrap-ansi@8.1.0: - dependencies: - ansi-styles: 6.2.1 - string-width: 5.1.2 - strip-ansi: 7.1.0 - - wrap-ansi@9.0.0: - dependencies: - ansi-styles: 6.2.1 - string-width: 7.2.0 - strip-ansi: 7.1.0 - - wrappy@1.0.2: {} - - xdg-basedir@5.1.0: {} - - xml-name-validator@4.0.0: {} - - y18n@5.0.8: {} - - yaml-eslint-parser@1.3.0: - dependencies: - eslint-visitor-keys: 3.4.3 - yaml: 2.7.1 - - yaml@2.7.1: {} - - yargs-parser@21.1.1: {} - - yargs@17.7.2: - dependencies: - cliui: 8.0.1 - escalade: 3.2.0 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - string-width: 4.2.3 - y18n: 5.0.8 - yargs-parser: 21.1.1 - - yocto-queue@0.1.0: {} - - yoctocolors-cjs@2.1.2: {} - - yoctocolors@2.1.1: {} - - zod-to-json-schema@3.24.5(zod@3.24.4): - dependencies: - zod: 3.24.4 - - zod@3.24.4: {} - - zwitch@2.0.4: {} diff --git a/pull_request_template.md b/pull_request_template.md new file mode 100644 index 00000000..fbe0f8a3 --- /dev/null +++ b/pull_request_template.md @@ -0,0 +1,29 @@ + + +## Pull request type + +Jira Link: [INT-](url) + + + +- [ ] Bugfix +- [ ] Feature +- [ ] Code style update (formatting, renaming) +- [ ] Refactoring (no functional changes, no api changes) +- [ ] Other (please describe): + +## How to test this PR + + + +## What is the new behavior? + + + +- +- +- + +## Other information diff --git a/src/README.md b/src/README.md deleted file mode 100644 index fe8100b6..00000000 --- a/src/README.md +++ /dev/null @@ -1,70 +0,0 @@ -![Storyblok ImagoType](https://raw.githubusercontent.com/storyblok/.github/refs/heads/main/profile/public/github-banner.png) - -# Storyblok CLI - -## Installation - -For the latest beta version, install the package using the following command: - -```bash -npm install storyblok@beta -``` - -Or for an specific beta version: - -```bash -npm install storyblok@4.0.0-beta. -``` - -## API - -| Command | Status | Notes | -|---------|--------|-------| -| [`login`](./commands/login/README.md) | βœ… | Improved DX and credentials storage in ~/.storyblok/credentials.json | -| [`logout`](./commands/logout/README.md) | βœ… | | -| [`user`](./commands/user/README.md) | βœ… | | -| [`languages pull`](./commands/languages/README.md) | βœ… | Replaces previous pull-languages | -| [`components pull`](./commands/components/pull/README.md) | βœ… | Replaces previous pull-components | -| [`components push`](./commands/components/push/README.md) | βœ… | Replaces previous push-components. Also handles dependencies such as groups, tags, presets and whitelists. (Datasources is pending) | -| `components delete` | πŸ“ | Will replace delete-component and delete-components | -| [`migrations generate`](./commands/migrations/generate/README.md) | βœ… | Replaces previous generate-migrations | -| [`migrations run`](./commands/migrations/run/README.md) | βœ… | Replaces previous run-migrations | -| [`migrations rollback`](./commands/migrations/rollback/README.md) | βœ… | Replaces previous rollback-migrations | -| [`types generate`](./commands/types/generate/README.md) | βœ… | Replaces previous generate-typescript-typedefs | -| [`signup`](./commands/signup/README.md) | βœ… | Opens the Storyblok signup page in your browser | -| `sync` | ⚠️ | Pending new API endpoint implementation for improved performance and reliability (Check below for more details) | -| `datasources pull` | πŸ“ | | -| `datasources push` | πŸ“ | | -| `datasources delete` | πŸ“ | Will replace delete-datasources | -| `select` | πŸ’¬ | To be discussed | -| `quickstart` | πŸ’¬ | To be discussed | -| `spaces` | πŸ’¬ | To be discussed | -| `import` | πŸ’¬ | To be discussed | - -### Status Legend -- βœ… Ready: Feature is implemented and ready to use -- πŸ“ Planned: Feature is planned for future implementation -- ⚠️ v3: Feature is available only in [v3](https://github.com/storyblok/storyblok-cli/tree/v3) -- πŸ’¬ TBD: Feature is under discussion - -## Sync Command - -> [!IMPORTANT] -> The sync command is being reimagined as a new API endpoint that will perform synchronization between spaces. This new implementation, compared to the current version, will provide: -> - Faster synchronization -> - Improved reliability by running on our backend servers -> - Better handling of large-scale synchronization operations - -If you wish to continue using the `sync` command, please refer to the [v3](https://github.com/storyblok/storyblok-cli/tree/v3?tab=readme-ov-file#sync) documentation and use `storyblok@v3.36.1` or previous. - -## Global Options - -These options are available for all commands: - -| Option | Description | Default | -|--------|-------------|---------| -| `-v, --verbose` | Enable verbose output for debugging | `false` | -| `--ci` | Enable CI mode (coming soon) | `false` | - -> [!TIP] -> When reporting a bug or opening a support ticket, please run the command with the `--verbose` flag and add the output to it. This will help us better understand and resolve the issue. diff --git a/src/api.test.ts b/src/api.test.ts deleted file mode 100644 index 062c074d..00000000 --- a/src/api.test.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { mapiClient } from './api'; - -// TODO: Test the api client -describe.todo('api', () => { - describe('mapiClient', () => { - it('should create a mapi client', () => { - const client = mapiClient(); - expect(client).toBeDefined(); - }); - }); -}); diff --git a/src/api.ts b/src/api.ts deleted file mode 100644 index e9b08d7b..00000000 --- a/src/api.ts +++ /dev/null @@ -1,213 +0,0 @@ -import type { RegionCode } from './constants'; -import { getStoryblokUrl } from './utils/api-routes'; -import { delay, FetchError } from './utils/fetch'; - -export interface ManagementApiClientOptions { - token?: string; - url?: string; - region?: RegionCode; - maxRetries?: number; - baseDelay?: number; - verbose?: boolean; - onRequest?: (request: { path: string; method: string; headers: HeadersInit; body?: any }) => void; - onResponse?: (response: { path: string; method: string; status: number; data: any; attempt: number }) => void; -} - -export interface FetchOptions { - headers?: Record; - method?: string; - body?: any; - maxRetries?: number; - baseDelay?: number; -} - -export interface GetResponse { - data: T; - attempt: number; -} - -export interface MapiClient { - uuid: string; - get: (path: string, fetchOptions?: FetchOptions) => Promise>; - post: (path: string, fetchOptions?: FetchOptions) => Promise>; - put: (path: string, fetchOptions?: FetchOptions) => Promise>; - dispose: () => void; - getRequestCount: () => number; -} - -let instance: MapiClient | null = null; - -const createMapiClient = (options: ManagementApiClientOptions): MapiClient => { - const baseHeaders = { - 'Content-Type': 'application/json', - 'Authorization': options.token, - }; - - const state = { - uuid: `mapi-client-${Math.random().toString(36).substring(2, 15)}`, - baseHeaders, - url: options.url || getStoryblokUrl(options.region), - maxRetries: options.maxRetries ?? 6, - baseDelay: options.baseDelay ?? 500, - freeze: false, - }; - - // Internal: isRateLimitOwner is true for the request that first hits 429 and is responsible for lifting the freeze - const request = async ( - path: string, - fetchOptions?: FetchOptions, - attempt: number = 0, - isRateLimitOwner: boolean = false, - ): Promise> => { - // Only non-owner calls should wait for freeze - if (state.freeze && !isRateLimitOwner) { - if (options?.verbose) { - console.log(`⏳ ${path} - Waiting for rate limit to be resolved`); - } - await new Promise((resolve) => { - const checkFreeze = setInterval(() => { - if (!state.freeze) { - clearInterval(checkFreeze); - resolve(); - } - }, 50); - }); - // Add a random delay (e.g., 100-500ms) to spread out retries - await delay(100 + Math.random() * 400); - return request(path, fetchOptions, attempt); - } - - try { - if (options?.verbose) { - console.log(`${state.url}/${path} - Attempt ${attempt}`); - } - - // Prepare request data for interceptor - const requestData = { - path, - method: fetchOptions?.method || 'GET', - headers: { - ...state.baseHeaders, - ...fetchOptions?.headers, - } as Record, - body: fetchOptions?.body, - }; - - // Call request interceptor if provided - options?.onRequest?.(requestData); - - const res = await fetch(`${state.url}/${path}`, { - headers: requestData.headers as HeadersInit, - ...fetchOptions, - }); - - let data; - try { - data = await res.json(); - } - catch { - throw new FetchError('Non-JSON response', { - status: res.status, - statusText: res.statusText, - data: null, - }); - } - - // Call response interceptor if provided - options?.onResponse?.({ - path, - method: requestData.method, - status: res.status, - data, - attempt, - }); - - if (res.ok) { - if (options?.verbose) { - console.log(`βœ… ${path}`); - } - return { - data, - attempt, - }; - } - else { - throw new FetchError('Request failed', { - status: res.status, - statusText: res.statusText, - data, - }); - } - } - catch (error) { - if (error instanceof FetchError) { - if (error.response.status === 429 && attempt < state.maxRetries) { - if (options?.verbose) { - console.log(`❌ ${path} - Rate limit exceeded`); - } - let isOwner = isRateLimitOwner; - // Set freeze to true if this is the first 429 - if (!state.freeze) { - state.freeze = true; - isOwner = true; - } - const waitTime = state.baseDelay * 2 ** attempt + Math.random() * 100; - await delay(waitTime); - try { - // Pass isOwner down the retry chain - const result = await request(path, fetchOptions, attempt + 1, isOwner); - return result; - } - finally { - // Only the owner can lift the freeze - if (isOwner && state.freeze) { - state.freeze = false; - } - } - } - throw error; - } - // Always lift freeze if all retries are exhausted - if (state.freeze && isRateLimitOwner) { - state.freeze = false; - } - // For network errors or other non-HTTP errors, create a FetchError - throw new FetchError(error instanceof Error ? error.message : String(error), { - status: 0, - statusText: 'Network Error', - data: null, - }); - } - }; - - const get = async (path: string, fetchOptions?: FetchOptions) => { - return request(path, fetchOptions); - }; - - const post = async (path: string, fetchOptions?: FetchOptions) => { - return request(path, { ...fetchOptions, method: 'POST' }); - }; - - const put = async (path: string, fetchOptions?: FetchOptions) => { - return request(path, { ...fetchOptions, method: 'PUT' }); - }; - - instance = { - uuid: state.uuid, - get, - post, - put, - dispose: () => { - instance = null; - }, - } as MapiClient; - - return instance; -}; - -export function mapiClient(options?: ManagementApiClientOptions) { - if (!instance) { - instance = createMapiClient(options ?? {}); - } - return instance; -}; diff --git a/src/cli.ts b/src/cli.ts new file mode 100755 index 00000000..33bbcce4 --- /dev/null +++ b/src/cli.ts @@ -0,0 +1,624 @@ +#!/usr/bin/env node +//@ts-nocheck +import commander from "commander"; +import chalk from "chalk"; +import clear from "clear"; +import figlet from "figlet"; +import inquirer from "inquirer"; +import { ALL_REGIONS, EU_CODE, isRegion } from "@storyblok/region-helper"; +import updateNotifier from "update-notifier"; +import fs from "fs"; +import tasks from "./tasks"; +import { getQuestions, lastStep, api, creds, buildFilterQuery } from "./utils"; +import { SYNC_TYPES, COMMANDS } from "./constants"; +export * from "./types/index"; +import { dirname } from "node:path"; +import { fileURLToPath } from "node:url"; +import path from "path"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const rawPkg = fs.readFileSync(path.join(__dirname, "../package.json")); +const pkg = JSON.parse(rawPkg); +const program = new commander.Command(); +const allRegionsText = ALL_REGIONS.join(", "); + +clear(); +console.log(chalk.cyan(figlet.textSync("storyblok"))); +console.log(); +console.log(); +console.log("Hi, welcome to the Storyblok CLI"); +console.log(); + +// non-intrusive notify users if an update available +const notifyOptions = { + isGlobal: true, +}; + +updateNotifier({ pkg }).notify(notifyOptions); + +program.version(pkg.version); + +program.option("-s, --space [value]", "space ID"); + +// login +program + .command(COMMANDS.LOGIN) + .description("Login to the Storyblok cli") + .option("-t, --token ", "Token to login directly without questions, like for CI environments") + .option( + "-r, --region ", + `The region you would like to work in. Please keep in mind that the region must match the region of your space. This region flag will be used for the other cli's commands. You can use the values: ${allRegionsText}.`, + EU_CODE + ) + .action(async (options) => { + const { token, region } = options; + + if (api.isAuthorized()) { + console.log( + chalk.green("βœ“") + + " The user has been already logged. If you want to change the logged user, you must logout and login again" + ); + return; + } + + if (!isRegion(region)) { + console.log( + chalk.red("X") + + `The provided region ${region} is not valid. Please use one of the following: ${allRegionsText}` + ); + return; + } + + try { + await api.processLogin(token, region); + process.exit(0); + } catch (e) { + console.log(chalk.red("X") + " An error occurred when logging the user: " + e.message); + process.exit(1); + } + }); + +// getUser +program + .command("user") + .description("Get the currently logged in user") + .action(async () => { + if (api.isAuthorized()) { + try { + const user = await api.getUser(); + console.log(chalk.green("βœ“") + ` Hi ${user.friendly_name}, you current logged in with: ${creds.get().email}`); + } catch (e) { + console.log(chalk.red("X") + ` Please check if your current region matches your user's region: ${e.message}.`); + } finally { + process.exit(0); + } + } + console.log(chalk.red("X") + " There is currently no user logged."); + }); + +// logout +program + .command(COMMANDS.LOGOUT) + .description("Logout from the Storyblok cli") + .action(async () => { + try { + await api.logout(); + console.log("Logged out successfully! Token has been removed from .netrc file."); + process.exit(0); + } catch (e) { + console.log(chalk.red("X") + " An error occurred when logging out the user: " + e.message); + process.exit(1); + } + }); + +// pull-languages +program + .command("pull-languages") + .description("Download your space's languages schema as json") + .action(async () => { + console.log(`${chalk.blue("-")} Executing pull-languages task`); + const space = program.space; + if (!space) { + console.log(chalk.red("X") + " Please provide the space as argument --space YOUR_SPACE_ID."); + process.exit(0); + } + + try { + if (!api.isAuthorized()) { + await api.processLogin(); + } + + api.setSpaceId(space); + await tasks.pullLanguages(api, { space }); + } catch (e) { + console.log(chalk.red("X") + " An error occurred when executing the pull-languages task: " + e.message); + process.exit(1); + } + }); + +// pull-components +program + .command(COMMANDS.PULL_COMPONENTS) + .option("--sf, --separate-files [value]", "Argument to create a single file for each component") + .option("-p, --path ", "Path to save the component files") + .option("-f, --file-name ", "custom name to be used in file(s) name instead of space id") + .option("-ppn, --prefix-presets-names", "Prefixes the names of presets with the name of the components") + .option("--rd, --resolve-datasources", "Fill options for single/multiple option field with the linked datasource") + .description("Download your space's components schema as json") + .action(async (options) => { + console.log(`${chalk.blue("-")} Executing pull-components task`); + const space = program.space; + const { separateFiles, path, prefixPresetsNames, resolveDatasources } = options; + if (!space) { + console.log(chalk.red("X") + " Please provide the space as argument --space YOUR_SPACE_ID."); + process.exit(0); + } + + const fileName = options.fileName ? options.fileName : space; + + try { + if (!api.isAuthorized()) { + await api.processLogin(); + } + + api.setSpaceId(space); + await tasks.pullComponents(api, { fileName, separateFiles, path, prefixPresetsNames, resolveDatasources }); + } catch (e) { + errorHandler(e, COMMANDS.PULL_COMPONENTS); + } + }); + +// push-components +program + .command(COMMANDS.PUSH_COMPONENTS + " ") + .option("-p, --presets-source ", "Path to presets file") + .description( + "Download your space's components schema as json. The source parameter can be a URL to your JSON file or a path to it" + ) + .action(async (source, options) => { + console.log(`${chalk.blue("-")} Executing push-components task`); + const space = program.space; + const presetsSource = options.presetsSource; + + if (!space) { + console.log(chalk.red("X") + " Please provide the space as argument --space YOUR_SPACE_ID."); + process.exit(0); + } + + try { + if (!api.isAuthorized()) { + await api.processLogin(); + } + + api.setSpaceId(space); + await tasks.pushComponents(api, { source, presetsSource }); + } catch (e) { + errorHandler(e, COMMANDS.PUSH_COMPONENTS); + } + }); + +// delete-component +program + .command("delete-component ") + .description("Delete a single component on your space.") + .action(async (component) => { + console.log(`${chalk.blue("-")} Executing delete-component task`); + const space = program.space; + if (!space) { + console.log(chalk.red("X") + " Please provide the space as argument --space YOUR_SPACE_ID."); + process.exit(0); + } + try { + if (!api.isAuthorized()) { + await api.processLogin(); + } + + api.setSpaceId(space); + await tasks.deleteComponent(api, { comp: component }); + } catch (e) { + console.log(chalk.red("X") + " An error occurred when executing the delete-component task: " + e.message); + process.exit(1); + } + }); + +// delete-components +program + .command("delete-components ") + .description("Delete all components in your space that occur in your source file.") + .option("-r, --reverse", "Delete all components in your space that do not appear in your source.", false) + .option("--dryrun", "Does not perform any delete changes on your space.") + .action(async (source, options) => { + console.log(`${chalk.blue("-")} Executing delete-components task`); + const space = program.space; + if (!space) { + console.log(chalk.red("X") + " Please provide the space as argument --space YOUR_SPACE_ID."); + process.exit(0); + } + try { + if (!api.isAuthorized()) { + await api.processLogin(); + } + + api.setSpaceId(space); + await tasks.deleteComponents(api, { source, dryRun: !!options.dryrun, reversed: !!options.reverse }); + } catch (e) { + console.log(chalk.red("X") + " An error occurred when executing the delete-component task: " + e.message); + process.exit(1); + } + }); + +// scaffold +program + .command(COMMANDS.SCAFFOLD + " ") + .description("Scaffold component") + .action(async (name) => { + console.log(`${chalk.blue("-")} Scaffolding a component\n`); + + if (api.isAuthorized()) { + api.accessToken = creds.get().token || null; + } + + try { + await tasks.scaffold(api, name, program.space); + console.log(chalk.green("βœ“") + " Generated files: "); + console.log(chalk.green("βœ“") + " - views/components/_" + name + ".liquid"); + console.log(chalk.green("βœ“") + " - source/scss/components/below/_" + name + ".scss"); + process.exit(0); + } catch (e) { + console.log( + chalk.red("X") + " An error occurred when executing operations to create the component: " + e.message + ); + process.exit(1); + } + }); + +// select +program + .command(COMMANDS.SELECT) + .description("Usage to kickstart a boilerplate, fieldtype or theme") + .action(async () => { + console.log(`${chalk.blue("-")} Select a boilerplate, fieldtype or theme to initialize\n`); + + try { + const questions = getQuestions("select"); + const answers = await inquirer.prompt(questions); + + await lastStep(answers); + } catch (e) { + console.error(chalk.red("X") + " An error ocurred when execute the select command: " + e.message); + process.exit(1); + } + }); + +// sync +program + .command(COMMANDS.SYNC) + .description("Sync schemas, roles, folders and stories between spaces") + .requiredOption( + "--type ", + "Define what will be sync. Can be components, folders, stories, datasources or roles" + ) + .requiredOption("--source ", "Source space id") + .requiredOption("--target ", "Target space id") + .option("--starts-with ", "Sync only stories that starts with the given string") + .option("--filter", "Enable filter options to sync only stories that match the given filter. Required options: --keys; --operations; --values") + .option("--keys ", "Field names in your story object which should be used for filtering. Multiple keys should separated by comma.") + .option("--operations ", "Operations to be used for filtering. Can be: is, in, not_in, like, not_like, any_in_array, all_in_array, gt_date, lt_date, gt_int, lt_int, gt_float, lt_float. Multiple operations should be separated by comma.") + .option("--values ", "Values to be used for filtering. Any string or number. If you want to use multiple values, separate them with a comma. Multiple values should be separated by comma.") + .option("--components-groups ", "Synchronize components based on their group UUIDs separated by commas") + .option("--components-full-sync", "Synchronize components by overriding any property from source to target") + .action(async (options) => { + console.log(`${chalk.blue("-")} Sync data between spaces\n`); + + try { + if (!api.isAuthorized()) { + await api.processLogin(); + } + + const { + type, + target, + source, + startsWith, + filter, + keys, + operations, + values, + componentsGroups, + componentsFullSync + } = options; + + const _componentsGroups = componentsGroups ? componentsGroups.split(",") : null; + const _componentsFullSync = !!componentsFullSync; + const filterQuery = filter ? buildFilterQuery(keys, operations, values) : undefined; + const token = creds.get().token || null; + + const _types = type.split(",") || []; + _types.forEach((_type) => { + if (!SYNC_TYPES.includes(_type)) { + throw new Error(`The type ${_type} is not valid`); + } + }); + + await tasks.sync(_types, { + api, + token, + target, + source, + startsWith, + filterQuery, + _componentsGroups, + _componentsFullSync, + }); + + console.log("\n" + chalk.green("βœ“") + " Sync data between spaces successfully completed"); + } catch (e) { + errorHandler(e, COMMANDS.SYNC); + } + }); + +// quickstart +program + .command(COMMANDS.QUICKSTART) + .description("Start a project quickly") + .action(async () => { + try { + if (!api.isAuthorized()) { + await api.processLogin(); + } + + const space = program.space; + const questions = getQuestions("quickstart", { space }, api); + const answers = await inquirer.prompt(questions); + await tasks.quickstart(api, answers, space); + } catch (e) { + console.log(chalk.red("X") + " An error ocurred when execute quickstart operations: " + e.message); + process.exit(1); + } + }); + +program + .command(COMMANDS.GENERATE_MIGRATION) + .description("Generate a content migration file") + .requiredOption("-c, --component ", "Name of the component") + .requiredOption("-f, --field ", "Name of the component field") + .action(async (options) => { + const { field = "" } = options; + const { component = "" } = options; + + const space = program.space; + if (!space) { + console.log(chalk.red("X") + " Please provide the space as argument --space YOUR_SPACE_ID."); + process.exit(1); + } + + console.log(`${chalk.blue("-")} Creating the migration file in ./migrations/change_${component}_${field}.js\n`); + + try { + if (!api.isAuthorized()) { + await api.processLogin(); + } + + api.setSpaceId(space); + await tasks.generateMigration(api, component, field); + } catch (e) { + console.log(chalk.red("X") + " An error ocurred when generate the migration file: " + e.message); + process.exit(1); + } + }); + +program + .command(COMMANDS.RUN_MIGRATION) + .description("Run a migration file") + .requiredOption("-c, --component ", "Name of the component") + .requiredOption("-f, --field ", "Name of the component field") + .option("--dryrun", "Do not update the story content") + .option("--publish ", "Publish the content. It can be: all, published or published-with-changes") + .option("--publish-languages ", "Publish specific languages") + .action(async (options) => { + const field = options.field || ""; + const component = options.component || ""; + const isDryrun = !!options.dryrun; + const publish = options.publish || null; + const publishLanguages = options.publishLanguages || ""; + + const space = program.space; + if (!space) { + console.log(chalk.red("X") + " Please provide the space as argument --space YOUR_SPACE_ID."); + process.exit(1); + } + + const publishOptionsAvailable = ["all", "published", "published-with-changes"]; + if (publish && !publishOptionsAvailable.includes(publish)) { + console.log( + chalk.red("X") + " Please provide a correct publish option: all, published, or published-with-changes" + ); + process.exit(1); + } + + console.log(`${chalk.blue("-")} Processing the migration ./migrations/change_${component}_${field}.js\n`); + + try { + if (!api.isAuthorized()) { + await api.processLogin(); + } + + api.setSpaceId(space); + await tasks.runMigration(api, component, field, { isDryrun, publish, publishLanguages }); + } catch (e) { + console.log(chalk.red("X") + " An error ocurred when run the migration file: " + e.message); + process.exit(1); + } + }); + +program + .command(COMMANDS.ROLLBACK_MIGRATION) + .description("Rollback-migration a migration file") + .requiredOption("-c, --component ", "Name of the component") + .requiredOption("-f, --field ", "Name of the component field") + .action(async (options) => { + const field = options.field || ""; + const component = options.component || ""; + const space = program.space; + if (!space) { + console.log(chalk.red("X") + " Please provide the space as argument --space YOUR_SPACE_ID."); + process.exit(1); + } + + try { + if (!api.isAuthorized()) { + await api.processLogin(); + } + + api.setSpaceId(space); + + await tasks.rollbackMigration(api, field, component); + } catch (e) { + console.log(chalk.red("X") + " An error ocurred when run rollback-migration: " + e.message); + process.exit(1); + } + }); + +// list spaces +program + .command(COMMANDS.SPACES) + .description("List all spaces of the logged account") + .action(async () => { + try { + if (!api.isAuthorized()) { + await api.processLogin(); + } + const { region } = creds.get(); + + await tasks.listSpaces(api, region); + } catch (e) { + console.log(chalk.red("X") + " An error ocurred to listing spaces: " + e.message); + process.exit(1); + } + }); + +// import data +program + .command(COMMANDS.IMPORT) + .description("Import data from other systems and relational databases.") + .requiredOption("-f, --file ", "Name of the file") + .requiredOption("-t, --type ", "Type of the content") + .option("-fr, --folder ", "(Optional) This is a Id of folder in storyblok") + .option("-d, --delimiter ", 'If you are using a csv file, put the file delimiter, the default is ";"') + .action(async (options) => { + const space = program.space; + + try { + if (!api.isAuthorized()) { + await api.processLogin(); + } + + if (!space) { + console.log(chalk.red("X") + " Please provide the space as argument --space ."); + return; + } + + api.setSpaceId(space); + await tasks.importFiles(api, options); + + console.log(`${chalk.green("βœ“")} The import process was executed with success!`); + } catch (e) { + console.log(chalk.red("X") + " An error ocurred to import data : " + e.message); + process.exit(1); + } + }); + +// delete-datasources +program + .command(COMMANDS.DELETE_DATASOURCES) + .requiredOption("--space-id ", "Space id") + .option("--by-slug ", "Delete datasources by slug") + .option("--by-name ", "Delete datasources by name") + .action(async (options) => { + console.log(`${chalk.blue("-")} Executing ${COMMANDS.DELETE_DATASOURCES} task`); + + const { spaceId, bySlug, byName } = options; + + try { + if (!api.isAuthorized()) { + await api.processLogin(); + } + + api.setSpaceId(spaceId); + + await tasks.deleteDatasources(api, { byName, bySlug }); + } catch (e) { + errorHandler(e, COMMANDS.DELETE_DATASOURCES); + } + }); + +// Generate Typescript type definitions +program + .command(COMMANDS.GENERATE_TYPESCRIPT_TYPEDEFS) + // Providing backward-compatible flags with Storyblok Generate TS https://github.com/dohomi/storyblok-generate-ts + .requiredOption( + "--source, --sourceFilePaths ", + "Path(s) to the components JSON file(s) as comma separated values", + (paths, _previous) => paths.split(",") + ) + .option( + "--target, --destinationFilePath ", + "Path to the Typescript file that will be generated (default: `storyblok-component-types.d.ts`)" + ) + .option( + "--titlePrefix, --typeNamesPrefix ", + "A prefix that will be prepended to all the names of the generated types" + ) + .option( + "--titleSuffix, --typeNamesSuffix ", + "A suffix that will be appended to all the names of the generated types (*default*: `Storyblok`)" + ) + .option( + "--compilerOptions, --JSONSchemaToTSOptionsPath ", + "Path to a JSON file with a list of options supported by json-schema-to-typescript" + ) + .option("--customTypeParser, --customFieldTypesParserPath ", "Path to the parser file for Custom Field Types") + .action((options) => { + console.log(`${chalk.blue("-")} Executing ${COMMANDS.GENERATE_TYPESCRIPT_TYPEDEFS} task`); + + const { + sourceFilePaths, + destinationFilePath, + typeNamesPrefix, + typeNamesSuffix, + customFieldTypesParserPath, + JSONSchemaToTSOptionsPath, + } = options; + + try { + tasks.generateTypescriptTypedefs({ + sourceFilePaths, + destinationFilePath, + typeNamesPrefix, + typeNamesSuffix, + customFieldTypesParserPath, + JSONSchemaToTSOptionsPath, + }); + } catch (e) { + errorHandler(e, COMMANDS.GENERATE_TYPESCRIPT_TYPEDEFS); + } + }); + +program.parse(process.argv); + +if (program.rawArgs.length <= 2) { + program.help(); +} + +function errorHandler(e, command) { + if (/404/.test(e.message)) { + const allRegionsButDefault = ALL_REGIONS.filter((region) => region !== EU_CODE).join(" ,"); + console.log( + chalk.yellow("/!\\") + + ` If your space was not created under ${EU_CODE} region, you must provide the region (${allRegionsButDefault}) upon login.` + ); + } else { + console.log(chalk.red("X") + " An error occurred when executing the " + command + " task: " + e || e.message); + } + process.exit(1); +} diff --git a/src/commands/components/README.md b/src/commands/components/README.md deleted file mode 100644 index 4e16b7e2..00000000 --- a/src/commands/components/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Components Command - -The `components` module provides tools to manage Storyblok components and their dependencies. - -## Subcommands - -- [`pull`](./pull/README.md): Download components from your Storyblok space. -- [`push`](./push/README.md): Upload components and their dependencies (groups, tags, presets, whitelists) to your Storyblok space. -- `delete` (coming soon): Remove components from your Storyblok space. - -> See each subcommand for detailed usage, options, and examples. diff --git a/src/commands/components/actions.ts b/src/commands/components/actions.ts deleted file mode 100644 index f0192443..00000000 --- a/src/commands/components/actions.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './pull/actions'; -export * from './push/actions'; diff --git a/src/commands/components/command.ts b/src/commands/components/command.ts deleted file mode 100644 index 7d04bfae..00000000 --- a/src/commands/components/command.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { getProgram } from '../../program'; - -const program = getProgram(); // Get the shared singleton instance - -// Components root command -export const componentsCommand = program - .command('components') - .alias('comp') - .description(`Manage your space's block schema`) - .option('-s, --space ', 'space ID') - .option('-p, --path ', 'path to save the file. Default is .storyblok/components'); diff --git a/src/commands/components/constants.ts b/src/commands/components/constants.ts deleted file mode 100644 index bb6a42cc..00000000 --- a/src/commands/components/constants.ts +++ /dev/null @@ -1,66 +0,0 @@ -export interface SpaceComponent { - name: string; - display_name: string; - created_at: string; - updated_at: string; - id: number; - schema: Record; - image?: string; - preview_field?: string; - is_root?: boolean; - is_nestable?: boolean; - preview_tmpl?: string; - all_presets?: Record; - preset_id?: number; - real_name?: string; - component_group_uuid?: string; - color: null; - internal_tags_list: SpaceComponentInternalTag[]; - internal_tag_ids: string[]; - content_type_asset_preview?: string; -} - -export interface SpaceComponentGroup { - name: string; - id: number; - uuid: string; - parent_id: number | null; - parent_uuid: string | null; -} - -export interface SpaceComponentPreset { - id: number; - name: string; - preset: Record; - component_id: number; - space_id: number; - created_at: string; - updated_at: string; - image: string; - color: string; - icon: string; - description: string; -} - -export interface SpaceComponentInternalTag { - id: number; - name: string; - object_type?: 'asset' | 'component'; -} - -export interface SpaceData { - components: SpaceComponent[]; - groups: SpaceComponentGroup[]; - presets: SpaceComponentPreset[]; - internalTags: SpaceComponentInternalTag[]; -} - -export interface SpaceDataState { - local: SpaceData; - target: { - components: Map; - tags: Map; - groups: Map; - presets: Map; - }; -} diff --git a/src/commands/components/index.ts b/src/commands/components/index.ts deleted file mode 100644 index f2942678..00000000 --- a/src/commands/components/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -import './command'; -import './pull'; -import './push'; - -export * from './constants'; -export * from './pull/actions'; -export * from './pull/constants'; - -export * from './push/actions'; -export * from './push/constants'; diff --git a/src/commands/components/pull/README.md b/src/commands/components/pull/README.md deleted file mode 100644 index e866d906..00000000 --- a/src/commands/components/pull/README.md +++ /dev/null @@ -1,190 +0,0 @@ -# Components Pull Command - -The `components pull` command allows you to download components and their dependencies from your Storyblok space. - -## Basic Usage - -```bash -storyblok components pull --space YOUR_SPACE_ID -``` - -This will download all components and their dependencies to consolidated files: -``` -.storyblok/ -└── components/ - └── YOUR_SPACE_ID/ - β”œβ”€β”€ components.json # All components - β”œβ”€β”€ groups.json # Component groups - β”œβ”€β”€ presets.json # Component presets - └── tags.json # Component tags -``` - -> [!WARNING] -> The `--filename` option is ignored when using `--separate-files`. Each component will be saved with its own name. - -## Pull a Single Component - -```bash -storyblok components pull COMPONENT_NAME --space YOUR_SPACE_ID -``` - -This will download a single component and its dependencies to: -``` -.storyblok/ -└── components/ - └── YOUR_SPACE_ID/ - β”œβ”€β”€ COMPONENT_NAME.json # Single component - β”œβ”€β”€ groups.json # Component groups - β”œβ”€β”€ COMPONENT_NAME.presets.json # Component presets - └── tags.json # Component tags -``` - -## Options - -| Option | Description | Default | -|--------|-------------|---------| -| `-s, --space ` | (Required) The ID of the space to pull components from | - | -| `-f, --filename ` | Custom name for the components file | `components` | -| `--sf, --separate-files` | Create a separate file for each component | `false` | -| `--su, --suffix ` | Suffix to add to the files names | | -| `-p, --path ` | Custom path to store the files | `.storyblok/components` | - -## Examples - -1. Pull all components with default settings: -```bash -storyblok components pull --space 12345 -``` -Generates: -``` -.storyblok/ -└── components/ - └── 12345/ - β”œβ”€β”€ components.json # All components - β”œβ”€β”€ groups.json # Component groups - β”œβ”€β”€ presets.json # Component presets - └── tags.json # Component tags -``` - -2. Pull a single component: -```bash -storyblok components pull hero --space 12345 -``` -Generates: -``` -.storyblok/ -└── components/ - └── 12345/ - β”œβ”€β”€ hero.json # Single component - β”œβ”€β”€ groups.json # Component groups - β”œβ”€β”€ hero.presets.json # Component presets - └── tags.json # Component tags -``` - -3. Pull components with a custom file name: -```bash -storyblok components pull --space 12345 --filename my-components -``` -Generates: -``` -.storyblok/ -└── components/ - └── 12345/ - β”œβ”€β”€ my-components.json # All components - β”œβ”€β”€ groups.json # Component groups - β”œβ”€β”€ presets.json # Component presets - └── tags.json # Component tags -``` - -4. Pull components with custom suffix: -```bash -storyblok components pull --space 12345 --suffix dev -``` -Generates: -``` -.storyblok/ -└── components/ - └── 12345/ - β”œβ”€β”€ components.dev.json # All components - β”œβ”€β”€ groups.json # Component groups - β”œβ”€β”€ presets.json # Component presets - └── tags.json # Component tags -``` - -5. Pull components to separate files: -```bash -storyblok components pull --space 12345 --separate-files -``` -Generates: -``` -.storyblok/ -└── components/ - └── 12345/ - β”œβ”€β”€ hero.json # Individual components - β”œβ”€β”€ hero.presets.json # Component presets - β”œβ”€β”€ feature.json - β”œβ”€β”€ feature.presets.json - β”œβ”€β”€ ... - β”œβ”€β”€ groups.json # Component groups - └── tags.json # Component tags -``` - -6. Pull components to a custom path: -```bash -storyblok components pull --space 12345 --path ./backup -``` -Generates: -``` -backup/ -└── components/ - └── 12345/ - β”œβ”€β”€ components.json # All components - β”œβ”€β”€ groups.json # Component groups - β”œβ”€β”€ presets.json # Component presets - └── tags.json # Component tags -``` - -## File Structure - -The command follows this pattern for file generation: -``` -{path}/ -└── components/ - └── {spaceId}/ - β”œβ”€β”€ {filename}.{suffix}.json # Components file - β”œβ”€β”€ groups.json # Component groups - β”œβ”€β”€ presets.json # Component presets - └── tags.json # Component tags -``` - -When using `--separate-files`: -``` -{path}/ -└── components/ - └── {spaceId}/ - β”œβ”€β”€ {componentName1}.json # Individual components - β”œβ”€β”€ {componentName1}.presets.json # Component presets - β”œβ”€β”€ {componentName2}.json - β”œβ”€β”€ {componentName2}.presets.json - β”œβ”€β”€ ... - β”œβ”€β”€ groups.json # Component groups - └── tags.json # Component tags -``` - -Where: -- `{path}` is the base path (default: `.storyblok`) -- `{spaceId}` is your Storyblok Space ID -- `{filename}` is the name you specified (default: `components`) -- `{suffix}` is the suffix you specified (default: space ID) -- `{componentName}` is the name of the component - -## Notes - -- The space ID is required -- The command will create the necessary directories if they don't exist -- When using `--separate-files` or single component, presets are saved in separate files named `{componentName}.presets.json` -- The command downloads: - - Components - - Component groups - - Component presets - - Component tags diff --git a/src/commands/components/pull/actions.test.ts b/src/commands/components/pull/actions.test.ts deleted file mode 100644 index 057e6035..00000000 --- a/src/commands/components/pull/actions.test.ts +++ /dev/null @@ -1,291 +0,0 @@ -import { http, HttpResponse } from 'msw'; -import { setupServer } from 'msw/node'; -import { vol } from 'memfs'; -import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'; -import { fetchComponent, fetchComponents, saveComponentsToFiles } from './actions'; -import { mapiClient } from '../../../api'; - -const mockedComponents = [{ - name: 'component-name', - display_name: 'Component Name', - created_at: '2021-08-09T12:00:00Z', - updated_at: '2021-08-09T12:00:00Z', - id: 12345, - schema: { type: 'object' }, - color: null, - internal_tags_list: ['tag'], - internal_tag_ids: [1], -}, { - name: 'component-name-2', - display_name: 'Component Name 2', - created_at: '2021-08-09T12:00:00Z', - updated_at: '2021-08-09T12:00:00Z', - id: 12346, - schema: { type: 'object' }, - color: null, - internal_tags_list: ['tag'], - internal_tag_ids: [1], -}, { - name: 'name-2', - display_name: 'Name 2', - created_at: '2021-08-09T12:00:00Z', - updated_at: '2021-08-09T12:00:00Z', - id: 12346, - schema: { type: 'object' }, - color: null, - internal_tags_list: [], - internal_tag_ids: [], -}]; - -const handlers = [ - http.get('https://api.storyblok.com/v1/spaces/12345/components', async ({ request }) => { - const token = request.headers.get('Authorization'); - if (token === 'valid-token') { - return HttpResponse.json({ - components: mockedComponents, - }); - } - return new HttpResponse('Unauthorized', { status: 401 }); - }), -]; - -const server = setupServer(...handlers); - -beforeAll(() => server.listen({ onUnhandledRequest: 'error' })); - -afterEach(() => server.resetHandlers()); -afterAll(() => server.close()); - -vi.mock('node:fs'); -vi.mock('node:fs/promises'); - -describe('pull components actions', () => { - beforeEach(() => { - mapiClient().dispose(); - mapiClient({ - token: 'valid-token', - region: 'eu', - }); - }); - - it('should pull components successfully with a valid token', async () => { - const mockResponse = [{ - name: 'component-name', - display_name: 'Component Name', - created_at: '2021-08-09T12:00:00Z', - updated_at: '2021-08-09T12:00:00Z', - id: 12345, - schema: { type: 'object' }, - color: null, - internal_tags_list: ['tag'], - internal_tag_ids: [1], - }, { - name: 'component-name-2', - display_name: 'Component Name 2', - created_at: '2021-08-09T12:00:00Z', - updated_at: '2021-08-09T12:00:00Z', - id: 12346, - schema: { type: 'object' }, - color: null, - internal_tags_list: ['tag'], - internal_tag_ids: [1], - }, { - name: 'name-2', - display_name: 'Name 2', - created_at: '2021-08-09T12:00:00Z', - updated_at: '2021-08-09T12:00:00Z', - id: 12346, - schema: { type: 'object' }, - color: null, - internal_tags_list: [], - internal_tag_ids: [], - }]; - - const result = await fetchComponents('12345'); - expect(result).toEqual(mockResponse); - }); - - it('should fetch a component by name', async () => { - const mockResponse = { - components: [{ - name: 'component-name', - display_name: 'Component Name', - created_at: '2021-08-09T12:00:00Z', - updated_at: '2021-08-09T12:00:00Z', - id: 12345, - schema: { type: 'object' }, - color: null, - internal_tags_list: ['tag'], - internal_tag_ids: [1], - }], - }; - const result = await fetchComponent('12345', 'component-name'); - expect(result).toEqual(mockResponse.components[0]); - }); - - it('should choose the right component when multiple names match', async () => { - const mockResponse = { - components: [{ - name: 'name-2', - display_name: 'Name 2', - created_at: '2021-08-09T12:00:00Z', - updated_at: '2021-08-09T12:00:00Z', - id: 12346, - schema: { type: 'object' }, - color: null, - internal_tags_list: [], - internal_tag_ids: [], - }], - }; - // searching for 'name-2' would match both 'component-name-2' and 'name-2' - const result = await fetchComponent('12345', 'name-2'); - expect(result).toEqual(mockResponse.components[0]); - }); - - it('should throw an masked error for invalid token', async () => { - mapiClient().dispose(); - mapiClient({ - token: 'invalid-token', - region: 'eu', - }); - - await expect(fetchComponents('12345')).rejects.toThrow( - expect.objectContaining({ - name: 'API Error', - message: 'The user is not authorized to access the API', - cause: 'The user is not authorized to access the API', - errorId: 'unauthorized', - code: 401, - messageStack: [ - 'Failed to pull components', - 'The user is not authorized to access the API', - ], - }), - ); - }); - - describe('saveComponentsToFiles', () => { - it('should save components to files successfully', async () => { - vol.fromJSON({ - '/path/to/components/12345': null, - }); - - const components = [{ - name: 'component-name', - display_name: 'Component Name', - created_at: '2021-08-09T12:00:00Z', - updated_at: '2021-08-09T12:00:00Z', - id: 12345, - schema: { type: 'object' }, - color: null, - internal_tags_list: ['tag'], - internal_tag_ids: [1], - }]; - - await saveComponentsToFiles('12345', { components }, { - path: '/path/to/', - verbose: false, - space: '12345', - }); - - const files = vol.readdirSync('/path/to/components/12345'); - expect(files).toEqual(['components.json']); - }); - - it('should save components to files with custom filename', async () => { - vol.fromJSON({ - '/path/to2/': null, - }); - - const components = [{ - name: 'component-name', - display_name: 'Component Name', - created_at: '2021-08-09T12:00:00Z', - updated_at: '2021-08-09T12:00:00Z', - id: 12345, - schema: { type: 'object' }, - color: null, - internal_tags_list: ['tag'], - internal_tag_ids: [1], - }]; - - await saveComponentsToFiles('12345', { components }, { - path: '/path/to2/', - filename: 'custom', - verbose: false, - }); - - const files = vol.readdirSync('/path/to2/components/12345'); - expect(files).toEqual(['custom.json']); - }); - - it('should save components to files with custom suffix', async () => { - vol.fromJSON({ - '/path/to3/': null, - }); - - const components = [{ - name: 'component-name', - display_name: 'Component Name', - created_at: '2021-08-09T12:00:00Z', - updated_at: '2021-08-09T12:00:00Z', - id: 12345, - schema: { type: 'object' }, - color: null, - internal_tags_list: ['tag'], - internal_tag_ids: [1], - }]; - - try { - await saveComponentsToFiles('12345', { components }, { - path: '/path/to3/', - suffix: 'custom', - verbose: false, - }); - } - catch (error) { - console.log('TEST', error); - } - - const files = vol.readdirSync('/path/to3/components/12345'); - expect(files).toEqual(['components.custom.json']); - }); - - it('should save components to separate files', async () => { - vol.fromJSON({ - '/path/to4/': null, - }); - - const components = [{ - name: 'component-name', - display_name: 'Component Name', - created_at: '2021-08-09T12:00:00Z', - updated_at: '2021-08-09T12:00:00Z', - id: 12345, - schema: { type: 'object' }, - color: null, - internal_tags_list: ['tag'], - internal_tag_ids: [1], - }, { - name: 'component-name-2', - display_name: 'Component Name 2', - created_at: '2021-08-09T12:00:00Z', - updated_at: '2021-08-09T12:00:00Z', - id: 12346, - schema: { type: 'object' }, - color: null, - internal_tags_list: ['tag'], - internal_tag_ids: [1], - }]; - - await saveComponentsToFiles('12345', { components }, { - path: '/path/to4/', - separateFiles: true, - verbose: false, - }); - - const files = vol.readdirSync('/path/to4/components/12345'); - expect(files).toEqual(['component-name-2.json', 'component-name.json', 'groups.json', 'tags.json']); - }); - }); -}); diff --git a/src/commands/components/pull/actions.ts b/src/commands/components/pull/actions.ts deleted file mode 100644 index 1468d2f4..00000000 --- a/src/commands/components/pull/actions.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { handleAPIError, handleFileSystemError } from '../../../utils'; -import type { SpaceComponent, SpaceComponentGroup, SpaceComponentInternalTag, SpaceComponentPreset, SpaceData } from '../constants'; -import { join, resolve } from 'node:path'; -import { resolvePath, saveToFile } from '../../../utils/filesystem'; -import type { SaveComponentsOptions } from './constants'; -import { mapiClient } from '../../../api'; -// Components -export const fetchComponents = async (space: string): Promise => { - try { - const client = mapiClient(); - - const { data } = await client.get<{ - components: SpaceComponent[]; - }>(`spaces/${space}/components`, { - }); - return data.components; - } - catch (error) { - handleAPIError('pull_components', error as Error); - } -}; - -export const fetchComponent = async (space: string, componentName: string): Promise => { - try { - const client = mapiClient(); - - const { data } = await client.get<{ - components: SpaceComponent[]; - }>(`spaces/${space}/components?search=${encodeURIComponent(componentName)}`, { - }); - return data.components?.find(c => c.name === componentName); - } - catch (error) { - handleAPIError('pull_components', error as Error, `Failed to fetch component ${componentName}`); - } -}; - -// Component group actions -export const fetchComponentGroups = async (space: string): Promise => { - try { - const client = mapiClient(); - - const { data } = await client.get<{ - component_groups: SpaceComponentGroup[]; - }>(`spaces/${space}/component_groups`); - return data.component_groups; - } - catch (error) { - handleAPIError('pull_component_groups', error as Error); - } -}; - -// Component preset actions -export const fetchComponentPresets = async (space: string): Promise => { - try { - const client = mapiClient(); - - const { data } = await client.get<{ - presets: SpaceComponentPreset[]; - }>(`spaces/${space}/presets`); - return data.presets; - } - catch (error) { - handleAPIError('pull_component_presets', error as Error); - } -}; - -// Component internal tags -export const fetchComponentInternalTags = async (space: string): Promise => { - try { - const client = mapiClient(); - - const { data } = await client.get<{ - internal_tags: SpaceComponentInternalTag[]; - }>(`spaces/${space}/internal_tags`, { - }); - return data.internal_tags.filter(tag => tag.object_type === 'component'); - } - catch (error) { - handleAPIError('pull_component_internal_tags', error as Error); - } -}; - -// Filesystem actions - -export const saveComponentsToFiles = async ( - space: string, - spaceData: SpaceData, - options: SaveComponentsOptions, -) => { - const { components = [], groups = [], presets = [], internalTags = [] } = spaceData; - const { filename = 'components', suffix, path, separateFiles } = options; - // Ensure we always include the components/space folder structure regardless of custom path - const resolvedPath = path - ? resolve(process.cwd(), path, 'components', space) - : resolvePath(path, `components/${space}`); - - try { - if (separateFiles) { - // Save in separate files without nested structure - for (const component of components) { - const componentFilePath = join(resolvedPath, suffix ? `${component.name}.${suffix}.json` : `${component.name}.json`); - await saveToFile(componentFilePath, JSON.stringify(component, null, 2)); - - // Find and save associated presets - const componentPresets = presets.filter(preset => preset.component_id === component.id); - if (componentPresets.length > 0) { - const presetsFilePath = join(resolvedPath, suffix ? `${component.name}.presets.${suffix}.json` : `${component.name}.presets.json`); - await saveToFile(presetsFilePath, JSON.stringify(componentPresets, null, 2)); - } - // Always save groups in a consolidated file - const groupsFilePath = join(resolvedPath, suffix ? `groups.${suffix}.json` : `groups.json`); - await saveToFile(groupsFilePath, JSON.stringify(groups, null, 2)); - - // Always save internal tags in a consolidated file - const internalTagsFilePath = join(resolvedPath, suffix ? `tags.${suffix}.json` : `tags.json`); - await saveToFile(internalTagsFilePath, JSON.stringify(internalTags, null, 2)); - } - return; - } - - // Default to saving consolidated files - const componentsFilePath = join(resolvedPath, suffix ? `${filename}.${suffix}.json` : `${filename}.json`); - await saveToFile(componentsFilePath, JSON.stringify(components, null, 2)); - - if (groups.length > 0) { - const groupsFilePath = join(resolvedPath, suffix ? `groups.${suffix}.json` : `groups.json`); - await saveToFile(groupsFilePath, JSON.stringify(groups, null, 2)); - } - - if (presets.length > 0) { - const presetsFilePath = join(resolvedPath, suffix ? `presets.${suffix}.json` : `presets.json`); - await saveToFile(presetsFilePath, JSON.stringify(presets, null, 2)); - } - - if (internalTags.length > 0) { - const internalTagsFilePath = join(resolvedPath, suffix ? `tags.${suffix}.json` : `tags.json`); - await saveToFile(internalTagsFilePath, JSON.stringify(internalTags, null, 2)); - } - } - catch (error) { - handleFileSystemError('write', error as Error); - } -}; diff --git a/src/commands/components/pull/constants.ts b/src/commands/components/pull/constants.ts deleted file mode 100644 index bab434f9..00000000 --- a/src/commands/components/pull/constants.ts +++ /dev/null @@ -1,45 +0,0 @@ -import type { CommandOptions } from '../../../types'; - -/** - * Interface representing the options for the `pull-components` command. - */ -export interface PullComponentsOptions extends CommandOptions { - - /** - * The filename to save the file as. - * Defaults to `components`. The file will be saved as `..json`. - * @default `components - */ - filename?: string; - /** - * The suffix to add to the filename. - * Defaults to the space ID. - * @default space - */ - suffix?: string; - /** - * Indicates whether to save each component to a separate file. - * @default false - */ - separateFiles?: boolean; -} - -export interface SaveComponentsOptions extends PullComponentsOptions { - /** - * The path to save the components file to. - * Defaults to `.storyblok/components`. - * @default `.storyblok/components` - */ - path?: string; - /** - * The regex filter to apply to the components before pushing. - * @default `.*` - */ - filter?: string; - /** - * Indicates whether to read each component to a separate file. - * @default false - */ - separateFiles?: boolean; - -} diff --git a/src/commands/components/pull/index.test.ts b/src/commands/components/pull/index.test.ts deleted file mode 100644 index 688dbd45..00000000 --- a/src/commands/components/pull/index.test.ts +++ /dev/null @@ -1,308 +0,0 @@ -import { session } from '../../../session'; -import { CommandError, konsola } from '../../../utils'; -import { fetchComponent, fetchComponents, saveComponentsToFiles } from './actions'; -import chalk from 'chalk'; -import { colorPalette } from '../../../constants'; -// Import the main components module first to ensure proper initialization -import '../index'; -import { componentsCommand } from '../command'; - -vi.mock('./actions', () => ({ - fetchComponents: vi.fn(), - fetchComponent: vi.fn(), - fetchComponentGroups: vi.fn(), - fetchComponentPresets: vi.fn(), - fetchComponentInternalTags: vi.fn(), - saveComponentsToFiles: vi.fn(), -})); - -// Mocking the session module -vi.mock('../../../session', () => { - let _cache: Record | null = null; - const session = () => { - if (!_cache) { - _cache = { - state: { - isLoggedIn: false, - }, - updateSession: vi.fn(), - persistCredentials: vi.fn(), - initializeSession: vi.fn(), - }; - } - return _cache; - }; - - return { - session, - }; -}); - -vi.mock('../../../utils/konsola'); - -describe('pull', () => { - beforeEach(() => { - vi.resetAllMocks(); - vi.clearAllMocks(); - // Reset the option values - componentsCommand._optionValues = {}; - componentsCommand._optionValueSources = {}; - for (const command of componentsCommand.commands) { - command._optionValueSources = {}; - command._optionValues = {}; - } - }); - - describe('default mode', () => { - it('should prompt the user if the operation was sucessfull', async () => { - const mockResponse = [{ - name: 'component-name', - display_name: 'Component Name', - created_at: '2021-08-09T12:00:00Z', - updated_at: '2021-08-09T12:00:00Z', - id: 12345, - schema: { type: 'object' }, - color: null, - internal_tags_list: [], - internal_tag_ids: [], - }, { - name: 'component-name-2', - display_name: 'Component Name 2', - created_at: '2021-08-09T12:00:00Z', - updated_at: '2021-08-09T12:00:00Z', - id: 12346, - schema: { type: 'object' }, - color: null, - internal_tags_list: [], - internal_tag_ids: [], - }]; - - session().state = { - isLoggedIn: true, - password: 'valid-token', - region: 'eu', - }; - - vi.mocked(fetchComponents).mockResolvedValue(mockResponse); - - await componentsCommand.parseAsync(['node', 'test', 'pull', '--space', '12345']); - - expect(fetchComponents).toHaveBeenCalledWith('12345'); - expect(saveComponentsToFiles).toHaveBeenCalledWith('12345', { - components: mockResponse, - groups: [], - presets: [], - internalTags: [], - }, { - path: undefined, - separateFiles: false, - }); - expect(konsola.ok).toHaveBeenCalledWith(`Components downloaded successfully to ${chalk.hex(colorPalette.PRIMARY)(`.storyblok/components/12345/components.json`)}`); - }); - - it('should fetch a component by name', async () => { - const mockResponse = { - name: 'component-name', - display_name: 'Component Name', - created_at: '2021-08-09T12:00:00Z', - updated_at: '2021-08-09T12:00:00Z', - id: 12345, - schema: { type: 'object' }, - color: null, - internal_tags_list: ['tag'], - internal_tag_ids: [1], - }; - - session().state = { - isLoggedIn: true, - password: 'valid-token', - region: 'eu', - }; - vi.mocked(fetchComponent).mockResolvedValue(mockResponse); - await componentsCommand.parseAsync(['node', 'test', 'pull', 'component-name', '--space', '12345']); - expect(fetchComponent).toHaveBeenCalledWith('12345', 'component-name'); - expect(saveComponentsToFiles).toHaveBeenCalledWith('12345', { - components: [mockResponse], - groups: [], - presets: [], - internalTags: [], - }, { separateFiles: true, path: undefined }); - }); - - it('should throw an error if the component is not found', async () => { - const componentName = 'component-name'; - vi.mocked(fetchComponent).mockResolvedValue(undefined); - await componentsCommand.parseAsync(['node', 'test', 'pull', 'component-name', '--space', '12345']); - expect(konsola.warn).toHaveBeenCalledWith(`No component found with name "${componentName}"`); - }); - - it('should throw an error if the user is not logged in', async () => { - session().state = { - isLoggedIn: false, - }; - await componentsCommand.parseAsync(['node', 'test', 'pull', '--space', '12345']); - expect(konsola.error).toHaveBeenCalledWith('You are currently not logged in. Please run storyblok login to authenticate, or storyblok signup to sign up.', null, { - header: true, - }); - }); - - it('should throw an error if the space is not provided', async () => { - session().state = { - isLoggedIn: true, - password: 'valid-token', - region: 'eu', - }; - - const mockError = new CommandError(`Please provide the space as argument --space YOUR_SPACE_ID.`); - - await componentsCommand.parseAsync(['node', 'test', 'pull']); - expect(konsola.error).toHaveBeenCalledWith(mockError.message, null, { - header: true, - }); - }); - }); - - describe('--path option', () => { - it('should save the file at the provided path', async () => { - const mockResponse = [{ - name: 'component-name', - display_name: 'Component Name', - created_at: '2021-08-09T12:00:00Z', - updated_at: '2021-08-09T12:00:00Z', - id: 12345, - schema: { type: 'object' }, - color: null, - internal_tags_list: ['tag'], - internal_tag_ids: [1], - }]; - - session().state = { - isLoggedIn: true, - password: 'valid-token', - region: 'eu', - }; - - vi.mocked(fetchComponents).mockResolvedValue(mockResponse); - - await componentsCommand.parseAsync(['node', 'test', 'pull', '--space', '12345', '--path', '/path/to/components']); - expect(fetchComponents).toHaveBeenCalledWith('12345'); - expect(saveComponentsToFiles).toHaveBeenCalledWith('12345', { - components: mockResponse, - groups: [], - presets: [], - internalTags: [], - }, { path: '/path/to/components', separateFiles: false }); - expect(konsola.ok).toHaveBeenCalledWith(`Components downloaded successfully to ${chalk.hex(colorPalette.PRIMARY)(`/path/to/components/components/12345/components.json`)}`); - }); - }); - - describe('--filename option', () => { - it('should save the file with the custom filename', async () => { - const mockResponse = [{ - name: 'component-name', - display_name: 'Component Name', - created_at: '2021-08-09T12:00:00Z', - updated_at: '2021-08-09T12:00:00Z', - id: 12345, - schema: { type: 'object' }, - color: null, - internal_tags_list: ['tag'], - internal_tag_ids: [1], - }]; - - session().state = { - isLoggedIn: true, - password: 'valid-token', - region: 'eu', - }; - - vi.mocked(fetchComponents).mockResolvedValue(mockResponse); - - await componentsCommand.parseAsync(['node', 'test', 'pull', '--space', '12345', '--filename', 'custom']); - expect(fetchComponents).toHaveBeenCalledWith('12345'); - expect(saveComponentsToFiles).toHaveBeenCalledWith('12345', { - components: mockResponse, - groups: [], - presets: [], - internalTags: [], - }, { filename: 'custom', separateFiles: false }); - expect(konsola.ok).toHaveBeenCalledWith(`Components downloaded successfully to ${chalk.hex(colorPalette.PRIMARY)(`.storyblok/components/12345/custom.json`)}`); - }); - }); - - describe('--separate-files option', () => { - it('should save each component in a separate file', async () => { - const mockResponse = [{ - name: 'component-name', - display_name: 'Component Name', - created_at: '2021-08-09T12:00:00Z', - updated_at: '2021-08-09T12:00:00Z', - id: 12345, - schema: { type: 'object' }, - color: null, - internal_tags_list: ['tag'], - internal_tag_ids: [1], - }, { - name: 'component-name-2', - display_name: 'Component Name 2', - created_at: '2021-08-09T12:00:00Z', - updated_at: '2021-08-09T12:00:00Z', - id: 12346, - schema: { type: 'object' }, - color: null, - internal_tags_list: ['tag'], - internal_tag_ids: [1], - }]; - - session().state = { - isLoggedIn: true, - password: 'valid-token', - region: 'eu', - }; - - vi.mocked(fetchComponents).mockResolvedValue(mockResponse); - - await componentsCommand.parseAsync(['node', 'test', 'pull', '--space', '12345', '--separate-files']); - expect(fetchComponents).toHaveBeenCalledWith('12345'); - expect(saveComponentsToFiles).toHaveBeenCalledWith('12345', { - components: mockResponse, - groups: [], - presets: [], - internalTags: [], - }, { separateFiles: true, path: undefined }); - expect(konsola.ok).toHaveBeenCalledWith(`Components downloaded successfully to ${chalk.hex(colorPalette.PRIMARY)(`.storyblok/components/12345/`)}`); - }); - - it('should warn the user if the --filename is used along', async () => { - const mockResponse = [{ - name: 'component-name', - display_name: 'Component Name', - created_at: '2021-08-09T12:00:00Z', - updated_at: '2021-08-09T12:00:00Z', - id: 12345, - schema: { type: 'object' }, - color: null, - internal_tags_list: ['tag'], - internal_tag_ids: [1], - }]; - - session().state = { - isLoggedIn: true, - password: 'valid-token', - region: 'eu', - }; - - vi.mocked(fetchComponents).mockResolvedValue(mockResponse); - - await componentsCommand.parseAsync(['node', 'test', 'pull', '--space', '12345', '--separate-files', '--filename', 'custom']); - expect(fetchComponents).toHaveBeenCalledWith('12345'); - expect(saveComponentsToFiles).toHaveBeenCalledWith('12345', { - components: mockResponse, - groups: [], - presets: [], - internalTags: [], - }, { separateFiles: true, filename: 'custom' }); - expect(konsola.warn).toHaveBeenCalledWith(`The --filename option is ignored when using --separate-files`); - }); - }); -}); diff --git a/src/commands/components/pull/index.ts b/src/commands/components/pull/index.ts deleted file mode 100644 index 0292f3b9..00000000 --- a/src/commands/components/pull/index.ts +++ /dev/null @@ -1,135 +0,0 @@ -import type { PullComponentsOptions } from './constants'; - -import { Spinner } from '@topcli/spinner'; -import { colorPalette, commands } from '../../../constants'; -import { CommandError, handleError, isVitest, konsola, requireAuthentication } from '../../../utils'; -import { session } from '../../../session'; -import { fetchComponent, fetchComponentGroups, fetchComponentInternalTags, fetchComponentPresets, fetchComponents, saveComponentsToFiles } from '../actions'; -import { componentsCommand } from '../command'; -import chalk from 'chalk'; -import { getProgram } from '../../../program'; -import { mapiClient } from '../../../api'; - -const program = getProgram(); - -componentsCommand - .command('pull [componentName]') - .option('-f, --filename ', 'custom name to be used in file(s) name instead of space id') - .option('--sf, --separate-files', 'Argument to create a single file for each component') - .option('--su, --suffix ', 'suffix to add to the file name (e.g. components..json)') - .description(`Download your space's components schema as json. Optionally specify a component name to pull a single component.`) - .action(async (componentName: string | undefined, options: PullComponentsOptions) => { - konsola.title(` ${commands.COMPONENTS} `, colorPalette.COMPONENTS, componentName ? `Pulling component ${componentName}...` : 'Pulling components...'); - // Global options - const verbose = program.opts().verbose; - - // Command options - const { space, path } = componentsCommand.opts(); - const { separateFiles, suffix, filename = 'components' } = options; - - const { state, initializeSession } = session(); - await initializeSession(); - - if (!requireAuthentication(state, verbose)) { - return; - } - if (!space) { - handleError(new CommandError(`Please provide the space as argument --space YOUR_SPACE_ID.`), verbose); - return; - } - - const { password, region } = state; - - mapiClient({ - token: password, - region, - }); - - const spinnerGroups = new Spinner({ - verbose: !isVitest, - }); - const spinnerPresets = new Spinner({ - verbose: !isVitest, - }); - const spinnerInternalTags = new Spinner({ - verbose: !isVitest, - }); - const spinnerComponents = new Spinner({ - verbose: !isVitest, - }); - - try { - // Fetch components groups - spinnerGroups.start(`Fetching ${chalk.hex(colorPalette.COMPONENTS)('components groups')}`); - - const groups = await fetchComponentGroups(space); - spinnerGroups.succeed(`${chalk.hex(colorPalette.COMPONENTS)('Groups')} - Completed in ${spinnerGroups.elapsedTime.toFixed(2)}ms`); - - // Fetch components presets - spinnerPresets.start(`Fetching ${chalk.hex(colorPalette.COMPONENTS)('components presets')}`); - - const presets = await fetchComponentPresets(space); - spinnerPresets.succeed(`${chalk.hex(colorPalette.COMPONENTS)('Presets')} - Completed in ${spinnerPresets.elapsedTime.toFixed(2)}ms`); - - // Fetch components internal tags - spinnerInternalTags.start(`Fetching ${chalk.hex(colorPalette.COMPONENTS)('components internal tags')}`); - - const internalTags = await fetchComponentInternalTags(space); - spinnerInternalTags.succeed(`${chalk.hex(colorPalette.COMPONENTS)('Tags')} - Completed in ${spinnerInternalTags.elapsedTime.toFixed(2)}ms`); - - // Save everything using the new structure - let components; - spinnerComponents.start(`Fetching ${chalk.hex(colorPalette.COMPONENTS)('components')}`); - - if (componentName) { - const component = await fetchComponent(space, componentName); - if (!component) { - konsola.warn(`No component found with name "${componentName}"`); - return; - } - components = [component]; - } - else { - components = await fetchComponents(space); - if (!components || components.length === 0) { - konsola.warn(`No components found in the space ${space}`); - return; - } - } - spinnerComponents.succeed(`${chalk.hex(colorPalette.COMPONENTS)('Components')} - Completed in ${spinnerComponents.elapsedTime.toFixed(2)}ms`); - await saveComponentsToFiles( - space, - { components, groups: groups || [], presets: presets || [], internalTags: internalTags || [] }, - { ...options, path, separateFiles: separateFiles || !!componentName }, - ); - konsola.br(); - if (separateFiles) { - if (filename !== 'components') { - konsola.warn(`The --filename option is ignored when using --separate-files`); - } - const filePath = path ? `${path}/components/${space}/` : `.storyblok/components/${space}/`; - - konsola.ok(`Components downloaded successfully to ${chalk.hex(colorPalette.PRIMARY)(filePath)}`); - } - else if (componentName) { - const fileName = suffix ? `${filename}.${suffix}.json` : `${componentName}.json`; - const filePath = path ? `${path}/components/${space}/${fileName}` : `.storyblok/components/${space}/${fileName}`; - konsola.ok(`Component ${chalk.hex(colorPalette.PRIMARY)(componentName)} downloaded successfully in ${chalk.hex(colorPalette.PRIMARY)(filePath)}`); - } - else { - const fileName = suffix ? `${filename}.${suffix}.json` : `${filename}.json`; - const filePath = path ? `${path}/components/${space}/${fileName}` : `.storyblok/components/${space}/${fileName}`; - - konsola.ok(`Components downloaded successfully to ${chalk.hex(colorPalette.PRIMARY)(filePath)}`); - } - konsola.br(); - } - catch (error) { - spinnerGroups.failed(`Pulling ${chalk.hex(colorPalette.COMPONENTS)('Groups')} - Failed`); - spinnerPresets.failed(`Pulling ${chalk.hex(colorPalette.COMPONENTS)('Presets')} - Failed`); - spinnerInternalTags.failed(`Pulling ${chalk.hex(colorPalette.COMPONENTS)('Tags')} - Failed`); - spinnerComponents.failed(`Pulling ${chalk.hex(colorPalette.COMPONENTS)('Components')} - Failed`); - konsola.br(); - handleError(error as Error, verbose); - } - }); diff --git a/src/commands/components/push/README.md b/src/commands/components/push/README.md deleted file mode 100644 index 133d29ec..00000000 --- a/src/commands/components/push/README.md +++ /dev/null @@ -1,163 +0,0 @@ -# Components Push Command - -The `components push` command allows you to upload components and their dependencies to your Storyblok space. - -> [!WARNING] -> This command requires you have previously used the `components pull` command to download those components. If you used any flags during the pull (like `--suffix` or `--separate-files`), you must apply them with the same values when pushing to ensure files are found correctly. - -## Basic Usage - -```bash -storyblok components push --space YOUR_SPACE_ID -``` - -This will upload all components and their dependencies from: -``` -.storyblok/ -└── components/ - └── YOUR_SPACE_ID/ - β”œβ”€β”€ components.json # All components -``` - -## Push a Single Component - -```bash -storyblok components push COMPONENT_NAME --space YOUR_SPACE_ID -``` - -This will upload a single component and its dependencies from: -``` -.storyblok/ -└── components/ - └── YOUR_SPACE_ID/ - β”œβ”€β”€ COMPONENT_NAME.json # Single component -``` - -## Options - -| Option | Description | Default | -|--------|-------------|---------| -| `-s, --space ` | (Required) The ID of the space to push components to | - | -| `-f, --from ` | Source space ID to read components from | Target space ID | -| `--fi, --filter ` | Glob pattern to filter components by their name (e.g., "hero*" will match all components starting with "hero") | - | -| `--sf, --separate-files` | Read from separate files instead of consolidated files | `false` | -| `--su, --suffix ` | Suffix to add to the files names | - | -| `-p, --path ` | Custom path to read the files from | `.storyblok/components` | - -## Examples - -1. Push all components with default settings: -```bash -storyblok components push --space 12345 -``` -Reads from: -``` -.storyblok/ -└── components/ - └── 12345/ - β”œβ”€β”€ components.json # All components -``` - -2. Push a single component: -```bash -storyblok components push hero --space 12345 -``` -Reads from: -``` -.storyblok/ -└── components/ - └── 12345/ - β”œβ”€β”€ hero.json # Single component -``` - -3. Push components with filter: -```bash -storyblok components push --space 12345 --filter "hero*" -``` -Reads from: -``` -.storyblok/ -└── components/ - └── 12345/ - β”œβ”€β”€ components.json # All components -``` - -4. Push components from a different space: -```bash -storyblok components push --space 12345 --from 67890 -``` -Reads from: -``` -.storyblok/ -└── components/ - └── 67890/ - β”œβ”€β”€ components.json # All components -``` - -5. Push components from separate files: -```bash -storyblok components push --space 12345 --separate-files -``` -Reads from: -``` -.storyblok/ -└── components/ - └── 12345/ - β”œβ”€β”€ hero.json # Individual components - β”œβ”€β”€ hero.presets.json # Component presets - β”œβ”€β”€ feature.json - β”œβ”€β”€ feature.presets.json - β”œβ”€β”€ ... -``` - -6. Push components from a custom path: -```bash -storyblok components push --space 12345 --path ./backup -``` -Reads from: -``` -backup/ -└── components/ - └── 12345/ - β”œβ”€β”€ components.json # All components -``` - -## File Structure - -The command reads from the following file structure: -``` -{path}/ -└── components/ - └── {spaceId}/ - β”œβ”€β”€ components.{suffix}.json # Components file -``` - -When using `--separate-files`: -``` -{path}/ -└── components/ - └── {spaceId}/ - β”œβ”€β”€ {componentName1}.{suffix}.json # Individual components - β”œβ”€β”€ {componentName1}.presets.json # Component presets - β”œβ”€β”€ {componentName2}.{suffix}.json - β”œβ”€β”€ {componentName2}.presets.json - β”œβ”€β”€ ... -``` - -Where: -- `{path}` is the base path (default: `.storyblok`) -- `{spaceId}` is your Storyblok space ID -- `{suffix}` is the suffix in the file name if provided -- `{componentName}` is the name of the component - -## Notes - -- The target space ID is required -- The command will read from the specified path -- When using `--separate-files` or single component, presets are read from separate files named `{componentName}.presets.json` -- The command uploads: - - Components - - Component groups - - Component presets - - Component tags - - Component whitelists diff --git a/src/commands/components/push/actions.ts b/src/commands/components/push/actions.ts deleted file mode 100644 index 83d37085..00000000 --- a/src/commands/components/push/actions.ts +++ /dev/null @@ -1,349 +0,0 @@ -import { FileSystemError, handleAPIError, handleFileSystemError } from '../../../utils'; -import type { SpaceComponent, SpaceComponentGroup, SpaceComponentInternalTag, SpaceComponentPreset, SpaceData } from '../constants'; -import type { ReadComponentsOptions } from './constants'; -import { join } from 'node:path'; -import { readdir, readFile } from 'node:fs/promises'; -import { resolvePath } from '../../../utils/filesystem'; -import type { FileReaderResult } from '../../../types'; -import chalk from 'chalk'; -import { mapiClient } from '../../../api'; - -// Component actions -export const pushComponent = async (space: string, component: SpaceComponent): Promise => { - try { - const client = mapiClient(); - - const { data } = await client.post<{ - component: SpaceComponent; - }>(`spaces/${space}/components`, { - body: JSON.stringify(component), - }); - - return data.component; - } - catch (error) { - handleAPIError('push_component', error as Error, `Failed to push component ${component.name}`); - } -}; - -export const updateComponent = async (space: string, componentId: number, component: SpaceComponent): Promise => { - try { - const client = mapiClient(); - - const { data } = await client.put<{ - component: SpaceComponent; - }>(`spaces/${space}/components/${componentId}`, { - body: JSON.stringify(component), - }); - return data.component; - } - catch (error) { - handleAPIError('update_component', error as Error, `Failed to update component ${component.name}`); - } -}; - -export const upsertComponent = async ( - space: string, - component: SpaceComponent, - existingId?: number, -): Promise => { - if (existingId) { - // We know it exists, update directly - return await updateComponent(space, existingId, component); - } - else { - // New resource, create directly - return await pushComponent(space, component); - } -}; - -// Component group actions - -export const pushComponentGroup = async (space: string, componentGroup: SpaceComponentGroup): Promise => { - try { - const client = mapiClient(); - - const { data } = await client.post<{ - component_group: SpaceComponentGroup; - }>(`spaces/${space}/component_groups`, { - body: JSON.stringify(componentGroup), - }); - return data.component_group; - } - catch (error) { - handleAPIError('push_component_group', error as Error, `Failed to push component group ${componentGroup.name}`); - } -}; - -export const updateComponentGroup = async (space: string, groupId: number, componentGroup: SpaceComponentGroup): Promise => { - try { - const client = mapiClient(); - - const { data } = await client.put<{ - component_group: SpaceComponentGroup; - }>(`spaces/${space}/component_groups/${groupId}`, { - body: JSON.stringify(componentGroup), - }); - return data.component_group; - } - catch (error) { - handleAPIError('update_component_group', error as Error, `Failed to update component group ${componentGroup.name}`); - } -}; - -export const upsertComponentGroup = async ( - space: string, - group: SpaceComponentGroup, - existingId?: number, -): Promise => { - if (existingId) { - // We know it exists, update directly - return await updateComponentGroup(space, existingId, group); - } - else { - // New resource, create directly - return await pushComponentGroup(space, group); - } -}; - -// Component preset actions -export const pushComponentPreset = async (space: string, componentPreset: { preset: Partial }): Promise => { - try { - const client = mapiClient(); - - const { data } = await client.post<{ - preset: SpaceComponentPreset; - }>(`spaces/${space}/presets`, { - body: JSON.stringify(componentPreset), - }); - return data.preset; - } - catch (error) { - handleAPIError('push_component_preset', error as Error, `Failed to push component preset ${componentPreset.preset.name}`); - } -}; - -export const updateComponentPreset = async (space: string, presetId: number, componentPreset: { preset: Partial }): Promise => { - try { - const client = mapiClient(); - - const { data } = await client.put<{ - preset: SpaceComponentPreset; - }>(`spaces/${space}/presets/${presetId}`, { - body: JSON.stringify(componentPreset), - }); - return data.preset; - } - catch (error) { - handleAPIError('update_component_preset', error as Error, `Failed to update component preset ${componentPreset.preset.name}`); - } -}; - -export const upsertComponentPreset = async ( - space: string, - preset: Partial, - existingId?: number, -): Promise => { - if (existingId) { - // We know it exists, update directly - return await updateComponentPreset(space, existingId, { preset }); - } - else { - // New resource, create directly - return await pushComponentPreset(space, { preset }); - } -}; - -// Component internal tag actions - -export const pushComponentInternalTag = async (space: string, componentInternalTag: SpaceComponentInternalTag): Promise => { - try { - const client = mapiClient(); - - const { data } = await client.post<{ - internal_tag: SpaceComponentInternalTag; - }>(`spaces/${space}/internal_tags`, { - method: 'POST', - body: JSON.stringify(componentInternalTag), - }); - - return data.internal_tag; - } - catch (error) { - handleAPIError('push_component_internal_tag', error as Error, `Failed to push component internal tag ${componentInternalTag.name}`); - } -}; - -export const updateComponentInternalTag = async (space: string, tagId: number, componentInternalTag: SpaceComponentInternalTag): Promise => { - try { - const client = mapiClient(); - - const { data } = await client.put<{ - internal_tag: SpaceComponentInternalTag; - }>(`spaces/${space}/internal_tags/${tagId}`, { - method: 'PUT', - body: JSON.stringify(componentInternalTag), - }); - - return data.internal_tag; - } - catch (error) { - handleAPIError('update_component_internal_tag', error as Error, `Failed to update component internal tag ${componentInternalTag.name}`); - } -}; - -export const upsertComponentInternalTag = async ( - space: string, - tag: SpaceComponentInternalTag, - existingId?: number, -): Promise => { - if (existingId) { - // We know it exists, update directly - return await updateComponentInternalTag(space, existingId, tag); - } - else { - // New resource, create directly - return await pushComponentInternalTag(space, tag); - } -}; - -async function readJsonFile(filePath: string): Promise> { - try { - const content = (await readFile(filePath)).toString(); - if (!content) { - return { data: [] }; - } - const parsed = JSON.parse(content); - return { data: Array.isArray(parsed) ? parsed : [parsed] }; - } - catch (error) { - return { data: [], error: error as Error }; - } -} - -export const readComponentsFiles = async ( - options: ReadComponentsOptions): Promise => { - const { from, path, separateFiles = false, suffix, space } = options; - const resolvedPath = resolvePath(path, `components/${from}`); - - // Check if directory exists first - try { - await readdir(resolvedPath); - } - catch (error) { - const message = `No local components found for space ${chalk.bold(from)}. To push components, you need to pull them first: - -1. Pull the components from your source space: - ${chalk.cyan(`storyblok components pull --space ${from}`)} - -2. Then try pushing again: - ${chalk.cyan(`storyblok components push --space ${space} --from ${from}`)}`; - - throw new FileSystemError( - 'file_not_found', - 'read', - error as Error, - message, - ); - } - - if (separateFiles) { - return await readSeparateFiles(resolvedPath, suffix); - } - - return await readConsolidatedFiles(resolvedPath, suffix); -}; - -async function readSeparateFiles(resolvedPath: string, suffix?: string): Promise { - const files = await readdir(resolvedPath); - const components: SpaceComponent[] = []; - const presets: SpaceComponentPreset[] = []; - let groups: SpaceComponentGroup[] = []; - let internalTags: SpaceComponentInternalTag[] = []; - - const filteredFiles = files.filter((file) => { - if (suffix) { - return file.endsWith(`.${suffix}.json`); - } - else { - // Regex to match files with a pattern like ..json - return !/\.\w+\.json$/.test(file) || file.endsWith('.presets.json'); ; - } - }); - - for (const file of filteredFiles) { - const filePath = join(resolvedPath, file); - - if (file === 'groups.json' || file === `groups.${suffix}.json`) { - const result = await readJsonFile(filePath); - if (result.error) { - handleFileSystemError('read', result.error); - continue; - } - groups = result.data; - } - else if (file === 'tags.json' || file === `tags.${suffix}.json`) { - const result = await readJsonFile(filePath); - if (result.error) { - handleFileSystemError('read', result.error); - continue; - } - internalTags = result.data; - } - else if (file.endsWith('.presets.json') || file.endsWith(`.presets.${suffix}.json`)) { - const result = await readJsonFile(filePath); - if (result.error) { - handleFileSystemError('read', result.error); - continue; - } - presets.push(...result.data); - } - else if (file.endsWith('.json') || file.endsWith(`${suffix}.json`)) { - if (file === 'components.json' || file === `components.${suffix}.json`) { - continue; - } - const result = await readJsonFile(filePath); - if (result.error) { - handleFileSystemError('read', result.error); - continue; - } - components.push(...result.data); - } - } - - return { - components, - groups, - presets, - internalTags, - }; -} - -async function readConsolidatedFiles(resolvedPath: string, suffix?: string): Promise { - // Read required components file - const componentsPath = join(resolvedPath, suffix ? `components.${suffix}.json` : 'components.json'); - const componentsResult = await readJsonFile(componentsPath); - - if (componentsResult.error || !componentsResult.data.length) { - throw new FileSystemError( - 'file_not_found', - 'read', - componentsResult.error || new Error('Components file is empty'), - `No components found in ${componentsPath}. Please make sure you have pulled the components first.`, - ); - } - - // Read optional files - const [groupsResult, presetsResult, tagsResult] = await Promise.all([ - readJsonFile(join(resolvedPath, suffix ? `groups.${suffix}.json` : 'groups.json')), - readJsonFile(join(resolvedPath, suffix ? `presets.${suffix}.json` : 'presets.json')), - readJsonFile(join(resolvedPath, suffix ? `tags.${suffix}.json` : 'tags.json')), - ]); - - return { - components: componentsResult.data, - groups: groupsResult.data, - presets: presetsResult.data, - internalTags: tagsResult.data, - }; -} diff --git a/src/commands/components/push/constants.ts b/src/commands/components/push/constants.ts deleted file mode 100644 index 08e8e26d..00000000 --- a/src/commands/components/push/constants.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { CommandOptions } from '../../../types'; - -export interface PushComponentsOptions extends CommandOptions { - - /** - * The glob pattern filter to apply to components before pushing. - * Matching components and all their dependencies (groups, tags, other components) - * will be collected and pushed together. - * @default `.*` - */ - filter?: string; - /** - * Indicates whether to save each component to a separate file. - * @default false - */ - separateFiles?: boolean; - /** - * The source space id. - */ - from?: string; - /** - * Suffix to add to the component name. - */ - suffix?: string; -} - -export interface ReadComponentsOptions extends PushComponentsOptions { - /** - * The path to read the components file from. - * Defaults to `.storyblok/components`. - * @default `.storyblok/components` - */ - path?: string; - /** - * Target space - */ - space?: string; -} diff --git a/src/commands/components/push/graph-operations/__tests__/graph-integration.test.ts b/src/commands/components/push/graph-operations/__tests__/graph-integration.test.ts deleted file mode 100644 index b1f755ef..00000000 --- a/src/commands/components/push/graph-operations/__tests__/graph-integration.test.ts +++ /dev/null @@ -1,494 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { buildDependencyGraph, determineProcessingOrder, validateGraph } from '../dependency-graph'; -import { processAllResources } from '../resource-processor'; -import type { SpaceDataState } from '../../../constants'; - -// Mock the API functions -vi.mock('../../actions', () => ({ - upsertComponent: vi.fn(), - upsertComponentGroup: vi.fn(), - upsertComponentInternalTag: vi.fn(), - upsertComponentPreset: vi.fn(), - pushComponent: vi.fn(), -})); - -// Mock progress display -vi.mock('../../progress-display', () => ({ - progressDisplay: { - start: vi.fn(), - handleEvent: vi.fn(), - }, -})); - -describe('graph Integration Tests', () => { - beforeEach(() => { - vi.clearAllMocks(); - }); - - describe('source/Target Reconciliation', () => { - it('should correctly reconcile resources with different IDs between source and target spaces', () => { - // Scenario: Source space has resources with IDs 100-400, target space has same resources with IDs 500-800 - const spaceState: SpaceDataState = { - local: { - components: [{ - id: 300, - name: 'shared-component', - display_name: 'Shared Component', - created_at: '2023-01-01T00:00:00.000Z', - updated_at: '2023-01-01T00:00:00.000Z', - schema: {}, - color: null, - internal_tags_list: [], - internal_tag_ids: ['100'], // References source tag ID - component_group_uuid: 'shared-group-uuid', // References source group - }], - groups: [{ - id: 200, - name: 'shared-group', - uuid: 'shared-group-uuid', - parent_id: null, - parent_uuid: null, - }], - internalTags: [{ - id: 100, - name: 'shared-tag', - object_type: 'component' as const, - }], - presets: [{ - id: 400, - name: 'shared-preset', - preset: { title: 'Test Preset' }, - component_id: 300, // References source component ID - space_id: 1, - created_at: '2023-01-01T00:00:00.000Z', - updated_at: '2023-01-01T00:00:00.000Z', - image: '', - color: '', - icon: '', - description: '', - }], - }, - target: { - components: new Map([['shared-component', { - id: 700, - name: 'shared-component', - display_name: 'Shared Component', - created_at: '2023-01-01T00:00:00.000Z', - updated_at: '2023-01-01T00:00:00.000Z', - schema: {}, - color: null, - internal_tags_list: [], - internal_tag_ids: ['500'], // Different tag ID in target - component_group_uuid: 'shared-group-uuid', - }]]), - groups: new Map([['shared-group', { - id: 600, - name: 'shared-group', - uuid: 'shared-group-uuid', - parent_id: null, - parent_uuid: null, - }]]), - tags: new Map([['shared-tag', { - id: 500, - name: 'shared-tag', - object_type: 'component' as const, - }]]), - presets: new Map([['shared-preset', { - id: 800, - name: 'shared-preset', - preset: { title: 'Test Preset' }, - component_id: 700, // Different component ID in target - space_id: 2, - created_at: '2023-01-01T00:00:00.000Z', - updated_at: '2023-01-01T00:00:00.000Z', - image: '', - color: '', - icon: '', - description: '', - }]]), - }, - }; - - // Build the dependency graph - const graph = buildDependencyGraph({ spaceState }); - - // Validate the graph structure - expect(graph.nodes.size).toBe(4); // tag, group, component, preset - expect(graph.nodes.has('tag:100')).toBe(true); - expect(graph.nodes.has('group:shared-group-uuid')).toBe(true); - expect(graph.nodes.has('component:shared-component')).toBe(true); - expect(graph.nodes.has('preset:shared-preset')).toBe(true); - - // Check dependencies are correctly established - const componentNode = graph.nodes.get('component:shared-component')!; - const presetNode = graph.nodes.get('preset:shared-preset')!; - const tagNode = graph.nodes.get('tag:100')!; - const groupNode = graph.nodes.get('group:shared-group-uuid')!; - - // Component should depend on tag and group - expect(componentNode.dependencies.has('tag:100')).toBe(true); - expect(componentNode.dependencies.has('group:shared-group-uuid')).toBe(true); - - // Preset should depend on component - expect(presetNode.dependencies.has('component:shared-component')).toBe(true); - - // Check target data is correctly colocated - expect(componentNode.targetData?.id).toBe(700); - expect(tagNode.targetData?.id).toBe(500); - expect(groupNode.targetData?.id).toBe(600); - expect(presetNode.targetData?.id).toBe(800); - }); - - it('should handle hierarchical group dependencies correctly', () => { - const spaceState: SpaceDataState = { - local: { - components: [], - groups: [ - { - id: 1, - name: 'parent-group', - uuid: 'parent-uuid', - parent_id: null, - parent_uuid: null, - }, - { - id: 2, - name: 'child-group', - uuid: 'child-uuid', - parent_id: 1, - parent_uuid: 'parent-uuid', - }, - { - id: 3, - name: 'grandchild-group', - uuid: 'grandchild-uuid', - parent_id: 2, - parent_uuid: 'child-uuid', - }, - ], - internalTags: [], - presets: [], - }, - target: { - components: new Map(), - groups: new Map([ - ['parent-group', { id: 10, name: 'parent-group', uuid: 'parent-uuid', parent_id: null, parent_uuid: null }], - ['child-group', { id: 20, name: 'child-group', uuid: 'child-uuid', parent_id: 10, parent_uuid: 'parent-uuid' }], - ['grandchild-group', { id: 30, name: 'grandchild-group', uuid: 'grandchild-uuid', parent_id: 20, parent_uuid: 'child-uuid' }], - ]), - tags: new Map(), - presets: new Map(), - }, - }; - - const graph = buildDependencyGraph({ spaceState }); - const processingOrder = determineProcessingOrder(graph); - - // Parent should be processed before child, child before grandchild - const parentLevel = processingOrder.findIndex(level => level.nodes.includes('group:parent-uuid')); - const childLevel = processingOrder.findIndex(level => level.nodes.includes('group:child-uuid')); - const grandchildLevel = processingOrder.findIndex(level => level.nodes.includes('group:grandchild-uuid')); - - expect(parentLevel).toBeLessThan(childLevel); - expect(childLevel).toBeLessThan(grandchildLevel); - }); - - it('should resolve references correctly during processing', async () => { - const { upsertComponent, upsertComponentGroup, upsertComponentInternalTag, upsertComponentPreset } = await import('../../actions'); - - // Mock successful upserts with new target IDs - (upsertComponentInternalTag as any).mockResolvedValue({ id: 1001, name: 'test-tag', object_type: 'component' }); - (upsertComponentGroup as any).mockResolvedValue({ id: 2001, name: 'test-group', uuid: 'test-group-uuid', parent_id: null, parent_uuid: null }); - (upsertComponent as any).mockResolvedValue({ - id: 3001, - name: 'test-component', - display_name: 'Test Component', - created_at: '2023-01-01T00:00:00.000Z', - updated_at: '2023-01-01T00:00:00.000Z', - schema: {}, - color: null, - internal_tags_list: [], - internal_tag_ids: ['1001'], // Should be resolved to new tag ID - component_group_uuid: 'test-group-uuid', - }); - (upsertComponentPreset as any).mockResolvedValue({ - id: 4001, - name: 'test-preset', - preset: { title: 'Test' }, - component_id: 3001, // Should be resolved to new component ID - space_id: 1, - created_at: '2023-01-01T00:00:00.000Z', - updated_at: '2023-01-01T00:00:00.000Z', - image: '', - color: '', - icon: '', - description: '', - }); - - const spaceState: SpaceDataState = { - local: { - components: [{ - id: 100, - name: 'test-component', - display_name: 'Test Component', - created_at: '2023-01-01T00:00:00.000Z', - updated_at: '2023-01-01T00:00:00.000Z', - schema: {}, - color: null, - internal_tags_list: [], - internal_tag_ids: ['200'], // Source tag ID - component_group_uuid: 'test-group-uuid', - }], - groups: [{ - id: 300, - name: 'test-group', - uuid: 'test-group-uuid', - parent_id: null, - parent_uuid: null, - }], - internalTags: [{ - id: 200, - name: 'test-tag', - object_type: 'component' as const, - }], - presets: [{ - id: 400, - name: 'test-preset', - preset: { title: 'Test' }, - component_id: 100, // Source component ID - space_id: 1, - created_at: '2023-01-01T00:00:00.000Z', - updated_at: '2023-01-01T00:00:00.000Z', - image: '', - color: '', - icon: '', - description: '', - }], - }, - target: { - components: new Map(), - groups: new Map(), - tags: new Map(), - presets: new Map(), - }, - }; - - const graph = buildDependencyGraph({ spaceState }); - const results = await processAllResources(graph, 'test-space', 5, true); - - // All resources should be processed successfully - expect(results.successful).toHaveLength(4); - expect(results.failed).toHaveLength(0); - - // Verify that references were resolved correctly - const componentCall = (upsertComponent as any).mock.calls[0]; - const componentData = componentCall[1]; - - // Component should reference the resolved tag ID (1001) not the source ID (200) - expect(componentData.internal_tag_ids).toEqual(['1001']); - - const presetCall = (upsertComponentPreset as any).mock.calls[0]; - const presetData = presetCall[1]; - - // Preset should reference the resolved component ID (3001) not the source ID (100) - expect(presetData.component_id).toBe(3001); - }); - }); - - describe('complex Schema Dependencies', () => { - it('should resolve nested schema references correctly', async () => { - const { upsertComponent, upsertComponentGroup, upsertComponentInternalTag } = await import('../../actions'); - - // Mock successful upserts - (upsertComponentInternalTag as any).mockResolvedValue({ id: 1001, name: 'schema-tag', object_type: 'component' }); - (upsertComponentGroup as any).mockResolvedValue({ id: 2001, name: 'schema-group', uuid: 'schema-group-uuid', parent_id: null, parent_uuid: null }); - (upsertComponent as any) - .mockResolvedValueOnce({ id: 3001, name: 'base-component', display_name: 'Base', created_at: '2023-01-01T00:00:00.000Z', updated_at: '2023-01-01T00:00:00.000Z', schema: {}, color: null, internal_tags_list: [], internal_tag_ids: [] }) - .mockResolvedValueOnce({ - id: 3002, - name: 'complex-component', - display_name: 'Complex', - created_at: '2023-01-01T00:00:00.000Z', - updated_at: '2023-01-01T00:00:00.000Z', - schema: { - content: { - type: 'bloks', - component_whitelist: ['base-component'], - component_group_whitelist: ['schema-group-uuid'], // Should be resolved - component_tag_whitelist: [1001], // Should be resolved - }, - }, - color: null, - internal_tags_list: [], - internal_tag_ids: [], - }); - - const spaceState: SpaceDataState = { - local: { - components: [ - { - id: 1, - name: 'base-component', - display_name: 'Base', - created_at: '2023-01-01T00:00:00.000Z', - updated_at: '2023-01-01T00:00:00.000Z', - schema: {}, - color: null, - internal_tags_list: [], - internal_tag_ids: [], - }, - { - id: 2, - name: 'complex-component', - display_name: 'Complex', - created_at: '2023-01-01T00:00:00.000Z', - updated_at: '2023-01-01T00:00:00.000Z', - schema: { - content: { - type: 'bloks', - component_whitelist: ['base-component'], - component_group_whitelist: ['schema-group-uuid'], - component_tag_whitelist: [100], // Source tag ID - }, - }, - color: null, - internal_tags_list: [], - internal_tag_ids: [], - }, - ], - groups: [{ - id: 200, - name: 'schema-group', - uuid: 'schema-group-uuid', - parent_id: null, - parent_uuid: null, - }], - internalTags: [{ - id: 100, - name: 'schema-tag', - object_type: 'component' as const, - }], - presets: [], - }, - target: { - components: new Map(), - groups: new Map(), - tags: new Map(), - presets: new Map(), - }, - }; - - const graph = buildDependencyGraph({ spaceState }); - await processAllResources(graph, 'test-space', 5, true); - - // Verify that schema references were resolved - const complexComponentCall = (upsertComponent as any).mock.calls.find( - call => call[1].name === 'complex-component', - ); - - expect(complexComponentCall).toBeDefined(); - const schemaContent = complexComponentCall[1].schema.content; - - // Tag whitelist should be resolved to new tag ID - expect(schemaContent.component_tag_whitelist).toEqual([1001]); - - // Group whitelist should remain as UUID (UUIDs don't change) - expect(schemaContent.component_group_whitelist).toEqual(['schema-group-uuid']); - }); - }); - - describe('error Handling', () => { - it('should handle missing dependencies gracefully', () => { - const spaceState: SpaceDataState = { - local: { - components: [{ - id: 1, - name: 'component-with-missing-deps', - display_name: 'Component with Missing Deps', - created_at: '2023-01-01T00:00:00.000Z', - updated_at: '2023-01-01T00:00:00.000Z', - schema: {}, - color: null, - internal_tags_list: [], - internal_tag_ids: ['999'], // Non-existent tag - component_group_uuid: 'non-existent-group-uuid', - }], - groups: [], - internalTags: [], - presets: [], - }, - target: { - components: new Map(), - groups: new Map(), - tags: new Map(), - presets: new Map(), - }, - }; - - // Should not throw, but should handle missing dependencies - expect(() => { - const graph = buildDependencyGraph({ spaceState }); - validateGraph(graph); - }).not.toThrow(); - }); - - it('should detect circular dependencies', () => { - const spaceState: SpaceDataState = { - local: { - components: [ - { - id: 1, - name: 'component-a', - display_name: 'Component A', - created_at: '2023-01-01T00:00:00.000Z', - updated_at: '2023-01-01T00:00:00.000Z', - schema: { - content: { - type: 'bloks', - component_whitelist: ['component-b'], - }, - }, - color: null, - internal_tags_list: [], - internal_tag_ids: [], - }, - { - id: 2, - name: 'component-b', - display_name: 'Component B', - created_at: '2023-01-01T00:00:00.000Z', - updated_at: '2023-01-01T00:00:00.000Z', - schema: { - content: { - type: 'bloks', - component_whitelist: ['component-a'], // Circular reference - }, - }, - color: null, - internal_tags_list: [], - internal_tag_ids: [], - }, - ], - groups: [], - internalTags: [], - presets: [], - }, - target: { - components: new Map(), - groups: new Map(), - tags: new Map(), - presets: new Map(), - }, - }; - - const graph = buildDependencyGraph({ spaceState }); - - // Should detect the circular dependency but not throw (components can have circular refs) - expect(() => validateGraph(graph)).not.toThrow(); - - // Processing order should handle the circular dependency - const processingOrder = determineProcessingOrder(graph); - expect(processingOrder.length).toBeGreaterThan(0); - }); - }); -}); diff --git a/src/commands/components/push/graph-operations/__tests__/test-data.ts b/src/commands/components/push/graph-operations/__tests__/test-data.ts deleted file mode 100644 index 00abb768..00000000 --- a/src/commands/components/push/graph-operations/__tests__/test-data.ts +++ /dev/null @@ -1,313 +0,0 @@ -import type { - SpaceComponent, - SpaceComponentGroup, - SpaceComponentInternalTag, - SpaceComponentPreset, - SpaceDataState, -} from '../../../constants'; - -// ============================================================================= -// TEST DATA BUILDERS -// ============================================================================= - -export function createTestTag(overrides: Partial = {}): SpaceComponentInternalTag { - return { - id: 1, - name: 'test-tag', - object_type: 'component', - ...overrides, - }; -} - -export function createTestGroup(overrides: Partial = {}): SpaceComponentGroup { - return { - id: 1, - name: 'test-group', - uuid: 'test-group-uuid', - parent_id: null, - parent_uuid: null, - ...overrides, - }; -} - -export function createTestComponent(overrides: Partial = {}): SpaceComponent { - return { - id: 1, - name: 'test-component', - display_name: 'Test Component', - created_at: '2023-01-01T00:00:00.000Z', - updated_at: '2023-01-01T00:00:00.000Z', - schema: {}, - color: null, - internal_tags_list: [], - internal_tag_ids: [], - ...overrides, - }; -} - -export function createTestPreset(overrides: Partial = {}): SpaceComponentPreset { - return { - id: 1, - name: 'test-preset', - preset: { title: 'Test Preset' }, - component_id: 1, - space_id: 1, - created_at: '2023-01-01T00:00:00.000Z', - updated_at: '2023-01-01T00:00:00.000Z', - image: '', - color: '', - icon: '', - description: '', - ...overrides, - }; -} - -// ============================================================================= -// COMPLEX SCENARIO BUILDERS -// ============================================================================= - -/** - * Creates a scenario with hierarchical groups (parent-child relationships) - */ -export function createHierarchicalGroupsScenario() { - const parentGroup = createTestGroup({ - id: 1, - name: 'parent-group', - uuid: 'parent-uuid', - }); - - const childGroup = createTestGroup({ - id: 2, - name: 'child-group', - uuid: 'child-uuid', - parent_id: 1, - parent_uuid: 'parent-uuid', - }); - - const grandchildGroup = createTestGroup({ - id: 3, - name: 'grandchild-group', - uuid: 'grandchild-uuid', - parent_id: 2, - parent_uuid: 'child-uuid', - }); - - return { parentGroup, childGroup, grandchildGroup }; -} - -/** - * Creates a scenario with components that have complex schema dependencies - */ -export function createComplexSchemaDependenciesScenario() { - const tag1 = createTestTag({ id: 1, name: 'tag-1' }); - const tag2 = createTestTag({ id: 2, name: 'tag-2' }); - - const group1 = createTestGroup({ id: 1, name: 'group-1', uuid: 'group-1-uuid' }); - const group2 = createTestGroup({ id: 2, name: 'group-2', uuid: 'group-2-uuid' }); - - const baseComponent = createTestComponent({ - id: 1, - name: 'base-component', - schema: { - title: { type: 'text' }, - }, - }); - - const complexComponent = createTestComponent({ - id: 2, - name: 'complex-component', - component_group_uuid: 'group-1-uuid', - internal_tag_ids: ['1', '2'], - schema: { - content: { - type: 'bloks', - component_whitelist: ['base-component'], - component_group_whitelist: ['group-2-uuid'], - component_tag_whitelist: [1, 2], - }, - nested: { - type: 'blocks', - blocks: [{ - type: 'bloks', - component_whitelist: ['base-component'], - }], - }, - }, - }); - - return { tag1, tag2, group1, group2, baseComponent, complexComponent }; -} - -/** - * Creates a scenario with presets that depend on components - */ -export function createPresetDependenciesScenario() { - const component1 = createTestComponent({ - id: 1, - name: 'component-1', - }); - - const component2 = createTestComponent({ - id: 2, - name: 'component-2', - }); - - const preset1 = createTestPreset({ - id: 1, - name: 'preset-1', - component_id: 1, - preset: { title: 'Default Title 1' }, - }); - - const preset2 = createTestPreset({ - id: 2, - name: 'preset-2', - component_id: 2, - preset: { title: 'Default Title 2' }, - }); - - return { component1, component2, preset1, preset2 }; -} - -/** - * Creates a scenario where source and target spaces have different IDs - * This tests the core reconciliation logic - */ -export function createSourceTargetReconciliationScenario() { - // Source space data (what we're pushing from) - const sourceTag = createTestTag({ id: 100, name: 'shared-tag' }); - const sourceGroup = createTestGroup({ id: 200, name: 'shared-group', uuid: 'shared-group-uuid' }); - const sourceComponent = createTestComponent({ - id: 300, - name: 'shared-component', - component_group_uuid: 'shared-group-uuid', - internal_tag_ids: ['100'], - }); - const sourcePreset = createTestPreset({ - id: 400, - name: 'shared-preset', - component_id: 300, - }); - - // Target space data (what exists in destination, with different IDs) - const targetTag = createTestTag({ id: 500, name: 'shared-tag' }); - const targetGroup = createTestGroup({ id: 600, name: 'shared-group', uuid: 'shared-group-uuid' }); - const targetComponent = createTestComponent({ - id: 700, - name: 'shared-component', - component_group_uuid: 'shared-group-uuid', - internal_tag_ids: ['500'], // Note: different tag ID - }); - const targetPreset = createTestPreset({ - id: 800, - name: 'shared-preset', - component_id: 700, // Note: different component ID - }); - - return { - source: { tag: sourceTag, group: sourceGroup, component: sourceComponent, preset: sourcePreset }, - target: { tag: targetTag, group: targetGroup, component: targetComponent, preset: targetPreset }, - }; -} - -/** - * Creates a complete SpaceDataState for testing - */ -export function createTestSpaceDataState( - localData: { - components?: SpaceComponent[]; - groups?: SpaceComponentGroup[]; - tags?: SpaceComponentInternalTag[]; - presets?: SpaceComponentPreset[]; - } = {}, - targetData: { - components?: SpaceComponent[]; - groups?: SpaceComponentGroup[]; - tags?: SpaceComponentInternalTag[]; - presets?: SpaceComponentPreset[]; - } = {}, -): SpaceDataState { - return { - local: { - components: localData.components || [], - groups: localData.groups || [], - internalTags: localData.tags || [], - presets: localData.presets || [], - }, - target: { - components: new Map((targetData.components || []).map(c => [c.name, c])), - groups: new Map((targetData.groups || []).map(g => [g.name, g])), - tags: new Map((targetData.tags || []).map(t => [t.name, t])), - presets: new Map((targetData.presets || []).map(p => [p.name, p])), - }, - }; -} - -// ============================================================================= -// EDGE CASE SCENARIOS -// ============================================================================= - -/** - * Creates a scenario with circular dependencies (should be detected and handled) - */ -export function createCircularDependencyScenario() { - const componentA = createTestComponent({ - id: 1, - name: 'component-a', - schema: { - content: { - type: 'bloks', - component_whitelist: ['component-b'], - }, - }, - }); - - const componentB = createTestComponent({ - id: 2, - name: 'component-b', - schema: { - content: { - type: 'bloks', - component_whitelist: ['component-a'], // Circular reference - }, - }, - }); - - return { componentA, componentB }; -} - -/** - * Creates a scenario with missing dependencies (references that don't exist) - */ -export function createMissingDependenciesScenario() { - const component = createTestComponent({ - id: 1, - name: 'component-with-missing-deps', - component_group_uuid: 'non-existent-group-uuid', - internal_tag_ids: ['999'], // Non-existent tag - schema: { - content: { - type: 'bloks', - component_whitelist: ['non-existent-component'], - component_group_whitelist: ['non-existent-group-uuid'], - component_tag_whitelist: [999], - }, - }, - }); - - return { component }; -} - -/** - * Creates a scenario where target space has resources that don't exist in source - */ -export function createTargetOnlyResourcesScenario() { - const sourceComponent = createTestComponent({ id: 1, name: 'source-only' }); - const targetComponent1 = createTestComponent({ id: 2, name: 'source-only' }); - const targetComponent2 = createTestComponent({ id: 3, name: 'target-only' }); - - return { - source: [sourceComponent], - target: [targetComponent1, targetComponent2], - }; -} diff --git a/src/commands/components/push/graph-operations/dependency-graph.ts b/src/commands/components/push/graph-operations/dependency-graph.ts deleted file mode 100644 index f0c2bbfe..00000000 --- a/src/commands/components/push/graph-operations/dependency-graph.ts +++ /dev/null @@ -1,771 +0,0 @@ -import type { - SpaceComponent, - SpaceComponentGroup, - SpaceComponentInternalTag, - SpaceComponentPreset, -} from '../../constants'; -import type { DependencyGraph, GraphBuildingContext, NodeData, NodeType, ProcessingLevel, SchemaDependencies, TargetResourceInfo, UnifiedNode } from './types'; -import { upsertComponent, upsertComponentGroup, upsertComponentInternalTag, upsertComponentPreset } from '../actions'; - -// ============================================================================= -// GRAPH BUILDING -// ============================================================================= - -/** - * Builds a complete dependency graph from the space data with colocated target data. - * Each node in the graph represents a resource (component, group, or tag) and - * maintains bidirectional links to its dependencies and dependents. - * Target data is colocated with each node for efficient upsert operations. - */ -export function buildDependencyGraph(context: GraphBuildingContext): DependencyGraph { - const { spaceState } = context; - const graph: DependencyGraph = { nodes: new Map() }; - - // Helper function to establish bidirectional dependencies - function addDependency(dependentId: string, dependencyId: string) { - const dependent = graph.nodes.get(dependentId); - const dependency = graph.nodes.get(dependencyId); - - if (dependent && dependency) { - dependent.dependencies.add(dependencyId); - dependency.dependents.add(dependentId); - } - } - - // Create nodes for all tags with colocated target data - spaceState.local.internalTags.forEach((tag) => { - const nodeId = `tag:${tag.id}`; - const targetTag = spaceState.target.tags.get(tag.name); - const node = new TagNode(nodeId, tag, targetTag); - graph.nodes.set(nodeId, node); - }); - - // Create nodes for all groups with colocated target data - spaceState.local.groups.forEach((group) => { - const nodeId = `group:${group.uuid}`; - const targetGroup = spaceState.target.groups.get(group.name); - const node = new GroupNode(nodeId, group, targetGroup); - graph.nodes.set(nodeId, node); - }); - - // Create nodes for all components with colocated target data - spaceState.local.components.forEach((component) => { - const nodeId = `component:${component.name}`; - const targetComponent = spaceState.target.components.get(component.name); - const node = new ComponentNode(nodeId, component, targetComponent); - graph.nodes.set(nodeId, node); - }); - - // Create nodes for all presets with colocated target data - spaceState.local.presets.forEach((preset) => { - const nodeId = `preset:${preset.name}`; - const targetPreset = spaceState.target.presets.get(preset.name); - const targetData = targetPreset - ? { - resource: targetPreset, - id: targetPreset.id, - } - : undefined; - const node = new PresetNode(preset, targetData); - graph.nodes.set(nodeId, node); - }); - - // Add group parent dependencies - spaceState.local.groups.forEach((group) => { - if (group.parent_uuid) { - const childId = `group:${group.uuid}`; - const parentId = `group:${group.parent_uuid}`; - addDependency(childId, parentId); - } - }); - - // Add component dependencies based on schema analysis - spaceState.local.components.forEach((component) => { - const componentId = `component:${component.name}`; - - // Add dependencies on internal_tag_ids (direct component tag references) - if (component.internal_tag_ids && component.internal_tag_ids.length > 0) { - component.internal_tag_ids.forEach((tagId) => { - const tagNodeId = `tag:${tagId}`; - addDependency(componentId, tagNodeId); - }); - } - - // Add dependencies on component_group_uuid (component group assignment) - if (component.component_group_uuid) { - const groupId = `group:${component.component_group_uuid}`; - addDependency(componentId, groupId); - } - - // Add dependencies on preset_id (component preset reference) - if (component.preset_id) { - // Find the preset by ID and create dependency - const preset = spaceState.local.presets.find(p => p.id === component.preset_id); - if (preset) { - const presetId = `preset:${preset.name}`; - addDependency(componentId, presetId); - } - } - - if (component.schema) { - const dependencies = collectWhitelistDependencies(component.schema); - - // Add dependencies on groups (from schema whitelists) - dependencies.groupUuids.forEach((groupUuid) => { - const groupId = `group:${groupUuid}`; - addDependency(componentId, groupId); - }); - - // Add dependencies on tags (from schema whitelists) - dependencies.tagIds.forEach((tagId) => { - const tagNodeId = `tag:${tagId}`; - addDependency(componentId, tagNodeId); - }); - - // Add dependencies on other components - dependencies.componentNames.forEach((componentName) => { - const dependencyId = `component:${componentName}`; - addDependency(componentId, dependencyId); - }); - } - }); - - // Add preset dependencies on components - spaceState.local.presets.forEach((preset) => { - const presetId = `preset:${preset.name}`; - - // Find the component this preset belongs to - const component = spaceState.local.components.find(c => c.id === preset.component_id); - if (component) { - const componentId = `component:${component.name}`; - addDependency(presetId, componentId); - } - }); - - return graph; -} - -// ============================================================================= -// DEPENDENCY EXTRACTION -// ============================================================================= - -/** - * Extracts dependency information from a component schema. - * Traverses the schema to find references to groups, tags, and other components. - */ -export function collectWhitelistDependencies(schema: Record): SchemaDependencies { - const groupUuids = new Set(); - const tagIds = new Set(); - const componentNames = new Set(); - - function traverseField(field: Record) { - if (field.type === 'bloks') { - // Collect group dependencies - if (field.component_group_whitelist && Array.isArray(field.component_group_whitelist)) { - field.component_group_whitelist.forEach((uuid: string) => groupUuids.add(uuid)); - } - - // Collect tag dependencies - if (field.component_tag_whitelist && Array.isArray(field.component_tag_whitelist)) { - field.component_tag_whitelist.forEach((tagId: number) => tagIds.add(tagId)); - } - - // Collect component dependencies - if (field.component_whitelist && Array.isArray(field.component_whitelist)) { - field.component_whitelist.forEach((name: string) => componentNames.add(name)); - } - } - - // Recursively traverse nested fields - Object.values(field).forEach((value) => { - if (Array.isArray(value)) { - value.forEach((item) => { - if (typeof item === 'object' && item !== null) { - traverseField(item); - } - }); - } - else if (typeof value === 'object' && value !== null) { - traverseField(value); - } - }); - } - - // Traverse all fields in the schema - Object.values(schema).forEach((field) => { - if (typeof field === 'object' && field !== null) { - traverseField(field); - } - }); - - return { groupUuids, tagIds, componentNames }; -} - -// ============================================================================= -// GRAPH VALIDATION -// ============================================================================= - -/** - * Detects problematic cycles in the dependency graph. - * Returns an array of cycle descriptions that would prevent successful processing. - */ -export function detectProblematicCycles(graph: DependencyGraph): string[] { - const problematicCycles: string[] = []; - const visited = new Set(); - const recursionStack = new Set(); - - function dfs(nodeId: string, path: string[]): boolean { - if (recursionStack.has(nodeId)) { - // Found a cycle - check if it's problematic - const cycleStart = path.indexOf(nodeId); - const cycle = path.slice(cycleStart).concat(nodeId); - - // Cycles involving groups or tags are always problematic - const hasGroupOrTag = cycle.some(id => id.startsWith('group:') || id.startsWith('tag:')); - if (hasGroupOrTag) { - problematicCycles.push(`Problematic cycle: ${cycle.join(' β†’ ')}`); - } - - return true; - } - - if (visited.has(nodeId)) { - return false; - } - - visited.add(nodeId); - recursionStack.add(nodeId); - path.push(nodeId); - - const node = graph.nodes.get(nodeId); - if (node) { - for (const dependencyId of node.dependencies) { - if (dfs(dependencyId, [...path])) { - return true; - } - } - } - - recursionStack.delete(nodeId); - path.pop(); - return false; - } - - // Check each node for cycles - for (const nodeId of graph.nodes.keys()) { - if (!visited.has(nodeId)) { - dfs(nodeId, []); - } - } - - return problematicCycles; -} - -/** - * Detects circular component whitelists (which are allowed but worth noting). - */ -export function detectCircularWhitelists(graph: DependencyGraph): string[] { - const circularWhitelists: string[] = []; - const visited = new Set(); - const recursionStack = new Set(); - - function dfs(nodeId: string, path: string[]): boolean { - if (recursionStack.has(nodeId)) { - // Found a cycle - check if it's just component whitelists - const cycleStart = path.indexOf(nodeId); - const cycle = path.slice(cycleStart).concat(nodeId); - - // Only report if it's purely component-to-component cycles - const isComponentOnly = cycle.every(id => id.startsWith('component:')); - if (isComponentOnly) { - circularWhitelists.push(cycle.join(' β†’ ')); - } - - return true; - } - - if (visited.has(nodeId)) { - return false; - } - - visited.add(nodeId); - recursionStack.add(nodeId); - path.push(nodeId); - - const node = graph.nodes.get(nodeId); - if (node) { - for (const dependencyId of node.dependencies) { - if (dfs(dependencyId, [...path])) { - return true; - } - } - } - - recursionStack.delete(nodeId); - path.pop(); - return false; - } - - // Check each component node for circular whitelists - for (const nodeId of graph.nodes.keys()) { - if (nodeId.startsWith('component:') && !visited.has(nodeId)) { - dfs(nodeId, []); - } - } - - return circularWhitelists; -} - -/** - * Detects strongly connected components using Tarjan's algorithm. - * Returns an array of SCCs, where each SCC is an array of node IDs. - */ -export function detectStronglyConnectedComponents(nodeIds: string[], graph: DependencyGraph): string[][] { - const index = new Map(); - const lowLink = new Map(); - const onStack = new Set(); - const stack: string[] = []; - const sccs: string[][] = []; - let currentIndex = 0; - - function strongConnect(nodeId: string) { - // Set the depth index for this node to the smallest unused index - index.set(nodeId, currentIndex); - lowLink.set(nodeId, currentIndex); - currentIndex++; - stack.push(nodeId); - onStack.add(nodeId); - - // Consider successors of this node - const node = graph.nodes.get(nodeId); - if (node) { - for (const dependencyId of node.dependencies) { - if (nodeIds.includes(dependencyId)) { // Only consider nodes in the remaining set - if (!index.has(dependencyId)) { - // Successor has not yet been visited; recurse on it - strongConnect(dependencyId); - lowLink.set(nodeId, Math.min(lowLink.get(nodeId)!, lowLink.get(dependencyId)!)); - } - else if (onStack.has(dependencyId)) { - // Successor is in stack and hence in the current SCC - lowLink.set(nodeId, Math.min(lowLink.get(nodeId)!, index.get(dependencyId)!)); - } - } - } - } - - // If this is a root node, pop the stack and create an SCC - if (lowLink.get(nodeId) === index.get(nodeId)) { - const scc: string[] = []; - let w: string; - do { - w = stack.pop()!; - onStack.delete(w); - scc.push(w); - } while (w !== nodeId); - sccs.push(scc); - } - } - - // Run the algorithm for all unvisited nodes - for (const nodeId of nodeIds) { - if (!index.has(nodeId)) { - strongConnect(nodeId); - } - } - - return sccs; -} - -/** - * Determines the processing order using topological sorting with SCC handling. - * Returns an array of ProcessingLevels, which can be either regular or cyclic levels. - */ -export function determineProcessingOrder(graph: DependencyGraph): ProcessingLevel[] { - const levels: ProcessingLevel[] = []; - const inDegree = new Map(); - - // Calculate in-degrees for all nodes - for (const [nodeId, node] of graph.nodes) { - inDegree.set(nodeId, node.dependencies.size); - } - - // Process nodes level by level - while (inDegree.size > 0) { - // Find all nodes with no remaining dependencies - const currentLevel: string[] = []; - for (const [nodeId, degree] of inDegree) { - if (degree === 0) { - currentLevel.push(nodeId); - } - } - - if (currentLevel.length === 0) { - // If no nodes can be processed, we have cycles - detect SCCs - const remainingNodes = Array.from(inDegree.keys()); - const sccs = detectStronglyConnectedComponents(remainingNodes, graph); - - for (const scc of sccs) { - // Validate that SCCs only contain components (not groups/tags) - const hasNonComponent = scc.some(nodeId => - nodeId.startsWith('group:') || nodeId.startsWith('tag:'), - ); - - if (hasNonComponent) { - throw new Error(`Unsupported circular dependency involving groups, tags, or presets: ${scc.join(' β†’ ')}`); - } - - // Add as cyclic level - these will need special processing - levels.push({ nodes: scc, isCyclic: true }); - - // Remove SCC nodes from processing - scc.forEach(nodeId => inDegree.delete(nodeId)); - } - - continue; // Continue with remaining nodes after handling cycles - } - - // Add regular level - levels.push({ nodes: currentLevel, isCyclic: false }); - - // Remove processed nodes and update in-degrees - for (const nodeId of currentLevel) { - inDegree.delete(nodeId); - const node = graph.nodes.get(nodeId)!; - - // Decrease in-degree for all dependents - for (const dependentId of node.dependents) { - const currentDegree = inDegree.get(dependentId); - if (currentDegree !== undefined) { - inDegree.set(dependentId, currentDegree - 1); - } - } - } - } - - return levels; -} - -// ============================================================================= -// GRAPH VALIDATION -// ============================================================================= - -/** - * Validates the dependency graph and throws errors for critical issues. - */ -export function validateGraph(graph: DependencyGraph): void { - console.log(`πŸ“Š Built dependency graph with ${graph.nodes.size} nodes`); - - // Check for problematic cycles - const problematicCycles = detectProblematicCycles(graph); - if (problematicCycles.length > 0) { - throw new Error(`❌ Problematic cycles detected:\n${problematicCycles.join('\n')}`); - } - - // Report circular whitelists (informational) - const circularWhitelists = detectCircularWhitelists(graph); - if (circularWhitelists.length > 0) { - console.log(`ℹ️ Circular component whitelists detected (allowed): ${circularWhitelists.join(', ')}`); - } - - console.log(`βœ… Graph validation passed`); -} - -// ============================================================================= -// CONCRETE NODE IMPLEMENTATIONS -// ============================================================================= - -class GraphNode implements UnifiedNode { - id: string; - type: NodeType; - name: string; - sourceData: TSource; - targetData?: TargetResourceInfo; - dependencies: Set; - dependents: Set; - - constructor(id: string, type: NodeType, name: string, sourceData: TSource, targetResource?: TSource) { - this.id = id; - this.type = type; - this.name = name; - this.sourceData = sourceData; - this.dependencies = new Set(); - this.dependents = new Set(); - - // Set target data if target resource exists - if (targetResource) { - this.targetData = { - resource: targetResource, - id: (targetResource as any).id, - }; - } - } - - getName(): string { - return this.name; - } - - resolveReferences(_graph: DependencyGraph): void { - // Base implementation does nothing - override in derived classes if needed - } - - async upsert(_space: string): Promise { - throw new Error('upsert must be implemented by derived classes'); - } - - updateTargetData(result: TSource): void { - this.targetData = { - resource: result, - id: (result as any).id, - }; - } -} - -export class TagNode extends GraphNode { - constructor(id: string, data: SpaceComponentInternalTag, targetTag?: SpaceComponentInternalTag) { - super(id, 'tag', data.name, data, targetTag); - } - - resolveReferences(_graph: DependencyGraph): void { - // Tags don't have references to resolve - } - - async upsert(space: string): Promise { - const existingId = this.targetData?.id; - const result = await upsertComponentInternalTag(space, this.sourceData, existingId as number | undefined); - if (!result) { - throw new Error(`Failed to upsert tag ${this.name}`); - } - return result; - } -} - -export class GroupNode extends GraphNode { - constructor(id: string, data: SpaceComponentGroup, targetGroup?: SpaceComponentGroup) { - super(id, 'group', data.name, data, targetGroup); - } - - resolveReferences(graph: DependencyGraph): void { - // Resolve parent group reference if it exists - if (this.sourceData.parent_uuid) { - const parentNodeId = `group:${this.sourceData.parent_uuid}`; - const parentNode = graph.nodes.get(parentNodeId) as GroupNode; - - if (parentNode?.targetData) { - // Update the source data to use the target space parent ID - this.sourceData = { - ...this.sourceData, - parent_id: parentNode.targetData.id as number, - }; - } - } - } - - async upsert(space: string): Promise { - const existingId = this.targetData?.id; - const result = await upsertComponentGroup(space, this.sourceData, existingId as number | undefined); - if (!result) { - throw new Error(`Failed to upsert group ${this.name}`); - } - return result; - } -} - -export class ComponentNode extends GraphNode { - constructor(id: string, data: SpaceComponent, targetComponent?: SpaceComponent) { - super(id, 'component', data.name, data, targetComponent); - } - - resolveReferences(graph: DependencyGraph): void { - // Create a copy of source data to modify - const updatedData = { ...this.sourceData }; - - // Resolve component group reference - if (this.sourceData.component_group_uuid) { - const groupNodeId = `group:${this.sourceData.component_group_uuid}`; - const groupNode = graph.nodes.get(groupNodeId) as GroupNode; - - if (groupNode?.targetData) { - updatedData.component_group_uuid = groupNode.targetData.resource.uuid; - } - } - - // Resolve internal tag references - if (this.sourceData.internal_tag_ids && this.sourceData.internal_tag_ids.length > 0) { - const resolvedTagIds: string[] = []; - - for (const tagId of this.sourceData.internal_tag_ids) { - const tagNodeId = `tag:${tagId}`; - const tagNode = graph.nodes.get(tagNodeId) as TagNode; - - if (tagNode?.targetData) { - resolvedTagIds.push(String(tagNode.targetData.id)); - } - else { - // Keep original ID if not found in graph (might be a tag that already exists in target) - resolvedTagIds.push(tagId); - } - } - - updatedData.internal_tag_ids = resolvedTagIds; - } - - // Resolve preset reference - if (this.sourceData.preset_id) { - // Find the preset by ID and resolve to target preset ID - const preset = this.findPresetById(this.sourceData.preset_id, graph); - if (preset) { - const presetNodeId = `preset:${preset.name}`; - const presetNode = graph.nodes.get(presetNodeId); - - if (presetNode?.targetData) { - updatedData.preset_id = presetNode.targetData.id as number; - } - } - } - - // Resolve schema references (component whitelists, group whitelists, tag whitelists) - if (this.sourceData.schema) { - updatedData.schema = this.resolveSchemaReferences(this.sourceData.schema, graph); - } - - // Update the source data with resolved references - this.sourceData = updatedData; - } - - private findPresetById(presetId: number, graph: DependencyGraph): SpaceComponentPreset | null { - // Find preset by matching source preset_id - for (const [_nodeId, node] of graph.nodes) { - if (node.type === 'preset' && (node.sourceData as SpaceComponentPreset).id === presetId) { - return node.sourceData as SpaceComponentPreset; - } - } - return null; - } - - private resolveSchemaReferences(schema: Record, graph: DependencyGraph): Record { - const resolvedSchema = JSON.parse(JSON.stringify(schema)); // Deep copy - - function resolveField(field: any): any { - if (typeof field !== 'object' || field === null) { - return field; - } - - if (Array.isArray(field)) { - return field.map(resolveField); - } - - const resolvedField = { ...field }; - - // Resolve bloks field references - if (resolvedField.type === 'bloks') { - // Resolve component group whitelist - if (resolvedField.component_group_whitelist && Array.isArray(resolvedField.component_group_whitelist)) { - resolvedField.component_group_whitelist = resolvedField.component_group_whitelist.map((groupUuid: string) => { - const groupNodeId = `group:${groupUuid}`; - const groupNode = graph.nodes.get(groupNodeId) as GroupNode; - return groupNode?.targetData?.resource.uuid || groupUuid; - }); - } - - // Resolve component tag whitelist - if (resolvedField.component_tag_whitelist && Array.isArray(resolvedField.component_tag_whitelist)) { - resolvedField.component_tag_whitelist = resolvedField.component_tag_whitelist.map((tagId: number) => { - const tagNodeId = `tag:${tagId}`; - const tagNode = graph.nodes.get(tagNodeId) as TagNode; - return tagNode?.targetData?.id || tagId; - }); - } - - // Component whitelist doesn't need ID resolution as it uses names - } - - // Recursively resolve nested fields - Object.keys(resolvedField).forEach((key) => { - if (typeof resolvedField[key] === 'object' && resolvedField[key] !== null) { - resolvedField[key] = resolveField(resolvedField[key]); - } - }); - - return resolvedField; - } - - const result: Record = {}; - Object.keys(resolvedSchema).forEach((key) => { - result[key] = resolveField(resolvedSchema[key]); - }); - - return result; - } - - async upsert(space: string): Promise { - const existingId = this.targetData?.id; - const result = await upsertComponent(space, this.sourceData, existingId as number | undefined); - if (!result) { - throw new Error(`Failed to upsert component ${this.name}`); - } - return result; - } -} - -/** - * Preset node implementation - * Presets depend on components (via component_id) - */ -class PresetNode implements UnifiedNode { - public readonly id: string; - public readonly name: string; - public readonly type: NodeType = 'preset'; - public readonly sourceData: SpaceComponentPreset; - public targetData?: TargetResourceInfo; - public readonly dependencies = new Set(); - public readonly dependents = new Set(); - - constructor(preset: SpaceComponentPreset, targetData?: TargetResourceInfo) { - this.sourceData = preset; - this.targetData = targetData; - this.id = `preset:${preset.name}`; - this.name = preset.name; - } - - getName(): string { - return this.name; - } - - resolveReferences(graph: DependencyGraph): void { - // Find the component this preset belongs to and update component_id - const componentName = this.findComponentNameById(this.sourceData.component_id, graph); - if (componentName) { - const componentNode = graph.nodes.get(`component:${componentName}`); - if (componentNode?.targetData) { - this.sourceData.component_id = componentNode.targetData.id as number; - } - } - } - - private findComponentNameById(componentId: number, graph: DependencyGraph): string | null { - // Find component by matching source component_id - for (const [_nodeId, node] of graph.nodes) { - if (node.type === 'component' && (node.sourceData as SpaceComponent).id === componentId) { - return node.name; - } - } - return null; - } - - async upsert(space: string): Promise { - const existingId = this.targetData?.id as number | undefined; - const result = await upsertComponentPreset(space, this.sourceData, existingId); - if (!result) { - throw new Error(`Failed to upsert preset: ${this.name}`); - } - return result; - } - - updateTargetData(result: SpaceComponentPreset): void { - this.targetData = { - resource: result, - id: result.id, - }; - } -} diff --git a/src/commands/components/push/graph-operations/index.ts b/src/commands/components/push/graph-operations/index.ts deleted file mode 100644 index f0d2d533..00000000 --- a/src/commands/components/push/graph-operations/index.ts +++ /dev/null @@ -1,42 +0,0 @@ -import type { SpaceDataState } from '../../constants'; -import type { GraphBuildingContext, PushResults } from './types'; - -import { buildDependencyGraph, validateGraph } from './dependency-graph'; -import { processAllResources } from './resource-processor'; - -// Re-export commonly used utilities -export type { PushResults } from './types'; - -// ============================================================================= -// MAIN COORDINATOR -// ============================================================================= - -/** - * Main function to push components using graph-based dependency resolution. - * - * Architecture: - * - Build dependency graph with colocated target data - * - For each level: resolve references then process resources - * - Target data is embedded in each graph node for efficient upserts - * - * Benefits: - * - Deterministic processing order - * - References resolved when dependencies exist - * - Clean behavioral node abstraction with colocated data - * - Robust error handling and progress tracking - */ -export async function pushWithDependencyGraph( - space: string, - spaceState: SpaceDataState, - maxConcurrency: number = 5, -): Promise { - // Build and validate the dependency graph with colocated target data - const context: GraphBuildingContext = { spaceState }; - const graph = buildDependencyGraph(context); - validateGraph(graph); - - // Process all resources using the dependency graph - const results = await processAllResources(graph, space, maxConcurrency); - - return results; -} diff --git a/src/commands/components/push/graph-operations/resource-processor.ts b/src/commands/components/push/graph-operations/resource-processor.ts deleted file mode 100644 index a5f9b788..00000000 --- a/src/commands/components/push/graph-operations/resource-processor.ts +++ /dev/null @@ -1,274 +0,0 @@ -import { colorPalette } from '../../../../constants'; -import type { DependencyGraph, NodeProcessingResult, ProcessingLevel, PushResults } from './types'; -import { determineProcessingOrder } from './dependency-graph'; -import { progressDisplay } from '../progress-display'; -import { pushComponent } from '../actions'; -import type { SpaceComponent } from '../../constants'; -import chalk from 'chalk'; - -// ============================================================================= -// RESOURCE PROCESSING -// ============================================================================= - -/** - * Processes all resources with 2-pass per level approach. - */ -export async function processAllResources( - graph: DependencyGraph, - space: string, - maxConcurrency: number = 5, -): Promise { - const levels = determineProcessingOrder(graph); - const results: PushResults = { successful: [], failed: [] }; - - // Calculate total resources for progress tracking - const totalResources = levels.reduce((sum, level) => sum + level.nodes.length, 0); - - // Initialize progress display - progressDisplay.start(totalResources); - - for (const level of levels) { - if (level.isCyclic) { - // Handle circular dependencies with stub creation - const cyclicResults = await processCyclicLevel(level, graph, space, maxConcurrency); - mergeResults(results, cyclicResults); - } - else { - // Handle regular level - const levelResults = await processLevel(level.nodes, graph, space, maxConcurrency); - mergeResults(results, levelResults); - } - } - - // Show completion summary using progress display - progressDisplay.handleEvent({ - type: 'complete', - summary: { - updated: results.successful.length, - unchanged: 0, // No longer skip resources - all are processed - failed: results.failed.length, - }, - }); - - return results; -} - -/** - * Processes a cyclic level with circular dependency handling. - * Creates stub components for missing components in the cycle, then processes normally. - */ -async function processCyclicLevel( - level: ProcessingLevel, - graph: DependencyGraph, - space: string, - maxConcurrency: number, -): Promise { - // Clear current progress display and show circular dependency message - progressDisplay.clearProgress(); - console.log(`\nπŸ”„ Detected circular dependencies: ${level.nodes.map(id => id.replace('component:', '')).join(', ')}`); - - // STEP 1: Create stub components for any missing components in the cycle - await createStubComponents(level.nodes, graph, space); - - // STEP 2: Process the cyclic level normally (references can now resolve) - return await processLevel(level.nodes, graph, space, maxConcurrency); -} - -/** - * Creates stub components for missing components in circular dependencies. - */ -async function createStubComponents( - nodeIds: string[], - graph: DependencyGraph, - space: string, -): Promise { - const missingComponents: string[] = []; - - for (const nodeId of nodeIds) { - const node = graph.nodes.get(nodeId); - if (node && node.type === 'component' && !node.targetData) { - missingComponents.push(node.name); - } - } - - if (missingComponents.length === 0) { - return; // No missing components to create stubs for - } - - console.log(`πŸ“ Creating stub components for circular dependencies: ${missingComponents.join(', ')}`); - - // Create minimal stub components - for (const nodeId of nodeIds) { - const node = graph.nodes.get(nodeId); - if (node && node.type === 'component' && !node.targetData) { - try { - const stubComponent = createMinimalStubComponent(node.name); - const result = await pushComponent(space, stubComponent); - - if (result) { - // Update the node's target data so future references can resolve - node.updateTargetData(result); - console.log(`${chalk.green('βœ“')} Created stub component: ${node.name}`); - } - } - catch (error) { - console.error(`βœ— Failed to create stub component ${node.name}:`, error); - throw error; - } - } - } - - // Add a blank line before resuming normal processing - console.log(''); -} - -/** - * Creates a minimal stub component with only required fields. - */ -function createMinimalStubComponent(name: string): SpaceComponent { - return { - name, - display_name: name, - created_at: new Date().toISOString(), - updated_at: new Date().toISOString(), - id: 0, // Will be set by API - schema: {}, // Minimal empty schema - color: null, - internal_tags_list: [], - internal_tag_ids: [], - }; -} - -/** - * Processes a single level of nodes using 2-pass approach: - * Pass 1: Resolve references (dependencies from previous levels exist) - * Pass 2: Process all resources with resolved references - */ -async function processLevel( - level: string[], - graph: DependencyGraph, - space: string, - maxConcurrency: number, -): Promise { - // PASS 1: Resolve references for this level (now that dependencies from previous levels exist) - for (const nodeId of level) { - const node = graph.nodes.get(nodeId)!; - node.resolveReferences(graph); - } - - // PASS 2: Process all nodes in this level with resolved references - const semaphore: Array | null> = Array.from({ length: maxConcurrency }, () => null); - const promises: Promise[] = []; - - for (let i = 0; i < level.length; i++) { - const nodeId = level[i]; - - // Wait for an available slot - const slotIndex = i % maxConcurrency; - if (i >= maxConcurrency && semaphore[slotIndex]) { - await semaphore[slotIndex]; - } - - // Start processing the node - const promise = processNode(nodeId, graph, space); - promises.push(promise); - semaphore[slotIndex] = promise; - } - - const results = await Promise.all(promises); - return aggregateResults(results); -} - -/** - * Process node with resolved references - */ -async function processNode( - nodeId: string, - graph: DependencyGraph, - space: string, -): Promise { - const node = graph.nodes.get(nodeId)!; - // Track start time for individual process timing - const startTime = Date.now(); - - try { - // Always perform upsert - change detection removed due to unstable ID issues - // Create/update the resource with resolved references - const result = await node.upsert(space); - node.updateTargetData(result); - - const elapsedMs = Date.now() - startTime; - progressDisplay.handleEvent({ - type: 'success', - name: node.getName(), - resourceType: getResourceTypeName(node.type), - color: getResourceTypeColor(node.type), - elapsedMs, - }); - - return { name: node.getName() }; - } - catch (error) { - const elapsedMs = Date.now() - startTime; - progressDisplay.handleEvent({ - type: 'error', - name: node.getName(), - resourceType: getResourceTypeName(node.type), - error, - elapsedMs, - }); - return { name: node.getName(), error }; - } -} - -/** - * Aggregates results from multiple node processing operations - */ -function aggregateResults(results: NodeProcessingResult[]): PushResults { - const aggregated: PushResults = { successful: [], failed: [] }; - - for (const result of results) { - if (result.error) { - aggregated.failed.push({ name: result.name, error: result.error }); - } - else { - aggregated.successful.push(result.name); - } - } - - return aggregated; -} - -/** - * Merges results from multiple operations - */ -function mergeResults(target: PushResults, source: PushResults): void { - target.successful.push(...source.successful); - target.failed.push(...source.failed); -} - -/** - * Gets display name for a resource type - */ -function getResourceTypeName(type: string): string { - switch (type) { - case 'component': return 'component'; - case 'group': return 'group'; - case 'tag': return 'tag'; - case 'preset': return 'preset'; - default: return type; - } -} - -/** - * Gets color for a resource type - */ -function getResourceTypeColor(type: string): string { - switch (type) { - case 'component': return colorPalette.COMPONENTS; - case 'group': return colorPalette.GROUPS; - case 'tag': return colorPalette.TAGS; - case 'preset': return colorPalette.PRESETS; - default: return colorPalette.COMPONENTS; - } -} diff --git a/src/commands/components/push/graph-operations/types.ts b/src/commands/components/push/graph-operations/types.ts deleted file mode 100644 index 1801dbe9..00000000 --- a/src/commands/components/push/graph-operations/types.ts +++ /dev/null @@ -1,88 +0,0 @@ -import type { RegionCode } from '../../../../constants'; -import type { - SpaceComponent, - SpaceComponentGroup, - SpaceComponentInternalTag, - SpaceComponentPreset, - SpaceDataState, -} from '../../constants'; - -// ============================================================================= -// CORE TYPES -// ============================================================================= - -/** Types of nodes in our dependency graph */ -export type NodeType = 'component' | 'group' | 'tag' | 'preset'; - -/** Data that can be stored in a graph node */ -export type NodeData = SpaceComponent | SpaceComponentGroup | SpaceComponentInternalTag | SpaceComponentPreset; - -/** Target resource information colocated with graph nodes */ -export interface TargetResourceInfo { - resource: T; - id: string | number; -} - -/** A unified node that tracks both source and target resources */ -export interface UnifiedNode { - id: string; - name: string; - type: NodeType; - sourceData: T; - targetData?: TargetResourceInfo; - dependencies: Set; - dependents: Set; - - // Methods that each node must implement - getName: () => string; - resolveReferences: (graph: DependencyGraph) => void; - upsert: (space: string) => Promise; - updateTargetData: (result: T) => void; -} - -/** The complete dependency graph using unified nodes */ -export interface DependencyGraph { - nodes: Map>; -} - -/** Results from the push operation */ -export interface PushResults { - successful: string[]; - failed: Array<{ name: string; error: unknown }>; -} - -/** Dependencies extracted from component schemas */ -export interface SchemaDependencies { - groupUuids: Set; - tagIds: Set; - componentNames: Set; -} - -// ============================================================================= -// PROCESSING TYPES -// ============================================================================= - -/** Processing level that can be either regular or circular */ -export interface ProcessingLevel { - nodes: string[]; - isCyclic: boolean; -} - -/** Result from processing a single node */ -export interface NodeProcessingResult { - name: string; - error?: any; -} - -/** Configuration for the push operation */ -export interface PushConfig { - space: string; - password: string; - region: RegionCode; - maxConcurrency: number; -} - -/** Graph building context with source and target data */ -export interface GraphBuildingContext { - spaceState: SpaceDataState; -} diff --git a/src/commands/components/push/index.ts b/src/commands/components/push/index.ts deleted file mode 100644 index dd766fe8..00000000 --- a/src/commands/components/push/index.ts +++ /dev/null @@ -1,166 +0,0 @@ -import type { PushComponentsOptions } from './constants'; - -import { colorPalette, commands } from '../../../constants'; -import { getProgram } from '../../../program'; -import { CommandError, handleError, konsola, requireAuthentication } from '../../../utils'; -import { session } from '../../../session'; -import { readComponentsFiles } from './actions'; -import { componentsCommand } from '../command'; -import { filterSpaceDataByComponent, filterSpaceDataByPattern } from './utils'; -import { pushWithDependencyGraph } from './graph-operations'; -import chalk from 'chalk'; -import { mapiClient } from '../../../api'; -import { fetchComponentGroups, fetchComponentInternalTags, fetchComponentPresets, fetchComponents } from '../actions'; -import type { SpaceComponent, SpaceComponentGroup, SpaceComponentInternalTag, SpaceComponentPreset, SpaceDataState } from '../constants'; - -const program = getProgram(); // Get the shared singleton instance - -componentsCommand - .command('push [componentName]') - .description(`Push your space's components schema as json`) - .option('-f, --from ', 'source space id') - .option('--fi, --filter ', 'glob filter to apply to the components before pushing') - .option('--sf, --separate-files', 'Read from separate files instead of consolidated files') - .option('--su, --suffix ', 'Suffix to add to the component name') - - .action(async (componentName: string | undefined, options: PushComponentsOptions) => { - konsola.title(` ${commands.COMPONENTS} `, colorPalette.COMPONENTS, componentName ? `Pushing component ${componentName}...` : 'Pushing components...'); - // Global options - const verbose = program.opts().verbose; - const { space, path } = componentsCommand.opts(); - - const { from, filter } = options; - - // Check if the user is logged in - const { state, initializeSession } = session(); - await initializeSession(); - - if (!requireAuthentication(state, verbose)) { - return; - } - - // Check if the space is provided - if (!space) { - handleError(new CommandError(`Please provide the target space as argument --space TARGET_SPACE_ID.`), verbose); - return; - } - - if (!from) { - // If no source space is provided, use the target space as source - options.from = space; - } - - konsola.info(`Attempting to push components ${chalk.bold('from')} space ${chalk.hex(colorPalette.COMPONENTS)(options.from)} ${chalk.bold('to')} ${chalk.hex(colorPalette.COMPONENTS)(space)}`); - konsola.br(); - - const { password, region } = state; - - let requestCount = 0; - - mapiClient({ - token: password, - region, - onRequest: (_request) => { - requestCount++; - }, - }); - - try { - const spaceState: SpaceDataState = { - local: await readComponentsFiles({ - ...options, - path, - space, - }), - target: { - components: new Map(), - groups: new Map(), - tags: new Map(), - presets: new Map(), - }, - }; - - // Target space data - const promises = [ - fetchComponents(space), - fetchComponentGroups(space), - fetchComponentPresets(space), - fetchComponentInternalTags(space), - ]; - const [components, groups, presets, internalTags] = await Promise.all(promises); - - if (components) { - (components as SpaceComponent[]).forEach((component) => { - spaceState.target.components.set(component.name, component); - }); - } - - if (groups) { - (groups as SpaceComponentGroup[]).forEach((group) => { - spaceState.target.groups.set(group.name, group); - }); - } - - if (presets) { - (presets as SpaceComponentPreset[]).forEach((preset) => { - spaceState.target.presets.set(preset.name, preset); - }); - } - - if (internalTags) { - (internalTags as SpaceComponentInternalTag[]).forEach((tag) => { - spaceState.target.tags.set(tag.name, tag); - }); - } - - // If componentName is provided, filter space data to only include related resources - if (componentName) { - spaceState.local = filterSpaceDataByComponent(spaceState.local, componentName); - if (!spaceState.local.components.length) { - handleError(new CommandError(`Component "${componentName}" not found.`), verbose); - return; - } - } - // If filter pattern is provided, filter space data to match the pattern - else if (filter) { - spaceState.local = filterSpaceDataByPattern(spaceState.local, filter); - if (!spaceState.local.components.length) { - handleError(new CommandError(`No components found matching pattern "${filter}".`), verbose); - return; - } - konsola.info(`Filter applied: ${filter}`); - } - - if (!spaceState.local.components.length) { - konsola.warn('No components found. Please make sure you have pulled the components first.'); - return; - } - - const results = { - successful: [] as string[], - failed: [] as Array<{ name: string; error: unknown }>, - }; - - // Use optimized graph-based dependency resolution with colocated target data - konsola.info('Using graph-based dependency resolution'); - const graphResults = await pushWithDependencyGraph(space, spaceState, 5); - results.successful.push(...graphResults.successful); - results.failed.push(...graphResults.failed); - - if (results.failed.length > 0) { - if (!verbose) { - konsola.br(); - konsola.info('For more information about the error, run the command with the `--verbose` flag'); - } - else { - results.failed.forEach((failed) => { - handleError(failed.error as Error, verbose); - }); - } - } - console.log(`${requestCount} requests made`); - } - catch (error) { - handleError(error as Error, verbose); - } - }); diff --git a/src/commands/components/push/progress-display.ts b/src/commands/components/push/progress-display.ts deleted file mode 100644 index 41bb30a9..00000000 --- a/src/commands/components/push/progress-display.ts +++ /dev/null @@ -1,138 +0,0 @@ -import chalk from 'chalk'; -import { konsola } from '../../../utils/konsola'; - -export type ProcessingEvent = - | { type: 'start'; total: number } - | { type: 'success'; name: string; resourceType: string; color: string; elapsedMs?: number } - | { type: 'skip'; name: string; resourceType: string; elapsedMs?: number } - | { type: 'error'; name: string; resourceType: string; error: unknown; elapsedMs?: number } - | { type: 'complete'; summary: { updated: number; unchanged: number; failed: number } }; - -export class ProgressDisplay { - public total = 0; - private processed = 0; - private updated = 0; - private unchanged = 0; - private failed = 0; - private currentProgressLine = ''; - // Track start time for calculating elapsed time on completion - private startTime: number | null = null; - - start(total: number) { - this.total = total; - this.processed = 0; - this.updated = 0; - this.unchanged = 0; - this.failed = 0; - // Record the start time when processing begins - this.startTime = Date.now(); - konsola.br(); - console.log(`Processing ${total} resources...`); - this.updateProgress(); - } - - handleEvent(event: ProcessingEvent) { - switch (event.type) { - case 'start': - this.start(event.total); - break; - - case 'success': { - this.processed++; - this.updated++; - this.clearProgress(); - const successTimeString = event.elapsedMs ? chalk.dim(` (${this.formatElapsedTime(event.elapsedMs)})`) : ''; - console.log(`${chalk.green('βœ“')} ${this.capitalize(event.resourceType)}β†’ ${chalk.hex(event.color)(event.name)} - Updated${successTimeString}`); - this.updateProgress(); - break; - } - - case 'skip': { - this.processed++; - this.unchanged++; - // Optionally show timing for skipped items if they take longer than expected - const skipTimeString = event.elapsedMs && event.elapsedMs > 10 ? chalk.dim(` (${this.formatElapsedTime(event.elapsedMs)})`) : ''; - if (skipTimeString) { - this.clearProgress(); - console.log(`${chalk.dim('β€”')} ${this.capitalize(event.resourceType)}β†’ ${chalk.dim(event.name)} - Skipped${skipTimeString}`); - } - this.updateProgress(); - break; - } - - case 'error': { - this.processed++; - this.failed++; - this.clearProgress(); - const errorTimeString = event.elapsedMs ? chalk.dim(` (${this.formatElapsedTime(event.elapsedMs)})`) : ''; - console.log(`${chalk.red('βœ—')} ${this.capitalize(event.resourceType)}β†’ ${chalk.red(event.name)} - Failed${errorTimeString}`); - this.updateProgress(); - break; - } - - case 'complete': { - this.clearProgress(); - const { updated, unchanged, failed } = event.summary; - - // Calculate elapsed time if startTime was recorded - const elapsedTime = this.startTime ? Date.now() - this.startTime : null; - const timeString = elapsedTime ? this.formatElapsedTime(elapsedTime) : ''; - - konsola.ok(`Completed: ${updated} updated, ${unchanged} unchanged, ${failed} failed${timeString ? ` in ${timeString}` : ''}`, true); - konsola.br(); - // Show summary of skipped items when there are many - if (unchanged > 5) { - console.log(chalk.dim(` (${unchanged} resources were already up-to-date)`)); - } - break; - } - } - } - - private updateProgress() { - this.currentProgressLine = `[${this.processed}/${this.total}] ${this.updated} updated, ${this.unchanged} unchanged, ${this.failed} failed`; - process.stdout.write(`\r${this.currentProgressLine}`); - } - - clearProgress() { - if (this.currentProgressLine) { - process.stdout.write(`\r${' '.repeat(this.currentProgressLine.length)}\r`); - } - } - - private capitalize(str: string): string { - return str.charAt(0).toUpperCase() + str.slice(1); - } - - /** - * Formats elapsed time in milliseconds to a human-readable string - * @param ms - Time in milliseconds - * @returns Formatted time string (e.g., "1.2s", "2m 30s", "1h 5m") - */ - private formatElapsedTime(ms: number): string { - if (ms < 1000) { - return `${ms}ms`; - } - - const seconds = Math.floor(ms / 1000); - const minutes = Math.floor(seconds / 60); - const hours = Math.floor(minutes / 60); - - if (hours > 0) { - const remainingMinutes = minutes % 60; - return remainingMinutes > 0 ? `${hours}h ${remainingMinutes}m` : `${hours}h`; - } - - if (minutes > 0) { - const remainingSeconds = seconds % 60; - return remainingSeconds > 0 ? `${minutes}m ${remainingSeconds}s` : `${minutes}m`; - } - - // For times under a minute, show decimal precision for seconds - const preciseSeconds = (ms / 1000).toFixed(1); - return `${preciseSeconds}s`; - } -} - -// Global display instance - single source of truth for terminal output -export const progressDisplay = new ProgressDisplay(); diff --git a/src/commands/components/push/utils.ts b/src/commands/components/push/utils.ts deleted file mode 100644 index 957ce697..00000000 --- a/src/commands/components/push/utils.ts +++ /dev/null @@ -1,171 +0,0 @@ -import type { SpaceData } from '../constants'; -import { minimatch } from 'minimatch'; -import { collectWhitelistDependencies } from './graph-operations/dependency-graph'; - -/** - * Collects all dependencies (groups, tags, and components) for a set of components - */ -function collectAllDependencies( - components: SpaceData['components'], - allComponents: SpaceData['components'], - allGroups: SpaceData['groups'], - allTags: SpaceData['internalTags'], -) { - const requiredComponents = new Set(); - const requiredGroupUuids = new Set(); - const requiredTagIds = new Set(); - - // Add initial components - components.forEach(component => requiredComponents.add(component.name)); - - // Recursively collect component dependencies - function collectComponentDeps(componentName: string, visited = new Set()) { - if (visited.has(componentName)) { return; } // Prevent infinite loops - visited.add(componentName); - - const component = allComponents.find(c => c.name === componentName); - if (!component) { return; } - - // Collect direct component group assignment - if (component.component_group_uuid) { - requiredGroupUuids.add(component.component_group_uuid); - } - - // Collect direct internal tag assignments - if (component.internal_tag_ids && component.internal_tag_ids.length > 0) { - component.internal_tag_ids.forEach((tagId) => { - // Handle both string and number tag IDs - const numericTagId = typeof tagId === 'string' ? Number.parseInt(tagId, 10) : tagId; - if (!Number.isNaN(numericTagId)) { - requiredTagIds.add(numericTagId); - } - }); - } - - // Collect schema whitelist dependencies - if (component.schema) { - const schemaDeps = collectWhitelistDependencies(component.schema); - - // Add schema group dependencies - schemaDeps.groupUuids.forEach((groupUuid) => { - requiredGroupUuids.add(groupUuid); - }); - - // Add schema tag dependencies - schemaDeps.tagIds.forEach((tagId) => { - requiredTagIds.add(tagId); - }); - - // Add schema component dependencies (recursive) - schemaDeps.componentNames.forEach((componentName) => { - if (!requiredComponents.has(componentName)) { - requiredComponents.add(componentName); - collectComponentDeps(componentName, visited); - } - }); - } - } - - // Collect dependencies for all components - components.forEach(component => collectComponentDeps(component.name)); - - // Collect parent groups for hierarchical dependencies - function collectParentGroups(groupUuid: string, visited = new Set()) { - if (visited.has(groupUuid)) { return; } // Prevent infinite loops - visited.add(groupUuid); - - const group = allGroups.find(g => g.uuid === groupUuid); - if (group && group.parent_uuid) { - requiredGroupUuids.add(group.parent_uuid); - collectParentGroups(group.parent_uuid, visited); - } - } - - // Ensure we include parent groups for all required groups - const initialGroupUuids = Array.from(requiredGroupUuids); - initialGroupUuids.forEach(groupUuid => collectParentGroups(groupUuid)); - - // Filter to only include required resources - const filteredComponents = allComponents.filter(component => requiredComponents.has(component.name)); - const filteredGroups = allGroups.filter(group => requiredGroupUuids.has(group.uuid)); - const filteredTags = allTags.filter(tag => requiredTagIds.has(tag.id)); - - return { filteredComponents, filteredGroups, filteredTags }; -} - -/** - * Filters space data to only include a specific component and its dependencies - */ -export function filterSpaceDataByComponent(spaceData: SpaceData, componentName: string): SpaceData { - // Find the target component - const targetComponent = spaceData.components.find(component => component.name === componentName); - if (!targetComponent) { - return { - components: [], - groups: [], - internalTags: [], - presets: [], - }; - } - - // Collect all dependencies for this component - const { filteredComponents, filteredGroups, filteredTags } = collectAllDependencies( - [targetComponent], - spaceData.components, - spaceData.groups, - spaceData.internalTags, - ); - - // Find presets for all included components - const componentIds = filteredComponents.map(component => component.id); - const filteredPresets = spaceData.presets.filter( - preset => componentIds.includes(preset.component_id), - ); - - return { - components: filteredComponents, - groups: filteredGroups, - internalTags: filteredTags, - presets: filteredPresets, - }; -} - -/** - * Filters space data to only include components matching a glob pattern and their dependencies - */ -export function filterSpaceDataByPattern(spaceData: SpaceData, pattern: string): SpaceData { - // Filter components by pattern - const matchingComponents = spaceData.components.filter(component => - minimatch(component.name, pattern), - ); - - if (matchingComponents.length === 0) { - return { - components: [], - groups: [], - internalTags: [], - presets: [], - }; - } - - // Collect all dependencies for matching components - const { filteredComponents, filteredGroups, filteredTags } = collectAllDependencies( - matchingComponents, - spaceData.components, - spaceData.groups, - spaceData.internalTags, - ); - - // Find presets for all included components - const componentIds = filteredComponents.map(component => component.id); - const filteredPresets = spaceData.presets.filter( - preset => componentIds.includes(preset.component_id), - ); - - return { - components: filteredComponents, - groups: filteredGroups, - internalTags: filteredTags, - presets: filteredPresets, - }; -} diff --git a/src/commands/languages/README.md b/src/commands/languages/README.md deleted file mode 100644 index 108098d6..00000000 --- a/src/commands/languages/README.md +++ /dev/null @@ -1,99 +0,0 @@ -# Languages Command - -The `languages` command allows you to pull the available languages in your Storyblok space. - -## Basic Usage - -```bash -storyblok languages pull --space YOUR_SPACE_ID -``` - -This will download your space's languages configuration and save it to: -``` -.storyblok/ -└── languages/ - └── YOUR_SPACE_ID/ - └── languages.json -``` - -## Options - -| Option | Description | Default | -|--------|-------------|---------| -| `-s, --space ` | (Required) The ID of the space to pull languages from | - | -| `-f, --filename ` | Custom name for the languages file | `languages` | -| `--su, --suffix ` | Suffix to add to the file name | | -| `-p, --path ` | Custom path to store the file | `.storyblok/languages` | - -## Examples - -1. Pull languages with default settings: -```bash -storyblok languages pull --space 12345 -``` -Generates: -``` -.storyblok/ -└── languages/ - └── 12345/ - └── languages.json -``` - -2. Pull languages with custom filename: -```bash -storyblok languages pull --space 12345 --filename my-languages -``` -Generates: -``` -.storyblok/ -└── languages/ - └── 12345/ - └── my-languages.json -``` - -3. Pull languages with custom suffix: -```bash -storyblok languages pull --space 12345 --suffix dev -``` -Generates: -``` -.storyblok/ -└── languages/ - └── 12345/ - └── languages.dev.json -``` - -4. Pull languages to a custom path: -```bash -storyblok languages pull --space 12345 --path ./backup -``` -Generates: -``` -backup/ -└── languages/ - └── 12345/ - └── languages.json -``` - -## File Structure - -The command follows this pattern for file generation: -``` -{path}/ -└── languages/ - └── {spaceId}/ - └── {filename}.{suffix}.json -``` - -Where: -- `{path}` is the base path (default: `.storyblok`) -- `{spaceId}` is your Storyblok space ID -- `{filename}` is the name you specified (default: `languages`) -- `{suffix}` is the suffix you specified (default: space ID) - -## Notes - -- The space ID is required -- The command will create the necessary directories if they don't exist -- The generated file follows the Storyblok languages schema -- The file can be used for version control or backup purposes diff --git a/src/commands/languages/actions.test.ts b/src/commands/languages/actions.test.ts deleted file mode 100644 index 2f13722d..00000000 --- a/src/commands/languages/actions.test.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { http, HttpResponse } from 'msw'; -import { setupServer } from 'msw/node'; -import { vol } from 'memfs'; -import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; -import { fetchLanguages, saveLanguagesToFile } from './actions'; -import { FetchError } from 'src/utils/fetch'; -import { APIError } from 'src/utils'; - -const handlers = [ - http.get('https://api.storyblok.com/v1/spaces/12345', async ({ request }) => { - const token = request.headers.get('Authorization'); - if (token === 'valid-token') { - return HttpResponse.json({ - space: { - default_lang_name: 'en', - languages: [ - { - code: 'ca', - name: 'Catalan', - }, - { - code: 'fr', - name: 'French', - }, - ], - }, - }); - } - return new HttpResponse('Unauthorized', { status: 401 }); - }), -]; - -const server = setupServer(...handlers); - -beforeAll(() => server.listen({ onUnhandledRequest: 'error' })); - -afterEach(() => server.resetHandlers()); -afterAll(() => server.close()); - -vi.mock('node:fs'); -vi.mock('node:fs/promises'); - -describe('pull languages actions', () => { - beforeEach(() => { - vi.clearAllMocks(); - vol.reset(); - }); - - describe('fetchLanguages', () => { - it('should pull languages successfully with a valid token', async () => { - const mockResponse = { - default_lang_name: 'en', - languages: [ - { - code: 'ca', - name: 'Catalan', - }, - { - code: 'fr', - name: 'French', - }, - ], - }; - const result = await fetchLanguages('12345', 'valid-token', 'eu'); - expect(result).toEqual(mockResponse); - }); - }); - it.skip('should throw an masked error for invalid token', async () => { - // TODO: Fix this test, is the only of it's kind that is not working, it's not clear why - // Test return "Compared values have no visual difference." but fails anyway - const error = new FetchError('Non-JSON response', { status: 401, statusText: 'Unauthorized', data: null }); - await expect(fetchLanguages('12345', 'invalid-token', 'eu')).rejects.toThrow( - new APIError('unauthorized', 'pull_languages', error, `The user is not authorized to access the API`), - ); - }); - - describe('saveLanguagesToFile', () => { - it('should save a consolidated languages file', async () => { - const mockResponse = { - default_lang_name: 'en', - languages: [ - { - code: 'ca', - name: 'Catalan', - }, - { - code: 'fr', - name: 'French', - }, - ], - }; - await saveLanguagesToFile('12345', mockResponse, { - filename: 'languages', - path: '/temp', - verbose: false, - space: '12345', - }); - const content = vol.readFileSync('/temp/languages/12345/languages.json', 'utf8'); - expect(content).toBe(JSON.stringify(mockResponse, null, 2)); - }); - }); -}); diff --git a/src/commands/languages/actions.ts b/src/commands/languages/actions.ts deleted file mode 100644 index ab689391..00000000 --- a/src/commands/languages/actions.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { join } from 'node:path'; -import { handleAPIError, handleFileSystemError } from '../../utils'; -import { customFetch } from '../../utils/fetch'; -import { resolvePath, saveToFile } from '../../utils/filesystem'; -import type { PullLanguagesOptions } from './constants'; -import type { RegionCode } from '../../constants'; -import type { SpaceInternationalization } from '../../types'; -import { getStoryblokUrl } from '../../utils/api-routes'; - -export const fetchLanguages = async (space: string, token: string, region: RegionCode): Promise => { - try { - const url = getStoryblokUrl(region); - const response = await customFetch<{ - space: SpaceInternationalization; - }>(`${url}/spaces/${space}`, { - headers: { - Authorization: token, - }, - }); - - return { - default_lang_name: response.space.default_lang_name, - languages: response.space.languages, - }; - } - catch (error) { - handleAPIError('pull_languages', error); - } -}; - -export const saveLanguagesToFile = async (space: string, internationalizationOptions: SpaceInternationalization, options: PullLanguagesOptions) => { - try { - const { filename = 'languages', suffix, path } = options; - const data = JSON.stringify(internationalizationOptions, null, 2); - const name = suffix ? `${filename}.${suffix}.json` : `${filename}.json`; - const resolvedPath = resolvePath(path, `languages/${space}/`); - const filePath = join(resolvedPath, name); - - await saveToFile(filePath, data); - } - catch (error) { - handleFileSystemError('write', error as Error); - } -}; diff --git a/src/commands/languages/constants.ts b/src/commands/languages/constants.ts deleted file mode 100644 index d015ada7..00000000 --- a/src/commands/languages/constants.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { CommandOptions } from '../../types'; - -/** - * Interface representing the options for the `pull-languages` command. - */ -export interface PullLanguagesOptions extends CommandOptions { - /** - * The path to save the languages file to. - * Defaults to `.storyblok/languages`. - * @default `.storyblok/languages` - */ - path?: string; - /** - * The space ID. - * @required true - */ - space: string; - /** - * The filename to save the file as. - * Defaults to `languages`. The file will be saved as `..json`. - * @default `languages - */ - filename?: string; - /** - * The suffix to add to the filename. - * Defaults to the space ID. - * @default space - */ - suffix?: string; -} diff --git a/src/commands/languages/index.test.ts b/src/commands/languages/index.test.ts deleted file mode 100644 index bcf02bc3..00000000 --- a/src/commands/languages/index.test.ts +++ /dev/null @@ -1,228 +0,0 @@ -import chalk from 'chalk'; -import { languagesCommand } from '.'; -import { session } from '../../session'; -import { CommandError, konsola } from '../../utils'; -import { fetchLanguages, saveLanguagesToFile } from './actions'; -import { colorPalette } from '../../constants'; - -vi.mock('./actions', () => ({ - fetchLanguages: vi.fn(), - saveLanguagesToFile: vi.fn(), -})); - -vi.mock('../../creds', () => ({ - getCredentials: vi.fn(), - addCredentials: vi.fn(), - removeCredentials: vi.fn(), - removeAllCredentials: vi.fn(), -})); - -// Mocking the session module -vi.mock('../../session', () => { - let _cache: Record | null = null; - const session = () => { - if (!_cache) { - _cache = { - state: { - isLoggedIn: false, - }, - updateSession: vi.fn(), - persistCredentials: vi.fn(), - initializeSession: vi.fn(), - }; - } - return _cache; - }; - - return { - session, - }; -}); - -vi.mock('../../utils/konsola'); - -describe('languagesCommand', () => { - describe('pull', () => { - beforeEach(() => { - vi.resetAllMocks(); - vi.clearAllMocks(); - // Reset the option values - languagesCommand._optionValues = {}; - for (const command of languagesCommand.commands) { - command._optionValues = {}; - } - }); - describe('default mode', () => { - it('should prompt the user if operation was sucessfull', async () => { - const mockResponse = { - default_lang_name: 'en', - languages: [ - { - code: 'ca', - name: 'Catalan', - }, - { - code: 'fr', - name: 'French', - }, - ], - }; - session().state = { - isLoggedIn: true, - password: 'valid-token', - region: 'eu', - }; - - vi.mocked(fetchLanguages).mockResolvedValue(mockResponse); - await languagesCommand.parseAsync(['node', 'test', 'pull', '--space', '12345']); - expect(fetchLanguages).toHaveBeenCalledWith('12345', 'valid-token', 'eu'); - expect(saveLanguagesToFile).toHaveBeenCalledWith('12345', mockResponse, { - path: undefined, - }); - expect(konsola.ok).toHaveBeenCalledWith(`Languages schema downloaded successfully at ${chalk.hex(colorPalette.PRIMARY)(`.storyblok/languages/12345/languages.json`)}`, true); - }); - - it('should throw an error if the user is not logged in', async () => { - session().state = { - isLoggedIn: false, - }; - const mockError = new CommandError(`You are currently not logged in. Please run storyblok login to authenticate, or storyblok signup to sign up.`); - await languagesCommand.parseAsync(['node', 'test', 'pull', '--space', '12345']); - expect(konsola.error).toHaveBeenCalledWith(mockError.message, null, { - header: true, - }); - }); - - it('should throw an error if the space is not provided', async () => { - session().state = { - isLoggedIn: true, - password: 'valid-token', - region: 'eu', - }; - - const mockError = new CommandError(`Please provide the space as argument --space YOUR_SPACE_ID.`); - try { - await languagesCommand.parseAsync(['node', 'test', 'pull']); - } - catch (error) { - console.log('TEST languages', error); - } - expect(konsola.error).toHaveBeenCalledWith(mockError.message, null, { - header: true, - }); - }); - - it('should prompt a warning the user if no languages are found', async () => { - const mockResponse = { - default_lang_name: 'en', - languages: [], - }; - session().state = { - isLoggedIn: true, - password: 'valid-token', - region: 'eu', - }; - - vi.mocked(fetchLanguages).mockResolvedValue(mockResponse); - - await languagesCommand.parseAsync(['node', 'test', 'pull', '--space', '24568']); - expect(konsola.warn).toHaveBeenCalledWith(`No languages found in the space 24568`, true); - }); - }); - - describe('--path option', () => { - it('should save the file at the provided path', async () => { - const mockResponse = { - default_lang_name: 'en', - languages: [ - { - code: 'ca', - name: 'Catalan', - }, - { - code: 'fr', - name: 'French', - }, - ], - }; - session().state = { - isLoggedIn: true, - password: 'valid-token', - region: 'eu', - }; - - vi.mocked(fetchLanguages).mockResolvedValue(mockResponse); - await languagesCommand.parseAsync(['node', 'test', 'pull', '--space', '12345', '--path', '/tmp']); - expect(fetchLanguages).toHaveBeenCalledWith('12345', 'valid-token', 'eu'); - expect(saveLanguagesToFile).toHaveBeenCalledWith('12345', mockResponse, { - path: '/tmp', - }); - expect(konsola.ok).toHaveBeenCalledWith(`Languages schema downloaded successfully at ${chalk.hex(colorPalette.PRIMARY)(`/tmp/languages.json`)}`, true); - }); - }); - - describe('--filename option', () => { - it('should save the file with the provided filename', async () => { - const mockResponse = { - default_lang_name: 'en', - languages: [ - { - code: 'ca', - name: 'Catalan', - }, - { - code: 'fr', - name: 'French', - }, - ], - }; - session().state = { - isLoggedIn: true, - password: 'valid-token', - region: 'eu', - }; - - vi.mocked(fetchLanguages).mockResolvedValue(mockResponse); - await languagesCommand.parseAsync(['node', 'test', 'pull', '--space', '12345', '--filename', 'custom-languages']); - expect(fetchLanguages).toHaveBeenCalledWith('12345', 'valid-token', 'eu'); - expect(saveLanguagesToFile).toHaveBeenCalledWith('12345', mockResponse, { - filename: 'custom-languages', - path: undefined, - }); - expect(konsola.ok).toHaveBeenCalledWith(`Languages schema downloaded successfully at ${chalk.hex(colorPalette.PRIMARY)(`.storyblok/languages/12345/custom-languages.json`)}`, true); - }); - }); - - describe('--suffix option', () => { - it('should save the file with the provided suffix', async () => { - const mockResponse = { - default_lang_name: 'en', - languages: [ - { - code: 'ca', - name: 'Catalan', - }, - { - code: 'fr', - name: 'French', - }, - ], - }; - session().state = { - isLoggedIn: true, - password: 'valid-token', - region: 'eu', - }; - - vi.mocked(fetchLanguages).mockResolvedValue(mockResponse); - await languagesCommand.parseAsync(['node', 'test', 'pull', '--space', '12345', '--suffix', 'custom-suffix']); - expect(fetchLanguages).toHaveBeenCalledWith('12345', 'valid-token', 'eu'); - expect(saveLanguagesToFile).toHaveBeenCalledWith('12345', mockResponse, { - suffix: 'custom-suffix', - path: undefined, - }); - expect(konsola.ok).toHaveBeenCalledWith(`Languages schema downloaded successfully at ${chalk.hex(colorPalette.PRIMARY)(`.storyblok/languages/12345/languages.custom-suffix.json`)}`, true); - }); - }); - }); -}); diff --git a/src/commands/languages/index.ts b/src/commands/languages/index.ts deleted file mode 100644 index 1d285fa1..00000000 --- a/src/commands/languages/index.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { colorPalette, commands } from '../../constants'; -import { CommandError, handleError, isVitest, konsola, requireAuthentication } from '../../utils'; -import { getProgram } from '../../program'; -import { session } from '../../session'; -import { fetchLanguages, saveLanguagesToFile } from './actions'; -import chalk from 'chalk'; -import type { PullLanguagesOptions } from './constants'; -import { Spinner } from '@topcli/spinner'; - -const program = getProgram(); // Get the shared singleton instance - -export const languagesCommand = program - .command('languages') - .alias('lang') - .description(`Manage your space's languages`) - .option('-s, --space ', 'space ID') - .option('-p, --path ', 'path to save the file. Default is .storyblok/languages'); - -languagesCommand - .command('pull') - .description(`Download your space's languages schema as json`) - .option('-f, --filename ', 'filename to save the file as ..json') - .option('--su, --suffix ', 'suffix to add to the file name (e.g. languages..json). By default, the space ID is used.') - .action(async (options: PullLanguagesOptions) => { - konsola.title(` ${commands.LANGUAGES} `, colorPalette.LANGUAGES); - - // Global options - const verbose = program.opts().verbose; - - // Command options - const { space, path } = languagesCommand.opts(); - const { filename = 'languages', suffix = options.space } = options; - - const { state, initializeSession } = session(); - await initializeSession(); - - if (!requireAuthentication(state, verbose)) { - return; - } - if (!space) { - handleError(new CommandError(`Please provide the space as argument --space YOUR_SPACE_ID.`), verbose); - return; - } - - const { password, region } = state; - - const spinner = new Spinner({ - verbose: !isVitest, - }); - try { - spinner.start(`Fetching ${chalk.hex(colorPalette.LANGUAGES)('languages')}`); - - const internationalization = await fetchLanguages(space, password, region); - - if (!internationalization || internationalization.languages?.length === 0) { - spinner.failed(); - - konsola.warn(`No languages found in the space ${space}`, true); - konsola.br(); - return; - } - await saveLanguagesToFile(space, internationalization, { - ...options, - path, - }); - const fileName = suffix ? `${filename}.${suffix}.json` : `${filename}.json`; - const filePath = path ? `${path}/${fileName}` : `.storyblok/languages/${space}/${fileName}`; - spinner.succeed(); - konsola.ok(`Languages schema downloaded successfully at ${chalk.hex(colorPalette.PRIMARY)(filePath)}`, true); - } - catch (error) { - spinner.failed(); - konsola.br(); - handleError(error as Error, verbose); - } - konsola.br(); - }); diff --git a/src/commands/login/README.md b/src/commands/login/README.md deleted file mode 100644 index 761cf942..00000000 --- a/src/commands/login/README.md +++ /dev/null @@ -1,56 +0,0 @@ -# Login Command - -The `login` command allows you to authenticate with your Storyblok account. It supports multiple login methods and regions. - -## Basic Usage - -```bash -storyblok login -``` - -This will start an interactive login process where you can choose between: -- Email and password login -- Token-based login (SSO) - -### Get your personal access token - -Go to https://app.storyblok.com/#/me/account?tab=token and click on **Generate new token**. - -## Options - -| Option | Description | Default | -|--------|-------------|---------| -| `-t, --token ` | Login directly with a token (useful for CI environments) | - | -| `-r, --region ` | Set the region to work with (must match your space's region) | `eu` | - -## Examples - -1. Login with email and password: -```bash -storyblok login -``` - -2. Login with a token: -```bash -storyblok login --token PERSONAL_ACCESS_TOKEN -``` - -3. Login with a token in a specific region: -```bash -storyblok login --token PERSONAL_ACCESS_TOKEN --region us -``` - -## Notes - -- Credentials are stored securely in `~/.storyblok/credentials.json` -- The region setting will be used for all subsequent CLI commands -- If you're already logged in, you'll need to logout first to switch accounts -- For CI environments, it's recommended to use the `--token` option -- The CLI supports two-factor authentication (2FA) when using email login - -## Available Regions - -- `eu` - Europe -- `us` - United States -- `cn` - China -- `au` - Australia diff --git a/src/commands/login/actions.test.ts b/src/commands/login/actions.test.ts deleted file mode 100644 index e36a4a1e..00000000 --- a/src/commands/login/actions.test.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { afterAll, afterEach, beforeAll, expect } from 'vitest'; - -import { setupServer } from 'msw/node'; -import { http, HttpResponse } from 'msw'; -import { loginWithEmailAndPassword, loginWithOtp, loginWithToken } from './actions'; -import chalk from 'chalk'; -import { FetchError } from '../../utils/fetch'; -import { APIError } from '../../utils'; - -const emailRegex = /^[^\s@]+@[^\s@][^\s.@]*\.[^\s@]+$/; - -const handlers = [ - http.get('https://api.storyblok.com/v1/users/me', async ({ request }) => { - const token = request.headers.get('Authorization'); - if (token === 'valid-token') { - return HttpResponse.json({ data: 'user data' }); - } - return new HttpResponse('Unauthorized', { status: 401 }); - }), - http.post('https://api.storyblok.com/v1/users/login', async ({ request }) => { - const body = await request.json() as { email: string; password: string }; - - if (!emailRegex.test(body.email)) { - return new HttpResponse('Unprocessable Entity', { status: 422 }); - } - - if (body?.email === 'julio.iglesias@storyblok.com' && body?.password === 'password') { - return HttpResponse.json({ otp_required: true }); - } - else { - return new HttpResponse('Unauthorized', { status: 401 }); - } - }), -]; - -const server = setupServer(...handlers); - -beforeAll(() => server.listen({ onUnhandledRequest: 'error' })); - -afterEach(() => server.resetHandlers()); -afterAll(() => server.close()); - -describe('login actions', () => { - describe('loginWithToken', () => { - it('should login successfully with a valid token', async () => { - const mockResponse = { data: 'user data', perPage: 0, total: 0 }; - const result = await loginWithToken('valid-token', 'eu'); - expect(result).toEqual(mockResponse); - }); - - it('should throw an masked error for invalid token', async () => { - const error = new FetchError('Non-JSON response', { status: 401, statusText: 'Unauthorized', data: null }); - await expect(loginWithToken('invalid-token', 'eu')).rejects.toThrow( - new APIError('unauthorized', 'login_with_token', error, `The token provided ${chalk.bold('inva*********')} is invalid. - Please make sure you are using the correct token and try again.`), - ); - }); - - it('should throw a network error if response is empty (network)', async () => { - server.use( - http.get('https://api.storyblok.com/v1/users/me', () => { - return new HttpResponse(null, { status: 500 }); - }), - ); - await expect(loginWithToken('any-token', 'eu')).rejects.toThrow( - 'No response from server, please check if you are correctly connected to internet', - ); - }); - }); - - describe('loginWithEmailAndPassword', () => { - it('should get if the user requires otp', async () => { - const expected = { otp_required: true, perPage: 0, total: 0 }; - const result = await loginWithEmailAndPassword('julio.iglesias@storyblok.com', 'password', 'eu'); - expect(result).toEqual(expected); - }); - - it('should throw an error for invalid credentials', async () => { - await expect(loginWithEmailAndPassword('david.bisbal@storyblok.com', 'password', 'eu')).rejects.toThrow( - 'The provided credentials are invalid', - ); - }); - }); - - describe('loginWithOtp', () => { - it('should login successfully with valid email, password, and otp', async () => { - server.use( - http.post('https://api.storyblok.com/v1/users/login', async ({ request }) => { - const body = await request.json() as { email: string; password: string; otp_attempt: string }; - if (body?.email === 'julio.iglesias@storyblok.com' && body?.password === 'password' && body?.otp_attempt === '123456') { - return HttpResponse.json({ access_token: 'Awiwi' }); - } - - else { - return new HttpResponse('Unauthorized', { status: 401 }); - } - }), - ); - const expected = { access_token: 'Awiwi', perPage: 0, total: 0 }; - - const result = await loginWithOtp('julio.iglesias@storyblok.com', 'password', '123456', 'eu'); - - expect(result).toEqual(expected); - }); - }); -}); diff --git a/src/commands/login/actions.ts b/src/commands/login/actions.ts deleted file mode 100644 index e3d0d69a..00000000 --- a/src/commands/login/actions.ts +++ /dev/null @@ -1,59 +0,0 @@ -import chalk from 'chalk'; -import type { RegionCode } from '../../constants'; -import { customFetch, FetchError } from '../../utils/fetch'; -import { APIError, handleAPIError, maskToken } from '../../utils'; -import { getStoryblokUrl } from '../../utils/api-routes'; -import type { StoryblokLoginResponse, StoryblokLoginWithOtpResponse, StoryblokUser } from '../../types'; - -export const loginWithToken = async (token: string, region: RegionCode) => { - try { - const url = getStoryblokUrl(region); - return await customFetch<{ - user: StoryblokUser; - }>(`${url}/users/me`, { - headers: { - Authorization: token, - }, - }); - } - catch (error) { - if (error instanceof FetchError) { - const status = error.response.status; - - switch (status) { - case 401: - throw new APIError('unauthorized', 'login_with_token', error, `The token provided ${chalk.bold(maskToken(token))} is invalid. - Please make sure you are using the correct token and try again.`); - default: - throw new APIError('network_error', 'login_with_token', error); - } - } - throw new APIError('generic', 'login_with_token', error as FetchError, 'The provided credentials are invalid'); - } -}; - -export const loginWithEmailAndPassword = async (email: string, password: string, region: RegionCode) => { - try { - const url = getStoryblokUrl(region); - return await customFetch(`${url}/users/login`, { - method: 'POST', - body: { email, password }, - }); - } - catch (error) { - handleAPIError('login_email_password', error, 'The provided credentials are invalid'); - } -}; - -export const loginWithOtp = async (email: string, password: string, otp: string, region: RegionCode) => { - try { - const url = getStoryblokUrl(region); - return await customFetch(`${url}/users/login`, { - method: 'POST', - body: { email, password, otp_attempt: otp }, - }); - } - catch (error) { - handleAPIError('login_with_otp', error as Error); - } -}; diff --git a/src/commands/login/index.test.ts b/src/commands/login/index.test.ts deleted file mode 100644 index 42dd1fef..00000000 --- a/src/commands/login/index.test.ts +++ /dev/null @@ -1,234 +0,0 @@ -import { describe, expect, it, vi } from 'vitest'; -import { loginWithEmailAndPassword, loginWithOtp, loginWithToken } from './actions'; -import { loginCommand } from './'; -import { konsola } from '../../utils'; -import { input, password, select } from '@inquirer/prompts'; -import { regions } from '../../constants'; -import chalk from 'chalk'; -import { session } from '../../session'; // Import as module to mock properly - -vi.mock('./actions', () => ({ - loginWithEmailAndPassword: vi.fn(), - loginWithOtp: vi.fn(), - loginWithToken: vi.fn(), -})); - -vi.mock('../../creds', () => ({ - getCredentials: vi.fn(), - addCredentials: vi.fn(), - removeCredentials: vi.fn(), - removeAllCredentials: vi.fn(), -})); - -// Mocking the session module -vi.mock('../../session', () => { - let _cache: Record | null = null; - const session = () => { - if (!_cache) { - _cache = { - state: { - isLoggedIn: false, - }, - updateSession: vi.fn(), - persistCredentials: vi.fn(), - initializeSession: vi.fn(), - }; - } - return _cache; - }; - - return { - session, - }; -}); - -vi.mock('../../utils', async () => { - const actualUtils = await vi.importActual('../../utils'); - return { - ...actualUtils, - konsola: { - ok: vi.fn(), - title: vi.fn(), - br: vi.fn(), - error: vi.fn(), - }, - isVitestRunning: true, - handleError: (error: Error, header = false) => { - konsola.error(error, header); - // Optionally, prevent process.exit during tests - }, - }; -}); - -vi.mock('@inquirer/prompts', () => ({ - input: vi.fn(), - password: vi.fn(), - select: vi.fn(), -})); - -describe('loginCommand', () => { - beforeEach(() => { - vi.resetAllMocks(); - vi.clearAllMocks(); - }); - - describe('default interactive login', () => { - it('should prompt the user for login strategy when no token is provided', async () => { - await loginCommand.parseAsync(['node', 'test']); - - expect(select).toHaveBeenCalledWith(expect.objectContaining({ - message: 'How would you like to login?', - })); - }); - - describe('login-with-email strategy', () => { - beforeEach(() => { - vi.resetAllMocks(); - }); - it('should prompt the user for email and password when login-with-email is selected', async () => { - vi.mocked(select) - .mockResolvedValueOnce('login-with-email') // For login strategy - .mockResolvedValueOnce('eu'); // For region - - vi.mocked(input) - .mockResolvedValueOnce('user@example.com') // For email - .mockResolvedValueOnce('123456'); // For OTP code - - vi.mocked(password).mockResolvedValueOnce('test-password'); - - await loginCommand.parseAsync(['node', 'test']); - - expect(input).toHaveBeenCalledWith(expect.objectContaining({ - message: 'Please enter your email address:', - })); - - expect(password).toHaveBeenCalledWith(expect.objectContaining({ - message: 'Please enter your password:', - })); - - expect(select).toHaveBeenCalledWith(expect.objectContaining({ - message: 'Please select the region you would like to work in:', - })); - }); - - it('should login with email and password if provided using login-with-email strategy', async () => { - vi.mocked(select) - .mockResolvedValueOnce('login-with-email') // For login strategy - .mockResolvedValueOnce('eu'); // For region - - vi.mocked(input) - .mockResolvedValueOnce('user@example.com') // For email - .mockResolvedValueOnce('123456'); // For OTP code - - vi.mocked(password).mockResolvedValueOnce('test-password'); - - vi.mocked(loginWithEmailAndPassword).mockResolvedValueOnce({ otp_required: true }); - vi.mocked(loginWithOtp).mockResolvedValueOnce({ access_token: 'test-token' }); - - await loginCommand.parseAsync(['node', 'test']); - - expect(loginWithEmailAndPassword).toHaveBeenCalledWith('user@example.com', 'test-password', 'eu'); - - expect(loginWithOtp).toHaveBeenCalledWith('user@example.com', 'test-password', '123456', 'eu'); - }); - - it('should throw an error for invalid email and password', async () => { - vi.mocked(select).mockResolvedValueOnce('login-with-email'); - vi.mocked(input).mockResolvedValueOnce('eu'); - - const mockError = new Error('Error logging in with email and password'); - loginWithEmailAndPassword.mockRejectedValueOnce(mockError); - - await loginCommand.parseAsync(['node', 'test']); - - expect(konsola.error).toHaveBeenCalledWith(mockError, false); - }); - }); - - describe('login-with-token strategy', () => { - it('should prompt the user for token when login-with-token is selected', async () => { - select.mockResolvedValueOnce('login-with-token'); - password.mockResolvedValueOnce('test-token'); - - await loginCommand.parseAsync(['node', 'test']); - - expect(password).toHaveBeenCalledWith(expect.objectContaining({ - message: 'Please enter your token:', - })); - }); - - it('should login with token if token is provided using login-with-token strategy', async () => { - vi.mocked(select).mockResolvedValueOnce('login-with-token'); - vi.mocked(password).mockResolvedValueOnce('test-token'); - const mockUser = { email: 'user@example.com' }; - vi.mocked(loginWithToken).mockResolvedValue({ user: mockUser }); - - await loginCommand.parseAsync(['node', 'test', '--region', 'eu']); - - expect(password).toHaveBeenCalledWith(expect.objectContaining({ - message: 'Please enter your token:', - })); - // Verify that loginWithToken was called with the correct arguments - expect(loginWithToken).toHaveBeenCalledWith('test-token', 'eu'); - - // Verify that updateSession was called with the correct arguments - expect(session().updateSession).toHaveBeenCalledWith(mockUser.email, 'test-token', 'eu'); - }); - }); - }); - - describe('--token', () => { - it('should login with a valid token', async () => { - const mockToken = 'test-token'; - const mockUser = { email: 'test@example.com', friendly_name: 'Test User' }; - vi.mocked(loginWithToken).mockResolvedValue({ user: mockUser }); - - await loginCommand.parseAsync(['node', 'test', '--token', mockToken, '--region', 'eu']); - - expect(loginWithToken).toHaveBeenCalledWith(mockToken, 'eu'); - - expect(konsola.ok).toHaveBeenCalledWith('Successfully logged in to region Europe (eu). Welcome Test User.', true); - }); - - it('should login with a valid token in another region --region', async () => { - const mockToken = 'test-token'; - const mockUser = { email: 'test@example.com', friendly_name: 'Test User' }; - vi.mocked(loginWithToken).mockResolvedValue({ user: mockUser }); - - await loginCommand.parseAsync(['node', 'test', '--token', mockToken, '--region', 'us']); - - expect(loginWithToken).toHaveBeenCalledWith(mockToken, 'us'); - - expect(konsola.ok).toHaveBeenCalledWith('Successfully logged in to region United States (us). Welcome Test User.', true); - }); - - it('should throw an error for an invalid token', async () => { - const mockError = new Error(`The token provided ${chalk.bold('inva*********')} is invalid: ${chalk.bold('401 Unauthorized')} - - Please make sure you are using the correct token and try again.`); - - vi.mocked(loginWithToken).mockRejectedValue(mockError); - - await loginCommand.parseAsync(['node', 'test', '--token', 'invalid-token']); - - // expect(handleError).toHaveBeenCalledWith(mockError, true) - expect(konsola.error).toHaveBeenCalledWith(mockError, false); - }); - }); - - describe('--region', () => { - it('should handle invalid region error with correct message', async () => { - await loginCommand.parseAsync(['node', 'test', '--region', 'invalid-region']); - - expect(konsola.error).toHaveBeenCalledWith(expect.any(Error), false); - - // Access the error argument - const errorArg = vi.mocked(konsola.error).mock.calls[0][0]; - - // Build the expected error message - const expectedMessage = `The provided region: invalid-region is not valid. Please use one of the following values: ${Object.values(regions).join(' | ')}`; - - expect(errorArg.message).toBe(expectedMessage); - }); - }); -}); diff --git a/src/commands/login/index.ts b/src/commands/login/index.ts deleted file mode 100644 index 6b9de29a..00000000 --- a/src/commands/login/index.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { Spinner } from '@topcli/spinner'; -import chalk from 'chalk'; -import { input, password, select } from '@inquirer/prompts'; -import type { RegionCode } from '../../constants'; -import { colorPalette, commands, regionNames, regions } from '../../constants'; -import { getProgram } from '../../program'; -import { CommandError, handleError, isRegion, isVitest, konsola } from '../../utils'; -import { loginWithEmailAndPassword, loginWithOtp, loginWithToken } from './actions'; -import { session } from '../../session'; - -const program = getProgram(); // Get the shared singleton instance - -const allRegionsText = Object.values(regions).join(','); -const loginStrategy = { - message: 'How would you like to login?', - choices: [ - { - name: 'With email', - value: 'login-with-email', - short: 'Email', - }, - { - name: 'With Token (SSO)', - value: 'login-with-token', - short: 'Token', - }, - ], -}; - -export const loginCommand = program - .command(commands.LOGIN) - .description('Login to the Storyblok CLI') - .option('-t, --token ', 'Token to login directly without questions, like for CI environments') - .option( - '-r, --region ', - `The region you would like to work in. Please keep in mind that the region must match the region of your space. This region flag will be used for the other cli's commands. You can use the values: ${allRegionsText}.`, - ) - .action(async (options: { - token: string; - region: RegionCode; - }) => { - konsola.title(` ${commands.LOGIN} `, colorPalette.LOGIN); - // Global options - const verbose = program.opts().verbose; - // Command options - const { token, region } = options; - - const { state, updateSession, persistCredentials, initializeSession } = session(); - - await initializeSession(); - - if (state.isLoggedIn && !state.envLogin) { - konsola.ok(`You are already logged in. If you want to login with a different account, please logout first.`); - return; - } - - if (region && !isRegion(region)) { - handleError(new CommandError(`The provided region: ${region} is not valid. Please use one of the following values: ${Object.values(regions).join(' | ')}`)); - return; - } - - if (token) { - const spinner = new Spinner({ - verbose: !isVitest, - }); - try { - let userRegion = region; - if (!userRegion) { - userRegion = await select({ - message: 'Please select the region you would like to work in:', - choices: Object.values(regions).map((region: RegionCode) => ({ - name: regionNames[region], - value: region, - })), - default: regions.EU, - }); - } - spinner.start(`Logging in with token`); - const { user } = await loginWithToken(token, userRegion); - updateSession(user.email, token, userRegion); - await persistCredentials(userRegion); - spinner.succeed(); - - konsola.ok(`Successfully logged in to region ${chalk.hex(colorPalette.PRIMARY)(`${regionNames[userRegion]} (${userRegion})`)}. Welcome ${chalk.hex(colorPalette.PRIMARY)(user.friendly_name)}.`, true); - } - catch (error) { - spinner.failed(); - konsola.br(); - handleError(error as Error, verbose); - } - } - else { - const spinner = new Spinner({ - verbose: !isVitest, - }); - try { - const strategy = await select(loginStrategy); - if (strategy === 'login-with-token') { - const userToken = await password({ - message: 'Please enter your token:', - validate: (value: string) => { - return value.length > 0; - }, - }); - spinner.start(`Logging in with token`); - const { user } = await loginWithToken(userToken, region); - spinner.succeed(); - updateSession(user.email, userToken, region); - await persistCredentials(region); - - konsola.ok(`Successfully logged in to region ${chalk.hex(colorPalette.PRIMARY)(`${regionNames[region]} (${region})`)}. Welcome ${chalk.hex(colorPalette.PRIMARY)(user.friendly_name)}.`, true); - } - - else { - const userEmail = await input({ - message: 'Please enter your email address:', - required: true, - validate: (value: string) => { - const emailRegex = /^[^\s@]+@[^\s@][^\s.@]*\.[^\s@]+$/; - return emailRegex.test(value); - }, - }); - const userPassword = await password({ - message: 'Please enter your password:', - }); - - let userRegion = region; - if (!userRegion) { - userRegion = await select({ - message: 'Please select the region you would like to work in:', - choices: Object.values(regions).map((region: RegionCode) => ({ - name: regionNames[region], - value: region, - })), - default: regions.EU, - }); - } - - spinner.start(`Logging in with email`); - spinner.succeed(); - const response = await loginWithEmailAndPassword(userEmail, userPassword, userRegion); - - if (response?.otp_required) { - const otp = await input({ - message: 'Add the code from your Authenticator app, or the one we sent to your e-mail / phone:', - required: true, - }); - - const otpResponse = await loginWithOtp(userEmail, userPassword, otp, userRegion); - if (otpResponse?.access_token) { - updateSession(userEmail, otpResponse?.access_token, userRegion); - } - } - else if (response?.access_token) { - updateSession(userEmail, response.access_token, userRegion); - } - await persistCredentials(region); - - konsola.ok(`Successfully logged in to region ${chalk.hex(colorPalette.PRIMARY)(`${regionNames[userRegion]} (${userRegion})`)}. Welcome ${chalk.hex(colorPalette.PRIMARY)(userEmail)}.`, true); - } - } - catch (error) { - spinner.failed(); - konsola.br(); - handleError(error as Error, verbose); - } - } - - konsola.br(); - }); diff --git a/src/commands/logout/README.md b/src/commands/logout/README.md deleted file mode 100644 index 23bdbd74..00000000 --- a/src/commands/logout/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# Logout Command - -The `logout` command allows you to securely log out from your Storyblok account and remove stored credentials. - -## Basic Usage - -```bash -storyblok logout -``` - -This command will: -- Remove your stored credentials from `~/.storyblok/credentials.json` -- Clear your current session -- Show a success message when completed - -## Notes - -- The command will show a warning if you're already logged out -- All stored credentials are removed from your system -- You'll need to log in again to use other Storyblok CLI commands -- The command is safe to run multiple times - -## Examples - -1. Basic logout: -```bash -storyblok logout -``` diff --git a/src/commands/logout/index.test.ts b/src/commands/logout/index.test.ts deleted file mode 100644 index 1e61d24b..00000000 --- a/src/commands/logout/index.test.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { logoutCommand } from './'; -import { session } from '../../session'; - -import { removeAllCredentials } from '../../creds'; - -vi.mock('../../creds', () => ({ - getCredentials: vi.fn(), - addCredentials: vi.fn(), - removeCredentials: vi.fn(), - removeAllCredentials: vi.fn(), -})); - -// Mocking the session module -vi.mock('../../session', () => { - let _cache: Record | null = null; - const session = () => { - if (!_cache) { - _cache = { - state: { - isLoggedIn: true, - password: 'valid-token', - region: 'eu', - }, - updateSession: vi.fn(), - persistCredentials: vi.fn(), - initializeSession: vi.fn(), - }; - } - return _cache; - }; - - return { - session, - }; -}); - -describe('logoutCommand', () => { - beforeEach(() => { - vi.resetAllMocks(); - vi.clearAllMocks(); - }); - - it('should log out the user if has previously login', async () => { - session().state = { - isLoggedIn: true, - password: 'valid-token', - region: 'eu', - }; - await logoutCommand.parseAsync(['node', 'test']); - expect(removeAllCredentials).toHaveBeenCalled(); - }); - - it('should not log out the user if has not previously login', async () => { - session().state = { - isLoggedIn: false, - }; - await logoutCommand.parseAsync(['node', 'test']); - expect(removeAllCredentials).not.toHaveBeenCalled(); - }); -}); diff --git a/src/commands/logout/index.ts b/src/commands/logout/index.ts deleted file mode 100644 index f50d8615..00000000 --- a/src/commands/logout/index.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { removeAllCredentials } from '../../creds'; -import { colorPalette, commands } from '../../constants'; -import { getProgram } from '../../program'; -import { handleError, konsola } from '../../utils'; -import { session } from '../../session'; - -const program = getProgram(); // Get the shared singleton instance - -export const logoutCommand = program - .command(commands.LOGOUT) - .description('Logout from the Storyblok CLI') - .action(async () => { - konsola.title(` ${commands.LOGOUT} `, colorPalette.LOGOUT); - - const verbose = program.opts().verbose; - try { - const { state, initializeSession } = session(); - await initializeSession(); - if (!state.isLoggedIn || !state.password || !state.region) { - konsola.warn(`You are already logged out. If you want to login, please use the login command.`); - konsola.br(); - return; - } - await removeAllCredentials(); - - konsola.ok(`Successfully logged out.`, true); - konsola.br(); - } - catch (error) { - handleError(error as Error, verbose); - } - konsola.br(); - }); diff --git a/src/commands/migrations/README.md b/src/commands/migrations/README.md deleted file mode 100644 index 7dc15a86..00000000 --- a/src/commands/migrations/README.md +++ /dev/null @@ -1,37 +0,0 @@ -# Migrations Command - -The `migrations` module provides tools to manage and execute migrations for your Storyblok components. Migrations are useful for making changes to existing stories in your space, such as updating component structures or content. - -## Subcommands - -- [`generate`](./generate/README.md): Create a new migration file for a specific component. -- [`run`](./run/README.md): Execute migrations on stories in your space. -- [`rollback`](./rollback/README.md): Revert previously applied migrations. - -> See each subcommand for detailed usage, options, and examples. - -## Workflow - -1. **Generate** a migration file for the component you want to modify -2. **Run** the migration to apply changes to your stories -3. If needed, **Rollback** the migration to revert changes - -## File Structure - -All migration files are stored in: -``` -.storyblok/ -└── migrations/ - └── YOUR_SPACE_ID/ - β”œβ”€β”€ component1.js - β”œβ”€β”€ component2.js - └── ... -``` - -## Notes - -- You must be logged in to use any migration command -- The space ID is required for all commands -- Use `--dry-run` to preview changes before applying them -- Migration files should include both forward and rollback logic -- Use filters and queries to target specific stories when running migrations diff --git a/src/commands/migrations/command.ts b/src/commands/migrations/command.ts deleted file mode 100644 index 0a2932b7..00000000 --- a/src/commands/migrations/command.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { getProgram } from '../../program'; - -const program = getProgram(); // Get the shared singleton instance - -// Components root command -export const migrationsCommand = program - .command('migrations') - .alias('mig') - .description(`Manage your space's migrations`) - .option('-s, --space ', 'space ID') - .option('-p, --path ', 'path to save the file. Default is .storyblok/migrations'); diff --git a/src/commands/migrations/generate/README.md b/src/commands/migrations/generate/README.md deleted file mode 100644 index a8a2502a..00000000 --- a/src/commands/migrations/generate/README.md +++ /dev/null @@ -1,106 +0,0 @@ -# Migrations Generate Command - -The `migrations generate` command allows you to create a migration file for a specific component in your Storyblok space. - -## Basic Usage - -```bash -storyblok migrations generate COMPONENT_NAME --space YOUR_SPACE_ID -``` - -This will generate a migration file for the specified component: -``` -.storyblok/ -└── migrations/ - └── YOUR_SPACE_ID/ - └── COMPONENT_NAME.js # Migration file -``` - -## Options - -| Option | Description | Default | -|--------|-------------|---------| -| `-s, --space ` | (Required) The ID of the space to generate the migration for | - | -| `--su, --suffix ` | Suffix to add to the file name (e.g., `{component-name}.{suffix}.js`) | - | -| `-p, --path ` | Custom path to store the migration file | `.storyblok/migrations` | - -## Examples - -1. Generate a migration for a component: -```bash -storyblok migrations generate hero --space 12345 -``` -Generates: -``` -.storyblok/ -└── migrations/ - └── 12345/ - └── hero.js # Migration file -``` - -2. Generate a migration with a suffix: -```bash -storyblok migrations generate hero --space 12345 --suffix field-name-change -``` -Generates: -``` -.storyblok/ -└── migrations/ - └── 12345/ - └── hero.field-name-change.js # Migration file -``` - -3. Generate a migration in a custom path: -```bash -storyblok migrations generate hero --space 12345 --path ./backup -``` -Generates: -``` -backup/ -└── migrations/ - └── 12345/ - └── hero.js # Migration file -``` - -## File Structure - -The command follows this pattern for file generation: -``` -{path}/ -└── migrations/ - └── {spaceId}/ - └── {componentName}.{suffix}.js # Migration file -``` - -Where: -- `{path}` is the base path (default: `.storyblok`) -- `{spaceId}` is your Storyblok space ID -- `{componentName}` is the name of the component -- `{suffix}` is the suffix in the file name if provided - -## Notes - -- The space ID is required -- The component name is required -- The command will create the necessary directories if they don't exist -- The generated migration file contains a template with examples for common transformations: - ```js - export default function (block) { - // Example to change a string to boolean - // block.field_name = !!(block.field_name) - - // Example to transfer content from other field - // block.target_field = block.source_field - - // Example to transform an array - // block.array_field = block.array_field.map(item => ({ ...item, new_prop: 'value' })) - - // Example to combine fields - // block.fullname = `${block.name} ${block.lastname}` - - return block; - } - ``` -- The migration function receives a `block` parameter containing the component's data -- You can modify the block's fields and return the updated block -- Make sure to return the block at the end of the function diff --git a/src/commands/migrations/generate/actions.test.ts b/src/commands/migrations/generate/actions.test.ts deleted file mode 100644 index 9f6714a9..00000000 --- a/src/commands/migrations/generate/actions.test.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { generateMigration } from './actions'; -import { resolvePath, saveToFile } from '../../../utils/filesystem'; -import { join, resolve } from 'node:path'; -import { handleFileSystemError } from '../../../utils'; -import type { SpaceComponent } from '../../components'; - -// Mock the filesystem utils -vi.mock('../../../utils/filesystem', () => ({ - saveToFile: vi.fn(), - resolvePath: vi.fn(), -})); - -// Mock the error handler -vi.mock('../../../utils', () => ({ - handleFileSystemError: vi.fn(), -})); - -describe('generateMigration', () => { - const mockSpace = '295017'; - const mockPath = ''; - const mockComponent: SpaceComponent = { - name: 'test_component', - display_name: 'Test Component', - created_at: '2024-01-01', - updated_at: '2024-01-01', - id: 1, - schema: { - fullname: { - type: 'text', - pos: 0, - }, - email: { - type: 'text', - pos: 1, - }, - }, - color: null, - internal_tags_list: [], - internal_tag_ids: [], - }; - - beforeEach(() => { - // Reset all mocks before each test - vi.clearAllMocks(); - // Mock implementation for resolvePath - vi.mocked(resolvePath).mockImplementation((_, path) => join(process.cwd(), '.storyblok', path)); - }); - - afterEach(() => { - vi.resetAllMocks(); - }); - - it('should generate migration file with correct template', async () => { - // Arrange - const expectedPath = join(process.cwd(), '.storyblok', `migrations/${mockSpace}`); - const expectedFilePath = join(expectedPath, `${mockComponent.name}.js`); - - const expectedTemplate = `export default function (block) { - // Example to change a string to boolean - // block.field_name = !!(block.field_name) - - // Example to transfer content from other field - // block.target_field = block.source_field - - // Example to transform an array - // block.array_field = block.array_field.map(item => ({ ...item, new_prop: 'value' })) - - return block; -} -`; - - // Act - await generateMigration(mockSpace, mockPath, mockComponent); - - // Assert - expect(saveToFile).toHaveBeenCalledWith(expectedFilePath, expectedTemplate); - }); - - it('should use custom path when provided', async () => { - // Arrange - const customPath = '/custom/path'; - const expectedPath = resolve(process.cwd(), customPath, 'migrations', mockSpace); - const expectedFilePath = join(expectedPath, `${mockComponent.name}.js`); - - // Act - await generateMigration(mockSpace, customPath, mockComponent); - - // Assert - expect(saveToFile).toHaveBeenCalledTimes(1); - expect(saveToFile).toHaveBeenCalledWith( - expectedFilePath, - expect.any(String), - ); - }); - - it('should handle filesystem errors properly', async () => { - // Arrange - const mockError = new Error('Failed to write file'); - vi.mocked(saveToFile).mockRejectedValueOnce(mockError); - - // Act - await generateMigration(mockSpace, mockPath, mockComponent); - - // Assert - expect(handleFileSystemError).toHaveBeenCalledWith('write', mockError); - }); -}); diff --git a/src/commands/migrations/generate/actions.ts b/src/commands/migrations/generate/actions.ts deleted file mode 100644 index b50be602..00000000 --- a/src/commands/migrations/generate/actions.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { resolvePath, saveToFile } from '../../../utils/filesystem'; -import type { SpaceComponent } from '../../components/constants'; -import { join, resolve } from 'node:path'; -import { handleFileSystemError } from '../../../utils'; - -const getMigrationTemplate = () => { - return `export default function (block) { - // Example to change a string to boolean - // block.field_name = !!(block.field_name) - - // Example to transfer content from other field - // block.target_field = block.source_field - - // Example to transform an array - // block.array_field = block.array_field.map(item => ({ ...item, new_prop: 'value' })) - - return block; -} -`; -}; - -export const generateMigration = async (space: string, path: string | undefined, component: SpaceComponent, suffix?: string) => { - const resolvedPath = path - ? resolve(process.cwd(), path, 'migrations', space) - : resolvePath(path, `migrations/${space}`); - - const fileName = suffix ? `${component.name}.${suffix}.js` : `${component.name}.js`; - const migrationPath = join(resolvedPath, fileName); - - try { - await saveToFile(migrationPath, getMigrationTemplate()); - } - catch (error) { - handleFileSystemError('write', error as Error); - } -}; diff --git a/src/commands/migrations/generate/constants.ts b/src/commands/migrations/generate/constants.ts deleted file mode 100644 index 83b698a5..00000000 --- a/src/commands/migrations/generate/constants.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { CommandOptions } from '../../../types'; - -export interface MigrationsGenerateOptions extends CommandOptions { - suffix?: string; -} diff --git a/src/commands/migrations/generate/index.test.ts b/src/commands/migrations/generate/index.test.ts deleted file mode 100644 index be0776ad..00000000 --- a/src/commands/migrations/generate/index.test.ts +++ /dev/null @@ -1,168 +0,0 @@ -import { session } from '../../../session'; -import { CommandError, konsola } from '../../../utils'; -import { generateMigration } from './actions'; -// Import the main components module first to ensure proper initialization - -import '../index'; -import { migrationsCommand } from '../command'; -import { fetchComponent } from '../../../commands/components'; - -vi.mock('../../../utils', async () => { - const actualUtils = await vi.importActual('../../../utils'); - return { - ...actualUtils, - isVitestRunning: true, - konsola: { - ok: vi.fn(), - title: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - br: vi.fn(), - }, - handleError: (error: unknown, header = false) => { - konsola.error(error as string, header); - // Optionally, prevent process.exit during tests - }, - }; -}); - -// Mock the session module -vi.mock('../../../session', () => { - let _cache: Record | null = null; - const session = () => { - if (!_cache) { - _cache = { - state: { - isLoggedIn: true, - password: 'mock-token', - region: 'eu', - }, - updateSession: vi.fn(), - persistCredentials: vi.fn(), - initializeSession: vi.fn(), - logout: vi.fn(), - }; - } - return _cache; - }; - - return { - session, - }; -}); - -vi.mock('../../../commands/components', () => ({ - fetchComponent: vi.fn(), -})); - -vi.mock('./actions', () => ({ - generateMigration: vi.fn(), -})); - -describe('migrations generate command', () => { - beforeEach(() => { - vi.resetAllMocks(); - vi.clearAllMocks(); - - // Reset the option values - migrationsCommand._optionValues = {}; - migrationsCommand._optionValueSources = {}; - for (const command of migrationsCommand.commands) { - command._optionValueSources = {}; - command._optionValues = {}; - } - }); - - it('should generate a migration', async () => { - const mockComponent = { - name: 'component-name', - display_name: 'Component Name', - created_at: '2021-08-09T12:00:00Z', - updated_at: '2021-08-09T12:00:00Z', - id: 12345, - schema: { - field1: { - type: 'bloks', - restrict_type: 'tags', - component_tag_whitelist: [1, 2], - }, - }, - color: null, - internal_tags_list: [], - internal_tag_ids: [], - }; - - session().state = { - isLoggedIn: true, - password: 'valid-token', - region: 'eu', - }; - - vi.mocked(fetchComponent).mockResolvedValue(mockComponent); - - await migrationsCommand.parseAsync(['node', 'test', 'generate', 'component-name', '--space', '12345']); - - expect(generateMigration).toHaveBeenCalledWith('12345', undefined, mockComponent, undefined); - expect(konsola.ok).toHaveBeenCalledWith('You can find the migration file in .storyblok/migrations/12345/component-name.js'); - }); - - it('should generate a migration with a path', async () => { - const mockComponent = { - name: 'component-name', - display_name: 'Component Name', - created_at: '2021-08-09T12:00:00Z', - updated_at: '2021-08-09T12:00:00Z', - id: 12345, - schema: { - field1: { - type: 'bloks', - restrict_type: 'tags', - component_tag_whitelist: [1, 2], - }, - }, - color: null, - internal_tags_list: [], - internal_tag_ids: [], - }; - - session().state = { - isLoggedIn: true, - password: 'valid-token', - region: 'eu', - }; - - vi.mocked(fetchComponent).mockResolvedValue(mockComponent); - - await migrationsCommand.parseAsync(['node', 'test', 'generate', 'component-name', '--space', '12345', '--path', 'custom']); - - expect(generateMigration).toHaveBeenCalledWith('12345', 'custom', mockComponent, undefined); - expect(konsola.ok).toHaveBeenCalledWith('You can find the migration file in custom/migrations/12345/component-name.js'); - }); - - it('should throw an error if the component is not found', async () => { - session().state = { - isLoggedIn: true, - password: 'valid-token', - region: 'eu', - }; - - vi.mocked(fetchComponent).mockResolvedValue(undefined); - - await migrationsCommand.parseAsync(['node', 'test', 'generate', 'component-name', '--space', '12345']); - - const mockError = new CommandError('No component found with name "component-name"'); - expect(konsola.error).toHaveBeenCalledWith(mockError, false); - }); - - it('should throw an error if the component name is not provided', async () => { - session().state = { - isLoggedIn: true, - password: 'valid-token', - region: 'eu', - }; - - const mockError = new CommandError('Please provide the component name as argument storyblok migrations generate YOUR_COMPONENT_NAME.'); - await migrationsCommand.parseAsync(['node', 'test', 'generate', '--space', '12345']); - expect(konsola.error).toHaveBeenCalledWith(mockError, false); - }); -}); diff --git a/src/commands/migrations/generate/index.ts b/src/commands/migrations/generate/index.ts deleted file mode 100644 index a16b4691..00000000 --- a/src/commands/migrations/generate/index.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { Spinner } from '@topcli/spinner'; -import chalk from 'chalk'; - -import type { MigrationsGenerateOptions } from './constants'; -import { colorPalette, commands } from '../../../constants'; -import { getProgram } from '../../../program'; -import { CommandError, handleError, isVitest, konsola, requireAuthentication } from '../../../utils'; -import { session } from '../../../session'; -import { fetchComponent } from '../../../commands/components'; -import { migrationsCommand } from '../command'; -import { generateMigration } from './actions'; -import { mapiClient } from '../../../api'; - -const program = getProgram(); - -migrationsCommand - .command('generate [componentName]') - .description('Generate a migration file') - .option('--su, --suffix ', 'suffix to add to the file name (e.g. {component-name}..js)') - .action(async (componentName: string | undefined, options: MigrationsGenerateOptions) => { - konsola.title(` ${commands.MIGRATIONS} `, colorPalette.MIGRATIONS, componentName ? `Generating migration for component ${componentName}...` : 'Generating migrations...'); - // Global options - const verbose = program.opts().verbose; - - // Command options - const { space, path } = migrationsCommand.opts(); - - const { suffix } = options; - - if (!componentName) { - handleError(new CommandError(`Please provide the component name as argument ${chalk.hex(colorPalette.MIGRATIONS)('storyblok migrations generate YOUR_COMPONENT_NAME.')}`), verbose); - return; - } - - const { state, initializeSession } = session(); - await initializeSession(); - - if (!requireAuthentication(state, verbose)) { - return; - } - if (!space) { - handleError(new CommandError(`Please provide the space as argument --space YOUR_SPACE_ID.`), verbose); - return; - } - - const { password, region } = state; - - mapiClient({ - token: password, - region, - }); - - const spinner = new Spinner({ - verbose: !isVitest, - }).start(`Generating migration for component ${componentName}...`); - try { - const component = await fetchComponent(space, componentName); - - if (!component) { - spinner.failed(`Failed to fetch component ${componentName}. Make sure the component exists in your space.`); - handleError(new CommandError(`No component found with name "${componentName}"`), verbose); - return; - } - - await generateMigration(space, path, component, suffix); - - spinner.succeed(`Migration generated for component ${chalk.hex(colorPalette.MIGRATIONS)(componentName)} - Completed in ${spinner.elapsedTime.toFixed(2)}ms`); - - const fileName = suffix ? `${component.name}.${suffix}.js` : `${component.name}.js`; - const migrationPath = path ? `${path}/migrations/${space}/${fileName}` : `.storyblok/migrations/${space}/${fileName}`; - konsola.ok(`You can find the migration file in ${chalk.hex(colorPalette.MIGRATIONS)(migrationPath)}`); - } - catch (error) { - spinner.failed(`Failed to generate migration for component ${componentName}`); - handleError(error as Error, verbose); - } - }); diff --git a/src/commands/migrations/index.ts b/src/commands/migrations/index.ts deleted file mode 100644 index 91e191c2..00000000 --- a/src/commands/migrations/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -import './command'; -import './generate'; -import './run'; -import './rollback'; - -export * from './generate/actions'; -export * from './generate/constants'; - -export * from './rollback/actions'; - -export * from './run/actions'; -export * from './run/constants'; diff --git a/src/commands/migrations/rollback/README.md b/src/commands/migrations/rollback/README.md deleted file mode 100644 index 44b154d9..00000000 --- a/src/commands/migrations/rollback/README.md +++ /dev/null @@ -1,60 +0,0 @@ -# Migrations Rollback Command - -The `migrations rollback` command allows you to revert migrations that were previously applied to stories in your Storyblok space. - -> [!NOTE] -> Before rolling back migrations, you need to have run migrations first using the `migrations run` command. This ensures that rollback snapshots are available in the `.storyblok/migrations/{spaceId}/rollbacks` directory. See [migrations run](./run/README.md) for more details. - -## Basic Usage - -```bash -storyblok migrations rollback MIGRATION_FILE --space YOUR_SPACE_ID -``` - -This will rollback the specified migration file: -``` -.storyblok/ -└── migrations/ - └── YOUR_SPACE_ID/ - └── rollbacks/ - └── MIGRATION_FILE.json -``` - -## Options - -| Option | Description | Default | -|--------|-------------|---------| -| `-s, --space ` | (Required) The ID of the space to rollback migrations in | - | -| `-p, --path ` | Custom path to read migration files from | `.storyblok/migrations` | - -## Examples - -1. Rollback a specific migration: -```bash -storyblok migrations rollback migration-component.1746452866186.json --space 12345 -``` - -## File Structure - -The command reads from the following file structure: -``` -{path}/ -└── migrations/ - └── {spaceId}/ - └── rollbacks/ - └── {migrationFile}.json -``` - -Where: -- `{path}` is the base path (default: `.storyblok`) -- `{spaceId}` is your Storyblok space ID -- `{migrationFile}` is the name of the migration file to rollback - -## Notes - -- The space ID is required -- The migration file name is required -- The command will: - - Read the rollback data from the specified file - - Restore each story to its original state - - Update the stories in Storyblok diff --git a/src/commands/migrations/rollback/actions.test.ts b/src/commands/migrations/rollback/actions.test.ts deleted file mode 100644 index 8f431738..00000000 --- a/src/commands/migrations/rollback/actions.test.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { vol } from 'memfs'; -import { readRollbackFile, type RollbackData, saveRollbackData } from './actions'; -import { CommandError } from '../../../utils'; -import type { StoryContent } from '../../stories/constants'; - -// Mock dependencies -vi.mock('node:fs'); -vi.mock('node:fs/promises'); - -const mockStoryContent: StoryContent = { - _uid: 'test-uid', - component: 'test', - body: [], -}; - -describe('saveRollbackData', () => { - beforeEach(() => { - vol.reset(); - }); - - it('should save rollback data successfully', async () => { - const mockStories = [ - { - id: 1, - name: 'Test Story', - content: mockStoryContent, - }, - ]; - - await saveRollbackData({ - space: '12345', - path: '/test/path', - stories: mockStories, - migrationFile: 'test-migration.js', - }); - - // Check if file was created with correct content - const rollbackFiles = vol.readdirSync('/test/path/migrations/12345/rollbacks'); - expect(rollbackFiles.length).toBe(1); - expect(rollbackFiles[0]).toMatch(/test-migration\.\d+\.json/); - - const fileContent = vol.readFileSync(`/test/path/migrations/12345/rollbacks/${rollbackFiles[0]}`); - const savedContent = JSON.parse(fileContent.toString('utf8')); - expect(savedContent).toEqual({ - stories: [{ - storyId: 1, - name: 'Test Story', - content: mockStoryContent, - }], - }); - }); - - it('should create directory if it does not exist', async () => { - const mockStories = [ - { - id: 1, - name: 'Test Story', - content: mockStoryContent, - }, - ]; - - await saveRollbackData({ - space: '12345', - path: '/nonexistent/path', - stories: mockStories, - migrationFile: 'test-migration.js', - }); - - // Verify directory was created - expect(vol.existsSync('/nonexistent/path/migrations/12345/rollbacks')).toBe(true); - }); - - it('should handle empty stories array', async () => { - await saveRollbackData({ - space: '12345', - path: '/test/path', - stories: [], - migrationFile: 'test-migration.js', - }); - - const rollbackFiles = vol.readdirSync('/test/path/migrations/12345/rollbacks'); - expect(rollbackFiles.length).toBe(1); - - const fileContent = vol.readFileSync(`/test/path/migrations/12345/rollbacks/${rollbackFiles[0]}`); - const savedContent = JSON.parse(fileContent.toString('utf8')); - expect(savedContent.stories).toEqual([]); - }); -}); - -describe('readRollbackFile', () => { - beforeEach(() => { - vol.reset(); - }); - - it('should read rollback file successfully', async () => { - const mockRollbackData: RollbackData = { - stories: [ - { - storyId: 1, - name: 'Test Story', - content: mockStoryContent, - }, - ], - }; - - // Create mock rollback file - vol.fromJSON({ - '.storyblok/migrations/12345/rollbacks/test-migration.json': JSON.stringify(mockRollbackData), - }); - - const result = await readRollbackFile({ - space: '12345', - path: '.storyblok', - migrationFile: 'test-migration', - }); - - expect(result).toEqual(mockRollbackData); - }); - - it('should throw error when file does not exist', async () => { - await expect( - readRollbackFile({ - space: '12345', - path: '.storyblok', - migrationFile: 'nonexistent', - }), - ).rejects.toThrow(CommandError); - }); - - it('should throw error when file is invalid JSON', async () => { - // Create invalid JSON file - vol.fromJSON({ - '.storyblok/migrations/12345/rollbacks/test-migration.json': 'invalid json', - }); - - await expect( - readRollbackFile({ - space: '12345', - path: '.storyblok', - migrationFile: 'test-migration', - }), - ).rejects.toThrow(CommandError); - }); -}); diff --git a/src/commands/migrations/rollback/actions.ts b/src/commands/migrations/rollback/actions.ts deleted file mode 100644 index 58428d9c..00000000 --- a/src/commands/migrations/rollback/actions.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { join } from 'node:path'; -import { resolvePath, saveToFile } from '../../../utils/filesystem'; -import type { StoryContent } from '../../stories/constants'; -import { readFile } from 'node:fs/promises'; -import { CommandError } from '../../../utils'; - -export interface RollbackData { - stories: Array<{ - storyId: number; - name: string; - content: StoryContent; - }>; -} - -/** - * Save the rollback data for a migration - * @param options - Options for saving rollback data - * @param options.space - The space ID - * @param options.path - Base path for saving rollback data - * @param options.stories - Array of stories with their original content - * @param options.migrationFile - Name of the migration file being applied - */ -export async function saveRollbackData({ - space, - path, - stories, - migrationFile, -}: { - space: string; - path: string; - stories: Array<{ id: number; name: string; content: StoryContent }>; - migrationFile: string; -}): Promise { - // Create the rollback data structure - const rollbackData: RollbackData = { - stories: stories.map(story => ({ - storyId: story.id, - name: story.name, - content: story.content, - })), - }; - - // Resolve the path for rollbacks - const rollbacksPath = resolvePath(path, `migrations/${space}/rollbacks`); - - // The rollback file will have the same name as the migration file but with a timestamp suffix - const timestamp = Date.now(); - const rollbackFileName = `${migrationFile.replace('.js', '')}.${timestamp}.json`; - const rollbackFilePath = join(rollbacksPath, rollbackFileName); - - try { - // Save the rollback data as JSON - await saveToFile( - rollbackFilePath, - JSON.stringify(rollbackData, null, 2), - ); - } - catch (error) { - // If the directory doesn't exist, create it and try again - if ((error as NodeJS.ErrnoException).code === 'ENOENT') { - // Create the directory structure - const fs = await import('node:fs/promises'); - await fs.mkdir(rollbacksPath, { recursive: true }); - - // Try saving again - await saveToFile( - rollbackFilePath, - JSON.stringify(rollbackData, null, 2), - ); - } - else { - throw error; - } - } -} - -/** - * Read rollback data from a file - * @param options - Options for reading rollback data - * @param options.space - The space ID - * @param options.path - Base path for rollback files - * @param options.migrationFile - Name of the migration file to rollback - * @returns The rollback data containing stories to restore - */ -export async function readRollbackFile({ - space, - path, - migrationFile, -}: { - space: string; - path: string; - migrationFile: string; -}): Promise { - try { - const resolvedPath = resolvePath(path, `migrations/${space}/rollbacks`); - const rollbackFilePath = join(resolvedPath, migrationFile); - - // Read the rollback file - const filePath = rollbackFilePath.endsWith('.json') - ? rollbackFilePath - : `${rollbackFilePath}.json`; - - return JSON.parse(await readFile(filePath, 'utf-8')); - } - catch (error) { - throw new CommandError(`Failed to read rollback file: ${(error as Error).message}`); - } -} diff --git a/src/commands/migrations/rollback/index.test.ts b/src/commands/migrations/rollback/index.test.ts deleted file mode 100644 index 85901f31..00000000 --- a/src/commands/migrations/rollback/index.test.ts +++ /dev/null @@ -1,260 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { session } from '../../../session'; -import { konsola } from '../../../utils'; -import '../index'; -import { migrationsCommand } from '../command'; -import { readRollbackFile } from './actions'; -import { updateStory } from '../../stories/actions'; -import type { RollbackData } from './actions'; -import type { StoryContent } from '../../stories/constants'; - -vi.mock('../../../utils/konsola'); - -// Mock process.exit -const mockExit = vi.spyOn(process, 'exit').mockImplementation(() => undefined as never); - -// Mock the session module -vi.mock('../../../session', () => { - let _cache: Record | null = null; - const session = () => { - if (!_cache) { - _cache = { - state: { - isLoggedIn: true, - password: 'mock-token', - region: 'eu', - }, - updateSession: vi.fn(), - persistCredentials: vi.fn(), - initializeSession: vi.fn(), - logout: vi.fn(), - }; - } - return _cache; - }; - - return { session }; -}); - -vi.mock('../../stories/actions', () => ({ - updateStory: vi.fn(), -})); - -vi.mock('./actions', () => ({ - readRollbackFile: vi.fn(), -})); - -// Mock story content -const mockStoryContent: StoryContent = { - _uid: 'test-uid', - component: 'test', - body: [], -}; - -// Mock rollback data -const mockRollbackData: RollbackData = { - stories: [ - { - storyId: 1, - name: 'Test Story', - content: mockStoryContent, - }, - { - storyId: 2, - name: 'Another Story', - content: mockStoryContent, - }, - ], -}; - -describe('migrations rollback command', () => { - beforeEach(() => { - vi.resetAllMocks(); - vi.clearAllMocks(); - mockExit.mockClear(); - }); - - it('should rollback a migration successfully', async () => { - session().state = { - isLoggedIn: true, - password: 'valid-token', - region: 'eu', - }; - - vi.mocked(readRollbackFile).mockResolvedValue(mockRollbackData); - vi.mocked(updateStory).mockResolvedValue({ id: 1 } as any); - - await migrationsCommand.parseAsync([ - 'node', - 'test', - 'rollback', - 'test-migration', - '--space', - '12345', - ]); - - expect(readRollbackFile).toHaveBeenCalledWith({ - space: '12345', - path: undefined, - migrationFile: 'test-migration', - }); - - // Verify that updateStory was called for each story - expect(updateStory).toHaveBeenCalledTimes(2); - expect(updateStory).toHaveBeenCalledWith( - '12345', - 'valid-token', - 'eu', - 1, - { - story: { - content: mockStoryContent, - id: 1, - name: 'Test Story', - }, - force_update: '1', - }, - ); - expect(updateStory).toHaveBeenCalledWith( - '12345', - 'valid-token', - 'eu', - 2, - { - story: { - content: mockStoryContent, - id: 2, - name: 'Another Story', - }, - force_update: '1', - }, - ); - }); - - it('should handle not logged in error', async () => { - session().state = { - isLoggedIn: false, - password: undefined, - region: undefined, - }; - - await migrationsCommand.parseAsync([ - 'node', - 'test', - 'rollback', - 'test-migration', - '--space', - '12345', - ]); - - expect(konsola.error).toHaveBeenCalledWith('You are currently not logged in. Please run storyblok login to authenticate, or storyblok signup to sign up.', null, { - header: true, - }); - expect(readRollbackFile).not.toHaveBeenCalled(); - expect(updateStory).not.toHaveBeenCalled(); - }); - - it('should handle missing space error', async () => { - session().state = { - isLoggedIn: true, - password: 'valid-token', - region: 'eu', - }; - - await migrationsCommand.parseAsync([ - 'node', - 'test', - 'rollback', - 'test-migration', - ]); - - expect(konsola.error).toHaveBeenCalledWith( - 'Please provide the space as argument --space YOUR_SPACE_ID.', - null, - { - header: true, - }, - ); - expect(readRollbackFile).not.toHaveBeenCalled(); - expect(updateStory).not.toHaveBeenCalled(); - }); - - it('should handle rollback file read error', async () => { - session().state = { - isLoggedIn: true, - password: 'valid-token', - region: 'eu', - }; - - vi.mocked(readRollbackFile).mockRejectedValue(new Error('File not found')); - - await migrationsCommand.parseAsync([ - 'node', - 'test', - 'rollback', - 'test-migration', - '--space', - '12345', - ]); - - expect(konsola.error).toHaveBeenCalledWith( - 'Failed to rollback migration: File not found', - null, - { - header: true, - }, - ); - expect(updateStory).not.toHaveBeenCalled(); - }); - - it('should handle story update error', async () => { - session().state = { - isLoggedIn: true, - password: 'valid-token', - region: 'eu', - }; - - vi.mocked(readRollbackFile).mockResolvedValue(mockRollbackData); - vi.mocked(updateStory).mockRejectedValueOnce(new Error('Update failed')); - - await migrationsCommand.parseAsync([ - 'node', - 'test', - 'rollback', - 'test-migration', - '--space', - '12345', - ]); - - // Should still try to update the second story even if the first one fails - expect(updateStory).toHaveBeenCalledTimes(2); - }); - - it('should handle custom path option', async () => { - session().state = { - isLoggedIn: true, - password: 'valid-token', - region: 'eu', - }; - - vi.mocked(readRollbackFile).mockResolvedValue(mockRollbackData); - vi.mocked(updateStory).mockResolvedValue({ id: 1 } as any); - - await migrationsCommand.parseAsync([ - 'node', - 'test', - 'rollback', - 'test-migration', - '--space', - '12345', - '--path', - '/custom/path', - ]); - - expect(readRollbackFile).toHaveBeenCalledWith({ - space: '12345', - path: '/custom/path', - migrationFile: 'test-migration', - }); - }); -}); diff --git a/src/commands/migrations/rollback/index.ts b/src/commands/migrations/rollback/index.ts deleted file mode 100644 index c7b1f4f0..00000000 --- a/src/commands/migrations/rollback/index.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { colorPalette, commands } from '../../../constants'; -import { CommandError, handleError, konsola, requireAuthentication } from '../../../utils'; -import { getProgram } from '../../../program'; -import { migrationsCommand } from '../command'; -import { session } from '../../../session'; -import { readRollbackFile } from './actions'; -import { updateStory } from '../../stories/actions'; -import { Spinner } from '@topcli/spinner'; -import chalk from 'chalk'; - -const program = getProgram(); - -migrationsCommand.command('rollback [migrationFile]') - .description('Rollback a migration') - .action(async (migrationFile: string) => { - konsola.title(` ${commands.MIGRATIONS} `, colorPalette.MIGRATIONS, `Rolling back migration ${chalk.hex(colorPalette.MIGRATIONS)(migrationFile)}...`); - - const verbose = program.opts().verbose; - - // Command options - const { space, path } = migrationsCommand.opts(); - - const { state, initializeSession } = session(); - await initializeSession(); - - if (!requireAuthentication(state, verbose)) { - return; - } - - if (!space) { - handleError(new CommandError(`Please provide the space as argument --space YOUR_SPACE_ID.`), verbose); - return; - } - - const { password, region } = state; - - try { - // Read the rollback data - const rollbackData = await readRollbackFile({ - space, - path, - migrationFile, - }); - - // Restore each story to its original state - for (const story of rollbackData.stories) { - const spinner = new Spinner({ verbose }).start(`Restoring story ${chalk.hex(colorPalette.PRIMARY)(story.name || story.storyId)}...`); - try { - await updateStory(space, password, region, story.storyId, { - story: { - content: story.content, - id: story.storyId, - name: story.name, - }, - force_update: '1', - }); - spinner.succeed(`Restored story ${chalk.hex(colorPalette.PRIMARY)(story.name || story.storyId)}`); - } - catch (error) { - spinner.failed(`Failed to restore story ${chalk.hex(colorPalette.PRIMARY)(story.name || story.storyId)}: ${(error as Error).message}`); - } - } - } - catch (error) { - handleError(new CommandError(`Failed to rollback migration: ${(error as Error).message}`), verbose); - } - }); diff --git a/src/commands/migrations/run/README.md b/src/commands/migrations/run/README.md deleted file mode 100644 index e4c628db..00000000 --- a/src/commands/migrations/run/README.md +++ /dev/null @@ -1,128 +0,0 @@ -# Migrations Run Command - -The `migrations run` command allows you to execute migrations on stories in your Storyblok space. - -> [!NOTE] -> Before running migrations, you must create them by running the `migrations generate` command. See [migrations generate](./generate/README.md) for more details. - -> [!NOTE] -> When running a migration, a snapshot of the story's content will be automatically created in the `.storyblok/migrations/{spaceId}/rollbacks` directory with a timestamp. These can be used to rollback changes if needed. - -## Basic Usage - -```bash -storyblok migrations run --space YOUR_SPACE_ID -``` - -This will run all migrations found in: -``` -.storyblok/ -└── migrations/ - └── YOUR_SPACE_ID/ - β”œβ”€β”€ hero.js - β”œβ”€β”€ feature.js - └── ... -``` - -## Run a Single Component Migration - -```bash -storyblok migrations run COMPONENT_NAME --space YOUR_SPACE_ID -``` - -This will run migrations for the specified component: -``` -.storyblok/ -└── migrations/ - └── YOUR_SPACE_ID/ - └── COMPONENT_NAME.js -``` - -## Options - -| Option | Description | Default | -|--------|-------------|---------| -| `-s, --space ` | (Required) The ID of the space to run migrations in | - | -| `--fi, --filter ` | Glob pattern to filter migration filenames (e.g., "hero*" will match all migration files starting with "hero") | - | -| `-d, --dry-run` | Preview changes without applying them to Storyblok | `false` | -| `-q, --query ` | Filter stories by content attributes using Storyblok filter query syntax (e.g., `--query="[highlighted][in]=true"`) | - | -| `--starts-with ` | Filter stories by path (e.g., `--starts-with="/en/blog/"`) | - | -| `--publish ` | Publication mode: `all` (publish all stories), `published` (only publish stories that were already published), or `published-with-changes` (only publish stories that have unpublished changes) | - | -| `-p, --path ` | Custom path to read migration files from | `.storyblok/migrations` | - -## Examples - -1. Run all migrations: -```bash -storyblok migrations run --space 12345 -``` - -2. Run migrations for a specific component: -```bash -storyblok migrations run hero --space 12345 -``` - -3. Run migrations with a filter: -```bash -storyblok migrations run --space 12345 --filter "hero*" -``` - -4. Preview changes without applying them: -```bash -storyblok migrations run --space 12345 --dry-run -``` - -5. Run migrations and publish all stories: -```bash -storyblok migrations run --space 12345 --publish all -``` - -6. Run migrations and only publish stories that were already published: -```bash -storyblok migrations run --space 12345 --publish published -``` - -7. Run migrations and only publish stories that have unpublished changes: -```bash -storyblok migrations run --space 12345 --publish published-with-changes -``` - -8. Run migrations on stories in a specific path: -```bash -storyblok migrations run --space 12345 --starts-with "/en/blog/" -``` - -9. Run migrations on stories matching a query: -```bash -storyblok migrations run --space 12345 --query "[highlighted][in]=true" -``` - -## File Structure - -The command reads from the following file structure: -``` -{path}/ -└── migrations/ - └── {spaceId}/ - β”œβ”€β”€ {componentName1}.js - β”œβ”€β”€ {componentName2}.js - └── ... -``` - -Where: -- `{path}` is the base path (default: `.storyblok`) -- `{spaceId}` is your Storyblok space ID -- `{componentName}` is the name of the component - -## Notes - -- The space ID is required -- The command will: - - Read all migration files (or filtered by component/filter) - - Find stories that use the components - - Apply the migrations to the stories - - Update the stories in Storyblok (unless `--dry-run` is used) - - Create a snapshot of each story's content in `.storyblok/migrations/{spaceId}/rollbacks` with a timestamp for potential rollbacks -- Use `--dry-run` to preview changes before applying them -- Use `--publish` to control which stories are affected -- Use `--starts-with` and `--query` to filter which stories are affected diff --git a/src/commands/migrations/run/actions.test.ts b/src/commands/migrations/run/actions.test.ts deleted file mode 100644 index 66bd3068..00000000 --- a/src/commands/migrations/run/actions.test.ts +++ /dev/null @@ -1,179 +0,0 @@ -import { vol } from 'memfs'; -import { getMigrationFunction, readJavascriptFile, readMigrationFiles } from './actions'; -import { FileSystemError } from '../../../utils/error'; - -vi.mock('node:fs'); -vi.mock('node:fs/promises'); - -describe('readJavascriptFile', () => { - beforeEach(() => { - vol.reset(); - }); - - it('should read javascript file successfully', async () => { - vol.fromJSON({ - '/path/to/migrations/12345/migration-1.js': 'export default function (block) { return block; }', - }); - - const result = await readJavascriptFile('/path/to/migrations/12345/migration-1.js'); - expect(result).toEqual('export default function (block) { return block; }'); - }); - - it('should throw FileSystemError when file does not exist', async () => { - await expect(readJavascriptFile('/path/to/nonexistent.js')).rejects.toThrow(FileSystemError); - }); - - it('should throw FileSystemError when file is empty', async () => { - vol.fromJSON({ - '/path/to/migrations/12345/empty.js': '', - }); - - await expect(readJavascriptFile('/path/to/migrations/12345/empty.js')).rejects.toThrow(FileSystemError); - }); -}); - -describe('readMigrationFiles', () => { - it('should read migration files successfully', async () => { - vol.fromJSON({ - '/path/to/migrations/12346/migration-1.js': 'export default function (block) { return block; }', - }); - - const result = await readMigrationFiles({ - path: '/path/to/', - space: '12346', - }); - - expect(result).toEqual([ - { - name: 'migration-1.js', - content: 'export default function (block) { return block; }', - }, - ]); - }); - - it('should throw FileSystemError when directory does not exist', async () => { - await expect(readMigrationFiles({ - path: '/path/to/', - space: '12347', - })).rejects.toThrow(FileSystemError); - }); - - it('should return empty array when directory is empty', async () => { - vol.fromJSON({ - '/path/to/migrations/12348/': null, - }); - - const result = await readMigrationFiles({ - path: '/path/to/', - space: '12348', - }); - - expect(result).toEqual([]); - }); - - it('should throw FileSystemError when file does not end with .js', async () => { - vol.fromJSON({ - '/path/to/migrations/12349/migration-1.txt': 'export default function (block) { return block; }', - }); - - const result = await readMigrationFiles({ - path: '/path/to/', - space: '12349', - }); - - expect(result).toEqual([]); - }); - - it('should return empty array when no JS files are found', async () => { - vol.fromJSON({ - '/path/to/migrations/12349/migration-1.txt': 'export default function (block) { return block; }', - }); - - const result = await readMigrationFiles({ - path: '/path/to/', - space: '12349', - }); - - expect(result).toEqual([]); - }); - - it('should return filtered files when filter is provided', async () => { - vol.fromJSON({ - '/path/to/migrations/12350/migration-1.suffix.js': 'export default function (block) { return block; }', - '/path/to/migrations/12350/migration-2.js': 'export default function (block) { return block; }', - }); - - const result = await readMigrationFiles({ - path: '/path/to/', - space: '12350', - filter: '**.suffix.js', - }); - - expect(result.length).toBe(1); - expect(result[0].name).toBe('migration-1.suffix.js'); - }); - - it('should return empty array when file does not match filter', async () => { - vol.fromJSON({ - '/path/to/migrations/12350/migration-1.js': 'export default function (block) { return block; }', - '/path/to/migrations/12350/migration-2.js': 'export default function (block) { return block; }', - }); - - const result = await readMigrationFiles({ - path: '/path/to/', - space: '12350', - filter: 'component-*', - }); - - expect(result).toEqual([]); - }); -}); - -describe('getMigrationFunction', () => { - beforeEach(() => { - vol.reset(); - vi.resetModules(); - }); - - it('should return migration function successfully', async () => { - const mockMigrationFn = (block: any) => ({ ...block, migrated: true }); - - vol.fromJSON({ - '/path/to/migrations/12351/migration-1.js': 'export default function (block) { return block; }', - }); - - // Mock the module system to handle dynamic imports - vi.doMock('/path/to/migrations/12351/migration-1.js', () => ({ - default: mockMigrationFn, - })); - - const result = await getMigrationFunction('migration-1.js', '12351', '/path/to/'); - - expect(result).toBe(mockMigrationFn); - expect(typeof result).toBe('function'); - - // Test the function works - const testBlock = { foo: 'bar' }; - expect(result!(testBlock)).toEqual({ foo: 'bar', migrated: true }); - }); - - it('should return null when module does not export a default function', async () => { - vol.fromJSON({ - '/path/to/migrations/12351/migration-2.js': 'export const something = true;', - }); - - // Mock the module without a default export - vi.doMock('/path/to/migrations/12351/migration-2.js', () => ({ - something: true, - })); - - const result = await getMigrationFunction('migration-2.js', '12351', '/path/to/'); - expect(result).toBeNull(); - }); - - it('should return null when file does not exist', async () => { - // No need to mock anything - the import will fail naturally - const result = await getMigrationFunction('non-existent.js', '12351', '/path/to/'); - expect(result).toBeNull(); - }); -}); diff --git a/src/commands/migrations/run/actions.ts b/src/commands/migrations/run/actions.ts deleted file mode 100644 index a361bf27..00000000 --- a/src/commands/migrations/run/actions.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { readdir, readFile } from 'node:fs/promises'; -import { resolvePath } from '../../../utils/filesystem'; -import { FileSystemError } from '../../../utils/error'; -import { join } from 'node:path'; -import type { MigrationFile, ReadMigrationFilesOptions } from './constants'; -import { createRegexFromGlob, konsola } from '../../../utils'; -import type { StoryContent } from '../../stories/constants'; - -export async function readJavascriptFile(filePath: string): Promise { - try { - const content = await readFile(filePath, 'utf-8'); - if (!content) { - throw new FileSystemError('invalid_argument', 'read', new Error(`File ${filePath} is empty`)); - } - return content; - } - catch (error) { - throw new FileSystemError('file_not_found', 'read', error as Error); - } -} - -export async function readMigrationFiles(options: ReadMigrationFilesOptions): Promise { - const { space, path, filter } = options; - const resolvedPath = resolvePath(path, `migrations/${space}`); - - // Check if directory exists first - try { - await readdir(resolvedPath); - } - catch (error) { - const message = `No directory found for space "${space}". Please make sure you have pulled the migrations first by running:\n\n storyblok migrations pull --space ${space}`; - throw new FileSystemError( - 'file_not_found', - 'read', - error as Error, - message, - ); - } - - try { - const dirFiles = await readdir(resolvedPath); - const migrationFiles: MigrationFile[] = []; - const filterRegex = filter ? createRegexFromGlob(filter) : null; - - if (dirFiles.length > 0) { - for (const file of dirFiles) { - if (!file.endsWith('.js')) { - continue; - } - - // Apply glob filter if provided - if (filterRegex && !filterRegex.test(file)) { - continue; - } - - const filePath = join(resolvedPath, file); - const content = await readJavascriptFile(filePath); - - migrationFiles.push({ - name: file, - content, - }); - } - } - - return migrationFiles; - } - catch (error) { - throw new FileSystemError( - 'file_not_found', - 'read', - error as Error, - ); - } -} - -/** - * Loads a migration function from a file using dynamic import - * @param fileName - The name of the migration file - * @param space - The space ID - * @param basePath - The base path for migrations - * @returns The migration function or null if loading failed - */ -export async function getMigrationFunction(fileName: string, space: string, basePath: string): Promise<((block: any) => any) | null> { - try { - const resolvedPath = resolvePath(basePath, `migrations/${space}`); - const filePath = join(resolvedPath, fileName); - - // Use dynamic import to load the module - const migrationModule = await import(`file://${filePath}`); - - // Get the default export which should be the migration function - if (typeof migrationModule.default === 'function') { - return migrationModule.default; - } - - konsola.error(`Migration file "${fileName}" does not export a default function.`); - return null; - } - catch (error) { - konsola.error(`Error loading migration function from "${fileName}": ${(error as Error).message}`); - return null; - } -} - -/** - * Recursively applies a migration function to all blocks in a content object that match the target component - * @param content - The content object to process - * @param migrationFunction - The migration function to apply - * @param targetComponent - The component name to target for migration - * @returns Whether any blocks were modified - */ -export function applyMigrationToAllBlocks(content: StoryContent, migrationFunction: (block: StoryContent) => StoryContent, targetComponent: string): boolean { - if (!content || typeof content !== 'object') { - return false; - } - - let modified = false; - - // Get the base component name (everything before the first dot) - const baseTargetComponent = targetComponent.split('.')[0]; - - // If the content has a component property and it matches the base component name - if (content.component === baseTargetComponent) { - // Apply the migration function to this block - const migratedContent = migrationFunction({ ...content }); - Object.assign(content, migratedContent); - modified = true; - } - - // Recursively process all properties that might contain nested blocks - for (const key in content) { - if (Object.prototype.hasOwnProperty.call(content, key)) { - const value = content[key]; - - // Process arrays (might contain blocks) - if (Array.isArray(value)) { - for (let i = 0; i < value.length; i++) { - if (value[i] && typeof value[i] === 'object') { - const blockModified = applyMigrationToAllBlocks(value[i], migrationFunction, targetComponent); - modified = modified || blockModified; - } - } - } - // Process nested objects (might be blocks) - else if (value && typeof value === 'object') { - const blockModified = applyMigrationToAllBlocks(value, migrationFunction, targetComponent); - modified = modified || blockModified; - } - } - } - - return modified; -} diff --git a/src/commands/migrations/run/constants.ts b/src/commands/migrations/run/constants.ts deleted file mode 100644 index f43901c0..00000000 --- a/src/commands/migrations/run/constants.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { CommandOptions } from '../../../types'; - -export interface MigrationsRunOptions extends CommandOptions { - dryRun?: boolean; - filter?: string; - query?: string; - startsWith?: string; - publish?: 'all' | 'published' | 'published-with-changes'; -} - -export interface ReadMigrationFilesOptions { - space: string; - path: string; - filter?: string; -} - -export interface MigrationFile { - name: string; - content: string; -} diff --git a/src/commands/migrations/run/index.test.ts b/src/commands/migrations/run/index.test.ts deleted file mode 100644 index 1eafc2f0..00000000 --- a/src/commands/migrations/run/index.test.ts +++ /dev/null @@ -1,1126 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { session } from '../../../session'; -import { konsola } from '../../../utils'; -// Import the main components module first to ensure proper initialization - -import '../index'; -import { migrationsCommand } from '../command'; -import { readMigrationFiles } from './actions'; -import { fetchStoriesByComponent, fetchStory, updateStory } from '../../stories/actions'; -import { handleMigrations, summarizeMigrationResults } from './operations'; -import type { Story } from '../../stories/constants'; - -vi.mock('../../../utils', async () => { - const actualUtils = await vi.importActual('../../../utils'); - return { - ...actualUtils, - isVitestRunning: true, - konsola: { - ok: vi.fn(), - title: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - info: vi.fn(), - br: vi.fn(), - }, - handleError: (error: unknown, header = false) => { - konsola.error(error as string, header); - // Optionally, prevent process.exit during tests - }, - }; -}); - -// Mock the session module -vi.mock('../../../session', () => { - let _cache: Record | null = null; - const session = () => { - if (!_cache) { - _cache = { - state: { - isLoggedIn: true, - password: 'mock-token', - region: 'eu', - }, - updateSession: vi.fn(), - persistCredentials: vi.fn(), - initializeSession: vi.fn(), - logout: vi.fn(), - }; - } - return _cache; - }; - - return { - session, - }; -}); - -vi.mock('../../../commands/stories/actions', () => ({ - fetchStoriesByComponent: vi.fn(), - fetchStory: vi.fn(), - updateStory: vi.fn(), -})); - -vi.mock('./actions', () => ({ - readJavascriptFile: vi.fn(), - readMigrationFiles: vi.fn(), - getMigrationFunction: vi.fn(), - applyMigrationToAllBlocks: vi.fn(), -})); - -vi.mock('./operations.ts', () => ({ - handleMigrations: vi.fn(), - summarizeMigrationResults: vi.fn(), -})); - -// Helper function to create mock stories -const createMockStory = (overrides: Partial = {}): Story => ({ - id: 517473243, - name: 'Test Story', - uuid: 'uuid-1', - slug: 'test-story', - full_slug: 'test-story', - content: { - _uid: '4b16d1ea-4306-47c5-b901-9d67d5babf53', - component: 'page', - body: [ - { - _uid: '216ba4ef-1298-4b7d-8ce0-7487e6db15cc', - component: 'migration-component', - unchanged: 'unchanged', - }, - ], - }, - created_at: '2023-01-01T00:00:00Z', - updated_at: '2023-01-01T00:00:00Z', - published_at: '2023-01-01T00:00:00Z', - first_published_at: '2023-01-01T00:00:00Z', - published: true, - unpublished_changes: false, - is_startpage: false, - is_folder: false, - pinned: false, - parent_id: null, - group_id: 'group-1', - parent: null, - path: null, - position: 0, - sort_by_date: null, - tag_list: [], - disable_fe_editor: false, - default_root: null, - preview_token: null, - meta_data: null, - release_id: null, - last_author: null, - last_author_id: null, - alternates: [], - translated_slugs: null, - translated_slugs_attributes: null, - localized_paths: null, - breadcrumbs: [], - scheduled_dates: null, - favourite_for_user_ids: [], - imported_at: null, - deleted_at: null, - ...overrides, -}); - -// Mock stories data -const mockStories: Story[] = [ - createMockStory({ - id: 517473243, - name: 'Blog Post', - uuid: 'uuid-1', - slug: 'blog-post', - full_slug: 'blog-post', - published: true, - }), - createMockStory({ - id: 517473244, - name: 'Draft Post', - uuid: 'uuid-2', - slug: 'draft-post', - full_slug: 'draft-post', - published: false, - published_at: null, - first_published_at: null, - }), -]; - -const mockSingleStory = mockStories[0]; - -describe('migrations run command', () => { - beforeEach(() => { - vi.resetAllMocks(); - vi.clearAllMocks(); - }); - - it('should run migrations', async () => { - const mockMigrationFiles = [ - { - name: 'migration-component.js', - content: 'export default function (block) {\n' - + ' // Example to change a string to boolean\n' - + ' // block.field_name = !!(block.field_name)\n' - + '\n' - + ' // Example to transfer content from other field\n' - + ' // block.target_field = block.source_field\n' - + '\n' - + ' // Example to transform an array\n' - + ' // block.array_field = block.array_field.map(item => ({ ...item, new_prop: \'value\' }))\n' - + ' /* block.amount = Number(block.amount) + 1;\n' - + ' block.highlighted = !block.highlighted; */\n' - + ' block.unchanged = \'unchanged\';\n' - + '\n' - + ' return block;\n' - + '}\n', - }, - { - name: 'migration-component.amount.js', - content: 'export default function (block) {\n' - + ' // Example to change a string to boolean\n' - + ' // block.field_name = !!(block.field_name)\n' - + '\n' - + ' // Example to transfer content from other field\n' - + ' // block.target_field = block.source_field\n' - + '\n' - + ' // Example to transform an array\n' - + ' // block.array_field = block.array_field.map(item => ({ ...item, new_prop: \'value\' }))\n' - + ' block.amount = Number(block.amount) + 1\n' - + ' return block;\n' - + '}\n', - }, - { - name: 'simple_component.js', - content: 'export default function (block) {\n' - + ' // Example to change a string to boolean\n' - + ' // block.field_name = !!(block.field_name)\n' - + '\n' - + ' // Example to transfer content from other field\n' - + ' // block.target_field = block.source_field\n' - + '\n' - + ' // Example to transform an array\n' - + ' // block.array_field = block.array_field.map(item => ({ ...item, new_prop: \'value\' }))\n' - + ' block.fullname = block.name + " " + block.lastname;\n' - + ' return block;\n' - + '}\n', - }, - ]; - - const mockMigrationResults = { - successful: [ - { - storyId: 517473243, - name: 'Home', - migrationName: 'migration-component.js', - content: mockSingleStory.content, - }, - ], - failed: [], - skipped: [], - }; - - session().state = { - isLoggedIn: true, - password: 'valid-token', - region: 'eu', - }; - - vi.mocked(readMigrationFiles).mockResolvedValue( - mockMigrationFiles, - ); - - vi.mocked(fetchStoriesByComponent).mockResolvedValue(mockStories); - - vi.mocked(fetchStory).mockResolvedValue(mockSingleStory); - - vi.mocked(handleMigrations).mockResolvedValue(mockMigrationResults); - - vi.mocked(updateStory).mockResolvedValue(mockSingleStory); - - await migrationsCommand.parseAsync(['node', 'test', 'run', '--space', '12345']); - - expect(readMigrationFiles).toHaveBeenCalledWith({ - space: '12345', - path: undefined, - filter: undefined, - }); - - expect(fetchStoriesByComponent).toHaveBeenCalledWith( - { - spaceId: '12345', - token: 'valid-token', - region: 'eu', - }, - {}, - ); - expect(fetchStory).toHaveBeenCalledWith('12345', 'valid-token', 'eu', '517473243'); - expect(handleMigrations).toHaveBeenCalledWith({ - migrationFiles: mockMigrationFiles, - stories: mockStories, - space: '12345', - path: undefined, - componentName: undefined, - password: 'valid-token', - region: 'eu', - }); - - expect(summarizeMigrationResults).toHaveBeenCalledWith(mockMigrationResults); - - expect(updateStory).toHaveBeenCalledWith( - '12345', - 'valid-token', - 'eu', - 517473243, - { - story: { - content: mockSingleStory.content, - id: 517473243, - name: 'Home', - }, - force_update: '1', - }, - ); - }); - - it('should run migrations with a component name', async () => { - const mockMigrationFiles = [ - { - name: 'migration-component.js', - content: 'export default function (block) {\n' - + ' block.unchanged = \'unchanged\';\n' - + ' return block;\n' - + '}\n', - }, - { - name: 'migration-component.amount.js', - content: 'export default function (block) {\n' - + ' block.amount = Number(block.amount) + 1\n' - + ' return block;\n' - + '}\n', - }, - ]; - - const mockMigrationResults = { - successful: [ - { - storyId: 517473243, - name: 'Home', - migrationName: 'migration-component.amount.js', - content: mockSingleStory.content, - }, - ], - failed: [], - skipped: [], - }; - - session().state = { - isLoggedIn: true, - password: 'valid-token', - region: 'eu', - }; - - vi.mocked(readMigrationFiles).mockResolvedValue(mockMigrationFiles); - vi.mocked(fetchStoriesByComponent).mockResolvedValue(mockStories); - vi.mocked(fetchStory).mockResolvedValue(mockSingleStory); - vi.mocked(handleMigrations).mockResolvedValue(mockMigrationResults); - vi.mocked(updateStory).mockResolvedValue(mockSingleStory); - - await migrationsCommand.parseAsync(['node', 'test', 'run', 'migration-component', '--space', '12345']); - - expect(readMigrationFiles).toHaveBeenCalledWith({ - space: '12345', - path: undefined, - filter: undefined, - }); - - expect(fetchStoriesByComponent).toHaveBeenCalledWith( - { - spaceId: '12345', - token: 'valid-token', - region: 'eu', - }, - { - componentName: 'migration-component', - }, - ); - expect(fetchStory).toHaveBeenCalledWith('12345', 'valid-token', 'eu', '517473243'); - expect(handleMigrations).toHaveBeenCalledWith({ - migrationFiles: mockMigrationFiles, - stories: mockStories, - space: '12345', - path: undefined, - componentName: 'migration-component', - password: 'valid-token', - region: 'eu', - }); - - expect(summarizeMigrationResults).toHaveBeenCalledWith(mockMigrationResults); - - expect(updateStory).toHaveBeenCalledWith( - '12345', - 'valid-token', - 'eu', - 517473243, - { - story: { - content: mockSingleStory.content, - id: 517473243, - name: 'Home', - }, - force_update: '1', - }, - ); - }); - - it('should run migrations by filter', async () => { - const mockMigrationFiles = [ - { - name: 'migration-component.amount.js', - content: 'export default function (block) {\n' - + ' block.amount = Number(block.amount) + 1\n' - + ' return block;\n' - + '}\n', - }, - ]; - - const mockMigrationResults = { - successful: [ - { - storyId: 517473243, - name: 'Home', - migrationName: 'migration-component.amount.js', - content: mockSingleStory.content, - }, - ], - failed: [], - skipped: [], - }; - - session().state = { - isLoggedIn: true, - password: 'valid-token', - region: 'eu', - }; - - vi.mocked(readMigrationFiles).mockResolvedValue(mockMigrationFiles); - vi.mocked(fetchStoriesByComponent).mockResolvedValue(mockStories); - vi.mocked(fetchStory).mockResolvedValue(mockSingleStory); - vi.mocked(handleMigrations).mockResolvedValue(mockMigrationResults); - vi.mocked(updateStory).mockResolvedValue(mockSingleStory); - - await migrationsCommand.parseAsync(['node', 'test', 'run', '--space', '12345', '--filter', '*.amount.js']); - - expect(readMigrationFiles).toHaveBeenCalledWith({ - space: '12345', - path: undefined, - filter: '*.amount.js', - }); - - expect(fetchStoriesByComponent).toHaveBeenCalledWith( - { - spaceId: '12345', - token: 'valid-token', - region: 'eu', - }, - {}, - ); - expect(fetchStory).toHaveBeenCalledWith('12345', 'valid-token', 'eu', '517473243'); - expect(handleMigrations).toHaveBeenCalledWith({ - migrationFiles: mockMigrationFiles, - stories: mockStories, - space: '12345', - path: undefined, - componentName: undefined, - password: 'valid-token', - region: 'eu', - }); - - expect(summarizeMigrationResults).toHaveBeenCalledWith(mockMigrationResults); - - expect(updateStory).toHaveBeenCalledWith( - '12345', - 'valid-token', - 'eu', - 517473243, - { - story: { - content: mockSingleStory.content, - id: 517473243, - name: 'Home', - }, - force_update: '1', - }, - ); - }); - - it('should run the migrations with a dry run', async () => { - const mockMigrationFiles = [ - { - name: 'migration-component.js', - content: 'export default function (block) {\n' - + ' block.unchanged = \'unchanged\';\n' - + ' return block;\n' - + '}\n', - }, - ]; - - const mockMigrationResults = { - successful: [ - { - storyId: 517473243, - name: 'Home', - migrationName: 'migration-component.js', - content: mockSingleStory.content, - }, - ], - failed: [], - skipped: [], - }; - - session().state = { - isLoggedIn: true, - password: 'valid-token', - region: 'eu', - }; - - vi.mocked(readMigrationFiles).mockResolvedValue(mockMigrationFiles); - vi.mocked(fetchStoriesByComponent).mockResolvedValue(mockStories); - vi.mocked(fetchStory).mockResolvedValue(mockSingleStory); - vi.mocked(handleMigrations).mockResolvedValue(mockMigrationResults); - - await migrationsCommand.parseAsync(['node', 'test', 'run', '--space', '12345', '--dry-run']); - - // Key assertion: updateStory should not be called in dry run mode - expect(updateStory).not.toHaveBeenCalled(); - }); - - it('should not update stories when no successful migrations', async () => { - const mockMigrationFiles = [ - { - name: 'migration-component.js', - content: 'export default function (block) {\n' - + ' block.unchanged = \'unchanged\';\n' - + ' return block;\n' - + '}\n', - }, - ]; - - const mockMigrationResults = { - successful: [], - failed: [ - { - storyId: 517473243, - name: 'Home', - migrationName: 'migration-component.js', - error: 'Migration failed', - }, - ], - skipped: [], - }; - - session().state = { - isLoggedIn: true, - password: 'valid-token', - region: 'eu', - }; - - vi.mocked(readMigrationFiles).mockResolvedValue(mockMigrationFiles); - vi.mocked(fetchStoriesByComponent).mockResolvedValue(mockStories); - vi.mocked(fetchStory).mockResolvedValue(mockSingleStory); - vi.mocked(handleMigrations).mockResolvedValue(mockMigrationResults); - - await migrationsCommand.parseAsync(['node', 'test', 'run', '--space', '12345']); - - expect(readMigrationFiles).toHaveBeenCalledWith({ - space: '12345', - path: undefined, - filter: undefined, - }); - - expect(fetchStoriesByComponent).toHaveBeenCalledWith( - { - spaceId: '12345', - token: 'valid-token', - region: 'eu', - }, - {}, - ); - expect(fetchStory).toHaveBeenCalledWith('12345', 'valid-token', 'eu', '517473243'); - expect(handleMigrations).toHaveBeenCalledWith({ - migrationFiles: mockMigrationFiles, - stories: mockStories, - space: '12345', - path: undefined, - componentName: undefined, - password: 'valid-token', - region: 'eu', - }); - - expect(summarizeMigrationResults).toHaveBeenCalledWith(mockMigrationResults); - - // Key assertion: updateStory should not be called when there are no successful migrations - expect(updateStory).not.toHaveBeenCalled(); - expect(konsola.info).toHaveBeenCalledWith('No stories were modified by the migrations.'); - }); - - it('should run migrations with starts_with filter', async () => { - const mockMigrationFiles = [ - { - name: 'migration-component.js', - content: 'export default function (block) {\n' - + ' block.unchanged = \'unchanged\';\n' - + ' return block;\n' - + '}\n', - }, - ]; - - const mockMigrationResults = { - successful: [ - { - storyId: 517473243, - name: 'Blog Post', - migrationName: 'migration-component.js', - content: mockSingleStory.content, - }, - ], - failed: [], - skipped: [], - }; - - session().state = { - isLoggedIn: true, - password: 'valid-token', - region: 'eu', - }; - - vi.mocked(readMigrationFiles).mockResolvedValue(mockMigrationFiles); - vi.mocked(fetchStoriesByComponent).mockResolvedValue(mockStories); - vi.mocked(fetchStory).mockResolvedValue(mockSingleStory); - vi.mocked(handleMigrations).mockResolvedValue(mockMigrationResults); - vi.mocked(updateStory).mockResolvedValue(mockSingleStory); - - await migrationsCommand.parseAsync(['node', 'test', 'run', '--space', '12345', '--starts-with', '/en/blog/']); - - expect(readMigrationFiles).toHaveBeenCalledWith({ - space: '12345', - path: undefined, - filter: undefined, - }); - - expect(fetchStoriesByComponent).toHaveBeenCalledWith( - { - spaceId: '12345', - token: 'valid-token', - region: 'eu', - }, - { - starts_with: '/en/blog/', - }, - ); - expect(fetchStory).toHaveBeenCalledWith('12345', 'valid-token', 'eu', '517473243'); - expect(handleMigrations).toHaveBeenCalledWith({ - migrationFiles: mockMigrationFiles, - stories: mockStories, - space: '12345', - path: undefined, - componentName: undefined, - password: 'valid-token', - region: 'eu', - }); - - expect(summarizeMigrationResults).toHaveBeenCalledWith(mockMigrationResults); - - expect(updateStory).toHaveBeenCalledWith( - '12345', - 'valid-token', - 'eu', - 517473243, - { - story: { - content: mockSingleStory.content, - id: 517473243, - name: 'Blog Post', - }, - force_update: '1', - }, - ); - }); - - it('should publish all stories when publish=all', async () => { - const mockMigrationFiles = [ - { - name: 'migration-component.js', - content: 'export default function (block) {\n' - + ' block.unchanged = \'unchanged\';\n' - + ' return block;\n' - + '}\n', - }, - ]; - - const mockStories = [ - { - id: 517473243, - name: 'Blog Post', - uuid: 'uuid-1', - slug: 'blog-post', - full_slug: 'blog-post', - content: { - _uid: '4b16d1ea-4306-47c5-b901-9d67d5babf53', - component: 'page', - body: [ - { - _uid: '216ba4ef-1298-4b7d-8ce0-7487e6db15cc', - component: 'migration-component', - unchanged: 'unchanged', - }, - ], - }, - created_at: '2023-01-01T00:00:00Z', - updated_at: '2023-01-01T00:00:00Z', - published_at: '2023-01-01T00:00:00Z', - first_published_at: '2023-01-01T00:00:00Z', - published: true, - unpublished_changes: false, - is_startpage: false, - is_folder: false, - pinned: false, - parent_id: null, - group_id: 'group-1', - parent: null, - path: null, - position: 0, - sort_by_date: null, - tag_list: [], - disable_fe_editor: false, - default_root: null, - preview_token: null, - meta_data: null, - release_id: null, - last_author: null, - last_author_id: null, - alternates: [], - translated_slugs: null, - translated_slugs_attributes: null, - localized_paths: null, - breadcrumbs: [], - scheduled_dates: null, - favourite_for_user_ids: [], - imported_at: null, - deleted_at: null, - }, - { - id: 517473244, - name: 'Draft Post', - uuid: 'uuid-2', - slug: 'draft-post', - full_slug: 'draft-post', - content: { - _uid: '4b16d1ea-4306-47c5-b901-9d67d5babf54', - component: 'page', - body: [ - { - _uid: '216ba4ef-1298-4b7d-8ce0-7487e6db15cd', - component: 'migration-component', - unchanged: 'unchanged', - }, - ], - }, - created_at: '2023-01-01T00:00:00Z', - updated_at: '2023-01-01T00:00:00Z', - published_at: null, - first_published_at: null, - published: false, - unpublished_changes: false, - is_startpage: false, - is_folder: false, - pinned: false, - parent_id: null, - group_id: 'group-1', - parent: null, - path: null, - position: 0, - sort_by_date: null, - tag_list: [], - disable_fe_editor: false, - default_root: null, - preview_token: null, - meta_data: null, - release_id: null, - last_author: null, - last_author_id: null, - alternates: [], - translated_slugs: null, - translated_slugs_attributes: null, - localized_paths: null, - breadcrumbs: [], - scheduled_dates: null, - favourite_for_user_ids: [], - imported_at: null, - deleted_at: null, - }, - ] as Story[]; - - const mockSingleStory = mockStories[0]; - - const mockMigrationResults = { - successful: [ - { - storyId: 517473243, - name: 'Blog Post', - migrationName: 'migration-component.js', - content: mockSingleStory.content, - }, - { - storyId: 517473244, - name: 'Draft Post', - migrationName: 'migration-component.js', - content: mockStories[1].content, - }, - ], - failed: [], - skipped: [], - }; - - session().state = { - isLoggedIn: true, - password: 'valid-token', - region: 'eu', - }; - - vi.mocked(readMigrationFiles).mockResolvedValue(mockMigrationFiles); - vi.mocked(fetchStoriesByComponent).mockResolvedValue(mockStories); - vi.mocked(fetchStory).mockResolvedValue(mockSingleStory); - vi.mocked(handleMigrations).mockResolvedValue(mockMigrationResults); - vi.mocked(updateStory).mockResolvedValue(mockSingleStory); - - await migrationsCommand.parseAsync(['node', 'test', 'run', '--space', '12345', '--publish', 'all']); - - // Verify that updateStory was called with publish=1 for all stories - expect(updateStory).toHaveBeenCalledWith( - '12345', - 'valid-token', - 'eu', - 517473243, - { - story: { - content: mockSingleStory.content, - id: 517473243, - name: 'Blog Post', - }, - force_update: '1', - publish: 1, - }, - ); - - expect(updateStory).toHaveBeenCalledWith( - '12345', - 'valid-token', - 'eu', - 517473244, - { - story: { - content: mockStories[1].content, - id: 517473244, - name: 'Draft Post', - }, - force_update: '1', - publish: 1, - }, - ); - }); - - it('should only publish already published stories when publish=published', async () => { - const mockMigrationFiles = [ - { - name: 'migration-component.js', - content: 'export default function (block) {\n' - + ' block.unchanged = \'unchanged\';\n' - + ' return block;\n' - + '}\n', - }, - ]; - - const mockStories = [ - { - id: 517473243, - name: 'Published Post', - uuid: 'uuid-1', - slug: 'published-post', - full_slug: 'published-post', - content: { - _uid: '4b16d1ea-4306-47c5-b901-9d67d5babf53', - component: 'page', - body: [ - { - _uid: '216ba4ef-1298-4b7d-8ce0-7487e6db15cc', - component: 'migration-component', - unchanged: 'unchanged', - }, - ], - }, - created_at: '2023-01-01T00:00:00Z', - updated_at: '2023-01-01T00:00:00Z', - published_at: '2023-01-01T00:00:00Z', - first_published_at: '2023-01-01T00:00:00Z', - published: true, - unpublished_changes: false, - is_startpage: false, - is_folder: false, - pinned: false, - parent_id: null, - group_id: 'group-1', - parent: null, - path: null, - position: 0, - sort_by_date: null, - tag_list: [], - disable_fe_editor: false, - default_root: null, - preview_token: null, - meta_data: null, - release_id: null, - last_author: null, - last_author_id: null, - alternates: [], - translated_slugs: null, - translated_slugs_attributes: null, - localized_paths: null, - breadcrumbs: [], - scheduled_dates: null, - favourite_for_user_ids: [], - imported_at: null, - deleted_at: null, - }, - { - id: 517473244, - name: 'Draft Post', - uuid: 'uuid-2', - slug: 'draft-post', - full_slug: 'draft-post', - content: { - _uid: '4b16d1ea-4306-47c5-b901-9d67d5babf54', - component: 'page', - body: [ - { - _uid: '216ba4ef-1298-4b7d-8ce0-7487e6db15cd', - component: 'migration-component', - unchanged: 'unchanged', - }, - ], - }, - created_at: '2023-01-01T00:00:00Z', - updated_at: '2023-01-01T00:00:00Z', - published_at: null, - first_published_at: null, - published: false, - unpublished_changes: false, - is_startpage: false, - is_folder: false, - pinned: false, - parent_id: null, - group_id: 'group-1', - parent: null, - path: null, - position: 0, - sort_by_date: null, - tag_list: [], - disable_fe_editor: false, - default_root: null, - preview_token: null, - meta_data: null, - release_id: null, - last_author: null, - last_author_id: null, - alternates: [], - translated_slugs: null, - translated_slugs_attributes: null, - localized_paths: null, - breadcrumbs: [], - scheduled_dates: null, - favourite_for_user_ids: [], - imported_at: null, - deleted_at: null, - }, - ] as Story[]; - - const mockSingleStory = mockStories[0]; - - const mockMigrationResults = { - successful: [ - { - storyId: 517473243, - name: 'Published Post', - migrationName: 'migration-component.js', - content: mockSingleStory.content, - }, - { - storyId: 517473244, - name: 'Draft Post', - migrationName: 'migration-component.js', - content: mockStories[1].content, - }, - ], - failed: [], - skipped: [], - }; - - session().state = { - isLoggedIn: true, - password: 'valid-token', - region: 'eu', - }; - - vi.mocked(readMigrationFiles).mockResolvedValue(mockMigrationFiles); - vi.mocked(fetchStoriesByComponent).mockResolvedValue(mockStories); - vi.mocked(fetchStory).mockResolvedValue(mockSingleStory); - vi.mocked(handleMigrations).mockResolvedValue(mockMigrationResults); - vi.mocked(updateStory).mockResolvedValue(mockSingleStory); - - await migrationsCommand.parseAsync(['node', 'test', 'run', '--space', '12345', '--publish', 'published']); - - // Verify that updateStory was called with publish=1 only for published stories - expect(updateStory).toHaveBeenCalledWith( - '12345', - 'valid-token', - 'eu', - 517473243, - { - story: { - content: mockSingleStory.content, - id: 517473243, - name: 'Published Post', - }, - force_update: '1', - publish: 1, - }, - ); - - // Verify that updateStory was called without publish=1 for unpublished stories - expect(updateStory).toHaveBeenCalledWith( - '12345', - 'valid-token', - 'eu', - 517473244, - { - story: { - content: mockStories[1].content, - id: 517473244, - name: 'Draft Post', - }, - force_update: '1', - }, - ); - }); - - it('should not publish any stories when publish option is not set', async () => { - const mockMigrationFiles = [ - { - name: 'migration-component.js', - content: 'export default function (block) {\n' - + ' block.unchanged = \'unchanged\';\n' - + ' return block;\n' - + '}\n', - }, - ]; - - const mockStories = [ - { - id: 517473243, - name: 'Published Post', - uuid: 'uuid-1', - slug: 'published-post', - full_slug: 'published-post', - content: { - _uid: '4b16d1ea-4306-47c5-b901-9d67d5babf53', - component: 'page', - body: [ - { - _uid: '216ba4ef-1298-4b7d-8ce0-7487e6db15cc', - component: 'migration-component', - unchanged: 'unchanged', - }, - ], - }, - created_at: '2023-01-01T00:00:00Z', - updated_at: '2023-01-01T00:00:00Z', - published_at: '2023-01-01T00:00:00Z', - first_published_at: '2023-01-01T00:00:00Z', - published: true, - unpublished_changes: false, - is_startpage: false, - is_folder: false, - pinned: false, - parent_id: null, - group_id: 'group-1', - parent: null, - path: null, - position: 0, - sort_by_date: null, - tag_list: [], - disable_fe_editor: false, - default_root: null, - preview_token: null, - meta_data: null, - release_id: null, - last_author: null, - last_author_id: null, - alternates: [], - translated_slugs: null, - translated_slugs_attributes: null, - localized_paths: null, - breadcrumbs: [], - scheduled_dates: null, - favourite_for_user_ids: [], - imported_at: null, - deleted_at: null, - }, - ] as Story[]; - - const mockSingleStory = mockStories[0]; - - const mockMigrationResults = { - successful: [ - { - storyId: 517473243, - name: 'Published Post', - migrationName: 'migration-component.js', - content: mockSingleStory.content, - }, - ], - failed: [], - skipped: [], - }; - - session().state = { - isLoggedIn: true, - password: 'valid-token', - region: 'eu', - }; - - vi.mocked(readMigrationFiles).mockResolvedValue(mockMigrationFiles); - vi.mocked(fetchStoriesByComponent).mockResolvedValue(mockStories); - vi.mocked(fetchStory).mockResolvedValue(mockSingleStory); - vi.mocked(handleMigrations).mockResolvedValue(mockMigrationResults); - vi.mocked(updateStory).mockResolvedValue(mockSingleStory); - - await migrationsCommand.parseAsync(['node', 'test', 'run', '--space', '12345']); - - // Verify that updateStory was called without publish=1 - expect(updateStory).toHaveBeenCalledWith( - '12345', - 'valid-token', - 'eu', - 517473243, - { - story: { - content: mockSingleStory.content, - id: 517473243, - name: 'Published Post', - }, - force_update: '1', - }, - ); - }); -}); diff --git a/src/commands/migrations/run/index.ts b/src/commands/migrations/run/index.ts deleted file mode 100644 index 8d00f927..00000000 --- a/src/commands/migrations/run/index.ts +++ /dev/null @@ -1,252 +0,0 @@ -import { Spinner } from '@topcli/spinner'; -import { getProgram } from '../../../program'; -import { colorPalette, commands } from '../../../constants'; -import { CommandError, handleError, isVitest, konsola, requireAuthentication } from '../../../utils'; -import { session } from '../../../session'; -import type { MigrationsRunOptions } from './constants'; -import { migrationsCommand } from '../command'; -import { fetchStoriesByComponent, fetchStory, updateStory } from '../../stories/actions'; -import { readMigrationFiles } from './actions'; -import { handleMigrations, summarizeMigrationResults } from './operations'; -import type { Story, StoryContent } from '../../stories/constants'; -import chalk from 'chalk'; -import { isStoryPublishedWithoutChanges, isStoryWithUnpublishedChanges } from '../../stories/utils'; - -const program = getProgram(); - -migrationsCommand.command('run [componentName]') - .description('Run migrations') - .option('--fi, --filter ', 'glob filter to apply to the components before pushing') - .option('-d, --dry-run', 'Preview changes without applying them to Storyblok') - .option('-q, --query ', 'Filter stories by content attributes using Storyblok filter query syntax. Example: --query="[highlighted][in]=true"') - .option('--starts-with ', 'Filter stories by path. Example: --starts-with="/en/blog/"') - .option('--publish ', 'Options for publication mode: all | published | published-with-changes') - .action(async (componentName: string | undefined, options: MigrationsRunOptions) => { - konsola.title(` ${commands.MIGRATIONS} `, colorPalette.MIGRATIONS, componentName ? `Running migrations for component ${componentName}...` : 'Running migrations...'); - - // Global options - const verbose = program.opts().verbose; - - const { filter, dryRun = false, query, startsWith, publish } = options; - - // Command options - const { space, path } = migrationsCommand.opts(); - - const { state, initializeSession } = session(); - await initializeSession(); - - if (!requireAuthentication(state, verbose)) { - return; - } - if (!space) { - handleError(new CommandError(`Please provide the space as argument --space YOUR_SPACE_ID.`), verbose); - return; - } - - const { password, region } = state; - - try { - const spinner = new Spinner({ - verbose: !isVitest, - }).start(`Fetching migration files and stories...`); - - // Read migration files - const migrationFiles = await readMigrationFiles({ - space, - path, - filter, - }); - - if (migrationFiles.length === 0) { - spinner.failed(`No migration files found for space "${space}"${filter ? ` matching filter "${filter}"` : ''}.`); - return; - } - - // Filter migrations based on component name if provided - const filteredMigrations = componentName - ? migrationFiles.filter((file) => { - // Match any migration file that starts with the component name and is followed by either - // the end of the filename or a dot - return file.name.match(new RegExp(`^${componentName}(\\..*)?\.js$`)); - }) - : migrationFiles; - - if (filteredMigrations.length === 0) { - spinner.failed(`No migration files found${componentName ? ` for component "${componentName}"` : ''}${filter ? ` matching filter "${filter}"` : ''} in space "${space}".`); - return; - } - - // Spinner doesn't have update method, so we'll stop and start a new one - spinner.succeed(`Found ${filteredMigrations.length} migration files.`); - const storiesSpinner = new Spinner({ verbose: !isVitest }).start(`Fetching stories...`); - - // Fetch stories using the base component name - const stories = await fetchStoriesByComponent( - { - spaceId: space, - token: password, - region, - }, - // Filter options - { - componentName, - query, - starts_with: startsWith, - }, - ); - - if (!stories || stories.length === 0) { - storiesSpinner.failed(`No stories found${componentName ? ` for component "${componentName}"` : ''}.`); - return; - } - - // Fetch full content for each story - const storiesWithContent = await Promise.all(stories.map(async (story) => { - const fullStory = await fetchStory(space, password, region, story.id.toString()); - return { - ...story, - content: fullStory?.content, - }; - })); - - // Filter out stories with no content - const validStories = storiesWithContent.filter(story => story.content); - - // Build filter message parts - const filterParts = []; - if (componentName) { - filterParts.push(`component "${componentName}"`); - } - if (startsWith) { - filterParts.push(chalk.hex(colorPalette.PRIMARY)(`starts_with=${startsWith}`)); - } - if (query) { - filterParts.push(chalk.hex(colorPalette.PRIMARY)(`filter_query=${query}`)); - } - - // Create filter message - const filterMessage = filterParts.length > 0 - ? ` (filtered by ${filterParts.join(' and ')})` - : ''; - - // Spinner doesn't have update method, so we'll stop and start a new one - storiesSpinner.succeed(`Fetched ${validStories.length} ${validStories.length === 1 ? 'story' : 'stories'} with related content${filterMessage}.`); - - // Process migrations using the new operations module - const processingSpinner = new Spinner({ verbose: !isVitest }).start(`Processing migrations...`); - processingSpinner.succeed(`Starting to process ${validStories.length} stories with ${filteredMigrations.length} migrations...`); - - const migrationResults = await handleMigrations({ - migrationFiles: filteredMigrations, - stories: validStories, - space, - path, - componentName, - password, - region, - }); - - // Summarize the results - summarizeMigrationResults(migrationResults); - - // Update the stories in Storyblok with the modified content - if (migrationResults.successful.length > 0 && !dryRun) { - const updateSpinner = new Spinner({ verbose: !isVitest }).start(`Updating stories in Storyblok...`); - - // Group successful migrations by story ID to get the latest content for each story - const storiesByIdMap = new Map(); - - // Get the latest content for each story (in case multiple migrations were applied) - migrationResults.successful.forEach((result) => { - // Find the original story to get its published status - const originalStory = validStories.find(s => s.id === result.storyId); - storiesByIdMap.set(result.storyId, { - id: result.storyId, - name: result.name, - content: result.content, - published: originalStory?.published, - published_at: originalStory?.published_at || undefined, - unpublished_changes: originalStory?.unpublished_changes, - }); - }); - - const storiesToUpdate = Array.from(storiesByIdMap.values()); - - if (storiesToUpdate.length === 0) { - updateSpinner.succeed(`No stories need to be updated in Storyblok.`); - } - else { - updateSpinner.succeed(`Found ${storiesToUpdate.length} stories to update.`); - - // Update each story - let successCount = 0; - let failCount = 0; - - for (const story of storiesToUpdate) { - const storySpinner = new Spinner({ verbose: !isVitest }).start(`Updating story ${chalk.hex(colorPalette.PRIMARY)(story.name || story.id.toString())}...`); - const payload: { - story: Partial; - force_update?: string; - publish?: number; - } = { - story: { - content: story.content, - id: story.id, - name: story.name, - }, - force_update: '1', - }; - - // If the story is published and has no unpublished changes, publish it - if (publish === 'published' && isStoryPublishedWithoutChanges(story)) { - payload.publish = 1; - } - - // If the story is published and has unpublished changes, publish it - if (publish === 'published-with-changes' && isStoryWithUnpublishedChanges(story)) { - payload.publish = 1; - } - - // If the story is not published, publish it - if (publish === 'all') { - payload.publish = 1; - } - - try { - const updatedStory = await updateStory(space, password, region, story.id, payload); - - if (updatedStory) { - successCount++; - storySpinner.succeed(`Updated story ${chalk.hex(colorPalette.PRIMARY)(story.name || story.id.toString())} - Completed in ${storySpinner.elapsedTime.toFixed(2)}ms`); - } - else { - failCount++; - storySpinner.failed(`Failed to update story ${chalk.hex(colorPalette.PRIMARY)(story.name || story.id.toString())}`); - } - } - catch (error) { - failCount++; - storySpinner.failed(`Failed to update story ${chalk.hex(colorPalette.PRIMARY)(story.name || story.id.toString())}: ${(error as Error).message}`); - } - } - - // Show summary - if (failCount > 0) { - konsola.warn(`Updated ${successCount} stories successfully, ${failCount} failed.`); - } - else if (successCount > 0) { - konsola.ok(`Successfully updated ${successCount} stories in Storyblok.`, true); - } - } - } - else if (dryRun) { - konsola.info(`Dry run mode: No stories were updated in Storyblok.`); - } - else if (migrationResults.successful.length === 0) { - konsola.info(`No stories were modified by the migrations.`); - } - } - catch (error) { - handleError(error as Error, verbose); - } - }); diff --git a/src/commands/migrations/run/operations.ts b/src/commands/migrations/run/operations.ts deleted file mode 100644 index 58db9279..00000000 --- a/src/commands/migrations/run/operations.ts +++ /dev/null @@ -1,270 +0,0 @@ -import { Spinner } from '@topcli/spinner'; -import chalk from 'chalk'; -import { colorPalette } from '../../../constants'; -import { isVitest, konsola } from '../../../utils'; -import type { RegionCode } from '../../../constants'; -import type { StoryContent } from '../../stories/constants'; -import { applyMigrationToAllBlocks, getMigrationFunction } from './actions'; -import { getComponentNameFromFilename } from '../../../utils/filesystem'; -import type { MigrationFile } from './constants'; -import { hash } from 'ohash'; -import { saveRollbackData } from '../rollback/actions'; - -/** - * Handles the processing of migration files for stories - * @param options - Options for handling migrations - * @param options.migrationFiles - Array of migration files to process - * @param options.stories - Array of stories to apply migrations to - * @param options.space - Space ID where the stories are located - * @param options.path - Path to the migrations directory - * @param options.componentName - Optional component name to filter migrations - * @param options.password - Optional password for authentication - * @param options.region - Optional region code for API requests - * @returns {Promise<{ - * successful: Array<{ - * storyId: number; - * name: string; - * migrationName: string; - * content: StoryContent; - * }>; - * failed: Array<{ - * storyId: number; - * migrationName: string; - * error: unknown; - * }>; - * skipped: Array<{ - * storyId: number; - * name: string; - * migrationName: string; - * reason: string; - * }>; - * }>} Object containing arrays of successful, failed, and skipped migrations - */ -export async function handleMigrations({ - migrationFiles, - stories, - space, - path, - componentName, -}: { - migrationFiles: MigrationFile[]; - stories: Array<{ id: number; name: string; content?: StoryContent }>; - space: string; - path: string; - componentName?: string; - password?: string; - region?: RegionCode; -}): Promise<{ - successful: Array<{ - storyId: number; - name: string; - migrationName: string; - content: StoryContent; - }>; - failed: Array<{ storyId: number; migrationName: string; error: unknown }>; - skipped: Array<{ storyId: number; name: string; migrationName: string; reason: string }>; - }> { - const results = { - successful: [] as Array<{ - storyId: number; - name: string; - migrationName: string; - content: StoryContent; - }>, - failed: [] as Array<{ storyId: number; migrationName: string; error: unknown }>, - skipped: [] as Array<{ storyId: number; name: string; migrationName: string; reason: string }>, - }; - - // Filter migrations based on component name if provided - const relevantMigrations = componentName - ? migrationFiles.filter((file) => { - const targetComponent = getComponentNameFromFilename(file.name); - return targetComponent.split('.')[0] === componentName; - }) - : migrationFiles; - - // Process each migration file - for (const migrationFile of relevantMigrations) { - // Filter out stories without content - const validStories = stories.filter(story => story.content) as Array<{ id: number; name: string; content: StoryContent }>; - - // Skip processing if no valid stories - if (validStories.length === 0) { - continue; - } - - // First save all original states of stories before any modification - // This ensures we have a snapshot before any change is made - await saveRollbackData({ - space, - path, - stories: validStories, - migrationFile: migrationFile.name, - }); - - // Load the migration function using dynamic import - const migrationFunction = await getMigrationFunction(migrationFile.name, space, path); - - if (!migrationFunction) { - // If migration function fails to load, mark all stories as failed for this migration - stories.forEach((story) => { - results.failed.push({ - storyId: story.id, - migrationName: migrationFile.name, - error: new Error(`Failed to load migration function from file "${migrationFile.name}"`), - }); - }); - continue; - } - - // Determine the target component from the migration filename if not explicitly provided - const targetComponent = componentName || getComponentNameFromFilename(migrationFile.name); - - // Process each story - for (const story of stories) { - if (!story.content) { - results.failed.push({ - storyId: story.id, - migrationName: migrationFile.name, - error: new Error('Story content is missing'), - }); - continue; - } - - // Create a deep copy of the story content to avoid modifying the original - const storyContent = structuredClone(story.content) as StoryContent; - - // Calculate the original content hash for later comparison - const originalContentHash = hash(story.content); - - try { - // Apply the migration function to all matching components in the content - const modified = applyMigrationToAllBlocks(storyContent, migrationFunction, targetComponent); - - // Calculate the new content hash - const newContentHash = hash(storyContent); - - // Check if the content was actually modified by comparing hashes - const contentChanged = originalContentHash !== newContentHash; - - if (modified && contentChanged) { - const spinner = new Spinner({ verbose: !isVitest }); - spinner.start(`Applying migration ${chalk.hex(colorPalette.MIGRATIONS)(migrationFile.name)} to story ${chalk.hex(colorPalette.PRIMARY)(story.name || story.id.toString())}...`); - spinner.succeed(`Migration ${chalk.hex(colorPalette.MIGRATIONS)(migrationFile.name)} applied to story ${chalk.hex(colorPalette.PRIMARY)(story.name || story.id.toString())} - Completed in ${spinner.elapsedTime.toFixed(2)}ms`); - - // Store the migration that was applied - results.successful.push({ - storyId: story.id, - name: story.name, - migrationName: migrationFile.name, - content: storyContent, - }); - } - else if (modified && !contentChanged) { - results.skipped.push({ - storyId: story.id, - name: story.name, - migrationName: migrationFile.name, - reason: 'No changes detected after migration', - }); - } - else { - // Get the base component name from the target component - const baseComponent = targetComponent.split('.')[0]; - // Only add to skipped without logging - results.skipped.push({ - storyId: story.id, - name: story.name, - migrationName: migrationFile.name, - reason: baseComponent === componentName ? 'No matching components found' : 'Different component target', - }); - } - } - catch (error) { - const spinner = new Spinner({ verbose: !isVitest }); - spinner.start(`Applying migration ${chalk.hex(colorPalette.MIGRATIONS)(migrationFile.name)} to story ${chalk.hex(colorPalette.PRIMARY)(story.name || story.id.toString())}...`); - spinner.failed(`Failed to apply migration ${chalk.hex(colorPalette.MIGRATIONS)(migrationFile.name)} to story ${chalk.hex(colorPalette.PRIMARY)(story.name || story.id.toString())}`); - results.failed.push({ - storyId: story.id, - migrationName: migrationFile.name, - error, - }); - } - } - } - - return results; -} - -/** - * Summarizes the results of the migration operations - * @param results - Object containing migration operation results - * @param results.successful - Array of successfully applied migrations - * @param results.failed - Array of failed migrations - * @param results.skipped - Array of skipped migrations - */ -export function summarizeMigrationResults(results: { - successful: Array<{ - storyId: number; - name: string; - migrationName: string; - content: StoryContent; - }>; - failed: Array<{ storyId: number; migrationName: string; error: unknown }>; - skipped: Array<{ storyId: number; name: string; migrationName: string; reason: string }>; -}): void { - const { successful, failed, skipped } = results; - - // Count unique stories that were successfully processed - const successfulStoryIds = new Set(successful.map(result => result.storyId)); - const failedStoryIds = new Set(failed.map(result => result.storyId)); - - // Count unique migrations that were successfully applied - const successfulMigrations = new Set(successful.map(result => result.migrationName)); - - konsola.br(); - konsola.ok(`Successfully applied ${successfulMigrations.size} migrations to ${successfulStoryIds.size} stories`, true); - - // Group skipped stories by reason - const skippedByReason = skipped.reduce((acc, item) => { - if (!acc[item.reason]) { - acc[item.reason] = []; - } - acc[item.reason].push(item); - return acc; - }, {} as Record); - - if (Object.keys(skippedByReason).length > 0) { - konsola.info(`Skipped migrations:`); - for (const [reason, items] of Object.entries(skippedByReason)) { - const uniqueStories = new Set(items.map(item => item.storyId)); - konsola.info(` β€’ ${reason}: ${uniqueStories.size} stories`); - } - } - - if (failed.length > 0) { - konsola.warn(`- Failed to apply migrations to ${failedStoryIds.size} stories`, true); - - // Group failures by story ID for better reporting - const failuresByStory = new Map>(); - - failed.forEach(({ storyId, migrationName, error }) => { - if (!failuresByStory.has(storyId)) { - failuresByStory.set(storyId, []); - } - failuresByStory.get(storyId)?.push({ migrationName, error }); - }); - - // Log failures grouped by story - failuresByStory.forEach((failures, storyId) => { - konsola.error(`Story ID ${storyId}:`); - failures.forEach(({ migrationName, error }) => { - konsola.error(`- Migration ${migrationName}: ${(error as Error).message}`); - }); - }); - } - else { - konsola.ok(`No failures reported`); - } - konsola.br(); -} diff --git a/src/commands/signup/README.md b/src/commands/signup/README.md deleted file mode 100644 index 239adad6..00000000 --- a/src/commands/signup/README.md +++ /dev/null @@ -1,35 +0,0 @@ -# Signup Command - -The signup command opens the Storyblok signup page in your browser, making it easy to create a new account. - -## Usage - -```bash -storyblok signup -``` - -## How it works - -1. **Browser Opening**: Opens the Storyblok signup page ([https://app.storyblok.com/#/signup](https://app.storyblok.com/#/signup)) in your default browser with UTM tracking parameters -2. **User Completes Signup**: Complete the signup process in the browser -3. **Next Steps**: After signup, run `storyblok login` to authenticate with the CLI - -## Example - -```bash -storyblok signup -# Complete signup in browser -storyblok login -``` - -## UTM Parameters - -The signup URL includes the following UTM parameters for tracking: -- `utm_source=storyblok-cli` -- `utm_medium=cli` -- `utm_campaign=signup` - -## Error Handling - -- Shows a warning if the user is already logged in -- Handles browser opening failures gracefully across different operating systems (macOS, Windows, Linux) diff --git a/src/commands/signup/actions.test.ts b/src/commands/signup/actions.test.ts deleted file mode 100644 index 37187826..00000000 --- a/src/commands/signup/actions.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { describe, expect, it } from 'vitest'; -import { buildSignupUrl } from './actions'; - -describe('signup actions', () => { - describe('buildSignupUrl', () => { - it('should build correct signup URL', () => { - const url = buildSignupUrl(); - - expect(url).toContain('https://app.storyblok.com/#/signup'); - expect(url).toContain('utm_source=storyblok-cli'); - expect(url).toContain('utm_medium=cli'); - expect(url).toContain('utm_campaign=signup'); - }); - - it('should include all UTM parameters', () => { - const url = buildSignupUrl(); - - expect(url).toContain('utm_source=storyblok-cli'); - expect(url).toContain('utm_medium=cli'); - expect(url).toContain('utm_campaign=signup'); - }); - - it('should use the correct Storyblok app URL', () => { - const url = buildSignupUrl(); - expect(url.startsWith('https://app.storyblok.com/#/signup')).toBe(true); - }); - }); -}); diff --git a/src/commands/signup/actions.ts b/src/commands/signup/actions.ts deleted file mode 100644 index 15815c0f..00000000 --- a/src/commands/signup/actions.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { exec } from 'node:child_process'; -import { promisify } from 'node:util'; - -const execAsync = promisify(exec); - -/** - * Build the signup URL with UTM parameters - */ -export function buildSignupUrl(): string { - const baseUrl = 'https://app.storyblok.com'; - - const utmParams = new URLSearchParams({ - utm_source: 'storyblok-cli', - utm_medium: 'cli', - utm_campaign: 'signup', - }); - - return `${baseUrl}/#/signup?${utmParams.toString()}`; -} - -/** - * Open the signup URL in the default browser - */ -export async function openSignupInBrowser(url: string): Promise { - let command: string; - - switch (process.platform) { - case 'darwin': - command = `open "${url}"`; - break; - case 'win32': - command = `start "" "${url}"`; - break; - default: - command = `xdg-open "${url}"`; - break; - } - - try { - await execAsync(command); - } - catch (error) { - throw new Error(`Failed to open browser: ${error}`); - } -} diff --git a/src/commands/signup/index.test.ts b/src/commands/signup/index.test.ts deleted file mode 100644 index f3342f94..00000000 --- a/src/commands/signup/index.test.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { signupCommand } from './'; - -// Mock dependencies -vi.mock('./actions', () => ({ - buildSignupUrl: vi.fn(() => 'https://app.storyblok.com/#/signup'), - openSignupInBrowser: vi.fn(), -})); - -vi.mock('../../session', () => ({ - session: () => ({ - state: { - isLoggedIn: false, - envLogin: false, - }, - initializeSession: vi.fn(), - }), -})); - -vi.mock('../../utils', async () => { - const actualUtils = await vi.importActual('../../utils'); - return { - ...actualUtils, - konsola: { - ok: vi.fn(), - title: vi.fn(), - info: vi.fn(), - br: vi.fn(), - error: vi.fn(), - }, - handleError: vi.fn(), - }; -}); - -describe('signupCommand', () => { - beforeEach(() => { - vi.resetAllMocks(); - vi.clearAllMocks(); - }); - - it('should be defined', () => { - expect(signupCommand).toBeDefined(); - }); - - it('should have correct command name and description', () => { - expect(signupCommand.name()).toBe('signup'); - expect(signupCommand.description()).toBe('Sign up for Storyblok'); - }); - - it('should not have any options', () => { - expect(signupCommand.options).toHaveLength(0); - }); -}); diff --git a/src/commands/signup/index.ts b/src/commands/signup/index.ts deleted file mode 100644 index 2f6b83de..00000000 --- a/src/commands/signup/index.ts +++ /dev/null @@ -1,47 +0,0 @@ -import chalk from 'chalk'; -import { colorPalette, commands } from '../../constants'; -import { getProgram } from '../../program'; -import { handleError, konsola } from '../../utils'; -import { buildSignupUrl, openSignupInBrowser } from './actions'; -import { session } from '../../session'; - -const program = getProgram(); // Get the shared singleton instance - -export const signupCommand = program - .command(commands.SIGNUP) - .description('Sign up for Storyblok') - .action(async () => { - konsola.title(` ${commands.SIGNUP} `, colorPalette.SIGNUP); - // Global options - const verbose = program.opts().verbose; - - const { state, initializeSession } = session(); - - await initializeSession(); - - if (state.isLoggedIn && !state.envLogin) { - konsola.ok(`You are already logged in. If you want to signup with a different account, please logout first.`); - return; - } - - try { - // Build the signup URL with UTM parameters - const signupUrl = buildSignupUrl(); - - konsola.info(`Opening Storyblok signup page...`); - konsola.info(`URL: ${chalk.dim(signupUrl)}`); - - // Open the browser - await openSignupInBrowser(signupUrl); - - konsola.ok(`Browser opened! Please complete the signup process.`); - konsola.br(); - konsola.info(`Once you've completed signup, run ${chalk.hex(colorPalette.PRIMARY)('storyblok login')} to authenticate with the CLI.`); - } - catch (error) { - konsola.br(); - handleError(error as Error, verbose); - } - - konsola.br(); - }); diff --git a/src/commands/stories/actions.test.ts b/src/commands/stories/actions.test.ts deleted file mode 100644 index e37d1e4a..00000000 --- a/src/commands/stories/actions.test.ts +++ /dev/null @@ -1,330 +0,0 @@ -import { http, HttpResponse } from 'msw'; -import { setupServer } from 'msw/node'; -import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; -import { fetchStories, fetchStoriesByComponent } from './actions'; -import { handleAPIError } from '../../utils/error'; -import type { Story } from './constants'; -import type { RegionCode } from '../../constants'; - -// Mock dependencies -vi.mock('../../utils/error', () => ({ - handleAPIError: vi.fn(), -})); - -// Mock stories data -const mockStories: Story[] = [ - { - id: 1, - name: 'Story 1', - uuid: 'uuid-1', - slug: 'story-1', - full_slug: 'story-1', - content: { - _uid: 'uid-1', - component: 'page', - }, - created_at: '2023-01-01T00:00:00Z', - updated_at: '2023-01-01T00:00:00Z', - published_at: '2023-01-01T00:00:00Z', - first_published_at: '2023-01-01T00:00:00Z', - published: true, - unpublished_changes: false, - is_startpage: false, - is_folder: false, - pinned: false, - parent_id: null, - group_id: 'group-1', - parent: null, - path: null, - position: 0, - sort_by_date: null, - tag_list: [], - disable_fe_editor: false, - default_root: null, - preview_token: null, - meta_data: null, - release_id: null, - last_author: null, - last_author_id: null, - alternates: [], - translated_slugs: null, - translated_slugs_attributes: null, - localized_paths: null, - breadcrumbs: [], - scheduled_dates: null, - favourite_for_user_ids: [], - imported_at: null, - deleted_at: null, - }, - { - id: 2, - name: 'Story 2', - uuid: 'uuid-2', - slug: 'story-2', - full_slug: 'story-2', - content: { - _uid: 'uid-2', - component: 'page', - }, - created_at: '2023-01-02T00:00:00Z', - updated_at: '2023-01-02T00:00:00Z', - published_at: '2023-01-02T00:00:00Z', - first_published_at: '2023-01-02T00:00:00Z', - published: true, - unpublished_changes: false, - is_startpage: false, - is_folder: false, - pinned: false, - parent_id: null, - group_id: 'group-2', - parent: null, - path: null, - position: 1, - sort_by_date: null, - tag_list: [], - disable_fe_editor: false, - default_root: null, - preview_token: null, - meta_data: null, - release_id: null, - last_author: null, - last_author_id: null, - alternates: [], - translated_slugs: null, - translated_slugs_attributes: null, - localized_paths: null, - breadcrumbs: [], - scheduled_dates: null, - favourite_for_user_ids: [], - imported_at: null, - deleted_at: null, - }, -]; - -// Set up MSW handlers -const handlers = [ - http.get('https://api.storyblok.com/v1/spaces/:spaceId/stories', ({ request }) => { - const token = request.headers.get('Authorization'); - - if (token !== 'test-token') { - return new HttpResponse(null, { status: 401 }); - } - - // Get URL to check for query parameters - const url = new URL(request.url); - const searchParams = url.searchParams; - - // If filter_query is present, handle it specially - if (url.searchParams.has('filter_query')) { - try { - const filterQuery = JSON.parse(decodeURIComponent(url.searchParams.get('filter_query') || '{}')); - - // If filtering for specific component - if (filterQuery.component && filterQuery.component.in) { - if (filterQuery.component.in.includes('article')) { - // Return only the first story for article component - return HttpResponse.json({ stories: [mockStories[0]] }); - } - } - } - catch { - // If JSON parsing fails, return an error - return new HttpResponse(null, { status: 400 }); - } - } - - // Handle filtering by published status - if (searchParams.has('is_published')) { - const isPublished = searchParams.get('is_published') === 'true'; - const filteredStories = mockStories.filter(story => story.published === isPublished); - return HttpResponse.json({ stories: filteredStories }); - } - - // Handle pagination - if (searchParams.has('page')) { - const page = Number.parseInt(searchParams.get('page') || '1', 10); - if (page > 1) { - // Simulate empty second page - return HttpResponse.json({ stories: [] }); - } - } - - // Handle tag filtering - if (searchParams.has('with_tag')) { - const tag = searchParams.get('with_tag'); - if (tag === 'featured') { - // Only return the first story for featured tag - return HttpResponse.json({ stories: [mockStories[0]] }); - } - } - - // Default response with all stories - return HttpResponse.json({ stories: mockStories }); - }), -]; - -const server = setupServer(...handlers); - -// Set up the MSW server -beforeAll(() => server.listen()); -afterEach(() => { - server.resetHandlers(); - vi.clearAllMocks(); -}); -afterAll(() => server.close()); - -describe('stories/actions', () => { - const mockSpace = '12345'; - const mockToken = 'test-token'; - const mockRegion = 'eu'; - - describe('fetchStories', () => { - it('should fetch stories without query parameters', async () => { - const result = await fetchStories(mockSpace, mockToken, mockRegion).catch(() => undefined); - expect(result).toEqual(mockStories); - }); - - it('should fetch stories with query parameters', async () => { - const result = await fetchStories(mockSpace, mockToken, mockRegion, { - with_tag: 'featured', - }).catch(() => undefined); - - // Should return only the first story due to the 'featured' tag filter in our handler - expect(result).toHaveLength(1); - expect(result?.[0].id).toBe(1); - }); - - it('should handle pagination correctly', async () => { - const result = await fetchStories(mockSpace, mockToken, mockRegion, { - page: 2, - }).catch(() => undefined); - - // Should return empty array for page 2 based on our handler - expect(result).toHaveLength(0); - }); - - it('should handle filtering by published status', async () => { - const result = await fetchStories(mockSpace, mockToken, mockRegion, { - is_published: true, - }).catch(() => undefined); - - // All our mock stories are published - expect(result).toHaveLength(2); - }); - - it('should handle complex query parameters with objects', async () => { - const result = await fetchStories(mockSpace, mockToken, mockRegion, { - filter_query: { - 'component': { in: ['article', 'news'] }, - 'content.category': { in: ['technology'] }, - }, - }).catch(() => undefined); - - // Should return only the first story based on our handler - expect(result).toHaveLength(2); - expect(result?.[0].id).toBe(1); - }); - - it('should handle unauthorized errors', async () => { - await fetchStories(mockSpace, 'invalid-token', mockRegion).catch(() => { - expect(handleAPIError).toHaveBeenCalledWith( - 'pull_stories', - expect.any(Error), - ); - }); - }); - - it('should handle server errors', async () => { - // Override handler to simulate a server error - server.use( - http.get('https://api.storyblok.com/v1/spaces/:spaceId/stories', () => { - return new HttpResponse(null, { status: 500 }); - }), - ); - - await fetchStories(mockSpace, mockToken, mockRegion).catch(() => { - expect(handleAPIError).toHaveBeenCalledWith( - 'pull_stories', - expect.any(Error), - ); - }); - }); - }); - - describe('fetchStoriesByComponent', () => { - const mockSpaceOptions = { - spaceId: '12345', - token: 'test-token', - region: 'eu' as RegionCode, - }; - - let requestUrl: string | undefined; - - beforeEach(() => { - requestUrl = undefined; - server.use( - http.get('*/stories*', ({ request }) => { - requestUrl = new URL(request.url).search; - return HttpResponse.json({ stories: [] }); - }), - ); - }); - - it('should fetch stories without filters', async () => { - await fetchStoriesByComponent(mockSpaceOptions); - expect(requestUrl).toBe('?per_page=100'); - }); - - it('should fetch stories with component filter', async () => { - await fetchStoriesByComponent(mockSpaceOptions, { - componentName: 'test-component', - }); - expect(requestUrl).toBe('?contain_component=test-component&per_page=100'); - }); - - it('should fetch stories with starts_with filter', async () => { - await fetchStoriesByComponent(mockSpaceOptions, { - starts_with: '/en/blog/', - }); - expect(requestUrl).toBe('?starts_with=%2Fen%2Fblog%2F&per_page=100'); - }); - - it('should fetch stories with filter_query parameter', async () => { - await fetchStoriesByComponent(mockSpaceOptions, { - query: '[highlighted][is]=true', - }); - expect(requestUrl).toBe('?per_page=100&filter_query[highlighted][is]=true'); - }); - - it('should handle already prefixed filter_query parameter', async () => { - await fetchStoriesByComponent(mockSpaceOptions, { - query: 'filter_query[highlighted][is]=true', - }); - expect(requestUrl).toBe('?per_page=100&filter_query[highlighted][is]=true'); - }); - - it('should handle multiple filters together', async () => { - await fetchStoriesByComponent(mockSpaceOptions, { - componentName: 'test-component', - starts_with: '/en/blog/', - query: '[highlighted][is]=true', - }); - expect(requestUrl).toBe('?starts_with=%2Fen%2Fblog%2F&contain_component=test-component&per_page=100&filter_query[highlighted][is]=true'); - }); - - it('should handle error responses', async () => { - server.use( - http.get('*/stories*', () => { - return new HttpResponse(null, { status: 404, statusText: 'Not Found' }); - }), - ); - - const result = await fetchStoriesByComponent(mockSpaceOptions); - expect(result).toEqual([]); - expect(handleAPIError).toHaveBeenCalledWith( - 'pull_stories', - expect.any(Error), - ); - }); - }); -}); diff --git a/src/commands/stories/actions.ts b/src/commands/stories/actions.ts deleted file mode 100644 index e0760989..00000000 --- a/src/commands/stories/actions.ts +++ /dev/null @@ -1,169 +0,0 @@ -import { customFetch } from '../../utils/fetch'; -import { getStoryblokUrl } from '../../utils/api-routes'; -import type { RegionCode, SpaceOptions } from '../../constants'; -import type { StoriesFilterOptions, StoriesQueryParams, Story } from './constants'; -import { handleAPIError } from '../../utils/error'; -import { objectToStringParams } from '../../utils'; - -/** - * Fetches stories from Storyblok Management API with optional query parameters - * @param space - The space ID - * @param token - The authentication token - * @param region - The region code - * @param params - Optional query parameters for filtering stories - * @returns Promise with an array of stories or undefined if error occurs - */ -export const fetchStories = async ( - space: string, - token: string, - region: RegionCode, - params?: StoriesQueryParams, -) => { - try { - const url = getStoryblokUrl(region); - const allStories: Story[] = []; - let currentPage = 1; - let hasMorePages = true; - - while (hasMorePages) { - // Extract filter_query params to handle them separately - const { filter_query, ...restParams } = params || {}; - - // Handle regular params with URLSearchParams - const regularParams = new URLSearchParams({ - ...objectToStringParams({ ...restParams, per_page: 100 }), - ...(currentPage > 1 && { page: currentPage.toString() }), - }).toString(); - - // Combine regular params with filter_query params (if any) - const queryString = filter_query - ? `${regularParams ? `${regularParams}&` : ''}${filter_query}` - : regularParams; - - const endpoint = `${url}/spaces/${space}/stories${queryString ? `?${queryString}` : ''}`; - - const response = await customFetch<{ - stories: Story[]; - per_page: number; - total: number; - }>(endpoint, { - headers: { - Authorization: token, - }, - }); - - allStories.push(...response.stories); - - // Check if we have more pages to fetch - const totalPages = Math.ceil(response.total / response.perPage); - hasMorePages = currentPage < totalPages; - currentPage++; - } - - return allStories; - } - catch (error) { - handleAPIError('pull_stories', error as Error); - } -}; - -export async function fetchStoriesByComponent( - spaceOptions: SpaceOptions, - filterOptions?: StoriesFilterOptions, -): Promise { - const { spaceId, token, region } = spaceOptions; - const { componentName = '', query, starts_with } = filterOptions || {}; - - // Convert filterOptions to StoriesQueryParams - const params: StoriesQueryParams = { - ...(starts_with && { starts_with }), - }; - - // Handle component filter - if (componentName) { - params.contain_component = componentName; - } - - // Handle query string if provided - if (query) { - // Add filter_query prefix to the query parameter if it doesn't have it already - params.filter_query = query.startsWith('filter_query') ? query : `filter_query${query}`; - } - - try { - const stories = await fetchStories(spaceId, token, region, params); - return stories ?? []; - } - catch (error) { - handleAPIError('pull_stories', error as Error); - } -} - -export const fetchStory = async ( - space: string, - token: string, - region: RegionCode, - storyId: string, -) => { - try { - const url = getStoryblokUrl(region); - const endpoint = `${url}/spaces/${space}/stories/${storyId}`; - - const response = await customFetch<{ - story: Story; - }>(endpoint, { - headers: { - Authorization: token, - }, - }); - return response.story; - } - catch (error) { - handleAPIError('pull_story', error as Error); - } -}; - -/** - * Updates a story in Storyblok with new content - * @param space - The space ID - * @param token - The authentication token - * @param region - The region code - * @param storyId - The ID of the story to update - * @param payload - The payload containing story data and update options - * @param payload.story - The story data to update - * @param payload.force_update - Whether to force the update (optional) - * @param payload.publish - Whether to publish the story (optional) - * @returns Promise with the updated story or undefined if error occurs - */ -export const updateStory = async ( - space: string, - token: string, - region: RegionCode, - storyId: number, - payload: { - story: Partial; - force_update?: string; - publish?: number; - }, -): Promise => { - try { - const url = getStoryblokUrl(region); - const endpoint = `${url}/spaces/${space}/stories/${storyId}`; - - const response = await customFetch<{ - story: Story; - }>(endpoint, { - method: 'PUT', - headers: { - 'Authorization': token, - 'Content-Type': 'application/json', - }, - body: JSON.stringify(payload), - }); - - return response.story; - } - catch (error) { - handleAPIError('update_story', error as Error); - } -}; diff --git a/src/commands/stories/constants.ts b/src/commands/stories/constants.ts deleted file mode 100644 index 3b19a67a..00000000 --- a/src/commands/stories/constants.ts +++ /dev/null @@ -1,171 +0,0 @@ -export interface StoryAlternate { - id: number; - name: string; - slug: string; - published: boolean; - full_slug: string; - is_folder: boolean; - parent_id?: number; -} - -export interface TranslatedSlug { - story_id?: number; - lang: string; - slug: string; - name: string | null; - published: boolean | null; -} - -export interface TranslatedSlugAttributes extends TranslatedSlug { - id?: number; -} - -export interface LocalizedPath { - path: string; - name: string | null; - lang: string; - published: boolean; -} - -export interface StoryContent { - _uid: string; - component: string; - [key: string]: any; -} - -export interface ParentInfo { - id: number; - slug: string; - name: string; - disable_fe_editor?: boolean; - uuid: string; -} - -export interface PreviewToken { - token: string; - timestamp: string; -} - -export interface LastAuthor { - id: number; - userid: string; - friendly_name: string; -} - -export interface BreadcrumbItem { - id: number; - name: string; - parent_id: number; - disable_fe_editor: boolean; - path: string; - slug: string; - translated_slugs?: TranslatedSlug[]; -} - -/** - * Storyblok Story object from Management API - * @see https://www.storyblok.com/docs/api/management/core-resources/stories/the-story-object - */ -export interface Story { - // Basic properties - id: number; - name: string; - uuid: string; - slug: string; - full_slug: string; - content: StoryContent; - - // Timestamps - created_at: string; - updated_at: string; - published_at: string | null; - first_published_at: string | null; - imported_at: string | null; - deleted_at: string | null; - - // Status properties - published: boolean; - unpublished_changes: boolean; - is_startpage: boolean; - is_folder: boolean; - pinned: boolean; - - // Structural properties - parent_id: number | null; - group_id: string; - parent: ParentInfo | null; - path: string | null; - position: number; - sort_by_date: string | null; - tag_list: string[]; - - // Editor and UI properties - disable_fe_editor: boolean; - default_root: string | null; - preview_token: PreviewToken | null; - - // Metadata - meta_data: Record | null; - release_id: number | null; - last_author: LastAuthor | null; - last_author_id: number | null; - - // Internationalization properties - alternates: StoryAlternate[]; - translated_slugs: TranslatedSlug[] | null; - translated_slugs_attributes: TranslatedSlugAttributes[] | null; - localized_paths: LocalizedPath[] | null; - - // Additional properties - breadcrumbs: BreadcrumbItem[]; - scheduled_dates: string | null; - favourite_for_user_ids: number[]; -} - -/** - * Query parameters for retrieving multiple stories from the Management API - * @see https://www.storyblok.com/docs/api/management/core-resources/stories/retrieve-multiple-stories - */ -export interface StoriesQueryParams { - // Pagination - page?: number; - - // Content filtering - contain_component?: string; - text_search?: string; - sort_by?: string; - pinned?: boolean; - excluding_ids?: string; - by_ids?: string; - by_uuids?: string; - with_tag?: string; - folder_only?: boolean; - story_only?: boolean; - with_parent?: number; - starts_with?: string; - in_trash?: boolean; - search?: string; - filter_query?: string; - in_release?: number; - is_published?: boolean; - by_slugs?: string; - mine?: boolean; - excluding_slugs?: string; - in_workflow_stages?: string; - by_uuids_ordered?: string; - with_slug?: string; - with_summary?: number; - scheduled_at_gt?: string; - scheduled_at_lt?: string; - favourite?: boolean; - reference_search?: string; - - // Allow string indexing for filter_query parameters - [key: string]: string | number | boolean | undefined; -} - -export interface StoriesFilterOptions { - componentName?: string; - query?: string; - starts_with?: string; -} diff --git a/src/commands/stories/index.ts b/src/commands/stories/index.ts deleted file mode 100644 index a82ced8a..00000000 --- a/src/commands/stories/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './actions'; -export * from './constants'; -export * from './utils'; diff --git a/src/commands/stories/utils.ts b/src/commands/stories/utils.ts deleted file mode 100644 index 884fb991..00000000 --- a/src/commands/stories/utils.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { Story } from './constants'; - -/** - * @method isStoryPublishedWithoutChanges - * @param {object} story - * @return {boolean} - */ -export const isStoryPublishedWithoutChanges = (story: Partial) => { - return story.published && !story.unpublished_changes; -}; - -/** - * @method isStoryWithUnpublishedChanges - * @param {object} story - * @return {boolean} - */ -export const isStoryWithUnpublishedChanges = (story: Partial) => { - return story.published && story.unpublished_changes; -}; diff --git a/src/commands/types/command.ts b/src/commands/types/command.ts deleted file mode 100644 index d2599fec..00000000 --- a/src/commands/types/command.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { getProgram } from '../../program'; - -const program = getProgram(); // Get the shared singleton instance - -// Components root command -export const typesCommand = program - .command('types') - .alias('ts') - .description(`Generate types d.ts for your component schemas`) - .option('-s, --space ', 'space ID') - .option('-p, --path ', 'path to save the file. Default is .storyblok/types'); diff --git a/src/commands/types/generate/README.md b/src/commands/types/generate/README.md deleted file mode 100644 index 4c7d7f58..00000000 --- a/src/commands/types/generate/README.md +++ /dev/null @@ -1,82 +0,0 @@ -# Types Generate Command - -The `types generate` command generates TypeScript type definitions (`.d.ts` files) for your Storyblok component schemas. This helps you maintain type safety when working with your Storyblok content. - -> [!WARNING] -> Before generating types, first pull your components using the `components pull` command. Make sure to use the same flags (`--separate-files`, `--suffix`) that you used when pulling components to ensure the types are generated correctly. - -## Basic Usage - -```bash -storyblok types generate --space -``` - -## Options - -| Option | Description | Default | -|--------|-------------|---------| -| `--sf, --separate-files` | Generate separate type definition files for each component | `false` | -| `--strict` | Enable strict mode with no loose typing | `false` | -| `--type-prefix ` | Prefix to be prepended to all generated component type names | - | -| `--suffix ` | Suffix for component names | - | -| `--custom-fields-parser ` | Path to the parser file for Custom Field Types | - | -| `--compiler-options ` | Path to the compiler options from json-schema-to-typescript | - | -| `--space ` | (Required) The ID of your Storyblok space | - | -| `--path ` | Path to the directory containing your component files | `.storyblok/components` | - -## Examples - -Generate types for all components: -```bash -storyblok types generate --space 12345 -``` - -Generate types with strict mode: -```bash -storyblok types generate --space 12345 --strict -``` - -Generate types with a custom prefix: -```bash -storyblok types generate --space 12345 --type-prefix Storyblok -``` - -Generate separate type files for each component: -```bash -storyblok types generate --space 12345 --separate-files -``` - -## File Structure - -The command will generate two files: -1. A `storyblok.d.ts` file with base Storyblok types (like `StoryblokAsset`, `StoryblokRichtext`, etc.) -2. A `storyblok-components.d.ts` file for each space inside the `.storyblok/types/{spaceId}/` directory with your component types - -### Example Structure - -When running: -```bash -storyblok types generate --space 295018 -``` - -The following structure will be created: - -``` -.storyblok/ -└── types/ - β”œβ”€β”€ storyblok.d.ts # Base Storyblok types - └── 295018/ - └── storyblok.d.ts # Your component types -``` - -> **Note:** -> The `{spaceId}` folder corresponds to the ID of your Storyblok space. -> The generated files are always placed under `.storyblok/types/` and `.storyblok/types/{spaceId}/`. - -## Notes - -- The command requires you to be logged in to Storyblok -- The space ID is required -- The generated types are based on your component schemas in Storyblok -- When using `--strict`, the generated types will be more precise but may require more explicit type handling in your code -- Custom field types can be handled by providing a parser file with `--custom-fields-parser` diff --git a/src/commands/types/generate/actions.test.ts b/src/commands/types/generate/actions.test.ts deleted file mode 100644 index 5252cd7f..00000000 --- a/src/commands/types/generate/actions.test.ts +++ /dev/null @@ -1,690 +0,0 @@ -import { generateStoryblokTypes, generateTypes, getComponentType, getStoryType } from './actions'; -import type { SpaceComponent, SpaceData } from '../../../commands/components/constants'; -import type { GenerateTypesOptions } from './constants'; -import { join, resolve } from 'node:path'; -import { vol } from 'memfs'; -import { readFileSync } from 'node:fs'; - -// Import the mocked functions -import { saveToFile } from '../../../utils/filesystem'; - -// Mock the filesystem module -vi.mock('../../../utils/filesystem', () => ({ - saveToFile: vi.fn().mockResolvedValue(undefined), - resolvePath: vi.fn().mockReturnValue('/mocked/resolved/path'), -})); - -// Mock the fs module -vi.mock('node:fs', () => ({ - readFileSync: vi.fn().mockReturnValue(''), -})); - -vi.mock('node:fs/promises'); -vi.mock('node:path', () => ({ - resolve: vi.fn().mockReturnValue('/mocked/path'), - join: vi.fn().mockReturnValue('/mocked/joined/path'), -})); - -// Create a mock for the custom fields parser -const mockCustomFieldsParser = vi.fn().mockImplementation((key, field) => { - if (field.field_type === 'native-color-picker') { - return { - [key]: { - properties: { - color: { type: 'string' }, - }, - required: ['color'], - type: 'object', - }, - }; - } - return {}; -}); - -// Mock the dynamic import -vi.mock('/mocked/path', () => ({ - default: mockCustomFieldsParser, -})); - -// Mock the import function -vi.mock('node:module', () => ({ - import: vi.fn().mockResolvedValue({ - default: mockCustomFieldsParser, - }), -})); - -// Set up the virtual file system with our custom fields parser -vol.fromJSON({ - '/path/to/custom/parser.ts': ` -export default (key: string, field: any) => { - switch (field.field_type) { - case 'native-color-picker': - return { - [key]: { - properties: { - color: { type: 'string' }, - }, - required: ['color'], - type: 'object', - }, - }; - default: - return {}; - } -}; -`, - // Add a mock storyblok.ts file for testing generateStoryblokTypes - '/mocked/path': ` -// Storyblok types -export type StoryblokPropertyType = 'text' | 'textarea' | 'number' | 'boolean' | 'multilink' | 'bloks' | 'custom'; - -export interface StoryblokText { - type: 'text'; - required?: boolean; -} - -export interface StoryblokTextarea { - type: 'textarea'; - required?: boolean; -} - -export interface StoryblokNumber { - type: 'number'; - required?: boolean; -} - -export interface StoryblokBoolean { - type: 'boolean'; - required?: boolean; -} - -export interface StoryblokMultilink { - type: 'multilink'; - required?: boolean; - email_link_type?: boolean; - asset_link_type?: boolean; -} - -export interface StoryblokBloks { - type: 'bloks'; - required?: boolean; - restrict_components?: boolean; - component_whitelist?: string[]; - component_group_whitelist?: string[]; - restrict_type?: 'groups' | 'components'; -} - -export interface StoryblokCustom { - type: 'custom'; - required?: boolean; - field_type?: string; -} -`, -}); - -// Set up the mock content for readFileSync -const mockStoryblokContent = vol.readFileSync('/mocked/path', 'utf-8') as string; -vi.mocked(readFileSync).mockImplementation((path) => { - if (path === '/mocked/path') { - return mockStoryblokContent; - } - return ''; -}); - -const mockSpaceData: SpaceData = { - components: [ - { - name: 'test_component', - display_name: 'Test Component', - created_at: '2023-01-01T00:00:00Z', - updated_at: '2023-01-01T00:00:00Z', - id: 1, - schema: { - title: { - type: 'text', - required: true, - }, - description: { - type: 'textarea', - required: false, - }, - }, - color: null, - internal_tags_list: [], - internal_tag_ids: [], - }, - ], - groups: [], - presets: [], - internalTags: [], -}; - -describe('generate types actions', () => { - it('should generate types successfully', async () => { - // Create mock options - const mockOptions: GenerateTypesOptions = { - strict: false, - }; - - // Call the function with the correct parameters - const result = await generateTypes(mockSpaceData, mockOptions); - - // Verify the result contains expected content - expect(result).toContain('// This file was generated by the storyblok CLI.'); - expect(result).toContain('// DO NOT MODIFY THIS FILE BY HAND.'); - expect(result).toContain('export interface TestComponent'); - expect(result).toContain('title: string'); - expect(result).toContain('description?: string'); - expect(result).toContain('component: "test_component"'); - expect(result).toContain('_uid: string'); - expect(result).toContain('[k: string]: unknown'); - }); - - it('should generate types successfully with strict mode', async () => { - const mockOptions: GenerateTypesOptions = { - strict: true, - }; - - const result = await generateTypes(mockSpaceData, mockOptions); - - expect(result).not.toContain('[k: string]: unknown'); - }); - - it('should handle customFieldsParser option', async () => { - // Create mock options with customFieldsParser - const mockOptions: GenerateTypesOptions = { - strict: false, - customFieldsParser: '/path/to/custom/parser.ts', - }; - - // Call the function with the customFieldsParser option - const result = await generateTypes(mockSpaceData, mockOptions); - - // Verify that the result is generated successfully - expect(result).toBeDefined(); - if (result) { - expect(typeof result).toBe('string'); - expect(result.length).toBeGreaterThan(0); - } - - // Verify that resolve was called with the customFieldsParser path - expect(resolve).toHaveBeenCalledWith('/path/to/custom/parser.ts'); - }); - - it('should handle compilerOptions option', async () => { - // Create mock options with compilerOptions - const mockOptions: GenerateTypesOptions = { - strict: false, - compilerOptions: '/path/to/compiler/options', - }; - - // Call the function with the compilerOptions option - const result = await generateTypes(mockSpaceData, mockOptions); - - // Verify that the result is generated successfully - expect(result).toBeDefined(); - if (result) { - expect(typeof result).toBe('string'); - expect(result.length).toBeGreaterThan(0); - } - - // Verify that resolve was called with the compilerOptions path - expect(resolve).toHaveBeenCalledWith('/path/to/compiler/options'); - }); - - it('should apply typePrefix to component type names', async () => { - // Create mock options with typePrefix - const mockOptions: GenerateTypesOptions = { - strict: false, - typePrefix: 'Custom', - }; - - // Call the function with the typePrefix option - const result = await generateTypes(mockSpaceData, mockOptions); - - // Verify that the result contains the expected prefixed type name - expect(result).toContain('export interface CustomTestComponent'); - expect(result).toContain('title: string'); - expect(result).toContain('description?: string'); - expect(result).toContain('component: "test_component"'); - expect(result).toContain('_uid: string'); - expect(result).toContain('[k: string]: unknown'); - }); -}); - -describe('getComponentType', () => { - it('should convert component name to PascalCase', () => { - const options: GenerateTypesOptions = {}; - expect(getComponentType('test_component', options)).toBe('TestComponent'); - }); - - it('should handle special characters in component name', () => { - const options: GenerateTypesOptions = {}; - expect(getComponentType('test-component!', options)).toBe('TestComponent'); - }); - - it('should handle emojis in component name', () => { - const options: GenerateTypesOptions = {}; - expect(getComponentType('testπŸ˜€component', options)).toBe('TestComponent'); - }); - - it('should handle multiple consecutive special characters', () => { - const options: GenerateTypesOptions = {}; - expect(getComponentType('test___component', options)).toBe('TestComponent'); - }); - - it('should handle component names starting with numbers', () => { - const options: GenerateTypesOptions = {}; - expect(getComponentType('123component', options)).toBe('_123component'); - }); - - it('should apply typePrefix when provided', () => { - const options: GenerateTypesOptions = { - typePrefix: 'Custom', - }; - expect(getComponentType('test_component', options)).toBe('CustomTestComponent'); - }); - - it('should handle empty typePrefix', () => { - const options: GenerateTypesOptions = { - typePrefix: '', - }; - expect(getComponentType('test_component', options)).toBe('TestComponent'); - }); - - it('should handle component names with spaces', () => { - const options: GenerateTypesOptions = {}; - expect(getComponentType('test component', options)).toBe('TestComponent'); - }); -}); - -describe('getStoryType', () => { - it('should convert property names to the correct format', () => { - // Test cases for different property name formats - expect(getStoryType('my_property')).toBe('ISbStoryData'); - expect(getStoryType('my-property')).toBe('ISbStoryData'); - expect(getStoryType('myProperty')).toBe('ISbStoryData'); - expect(getStoryType('MyProperty')).toBe('ISbStoryData'); - expect(getStoryType('my property')).toBe('ISbStoryData'); - expect(getStoryType('my_property_name')).toBe('ISbStoryData'); - }); - - it('should handle special characters and numbers', () => { - expect(getStoryType('my_property_123')).toBe('ISbStoryData'); - expect(getStoryType('my-property!')).toBe('ISbStoryData'); - expect(getStoryType('my_property@name')).toBe('ISbStoryData'); - }); - - it('should handle empty or single character properties', () => { - expect(getStoryType('')).toBe('ISbStoryData<>'); - expect(getStoryType('a')).toBe('ISbStoryData'); - }); - - it('should handle prefix', () => { - expect(getStoryType('my_property', 'Custom')).toBe('ISbStoryData'); - }); -}); - -describe('component property type annotations', () => { - it('should handle text property type', async () => { - // Create a component with text property type - const componentWithTextType: SpaceComponent = { - name: 'test_component', - display_name: 'Test Component', - created_at: '2023-01-01T00:00:00Z', - updated_at: '2023-01-01T00:00:00Z', - id: 1, - schema: { - title: { - type: 'text', - required: true, - }, - }, - color: null, - internal_tags_list: [], - internal_tag_ids: [], - }; - - // Create a space data with this component - const spaceData: SpaceData = { - components: [componentWithTextType], - groups: [], - presets: [], - internalTags: [], - }; - - // Generate types - const result = await generateTypes(spaceData, { strict: false }); - - // Verify that the result contains the expected property type - expect(result).toContain('title: string'); - }); - - it('should handle textarea property type', async () => { - // Create a component with textarea property type - const componentWithTextareaType: SpaceComponent = { - name: 'test_component', - display_name: 'Test Component', - created_at: '2023-01-01T00:00:00Z', - updated_at: '2023-01-01T00:00:00Z', - id: 1, - schema: { - description: { - type: 'textarea', - required: false, - }, - }, - color: null, - internal_tags_list: [], - internal_tag_ids: [], - }; - - // Create a space data with this component - const spaceData: SpaceData = { - components: [componentWithTextareaType], - groups: [], - presets: [], - internalTags: [], - }; - - // Generate types - const result = await generateTypes(spaceData, { strict: false }); - - // Verify that the result contains the expected property type - expect(result).toContain('description?: string'); - }); - - it('should handle number property type', async () => { - // Create a component with number property type - const componentWithNumberType: SpaceComponent = { - name: 'test_component', - display_name: 'Test Component', - created_at: '2023-01-01T00:00:00Z', - updated_at: '2023-01-01T00:00:00Z', - id: 1, - schema: { - count: { - type: 'number', - required: false, - }, - }, - color: null, - internal_tags_list: [], - internal_tag_ids: [], - }; - - // Create a space data with this component - const spaceData: SpaceData = { - components: [componentWithNumberType], - groups: [], - presets: [], - internalTags: [], - }; - - // Generate types - const result = await generateTypes(spaceData, { strict: false }); - - // Verify that the result contains the expected property type - expect(result).toContain('count?: string'); - }); - - it('should handle boolean property type', async () => { - // Create a component with boolean property type - const componentWithBooleanType: SpaceComponent = { - name: 'test_component', - display_name: 'Test Component', - created_at: '2023-01-01T00:00:00Z', - updated_at: '2023-01-01T00:00:00Z', - id: 1, - schema: { - isActive: { - type: 'boolean', - required: false, - }, - }, - color: null, - internal_tags_list: [], - internal_tag_ids: [], - }; - - // Create a space data with this component - const spaceData: SpaceData = { - components: [componentWithBooleanType], - groups: [], - presets: [], - internalTags: [], - }; - - // Generate types - const result = await generateTypes(spaceData, { strict: false }); - - // Verify that the result contains the expected property type - expect(result).toContain('isActive?: boolean'); - }); - - it('should handle multilink property type', async () => { - // Create a component with multilink property type - const componentWithMultilinkType: SpaceComponent = { - name: 'test_component', - display_name: 'Test Component', - created_at: '2023-01-01T00:00:00Z', - updated_at: '2023-01-01T00:00:00Z', - id: 1, - schema: { - link: { - type: 'multilink', - required: false, - email_link_type: false, - asset_link_type: true, - }, - }, - color: null, - internal_tags_list: [], - internal_tag_ids: [], - }; - - // Create a space data with this component - const spaceData: SpaceData = { - components: [componentWithMultilinkType], - groups: [], - presets: [], - internalTags: [], - }; - - // Generate types - const result = await generateTypes(spaceData, { strict: false }); - - // Verify that the result contains the expected property type - expect(result).toContain('link?:'); - }); - - it('should handle bloks property type with component restrictions', async () => { - // Create a component with bloks property type and component restrictions - const componentWithBloksType: SpaceComponent = { - name: 'test_component', - display_name: 'Test Component', - created_at: '2023-01-01T00:00:00Z', - updated_at: '2023-01-01T00:00:00Z', - id: 1, - schema: { - content: { - type: 'bloks', - required: false, - restrict_components: true, - component_whitelist: ['button', 'image'], - }, - }, - color: null, - internal_tags_list: [], - internal_tag_ids: [], - }; - - // Create a space data with this component - const spaceData: SpaceData = { - components: [componentWithBloksType], - groups: [], - presets: [], - internalTags: [], - }; - - // Generate types - const result = await generateTypes(spaceData, { strict: false }); - - // Verify that the result contains the expected property type - expect(result).toContain('content?:'); - }); - - it('should handle tabbed properties correctly', async () => { - // Create a component with tabbed properties - const componentWithTabbedProperties: SpaceComponent = { - name: 'test_component', - display_name: 'Test Component', - created_at: '2023-01-01T00:00:00Z', - updated_at: '2023-01-01T00:00:00Z', - id: 1, - schema: { - 'tab-content': { - type: 'tab', - display_name: 'Content', - }, - 'title': { - type: 'text', - required: true, - }, - }, - color: null, - internal_tags_list: [], - internal_tag_ids: [], - }; - - // Create a space data with this component - const spaceData: SpaceData = { - components: [componentWithTabbedProperties], - groups: [], - presets: [], - internalTags: [], - }; - - // Generate types - const result = await generateTypes(spaceData, { strict: false }); - - // Verify that the result contains the expected property type but not the tab - expect(result).toContain('title: string'); - expect(result).not.toContain('tab-content'); - }); - - it('should handle custom property type with customFieldsParser', async () => { - // Create a component with custom property type - const componentWithCustomType: SpaceComponent = { - name: 'test_component', - display_name: 'Test Component', - created_at: '2023-01-01T00:00:00Z', - updated_at: '2023-01-01T00:00:00Z', - id: 1, - schema: { - colorPicker: { - type: 'custom', - field_type: 'native-color-picker', - required: false, - }, - }, - color: null, - internal_tags_list: [], - internal_tag_ids: [], - }; - - // Create a space data with this component - const spaceData: SpaceData = { - components: [componentWithCustomType], - groups: [], - presets: [], - internalTags: [], - }; - - // Create mock options with customFieldsParser - const mockOptions: GenerateTypesOptions = { - strict: false, - customFieldsParser: '/path/to/custom/parser.ts', - }; - - // Reset the mock to ensure it's called with the right parameters - mockCustomFieldsParser.mockClear(); - - // Generate types - const result = await generateTypes(spaceData, mockOptions); - - // Verify that the custom fields parser was called - expect(mockCustomFieldsParser).toHaveBeenCalled(); - - // Verify that the result contains the expected property type - expect(result).toContain('colorPicker?:'); - expect(result).toContain('color: string'); - }); -}); - -describe('generateStoryblokTypes', () => { - beforeEach(() => { - vi.clearAllMocks(); - }); - - it('should generate Storyblok types successfully with default options', async () => { - // Call the function with default options - const result = await generateStoryblokTypes(); - - // Verify that the function returns true - expect(result).toBe(true); - - // Verify that readFileSync was called with the correct path - expect(readFileSync).toHaveBeenCalledWith('/mocked/path', 'utf-8'); - - // Verify that saveToFile was called with the correct parameters - const savedContent = vi.mocked(saveToFile).mock.calls[0][1]; - const lines = savedContent.split('\n'); - - // Verify the first three lines - expect(lines[0]).toBe('// This file was generated by the Storyblok CLI.'); - expect(lines[1]).toBe('// DO NOT MODIFY THIS FILE BY HAND.'); - expect(lines[2]).toBe('import type { ISbStoryData } from \'@storyblok/js\';'); - }); - - it('should generate Storyblok types with custom filename', async () => { - // Call the function with custom filename - const result = await generateStoryblokTypes({ filename: 'custom-storyblok' }); - - // Verify that the function returns true - expect(result).toBe(true); - - // Verify that saveToFile was called with the correct filename - expect(saveToFile).toHaveBeenCalledWith('/mocked/joined/path', expect.any(String)); - expect(join).toHaveBeenCalledWith(expect.any(String), 'custom-storyblok.d.ts'); - }); - - it('should generate Storyblok types with custom path', async () => { - // Call the function with custom path - const result = await generateStoryblokTypes({ path: '/custom/path' }); - - // Verify that the function returns true - expect(result).toBe(true); - - // Verify that resolve was called with the correct path - expect(resolve).toHaveBeenCalledWith(expect.any(String), '/custom/path', 'types'); - }); - - it('should extract all Storyblok type definitions', async () => { - // Call the function - await generateStoryblokTypes(); - - // Get the content passed to saveToFile - const savedContent = vi.mocked(saveToFile).mock.calls[0][1]; - - // Verify that all expected type definitions are included - expect(savedContent).toContain('export type StoryblokPropertyType = \'text\' | \'textarea\' | \'number\' | \'boolean\' | \'multilink\' | \'bloks\' | \'custom\';'); - expect(savedContent).toContain('export interface StoryblokText'); - expect(savedContent).toContain('export interface StoryblokTextarea'); - expect(savedContent).toContain('export interface StoryblokNumber'); - expect(savedContent).toContain('export interface StoryblokBoolean'); - expect(savedContent).toContain('export interface StoryblokMultilink'); - expect(savedContent).toContain('export interface StoryblokBloks'); - expect(savedContent).toContain('export interface StoryblokCustom'); - }); -}); diff --git a/src/commands/types/generate/actions.ts b/src/commands/types/generate/actions.ts deleted file mode 100644 index f8fd9dc2..00000000 --- a/src/commands/types/generate/actions.ts +++ /dev/null @@ -1,437 +0,0 @@ -import { compile, type JSONSchema } from 'json-schema-to-typescript'; -import type { SpaceComponent, SpaceData } from '../../../commands/components/constants'; -import { __dirname, capitalize, handleError, handleFileSystemError, toCamelCase, toPascalCase } from '../../../utils'; -import type { GenerateTypesOptions } from './constants'; -import type { StoryblokPropertyType } from '../../../types/storyblok'; -import { storyblokSchemas } from '../../../utils/storyblok-schemas'; -import { join, resolve } from 'node:path'; -import { resolvePath, saveToFile } from '../../../utils/filesystem'; -import { readFileSync } from 'node:fs'; -import type { ComponentPropertySchema } from '../../../types/schemas'; - -export interface ComponentGroupsAndNamesObject { - componentGroups: Map>; - componentNames: Set; -} - -// Constants -const STORY_TYPE = 'ISbStoryData'; -const DEFAULT_TYPEDEFS_HEADER = [ - '// This file was generated by the storyblok CLI.', - '// DO NOT MODIFY THIS FILE BY HAND.', -]; - -const getPropertyTypeAnnotation = (property: ComponentPropertySchema, prefix?: string) => { - // If a property type is one of the ones provided by Storyblok, return that type - // Casting as string[] to avoid TS error on using Array.includes on different narrowed types - if (Array.from(storyblokSchemas.keys()).includes(property.type as StoryblokPropertyType)) { - return { type: property.type }; - } - // Initialize property type as any (fallback type) - let type: string | string[] = 'unknown'; - - const options = property.options && property.options.length > 0 ? property.options.map((item: { value: string }) => item.value) : []; - - // Add empty option to options array - if (options.length > 0 && property.exclude_empty_option !== true) { - options.unshift(''); - } - - if (property.source === 'internal_stories') { - // Only if there is a filter_content_type, we can return a proper type - if (property.filter_content_type) { - if (typeof property.filter_content_type === 'string') { - return { - tsType: `(${getStoryType(property.filter_content_type, prefix)} | string )${property.type === 'options' ? '[]' : ''}`, - }; - } - - return { - tsType: `(${property.filter_content_type - .map(type2 => getStoryType(type2, prefix)) - // In this case property.type can be `option` or `options`. In case of `options` the type should be an array - .join(' | ')} | string )${property.type === 'options' ? '[]' : ''}`, - }; - } - } - - if ( - // If there is no `source` and there are options, the data source is the component itself - // TODO: check if this is an old behaviour (shouldn't this be handled as an "internal" source?) - (options.length > 0 && !property.source) - || property.source === 'internal_languages' - || property.source === 'external' - ) { - type = 'string'; - } - - if (property.source === 'internal') { - type = ['number', 'string']; - } - - if (property.type === 'option') { - if (options.length > 0) { - return { - type, - enum: options, - }; - } - - return { - type, - }; - } - - if (property.type === 'options') { - if (options.length > 0) { - return { - type: 'array', - items: { - enum: options, - }, - }; - } - - return { - type: 'array', - items: { type }, - }; - } - - switch (property.type) { - case 'bloks': - return { type: 'array' }; - case 'boolean': - return { type: 'boolean' }; - case 'datetime': - case 'image': - case 'markdown': - case 'number': - case 'text': - case 'textarea': - return { type: 'string' }; - default: - return { type: 'any' }; - } -}; - -export function getStoryType(property: string, prefix?: string) { - return `${STORY_TYPE}<${prefix ?? ''}${capitalize(toCamelCase(property))}>`; -} - -/** - * Generates a TypeScript type name for a component - * @param componentName - The name of the component - * @param options - Options for generating the type name - * @returns The generated type name - * - * The type name can be customized with the following options: - * - typePrefix: Prefix to be prepended to all generated component type names (can be set via --type-prefix flag) - */ -export const getComponentType = ( - componentName: string, - options: GenerateTypesOptions, -): string => { - const prefix = options.typePrefix ?? ''; - - // Sanitize the component name to handle special characters and emojis - const sanitizedName = componentName - // Replace any character that's not a letter or number with an underscore - .replace(/[^a-z0-9]/gi, '_') - // Replace multiple consecutive underscores with a single underscore - .replace(/_+/g, '_') - // Trim underscores from the beginning and end - .replace(/^_+|_+$/g, ''); - - // Convert to PascalCase - const componentType = toPascalCase(toCamelCase(`${prefix}_${sanitizedName}`)); - - // If the component type starts with a number, prefix it with an underscore - const isFirstCharacterNumber = !Number.isNaN(Number.parseInt(componentType.charAt(0))); - return isFirstCharacterNumber ? `_${componentType}` : componentType; -}; - -const getComponentPropertiesTypeAnnotations = async ( - component: SpaceComponent, - options: GenerateTypesOptions, - spaceData: SpaceData, - customFieldsParser?: (key: string, value: Record) => Record, -): Promise => { - return Object.entries>(component.schema).reduce(async (accPromise, [key, value]) => { - const acc = await accPromise; - - // Skip tabbed properties - if (key.startsWith('tab-')) { - return acc; - } - - const propertyType = value.type; - const propertyTypeAnnotation: JSONSchema = { - [key]: getPropertyTypeAnnotation(value as ComponentPropertySchema, options.typePrefix), - }; - - if (propertyType === 'custom' && customFieldsParser) { - const customField = typeof customFieldsParser === 'function' ? customFieldsParser(key, value) : {}; - return { - ...acc, - ...customField, - }; - } - - if (Array.from(storyblokSchemas.keys()).includes(propertyType as StoryblokPropertyType)) { - // For Storyblok property types, don't apply the prefix - const componentType = toPascalCase(toCamelCase(propertyType)); - propertyTypeAnnotation[key].tsType = `Storyblok${componentType}`; - } - - if (propertyType === 'multilink') { - const excludedLinktypes: string[] = [ - ...(!value.email_link_type ? ['{ linktype?: "email" }'] : []), - ...(!value.asset_link_type ? ['{ linktype?: "asset" }'] : []), - ]; - const componentType = toPascalCase(toCamelCase(propertyType)); - propertyTypeAnnotation[key].tsType - = excludedLinktypes.length > 0 ? `Exclude` : componentType; - } - - if (propertyType === 'bloks') { - if (value.restrict_components) { - // Components restricted by groups - if (value.restrict_type === 'groups') { - if ( - Array.isArray(value.component_group_whitelist) - && value.component_group_whitelist.length > 0 - ) { - // Find components that belong to the whitelisted groups - const componentsInGroupWhitelist = value.component_group_whitelist.reduce( - (components: string[], groupUUID: string) => { - // Find components that have this group UUID - const componentsInGroup = spaceData.components.filter( - component => component.component_group_uuid === groupUUID, - ); - - return componentsInGroup.length > 0 - ? [ - ...components, - ...componentsInGroup.map(component => getComponentType(component.name, options)), - ] - : components; - }, - [], - ); - - propertyTypeAnnotation[key].tsType - = componentsInGroupWhitelist.length > 0 ? `(${componentsInGroupWhitelist.join(' | ')})[]` : `never[]`; - } - } - else { - // Components restricted by 1-by-1 list - if (Array.isArray(value.component_whitelist) && value.component_whitelist.length > 0) { - propertyTypeAnnotation[key].tsType = `(${value.component_whitelist - .map((name: string) => getComponentType(name, options)) - .join(' | ')})[]`; - } - } - } - else { - // All components can be slotted in this property (AKA no restrictions) - // Add null check to ensure spaceData.components is defined - if (spaceData && Array.isArray(spaceData.components)) { - propertyTypeAnnotation[key].tsType = `(${spaceData.components - .map(component => getComponentType(component.name, options)) - .join(' | ')})[]`; - } - else { - // Fallback to never[] if components is undefined - propertyTypeAnnotation[key].tsType = `never[]`; - } - } - } - - return { ...acc, ...propertyTypeAnnotation }; - }, Promise.resolve({} as JSONSchema)); -}; - -const loadCustomFieldsParser = async (path: string): Promise<((key: string, value: Record) => Record) | undefined> => { - try { - const customFieldsParser = await import(resolve(path)); - return customFieldsParser.default; - } - catch (error) { - handleError(error as Error); - return undefined; - } -}; - -async function loadCompilerOptions(path: string) { - if (path) { - const compilerOptions = await import(resolve(path)); - return compilerOptions.default; - } - return {}; -} - -export const generateTypes = async ( - spaceData: SpaceData, - options: GenerateTypesOptions = { - strict: false, - }, -) => { - try { - const typeDefs = [...DEFAULT_TYPEDEFS_HEADER]; - const storyblokPropertyTypes = new Set(); - let customFieldsParser: ((key: string, value: Record) => Record) | undefined; - let compilerOptions: Record | undefined; - // Custom fields parser - if (options.customFieldsParser) { - customFieldsParser = await loadCustomFieldsParser(options.customFieldsParser); - } - - // Compiler options - if (options.compilerOptions) { - compilerOptions = await loadCompilerOptions(options.compilerOptions); - } - - const schemas = await Promise.all(spaceData.components.map(async (component) => { - // Get the component type name with proper handling of numbers at the start - const type = getComponentType(component.name, options); - const componentPropertiesTypeAnnotations = await getComponentPropertiesTypeAnnotations(component, options, spaceData, customFieldsParser); - const requiredFields = Object.entries>(component?.schema || {}).reduce( - (acc, [key, value]) => { - if (value.required) { - return [...acc, key]; - } - return acc; - }, - ['component', '_uid'], - ); - - // Check if any property has a type that's in storyblokSchemas.keys() - if (componentPropertiesTypeAnnotations) { - Object.entries(componentPropertiesTypeAnnotations).forEach(([_, property]) => { - if (property.type && Array.from(storyblokSchemas.keys()).includes(property.type as StoryblokPropertyType)) { - storyblokPropertyTypes.add(property.type as StoryblokPropertyType); - } - // Check if the property uses ISbStoryData - if (property.tsType && property.tsType.includes(STORY_TYPE)) { - storyblokPropertyTypes.add(STORY_TYPE); - } - }); - } - - const componentSchema: JSONSchema = { - $id: `#/${component.name}`, - title: type, // This is the key - we're using the properly formatted type name - type: 'object', - required: requiredFields, - properties: { - ...componentPropertiesTypeAnnotations, - component: { - type: 'string', - enum: [component.name], - }, - _uid: { - type: 'string', - }, - }, - }; - - return componentSchema; - })); - - const result = await Promise.all(schemas.map(async (schema) => { - // Use the title as the interface name - return await compile(schema, schema.title || schema.$id.replace('#/', ''), { - additionalProperties: !options.strict, - bannerComment: '', - ...compilerOptions, - }); - })); - - // Add imports for Storyblok types if needed - const imports: string[] = []; - - // Check if ISbStoryData is needed - const needsISbStoryData = storyblokPropertyTypes.has(STORY_TYPE); - if (needsISbStoryData) { - imports.push(`import type { ${STORY_TYPE} } from '@storyblok/js';`); - storyblokPropertyTypes.delete(STORY_TYPE); // Remove it so it's not included in the next import - } - - if (storyblokPropertyTypes.size > 0) { - const typeImports = Array.from(storyblokPropertyTypes).map((type) => { - const pascalType = toPascalCase(type); - return `Storyblok${pascalType}`; - }); - - imports.push(`import type { ${typeImports.join(', ')} } from '../storyblok.d.ts';`); - } - - const finalTypeDef = [...typeDefs, ...imports, ...result]; - - return [ - ...finalTypeDef, - ].join('\n'); - } - catch (error) { - handleError(error as Error); - } -}; - -export const saveTypesToFile = async (space: string, typedefString: string, options: SaveTypesOptions) => { - const { filename = 'storyblok-components', path } = options; - // Ensure we always include the components/space folder structure regardless of custom path - const resolvedPath = path - ? resolve(process.cwd(), path, 'types', space) - : resolvePath(path, `types/${space}`); - - try { - await saveToFile(join(resolvedPath, `${filename}.d.ts`), typedefString); - } - catch (error) { - handleFileSystemError('write', error as Error); - } -}; - -// Add SaveTypesOptions interface -export interface SaveTypesOptions { - filename?: string; - path?: string; -} - -/** - * Generates a d.ts file with the Storyblok type definitions - * @param options - Options for generating the types - * @returns Promise that resolves when the file is saved - */ -export const generateStoryblokTypes = async (options: SaveTypesOptions = {}) => { - const { filename = 'storyblok', path } = options; - - try { - // Get the path to the storyblok.ts file - - const storyblokTypesPath = resolve(__dirname, './index.d.ts'); - // Read the content of the storyblok.ts file - const storyblokTypesContent = readFileSync(storyblokTypesPath, 'utf-8'); - - // Define the content of the d.ts file - const typeDefs = [ - '// This file was generated by the Storyblok CLI.', - '// DO NOT MODIFY THIS FILE BY HAND.', - `import type { ${STORY_TYPE} } from '@storyblok/js';`, - storyblokTypesContent, - ].join('\n'); - - // Determine the path to save the file - const resolvedPath = path - ? resolve(process.cwd(), path, 'types') - : resolvePath(path, 'types'); - - await saveToFile(join(resolvedPath, `${filename}.d.ts`), typeDefs); - return true; - } - catch (error) { - handleFileSystemError('read', error as Error); - return false; - } -}; diff --git a/src/commands/types/generate/constants.ts b/src/commands/types/generate/constants.ts deleted file mode 100644 index 000030d9..00000000 --- a/src/commands/types/generate/constants.ts +++ /dev/null @@ -1,10 +0,0 @@ -export interface GenerateTypesOptions { - separateFiles?: boolean; - strict?: boolean; - typePrefix?: string; - filename?: string; - path?: string; - suffix?: string; - customFieldsParser?: string; - compilerOptions?: string; -} diff --git a/src/commands/types/generate/index.test.ts b/src/commands/types/generate/index.test.ts deleted file mode 100644 index d5eac74a..00000000 --- a/src/commands/types/generate/index.test.ts +++ /dev/null @@ -1,354 +0,0 @@ -import { session } from '../../../session'; -import { konsola } from '../../../utils'; -import { generateStoryblokTypes, generateTypes } from './actions'; -import chalk from 'chalk'; -import { colorPalette } from '../../../constants'; -// Import the main components module first to ensure proper initialization -import '../index'; -import { typesCommand } from '../command'; -import { readComponentsFiles } from '../../components/push/actions'; - -vi.mock('./actions', () => ({ - generateStoryblokTypes: vi.fn(), - generateTypes: vi.fn(), - getComponentType: vi.fn(), -})); - -vi.mock('../../components/push/actions', () => ({ - readComponentsFiles: vi.fn(), -})); - -// Mocking the session module -vi.mock('../../../session', () => { - let _cache: Record | null = null; - const session = () => { - if (!_cache) { - _cache = { - state: { - isLoggedIn: false, - }, - updateSession: vi.fn(), - persistCredentials: vi.fn(), - initializeSession: vi.fn(), - }; - } - return _cache; - }; - - return { - session, - }; -}); - -vi.mock('../../../utils', async () => { - const actualUtils = await vi.importActual('../../../utils'); - return { - ...actualUtils, - isVitestRunning: true, - konsola: { - ok: vi.fn(), - title: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - br: vi.fn(), - }, - handleError: (error: unknown, header = false) => { - konsola.error(error as string, header); - // Optionally, prevent process.exit during tests - }, - }; -}); - -describe('types generate', () => { - beforeEach(() => { - vi.resetAllMocks(); - vi.clearAllMocks(); - // Fix the linter errors by using a type assertion - (typesCommand as any)._optionValues = {}; - (typesCommand as any)._optionValueSources = {}; - for (const command of typesCommand.commands) { - (command as any)._optionValues = {}; - (command as any)._optionValueSources = {}; - } - }); - - describe('default mode', () => { - it('should prompt the user if the operation was sucessfull', async () => { - const mockResponse = [{ - name: 'component-name', - display_name: 'Component Name', - created_at: '2021-08-09T12:00:00Z', - updated_at: '2021-08-09T12:00:00Z', - id: 12345, - schema: { type: 'object' }, - color: null, - internal_tags_list: [], - internal_tag_ids: [], - }]; - - const mockSpaceData = { - components: mockResponse, - groups: [], - presets: [], - internalTags: [], - }; - - session().state = { - isLoggedIn: true, - password: 'valid-token', - region: 'eu', - }; - - vi.mocked(readComponentsFiles).mockResolvedValue(mockSpaceData); - - vi.mocked(generateStoryblokTypes).mockResolvedValue(true); - - await typesCommand.parseAsync(['node', 'test', 'generate', '--space', '12345']); - - expect(generateStoryblokTypes).toHaveBeenCalledWith({ - filename: undefined, - path: undefined, - }); - - expect(generateTypes).toHaveBeenCalledWith(mockSpaceData, { - - }); - - expect(konsola.ok).toHaveBeenCalledWith(`Successfully generated types for space ${chalk.hex(colorPalette.PRIMARY)('12345')}`, true); - }); - - it('should pass strict mode option to generateTypes when --strict flag is used', async () => { - const mockResponse = [{ - name: 'component-name', - display_name: 'Component Name', - created_at: '2021-08-09T12:00:00Z', - updated_at: '2021-08-09T12:00:00Z', - id: 12345, - schema: { type: 'object' }, - color: null, - internal_tags_list: [], - internal_tag_ids: [], - }]; - - const mockSpaceData = { - components: mockResponse, - groups: [], - presets: [], - internalTags: [], - }; - - session().state = { - isLoggedIn: true, - password: 'valid-token', - region: 'eu', - }; - - vi.mocked(readComponentsFiles).mockResolvedValue(mockSpaceData); - vi.mocked(generateStoryblokTypes).mockResolvedValue(true); - vi.mocked(generateTypes).mockResolvedValue('// Generated types'); - - // Run the command with the --strict flag - await typesCommand.parseAsync(['node', 'test', 'generate', '--space', '12345', '--strict']); - - // Verify that generateTypes was called with the strict option set to true - expect(generateTypes).toHaveBeenCalledWith(mockSpaceData, { - strict: true, - }); - }); - - it('should pass typePrefix option to generateTypes when --type-prefix flag is used', async () => { - const mockResponse = [{ - name: 'component-name', - display_name: 'Component Name', - created_at: '2021-08-09T12:00:00Z', - updated_at: '2021-08-09T12:00:00Z', - id: 12345, - schema: { type: 'object' }, - color: null, - internal_tags_list: [], - internal_tag_ids: [], - }]; - - const mockSpaceData = { - components: mockResponse, - groups: [], - presets: [], - internalTags: [], - }; - - session().state = { - isLoggedIn: true, - password: 'valid-token', - region: 'eu', - }; - - vi.mocked(readComponentsFiles).mockResolvedValue(mockSpaceData); - vi.mocked(generateStoryblokTypes).mockResolvedValue(true); - vi.mocked(generateTypes).mockResolvedValue('// Generated types'); - - // Run the command with the --type-prefix flag - await typesCommand.parseAsync(['node', 'test', 'generate', '--space', '12345', '--type-prefix', 'Custom']); - - // Verify that generateTypes was called with the typePrefix option set to 'Custom' - expect(generateTypes).toHaveBeenCalledWith(mockSpaceData, { - typePrefix: 'Custom', - }); - }); - - it('should pass suffix option to generateTypes when --suffix flag is used', async () => { - const mockResponse = [{ - name: 'component-name', - display_name: 'Component Name', - created_at: '2021-08-09T12:00:00Z', - updated_at: '2021-08-09T12:00:00Z', - id: 12345, - schema: { type: 'object' }, - color: null, - internal_tags_list: [], - internal_tag_ids: [], - }]; - - const mockSpaceData = { - components: mockResponse, - groups: [], - presets: [], - internalTags: [], - }; - - session().state = { - isLoggedIn: true, - password: 'valid-token', - region: 'eu', - }; - - vi.mocked(readComponentsFiles).mockResolvedValue(mockSpaceData); - vi.mocked(generateStoryblokTypes).mockResolvedValue(true); - vi.mocked(generateTypes).mockResolvedValue('// Generated types'); - - // Run the command with the --suffix flag - await typesCommand.parseAsync(['node', 'test', 'generate', '--space', '12345', '--suffix', 'Component']); - - // Verify that generateTypes was called with the suffix option set to 'Component' - expect(generateTypes).toHaveBeenCalledWith(mockSpaceData, { - suffix: 'Component', - }); - }); - - it('should pass separateFiles option to generateTypes when --separate-files flag is used', async () => { - const mockResponse = [{ - name: 'component-name', - display_name: 'Component Name', - created_at: '2021-08-09T12:00:00Z', - updated_at: '2021-08-09T12:00:00Z', - id: 12345, - schema: { type: 'object' }, - color: null, - internal_tags_list: [], - internal_tag_ids: [], - }]; - - const mockSpaceData = { - components: mockResponse, - groups: [], - presets: [], - internalTags: [], - }; - - session().state = { - isLoggedIn: true, - password: 'valid-token', - region: 'eu', - }; - - vi.mocked(readComponentsFiles).mockResolvedValue(mockSpaceData); - vi.mocked(generateStoryblokTypes).mockResolvedValue(true); - vi.mocked(generateTypes).mockResolvedValue('// Generated types'); - - // Run the command with the --separate-files flag - await typesCommand.parseAsync(['node', 'test', 'generate', '--space', '12345', '--separate-files']); - - // Verify that generateTypes was called with the separateFiles option set to true - expect(generateTypes).toHaveBeenCalledWith(mockSpaceData, { - separateFiles: true, - }); - }); - - it('should pass customFieldsParser option to generateTypes when --custom-fields-parser flag is used', async () => { - const mockResponse = [{ - name: 'component-name', - display_name: 'Component Name', - created_at: '2021-08-09T12:00:00Z', - updated_at: '2021-08-09T12:00:00Z', - id: 12345, - schema: { type: 'object' }, - color: null, - internal_tags_list: [], - internal_tag_ids: [], - }]; - - const mockSpaceData = { - components: mockResponse, - groups: [], - presets: [], - internalTags: [], - }; - - session().state = { - isLoggedIn: true, - password: 'valid-token', - region: 'eu', - }; - - vi.mocked(readComponentsFiles).mockResolvedValue(mockSpaceData); - vi.mocked(generateStoryblokTypes).mockResolvedValue(true); - vi.mocked(generateTypes).mockResolvedValue('// Generated types'); - - // Run the command with the --custom-fields-parser flag - await typesCommand.parseAsync(['node', 'test', 'generate', '--space', '12345', '--custom-fields-parser', '/path/to/parser.ts']); - - // Verify that generateTypes was called with the customFieldsParser option set to '/path/to/parser.ts' - expect(generateTypes).toHaveBeenCalledWith(mockSpaceData, { - customFieldsParser: '/path/to/parser.ts', - }); - }); - - it('should pass compilerOptions option to generateTypes when --compiler-options flag is used', async () => { - const mockResponse = [{ - name: 'component-name', - display_name: 'Component Name', - created_at: '2021-08-09T12:00:00Z', - updated_at: '2021-08-09T12:00:00Z', - id: 12345, - schema: { type: 'object' }, - color: null, - internal_tags_list: [], - internal_tag_ids: [], - }]; - - const mockSpaceData = { - components: mockResponse, - groups: [], - presets: [], - internalTags: [], - }; - - session().state = { - isLoggedIn: true, - password: 'valid-token', - region: 'eu', - }; - - vi.mocked(readComponentsFiles).mockResolvedValue(mockSpaceData); - vi.mocked(generateStoryblokTypes).mockResolvedValue(true); - vi.mocked(generateTypes).mockResolvedValue('// Generated types'); - - // Run the command with the --compiler-options flag - await typesCommand.parseAsync(['node', 'test', 'generate', '--space', '12345', '--compiler-options', '/path/to/options.json']); - - // Verify that generateTypes was called with the compilerOptions option set to '/path/to/options.json' - expect(generateTypes).toHaveBeenCalledWith(mockSpaceData, { - compilerOptions: '/path/to/options.json', - }); - }); - }); -}); diff --git a/src/commands/types/generate/index.ts b/src/commands/types/generate/index.ts deleted file mode 100644 index daf1a601..00000000 --- a/src/commands/types/generate/index.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { colorPalette, commands } from '../../../constants'; -import { CommandError, handleError, isVitest, konsola, requireAuthentication } from '../../../utils'; -import { getProgram } from '../../../program'; -import { session } from '../../../session'; -import { Spinner } from '@topcli/spinner'; -import { readComponentsFiles } from '../../components/push/actions'; -import type { GenerateTypesOptions } from './constants'; -import type { ReadComponentsOptions } from '../../components/push/constants'; -import { typesCommand } from '../command'; -import { generateStoryblokTypes, generateTypes, saveTypesToFile } from './actions'; - -const program = getProgram(); - -typesCommand - .command('generate') - .description('Generate types d.ts for your component schemas') - .option('--sf, --separate-files', '') - .option('--strict', 'strict mode, no loose typing') - .option('--type-prefix ', 'prefix to be prepended to all generated component type names') - .option('--suffix ', 'Components suffix') - .option('--custom-fields-parser ', 'Path to the parser file for Custom Field Types') - .option('--compiler-options ', 'path to the compiler options from json-schema-to-typescript') - .action(async (options: GenerateTypesOptions) => { - konsola.title(` ${commands.TYPES} `, colorPalette.TYPES, 'Generating types...'); - // Global options - const verbose = program.opts().verbose; - - // Command options - const { space, path } = typesCommand.opts(); - - const { state, initializeSession } = session(); - await initializeSession(); - - if (!requireAuthentication(state, verbose)) { - return; - } - if (!space) { - handleError(new CommandError(`Please provide the space as argument --space YOUR_SPACE_ID.`), verbose); - return; - } - - const spinner = new Spinner({ - verbose: !isVitest, - }); - - try { - spinner.start(`Generating types...`); - const spaceData = await readComponentsFiles({ - ...options as ReadComponentsOptions, - from: space, - path, - }); - - await generateStoryblokTypes({ - filename: options.filename, - path: options.path, - }); - - const typedefString = await generateTypes(spaceData, options); - - if (typedefString) { - await saveTypesToFile(space, typedefString, options); - } - - spinner.succeed(); - konsola.ok(`Successfully generated types for space ${space}`, true); - konsola.br(); - } - catch (error) { - spinner.failed(`Failed to generate types for space ${space}`); - konsola.br(); - handleError(error as Error, verbose); - } - }); diff --git a/src/commands/types/index.ts b/src/commands/types/index.ts deleted file mode 100644 index 80ba50d2..00000000 --- a/src/commands/types/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import './command'; -import './generate'; - -/* export * from './generate/actions'; -export * from './generate/constants'; */ diff --git a/src/commands/user/README.md b/src/commands/user/README.md deleted file mode 100644 index ef2edae5..00000000 --- a/src/commands/user/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# User Command - -This command allows you to view information about your currently logged-in Storyblok account. - -## Basic Usage - -```bash -storyblok user -``` - -This command will display: -- Your friendly name -- Your email address -- The region you're logged into - -## What the Command Does - -1. Checks if you're logged in -2. Fetches your user information from Storyblok -3. Displays your account details in a formatted way - -## Examples - -1. View your user information: -```bash -storyblok user -``` - -Output example: -``` -Hi John Doe, you are currently logged in with john@example.com on eu region -``` - -## Notes - -- The command uses your stored authentication token diff --git a/src/commands/user/actions.test.ts b/src/commands/user/actions.test.ts deleted file mode 100644 index 81d5da85..00000000 --- a/src/commands/user/actions.test.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { getUser } from './actions'; -import { http, HttpResponse } from 'msw'; -import { setupServer } from 'msw/node'; -import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; -import { APIError } from '../../utils'; -import { FetchError } from '../../utils/fetch'; - -const handlers = [ - http.get('https://api.storyblok.com/v1/users/me', async ({ request }) => { - const token = request.headers.get('Authorization'); - if (token === 'valid-token') { - return HttpResponse.json({ data: 'user data' }); - } - return new HttpResponse('Unauthorized', { status: 401 }); - }), -]; - -const server = setupServer(...handlers); - -beforeAll(() => server.listen({ onUnhandledRequest: 'error' })); - -afterEach(() => server.resetHandlers()); -afterAll(() => server.close()); - -describe('user actions', () => { - beforeEach(() => { - vi.clearAllMocks(); - }); - - describe('getUser', () => { - it('should get user successfully with a valid token', async () => { - const mockResponse = { data: 'user data', perPage: 0, total: 0 }; - const result = await getUser('valid-token', 'eu'); - expect(result).toEqual(mockResponse); - }); - }); - - it('should throw an masked error for invalid token', async () => { - const error = new FetchError('Non-JSON response', { - status: 401, - statusText: 'Unauthorized', - data: null, - }); - await expect(getUser('invalid-token', 'eu')).rejects.toThrow( - new APIError('unauthorized', 'get_user', error, `The token provided inva********* is invalid. - Please make sure you are using the correct token and try again.`), - ); - }); - - it('should throw a network error if response is empty (network)', async () => { - server.use( - http.get('https://api.storyblok.com/v1/users/me', () => { - return new HttpResponse(null, { status: 500 }); - }), - ); - await expect(getUser('any-token', 'eu')).rejects.toThrow( - 'No response from server, please check if you are correctly connected to internet', - ); - }); -}); diff --git a/src/commands/user/actions.ts b/src/commands/user/actions.ts deleted file mode 100644 index ae50e711..00000000 --- a/src/commands/user/actions.ts +++ /dev/null @@ -1,34 +0,0 @@ -import chalk from 'chalk'; -import type { RegionCode } from '../../constants'; -import { customFetch, FetchError } from '../../utils/fetch'; -import { APIError, maskToken } from '../../utils'; -import { getStoryblokUrl } from '../../utils/api-routes'; -import type { StoryblokUser } from '../../types'; - -export const getUser = async (token: string, region: RegionCode) => { - try { - const url = getStoryblokUrl(region); - const response = await customFetch<{ - user: StoryblokUser; - }>(`${url}/users/me`, { - headers: { - Authorization: token, - }, - }); - return response; - } - catch (error) { - if (error instanceof FetchError) { - const status = error.response.status; - - switch (status) { - case 401: - throw new APIError('unauthorized', 'get_user', error, `The token provided ${chalk.bold(maskToken(token))} is invalid. - Please make sure you are using the correct token and try again.`); - default: - throw new APIError('network_error', 'get_user', error); - } - } - throw new APIError('generic', 'get_user', error as FetchError); - } -}; diff --git a/src/commands/user/index.test.ts b/src/commands/user/index.test.ts deleted file mode 100644 index d8d6c221..00000000 --- a/src/commands/user/index.test.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { userCommand } from './'; -import { getUser } from './actions'; -import { konsola } from '../../utils'; -import { session } from '../../session'; -import chalk from 'chalk'; - -vi.mock('./actions', () => ({ - getUser: vi.fn(), -})); - -vi.mock('../../creds', () => ({ - isAuthorized: vi.fn(), -})); - -// Mocking the session module -vi.mock('../../session', () => { - let _cache; - const session = () => { - if (!_cache) { - _cache = { - state: { - isLoggedIn: false, - }, - updateSession: vi.fn(), - persistCredentials: vi.fn(), - initializeSession: vi.fn(), - }; - } - return _cache; - }; - - return { - session, - }; -}); - -vi.mock('../../utils/konsola'); - -describe('userCommand', () => { - beforeEach(() => { - vi.resetAllMocks(); - vi.clearAllMocks(); - }); - - it('should show the user information', async () => { - const mockResponse = { - user: { - friendly_name: 'John Doe', - email: 'john.doe@storyblok.com', - }, - }; - session().state = { - isLoggedIn: true, - password: 'valid-token', - region: 'eu', - }; - vi.mocked(getUser).mockResolvedValue(mockResponse); - await userCommand.parseAsync(['node', 'test']); - - expect(getUser).toHaveBeenCalledWith('valid-token', 'eu'); - expect(konsola.ok).toHaveBeenCalledWith( - `Hi ${chalk.bold('John Doe')}, you are currently logged in with ${chalk.hex('#45bfb9')(mockResponse.user.email)} on ${chalk.bold('eu')} region`, - true, - ); - }); - - it('should show an error if the user is not logged in', async () => { - session().state = { - isLoggedIn: false, - }; - await userCommand.parseAsync(['node', 'test']); - - expect(konsola.error).toHaveBeenCalledWith('You are currently not logged in. Please run storyblok login to authenticate, or storyblok signup to sign up.', null, { - header: true, - }); - }); - - it('should show an error if the user information cannot be fetched', async () => { - session().state = { - isLoggedIn: true, - password: 'valid-token', - region: 'eu', - }; - - const mockError = new Error('Network error'); - - vi.mocked(getUser).mockRejectedValue(mockError); - - await userCommand.parseAsync(['node', 'test']); - - expect(konsola.error).toHaveBeenCalledWith(mockError.message, null, { - header: true, - }); - }); -}); diff --git a/src/commands/user/index.ts b/src/commands/user/index.ts deleted file mode 100644 index 58082ca0..00000000 --- a/src/commands/user/index.ts +++ /dev/null @@ -1,39 +0,0 @@ -import chalk from 'chalk'; -import { colorPalette, commands } from '../../constants'; -import { getProgram } from '../../program'; -import { handleError, isVitest, konsola, requireAuthentication } from '../../utils'; -import { getUser } from './actions'; -import { session } from '../../session'; -import { Spinner } from '@topcli/spinner'; - -const program = getProgram(); // Get the shared singleton instance - -export const userCommand = program - .command(commands.USER) - .description('Get the current user') - .action(async () => { - konsola.title(` ${commands.USER} `, colorPalette.USER); - const { state, initializeSession } = session(); - await initializeSession(); - - if (!requireAuthentication(state)) { - return; - } - const spinner = new Spinner({ - verbose: !isVitest, - }).start(`Fetching user info`); - try { - const { password, region } = state; - if (!password || !region) { - throw new Error('No password or region found'); - } - const { user } = await getUser(password, region); - spinner.succeed(); - konsola.ok(`Hi ${chalk.bold(user.friendly_name)}, you are currently logged in with ${chalk.hex(colorPalette.PRIMARY)(user.email)} on ${chalk.bold(region)} region`, true); - } - catch (error) { - spinner.failed(); - handleError(error as Error, true); - } - konsola.br(); - }); diff --git a/src/constants.js b/src/constants.js new file mode 100644 index 00000000..e6db717f --- /dev/null +++ b/src/constants.js @@ -0,0 +1,30 @@ +export const SYNC_TYPES = [ + 'folders', + 'components', + 'roles', + 'stories', + 'datasources' +] + +export const COMMANDS = { + GENERATE_MIGRATION: 'generate-migration', + IMPORT: 'import', + LOGIN: 'login', + LOGOUT: 'logout', + PULL_COMPONENTS: 'pull-components', + PUSH_COMPONENTS: 'push-components', + QUICKSTART: 'quickstart', + ROLLBACK_MIGRATION: 'rollback-migration', + RUN_MIGRATION: 'run-migration', + SCAFFOLD: 'scaffold', + SELECT: 'select', + SPACES: 'spaces', + SYNC: 'sync', + DELETE_DATASOURCES: 'delete-datasources', + GENERATE_TYPESCRIPT_TYPEDEFS: 'generate-typescript-typedefs' +} + +export const DEFAULT_AGENT = { + SB_Agent: 'SB-CLI', + SB_Agent_Version: process.env.npm_package_version || '3.0.0' +} diff --git a/src/constants.ts b/src/constants.ts deleted file mode 100644 index 1d567873..00000000 --- a/src/constants.ts +++ /dev/null @@ -1,74 +0,0 @@ -export const commands = { - LOGIN: 'login', - LOGOUT: 'logout', - SIGNUP: 'signup', - USER: 'user', - COMPONENTS: 'Components', - LANGUAGES: 'languages', - MIGRATIONS: 'Migrations', - TYPES: 'Types', -} as const; - -export const colorPalette = { - PRIMARY: '#8d60ff', - LOGIN: '#dad4ff', - LOGOUT: '#6d6d6d', - SIGNUP: '#b6ff6d', - USER: '#71d300', - COMPONENTS: '#a185ff', - LANGUAGES: '#f5c003', - MIGRATIONS: '#8CE2FF', - TYPES: '#3178C6', - GROUPS: '#4ade80', - TAGS: '#fbbf24', - PRESETS: '#a855f7', -} as const; - -export interface ReadonlyArray { - includes: (searchElement: any, fromIndex?: number) => searchElement is T; -} -export const regionCodes = ['eu', 'us', 'cn', 'ca', 'ap'] as const; -export type RegionCode = typeof regionCodes[number]; - -export const regions: Record, RegionCode> = { - EU: 'eu', - US: 'us', - CN: 'cn', - CA: 'ca', - AP: 'ap', -} as const; - -export const regionsDomain: Record = { - eu: 'api.storyblok.com', - us: 'api-us.storyblok.com', - cn: 'app.storyblokchina.cn', - ca: 'api-ca.storyblok.com', - ap: 'api-ap.storyblok.com', -} as const; - -export const managementApiRegions: Record = { - eu: 'mapi.storyblok.com', - us: 'mapi-us.storyblok.com', - cn: 'mapi.storyblokchina.cn', - ca: 'mapi-ca.storyblok.com', - ap: 'mapi-ap.storyblok.com', -} as const; - -export const regionNames: Record = { - eu: 'Europe', - us: 'United States', - cn: 'China', - ca: 'Canada', - ap: 'Australia', -} as const; - -export const DEFAULT_AGENT = { - SB_Agent: 'SB-CLI', - SB_Agent_Version: process.env.npm_package_version || '4.x', -} as const; - -export interface SpaceOptions { - spaceId: string; - token: string; - region: RegionCode; -} diff --git a/src/creds.test.ts b/src/creds.test.ts deleted file mode 100644 index 56dd3565..00000000 --- a/src/creds.test.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { addCredentials, getCredentials, removeAllCredentials } from './creds'; -import { vol } from 'memfs'; -import type { StoryblokCredentials } from './types'; -// tell vitest to use fs mock from __mocks__ folder -// this can be done in a setup file if fs should always be mocked -vi.mock('node:fs'); -vi.mock('node:fs/promises'); - -beforeEach(() => { - // reset the state of in-memory fs - vol.reset(); -}); - -describe('creds', async () => { - describe('getCredentials', () => { - it('should return the parsed content of credentials.json file', async () => { - vol.fromJSON({ - 'test/credentials.json': JSON.stringify({ - 'api.storyblok.com': { - login: 'julio.iglesias@storyblok.com', - password: 'my_access_token', - region: 'eu', - }, - }), - }, '/temp'); - - const credentials = await getCredentials('/temp/test/credentials.json') as StoryblokCredentials; - - expect(credentials['api.storyblok.com']).toEqual({ - login: 'julio.iglesias@storyblok.com', - password: 'my_access_token', - region: 'eu', - }); - }); - it('should create a credentials.json file if it does not exist', async () => { - const credentials = await getCredentials('/temp/test/nonexistent.json'); - expect(credentials).toEqual(null); - }); - }); - - describe('addCredentials', () => { - it('should add a new entry to an empty credentials file', async () => { - vol.fromJSON({ - 'test/credentials.json': '{}', - }, '/temp'); - - await addCredentials({ - filePath: '/temp/test/credentials.json', - machineName: 'api.storyblok.com', - login: 'julio.iglesias@storyblok.com', - password: 'my_access_token', - region: 'eu', - }); - - const content = vol.readFileSync('/temp/test/credentials.json', 'utf8'); - expect(content).toBe('{\n "api.storyblok.com": {\n "login": "julio.iglesias@storyblok.com",\n "password": "my_access_token",\n "region": "eu"\n }\n}'); - }); - }); - - describe('removeAllCredentials', () => { - it('should remove an entry from credentials file', async () => { - vol.fromJSON({ - 'test/credentials.json': JSON.stringify({ - 'api.storyblok.com': { - login: 'julio.iglesias@storyblok.com', - password: 'my_access_token', - region: 'eu', - }, - }), - }, '/temp'); - - await removeAllCredentials('/temp/test'); - - const content = vol.readFileSync('/temp/test/credentials.json', 'utf8'); - expect(content).toBe('{}'); - }); - }); -}); diff --git a/src/creds.ts b/src/creds.ts deleted file mode 100644 index d42e46c3..00000000 --- a/src/creds.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { access } from 'node:fs/promises'; -import { join } from 'node:path'; -import { FileSystemError, handleFileSystemError } from './utils'; -import { getStoryblokGlobalPath, readFile, saveToFile } from './utils/filesystem'; -import type { StoryblokCredentials } from './types'; - -export const getCredentials = async (filePath = join(getStoryblokGlobalPath(), 'credentials.json')): Promise => { - try { - await access(filePath); - const content = await readFile(filePath); - const parsedContent = JSON.parse(content); - - // Return null if the parsed content is an empty object - if (Object.keys(parsedContent).length === 0) { - return null; - } - - return parsedContent; - } - catch (error) { - if ((error as NodeJS.ErrnoException).code === 'ENOENT') { - // File doesn't exist, create it with empty credentials - await saveToFile(filePath, JSON.stringify({}, null, 2), { mode: 0o600 }); - return null; - } - handleFileSystemError('read', error as NodeJS.ErrnoException); - return null; - } -}; - -export const addCredentials = async ({ - filePath = join(getStoryblokGlobalPath(), 'credentials.json'), - machineName, - login, - password, - region, -}: Record) => { - const credentials = { - ...await getCredentials(filePath), - [machineName]: { - login, - password, - region, - }, - }; - - try { - await saveToFile(filePath, JSON.stringify(credentials, null, 2), { mode: 0o600 }); - } - catch (error) { - throw new FileSystemError('invalid_argument', 'write', error as NodeJS.ErrnoException, `Error adding/updating entry for machine ${machineName} in credentials.json file`); - } -}; - -export const removeAllCredentials = async (filepath: string = getStoryblokGlobalPath()) => { - const filePath = join(filepath, 'credentials.json'); - await saveToFile(filePath, JSON.stringify({}, null, 2), { mode: 0o600 }); -}; diff --git a/src/index.ts b/src/index.ts deleted file mode 100644 index 8cde6e33..00000000 --- a/src/index.ts +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env node -import dotenv from 'dotenv'; - -import { handleError, konsola } from './utils'; -import { getProgram } from './program'; -import './commands/login'; -import './commands/logout'; -import './commands/signup'; -import './commands/user'; -import './commands/components'; -import './commands/languages'; -import './commands/migrations'; -import './commands/types'; -import pkg from '../package.json'; - -import { colorPalette } from './constants'; - -export * from './types/storyblok'; - -dotenv.config(); // This will load variables from .env into process.env -const program = getProgram(); - -konsola.br(); -konsola.br(); -konsola.title(` Storyblok CLI `, colorPalette.PRIMARY); - -program.option('--verbose', 'Enable verbose output'); -program.version(pkg.version, '-v, --vers', 'Output the current version'); -program.helpOption('-h, --help', 'Display help for command'); - -program.on('command:*', () => { - console.error(`Invalid command: ${program.args.join(' ')}`); - konsola.br(); - program.help(); -}); - -try { - program.parse(process.argv); -} -catch (error) { - handleError(error as Error); -} diff --git a/src/program.test.ts b/src/program.test.ts deleted file mode 100644 index 742933a6..00000000 --- a/src/program.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -// program.test.ts -import { beforeAll, describe, expect, it } from 'vitest'; - -// Import the function after setting up mocks -import { getProgram } from './program'; // Import resolve to mock -import type { Command } from 'commander'; - -let program: Command; -describe('program', () => { - beforeAll(() => { - program = getProgram(); - }); - it('should be defined', () => { - // Reset the program instance and mock return values before each test - expect(program).toBeDefined(); - }); - - it('should have the same name as package.json', () => { - expect(program.name()).toBe('storyblok'); - }); - - it('should have the same description as package.json', () => { - expect(program.description()).toBe('Storyblok CLI'); - }); - - // TODO: - // Add test for handlingError L30-32 -}); diff --git a/src/program.ts b/src/program.ts deleted file mode 100644 index 71d4260f..00000000 --- a/src/program.ts +++ /dev/null @@ -1,49 +0,0 @@ -// program.ts -import { Command } from 'commander'; -import { __dirname, handleError } from './utils'; -import type { NormalizedPackageJson } from 'read-package-up'; -import { readPackageUp } from 'read-package-up'; - -let packageJson: NormalizedPackageJson; -// Read package.json for metadata -const result = await readPackageUp({ - cwd: __dirname, -}); - -if (!result) { - console.debug('Metadata not found'); - packageJson = { - name: 'storyblok', - description: 'Storyblok CLI', - version: '0.0.0', - } as NormalizedPackageJson; -} -else { - packageJson = result.packageJson; -} - -// Declare a variable to hold the singleton instance -let programInstance: Command | null = null; - -// Singleton function to get the instance of program -/** - * Get the shared program singleton instance - * - * @export getProgram - * @return {*} {Command} - */ -export function getProgram(): Command { - if (!programInstance) { - programInstance = new Command(); - programInstance - .name(packageJson.name) - .description(packageJson.description || '') - .version(packageJson.version); - - // Global error handling - programInstance.configureOutput({ - writeErr: str => handleError(new Error(str)), - }); - } - return programInstance; -} diff --git a/src/session.test.ts b/src/session.test.ts deleted file mode 100644 index 29846ee3..00000000 --- a/src/session.test.ts +++ /dev/null @@ -1,72 +0,0 @@ -// session.test.ts -import { session } from './session'; - -import { getCredentials } from './creds'; -import type { Mock } from 'vitest'; - -vi.mock('./creds', () => ({ - getCredentials: vi.fn(), -})); - -const mockedGetCredentials = getCredentials as Mock; - -describe('session', () => { - beforeEach(() => { - vi.resetAllMocks(); - vi.clearAllMocks(); - }); - describe('session initialization with json', () => { - it('should initialize session with json credentials', async () => { - mockedGetCredentials.mockReturnValue({ - 'api.storyblok.com': { - login: 'test_login', - password: 'test_token', - region: 'test_region', - }, - }); - const userSession = session(); - await userSession.initializeSession(); - expect(userSession.state.isLoggedIn).toBe(true); - expect(userSession.state.login).toBe('test_login'); - expect(userSession.state.password).toBe('test_token'); - expect(userSession.state.region).toBe('test_region'); - }); - }); - describe('session initialization with environment variables', () => { - beforeEach(() => { - // Clear environment variables before each test - delete process.env.STORYBLOK_LOGIN; - delete process.env.STORYBLOK_TOKEN; - delete process.env.STORYBLOK_REGION; - delete process.env.TRAVIS_STORYBLOK_LOGIN; - delete process.env.TRAVIS_STORYBLOK_TOKEN; - delete process.env.TRAVIS_STORYBLOK_REGION; - }); - - it('should initialize session from STORYBLOK_ environment variables', async () => { - process.env.STORYBLOK_LOGIN = 'test_login'; - process.env.STORYBLOK_TOKEN = 'test_token'; - process.env.STORYBLOK_REGION = 'test_region'; - - const userSession = session(); - await userSession.initializeSession(); - expect(userSession.state.isLoggedIn).toBe(true); - expect(userSession.state.login).toBe('test_login'); - expect(userSession.state.password).toBe('test_token'); - expect(userSession.state.region).toBe('test_region'); - }); - - it('should initialize session from TRAVIS_STORYBLOK_ environment variables', async () => { - process.env.TRAVIS_STORYBLOK_LOGIN = 'test_login'; - process.env.TRAVIS_STORYBLOK_TOKEN = 'test_token'; - process.env.TRAVIS_STORYBLOK_REGION = 'test_region'; - - const userSession = session(); - await userSession.initializeSession(); - expect(userSession.state.isLoggedIn).toBe(true); - expect(userSession.state.login).toBe('test_login'); - expect(userSession.state.password).toBe('test_token'); - expect(userSession.state.region).toBe('test_region'); - }); - }); -}); diff --git a/src/session.ts b/src/session.ts deleted file mode 100644 index c093e64a..00000000 --- a/src/session.ts +++ /dev/null @@ -1,109 +0,0 @@ -// session.ts -import { type RegionCode, regionsDomain } from './constants'; -import { addCredentials, getCredentials } from './creds'; - -export interface SessionState { - isLoggedIn: boolean; - login?: string; - password?: string; - region?: RegionCode; - envLogin?: boolean; -} - -let sessionInstance: ReturnType | null = null; - -function createSession() { - const state: SessionState = { - isLoggedIn: false, - }; - - async function initializeSession() { - // First, check for environment variables - const envCredentials = getEnvCredentials(); - if (envCredentials) { - state.isLoggedIn = true; - state.login = envCredentials.login; - state.password = envCredentials.password; - state.region = envCredentials.region as RegionCode; - state.envLogin = true; - return; - } - - // If no environment variables, fall back to .storyblok/credentials.json - const credentials = await getCredentials(); - if (credentials) { - // Todo: evaluate this in future when we want to support multiple regions - const creds = Object.values(credentials)[0]; - state.isLoggedIn = true; - state.login = creds.login; - state.password = creds.password; - state.region = creds.region as RegionCode; - } - else { - // No credentials found; set state to logged out - state.isLoggedIn = false; - state.login = undefined; - state.password = undefined; - state.region = undefined; - } - state.envLogin = false; - } - - function getEnvCredentials() { - const envLogin = process.env.STORYBLOK_LOGIN || process.env.TRAVIS_STORYBLOK_LOGIN; - const envPassword = process.env.STORYBLOK_TOKEN || process.env.TRAVIS_STORYBLOK_TOKEN; - const envRegion = process.env.STORYBLOK_REGION || process.env.TRAVIS_STORYBLOK_REGION; - - if (envLogin && envPassword && envRegion) { - return { - login: envLogin, - password: envPassword, - region: envRegion, - }; - } - return null; - } - - async function persistCredentials(region: RegionCode) { - if (state.isLoggedIn && state.login && state.password && state.region) { - await addCredentials({ - machineName: regionsDomain[region] || 'api.storyblok.com', - login: state.login, - password: state.password, - region: state.region, - }); - } - else { - throw new Error('No credentials to save.'); - } - } - - function updateSession(login: string, password: string, region: RegionCode) { - state.isLoggedIn = true; - state.login = login; - state.password = password; - state.region = region; - } - - function logout() { - state.isLoggedIn = false; - state.login = undefined; - state.password = undefined; - state.region = undefined; - } - - return { - state, - initializeSession, - updateSession, - persistCredentials, - logout, - }; -} - -export function session() { - if (!sessionInstance) { - sessionInstance = createSession(); - } - return sessionInstance; -} diff --git a/src/tasks/delete-component.js b/src/tasks/delete-component.js new file mode 100644 index 00000000..94410abf --- /dev/null +++ b/src/tasks/delete-component.js @@ -0,0 +1,32 @@ +import chalk from 'chalk' +import { getComponentsFromName } from './migrations/utils' + +/** + * + * @param api {Object} + * @param comp {String | Number} + * @param dryrun {Boolean} + * @returns {Promise} + */ +const deleteComponent = async (api, { comp, dryrun = false }) => { + try { + let component + if (!isNaN(comp)) { + const { data } = await api.get(`components/${comp}`) + component = data.component + } else { + component = await getComponentsFromName(api, comp) + } + if (Object.keys(component).length === 0) { + return Promise.reject(new Error(`Component ${comp} not found.`)) + } + if (!dryrun) { + await api.delete(`components/${component.id}`) + } + console.log(chalk.green('βœ“') + ' Component ' + chalk.blue(component.name) + ' deleted.') + } catch (e) { + console.error(`${chalk.red('X')} An error occurred in delete-component task.`) + return Promise.reject(new Error(e)) + } +} +export default deleteComponent diff --git a/src/tasks/delete-components.js b/src/tasks/delete-components.js new file mode 100644 index 00000000..cda0f977 --- /dev/null +++ b/src/tasks/delete-components.js @@ -0,0 +1,113 @@ +import chalk from 'chalk' +import axios from 'axios' +import fs from 'fs' +import lodash from 'lodash' +import deleteComponent from './delete-component' +const { isEmpty } = lodash + +const isUrl = source => source.indexOf('http') === 0 + +/** + * Get the data from a local or remote JSON file + * @param {string} path the local path or remote url of the file + * @returns {Promise} return the data from the source or an error + */ +const getDataFromPath = async (path) => { + if (!path) { + return {} + } + const sources = path.split(',') + const isList = sources.length > 1 + + try { + if (isUrl(path)) { + return (await axios.get(path)).data + } else { + if (!isList) return JSON.parse(fs.readFileSync(sources[0], 'utf8')) + + const data = [] + sources.forEach((source) => { + data.push(JSON.parse(fs.readFileSync(source, 'utf8'))) + }) + return data + } + } catch (err) { + console.error(`${chalk.red('X')} Can not load json file from ${path}`) + return Promise.reject(err) + } +} + +/** + * Creat an array based in the content parameter and the key provided + * @param {object} content the data to create a list + * @param {string} key key to serch in the content + * @returns {Array} return the data from the source or an error + */ +const createContentList = (content, key) => { + if (content[key]) return content[key] + else if (Array.isArray(content)) return [...content] + else return !isEmpty(content) ? [content] : [] +} + +/** + * Delete all components from your Space that occur in your Local JSON. + * @param api {Object} + * @param source {String} + * @param reversed {Boolean} Or delete those components on your Space that do not appear in the JSON. + * @param dryRun {Boolean} + * @returns {Promise} + */ +const deleteComponents = async (api, { source, reversed = false, dryRun = false }) => { + try { + const rawComponents = (await getDataFromPath(source)) || [] + const sourceComponents = createContentList(rawComponents, 'components') + if (!reversed) { + return deleteAllComponents(api, sourceComponents, dryRun) + } + const spaceComponents = await api.getComponents() + return deleteComponentsReversed(api, sourceComponents, spaceComponents, dryRun) + } catch (e) { + console.error(`${chalk.red('X')} Can not delete with invalid json - please provide a valid json file`) + return Promise.reject(new Error('Can not delete with invalid json - please provide a valid json file')) + } +} + +/** + * Delete all given components + * @param api {Object} + * @param components {Object[]} + * @param dryrun {Boolean} + * @returns {Promise} + */ +const deleteAllComponents = async (api, components, dryrun) => { + for (const c of components) { + await deleteComponentAndSkip(api, c, dryrun) + } +} + +/** + * Delete all components which do not appear in components but in the space components + * @param api {Object} + * @param components {Object[]} + * @param spaceComponents {Object[]} + * @param dryrun {Boolean} + * @returns {Promise} + */ +const deleteComponentsReversed = async (api, components, spaceComponents, dryrun) => { + const toDeleteSpaceComponents = spaceComponents + .filter(spaceComponent => components.findIndex(o => o.name === spaceComponent.name) < 0) + console.log(chalk.blue('-') + ' Deleting all components which do not appear in the given source.') + for (const c of toDeleteSpaceComponents) { + await deleteComponentAndSkip(api, c, dryrun) + } +} + +const deleteComponentAndSkip = async (api, c, dryrun) => { + try { + return await deleteComponent(api, { comp: c.name, dryrun: dryrun }) + } catch (e) { + console.log(chalk.red('-') + ' Error deleting component ' + chalk.blue(c.name) + '! Skipped...') + } +} + +export default deleteComponents diff --git a/src/tasks/delete-datasources.js b/src/tasks/delete-datasources.js new file mode 100644 index 00000000..080f130d --- /dev/null +++ b/src/tasks/delete-datasources.js @@ -0,0 +1,41 @@ +import chalk from 'chalk' + +/** + * @method deleteDatasources + * @param {Object} api + * @param {Object} options { fileName: string, separateFiles: Boolean, path: String } + * @return {Promise} + */ +const deleteDatasources = async (api, options) => { + const { byName, bySlug } = options + + try { + let datasources = await api.getDatasources() + + if (bySlug) { + datasources = datasources.filter(datasource => datasource.slug.toLowerCase().startsWith(bySlug.toLowerCase())) + const filteredSlugs = datasources.map(obj => obj.slug) + const formattedSlugs = filteredSlugs.join(', ') + + console.log(`${chalk.blue('-')} Datasources where slug starts with ${bySlug}: ${formattedSlugs}`) + } + + if (byName) { + datasources = datasources.filter(datasource => datasource.name.toLowerCase().startsWith(byName.toLowerCase())) + const filteredNames = datasources.map(obj => obj.name) + const formattedNames = filteredNames.join(', ') + + console.log(`${chalk.blue('-')} Datasources where name starts with ${byName}: ${formattedNames}`) + } + + for (const datasource of datasources) { + console.log(`${chalk.blue('-')} Deleting ${datasource.name}`) + await api.deleteDatasource(datasource.id) + } + } catch (e) { + console.error(`${chalk.red('X')} An error ocurred in delete-components task when deleting a datasource`) + return Promise.reject(new Error(e)) + } +} + +export default deleteDatasources diff --git a/src/tasks/generate-typescript-typedefs.ts b/src/tasks/generate-typescript-typedefs.ts new file mode 100644 index 00000000..d5413e77 --- /dev/null +++ b/src/tasks/generate-typescript-typedefs.ts @@ -0,0 +1,79 @@ +import chalk from "chalk"; +import fs from "fs"; +import type { GenerateTypescriptTypedefsCLIOptions, JSONSchemaToTSOptions } from "../types"; +import { GenerateTypesFromJSONSchemas } from "../utils/typescript/generateTypesFromJSONSchema"; +import type { JSONSchema } from "json-schema-to-typescript"; + +type GenerateTSTypedefs = (options: GenerateTypescriptTypedefsCLIOptions) => void; + +const generateTypescriptTypedefs: GenerateTSTypedefs = async ({ + sourceFilePaths, + destinationFilePath = "./storyblok-component-types.d.ts", + typeNamesPrefix, + typeNamesSuffix = "Storyblok", + customFieldTypesParserPath, + JSONSchemaToTSOptionsPath, +}) => { + /** + * Get JSON Schemas from files looking at all the paths provided + * @param paths An array of paths to read from + * @returns An array of components JSONSchemas + */ + const getJSONSchemasFromPaths = (paths: string[]): JSONSchema[] | null => { + try { + return paths.map((sourceFilePath) => JSON.parse(fs.readFileSync(sourceFilePath, "utf8"))); + } catch (e) { + console.error( + `${chalk.red("X")} + Could not load JSON files from the provided paths: ${paths}. Please check if those files exist.` + ); + return null; + } + }; + + /** + * Get user-provided options for json-schema-to-typescript https://www.npmjs.com/package/json-schema-to-typescript#options + * @param path Path to a JSON file with the options + * @returns A POJO with the options + */ + const getJSONSchemaToTSOptionsFromPath = (path: string): Record | null => { + try { + return JSON.parse(fs.readFileSync(path, "utf8")); + } catch (e) { + console.error( + `${chalk.red("X")} + Could not load options from the JSON file at ${path}. Please check if the file exists and if it's properly formatted.` + ); + return null; + } + }; + + const JSONSchemaToTSCustomOptions = + JSONSchemaToTSOptionsPath && getJSONSchemaToTSOptionsFromPath(JSONSchemaToTSOptionsPath); + + // Merge custom provided options to our defaults + const JSONSchemaToTSOptions: JSONSchemaToTSOptions = { + bannerComment: "", // Remove much noise from the Typedefs file + unknownAny: false, // Smoother transition from a non-TS codebase to a TS codebase + ...JSONSchemaToTSCustomOptions, + }; + + const componentsJSONSchemaArray = getJSONSchemasFromPaths(sourceFilePaths)?.flatMap( + (componentsJSONSchema) => componentsJSONSchema.components || componentsJSONSchema + ); + + if (componentsJSONSchemaArray && componentsJSONSchemaArray.length > 0) { + const generator = await GenerateTypesFromJSONSchemas.init(componentsJSONSchemaArray, { + sourceFilePaths, + destinationFilePath, + typeNamesPrefix, + typeNamesSuffix, + customFieldTypesParserPath, + JSONSchemaToTSCustomOptions: JSONSchemaToTSOptions, + }); + + return await generator.generateTSFile(); + } +}; + +export default generateTypescriptTypedefs; diff --git a/src/tasks/import/import.js b/src/tasks/import/import.js new file mode 100644 index 00000000..d32f359f --- /dev/null +++ b/src/tasks/import/import.js @@ -0,0 +1,36 @@ +import chalk from 'chalk' +import { convertFile, sendContent, discoverExtension } from './utils' + +/** + * @typedef {Object} ImportDataOptions + * @property {string} type Type of content + * @property {string} folder Folder ID + * @property {string} delimiter Delimiter (For csv files) + * @property {string} file path to file + * + * @method importData + * @param {Object} api - Pass the api instance as a parameter + * @param {ImportDataOptions} options + * @return {Promise} + */ +const importFiles = async (api, options) => { + const { file } = options + + if (!api) { + console.log(chalk.red('X') + 'Api instance is required to make the request') + return [] + } + + try { + const extension = discoverExtension(file) + const dataFromFile = await convertFile(file, extension, options) + + await sendContent(api, dataFromFile) + } catch (e) { + console.log( + `${chalk.red('X')} An error ocurrend when process file and send it ${e.message}` + ) + return Promise.reject(e) + } +} +export default importFiles diff --git a/src/tasks/import/utils.js b/src/tasks/import/utils.js new file mode 100644 index 00000000..17d09275 --- /dev/null +++ b/src/tasks/import/utils.js @@ -0,0 +1,222 @@ +import csvReader from 'fast-csv' +import xmlConverter from 'xml-js' +import chalk from 'chalk' +import path from 'path' +import fs from 'fs' +import lodash from 'lodash' +const { isArray } = lodash + +/** + * @method discoverExtension + * @param {String} fileName - Name of the file + * @return {String} + */ +export const discoverExtension = (fileName) => { + const extension = path.extname(fileName) + + if (extension !== '') { + return extension.replace('.', '') + } + + return '' +} + +/** + * @method sendContent + * @param {Object} api - A api object + * @param {Object} contents - Object with the content + * @return {Promise} + */ +export const sendContent = async (api, contents) => { + for (const story of contents) { + try { + console.log( + `${chalk.blue('-')} Creating the story ${story.name}(${story.slug})` + ) + await api.post('stories', { story }) + + console.log( + `${chalk.green('βœ“')} ${story.name}(${story.slug}) has been created` + ) + } catch (e) { + console.log( + `${chalk.red('X')} An error ocurred when creating the story ${story.name}(${story.slug}): ${e.message}` + ) + } + + console.log('') + } +} + +// This function is used to sanitize the json generated by the xml2js() function, +// because with each text generated in the conversion it is encapsulated within +// an object { "_text": 'text'} + +const removeJsonTextAttribute = (value, parentElement) => { + const keyNo = Object.keys(parentElement._parent).length + const keyName = Object.keys(parentElement._parent)[keyNo - 1] + parentElement._parent[keyName] = !isNaN(value) ? Number(value) : value +} + +/** + * @method csvParser + * @param {String} data - CSV content + * @param {String} typeOfContent - Content type + * @param {Number} folderID - Storyblok folder id, default value is 0 + * @param {String} delimiter - Csv file delimiter, default value is ';' + * @return {Promise} + */ +export const csvParser = (data, typeOfContent, folderID = 0, delimiter = ';') => { + return new Promise((resolve, reject) => { + console.log() + console.log(`${chalk.blue('-')} Reading CSV file... `) + console.log() + + const story = [] + + csvReader.parseString(data, { headers: true, delimiter: delimiter }) + .on('error', error => reject(error)) + .on('data', line => { + const content = Object.keys(line).reduce((acc, key) => { + acc[key] = line[key] + return acc + }, {}) + + if (content.path) { + delete content.path + } + + story.push({ + slug: line.path || '', + name: line.title || '', + parent_id: folderID, + content: { + component: typeOfContent, + ...content + } + }) + }) + .on('end', () => { + resolve(story) + }) + }) +} + +const xmlFactoryOfStories = (line, typeOfContent, folderID) => { + const content = Object.keys(line).reduce((acc, key) => { + acc[key] = line[key] + return acc + }, {}) + + if (content.path) { + delete content.path + } + + return { + slug: line.path || '', + name: line.title || '', + parent_id: folderID, + content: { + component: typeOfContent, + ...content + } + } +} + +/** + * @method xmlParser + * @param {Object} data - XML content + * @param {String} typeOfContent - Content type + * @param {Number} folderID - Storyblok folder id, default value is 0 + * @return {Promise} + */ +export const xmlParser = async (data, typeOfContent, folderID = 0) => { + return new Promise((resolve, reject) => { + console.log() + console.log(`${chalk.blue('-')} Reading XML file... `) + console.log() + + try { + const options = { + compact: true, + trim: true, + ignoreDoctype: true, + textFn: removeJsonTextAttribute + } + const contentParsed = xmlConverter.xml2js(data, options) + + if (isArray(contentParsed.root.row)) { + const story = contentParsed.root.row.map(line => xmlFactoryOfStories(line, typeOfContent, folderID)) + return resolve(story) + } + const story = xmlFactoryOfStories(contentParsed.root.row, typeOfContent, folderID) + return resolve([story]) + } catch (error) { + return reject(error) + } + }) +} + +/** + * @method jsonParser + * @param {Object} data - Json with content + * @param {String} typeOfContent - Content type + * @param {Number} folderID - Storyblok folder id, default value is 0 + * @return {Promise} + */ +export const jsonParser = async (data, typeOfContent, folderID = 0) => { + console.log() + console.log(`${chalk.blue('-')} Reading JSON file... `) + console.log() + + const copyData = JSON.parse(data) + const story = [] + + for (const key of copyData) { + const { path, title, ...rest } = key + story.push({ + slug: path || '', + name: title || '', + parent_id: folderID, + content: { + component: typeOfContent, + ...rest + } + }) + } + return Promise.resolve(story) +} + +/** + * @typedef {Object} ConvertFileOptions + * @property {string} type Type of content + * @property {string} folder Folder ID + * @property {string} delimiter Delimiter (For csv files) + * + * @method convertFile + * @param {string} file path to file + * @param {string} extension file extension + * @param {ConvertFileOptions} options options to parser functions + */ +export const convertFile = (file, extension, options) => { + const { + type, + folder, + delimiter + } = options + + if (extension === 'csv') { + const dataFromFile = fs.readFileSync(file) + return csvParser(dataFromFile, type, folder, delimiter) + } + if (extension === 'xml') { + const dataFromFile = fs.readFileSync(file) + return xmlParser(dataFromFile, type, folder) + } + if (extension === 'json') { + const dataFromFile = fs.readFileSync(file, 'utf-8') + return jsonParser(dataFromFile, type, folder) + } + + return Promise.reject(new Error('This file extension is not supported. Please use .xml, .json or .csv')) +} diff --git a/src/tasks/index.js b/src/tasks/index.js new file mode 100644 index 00000000..25543f55 --- /dev/null +++ b/src/tasks/index.js @@ -0,0 +1,33 @@ +import sync from './sync' +import scaffold from './scaffold' +import quickstart from './quickstart' +import pullComponents from './pull-components' +import pullLanguages from './pull-languages' +import pushComponents from './push-components' +import generateMigration from './migrations/generate' +import runMigration from './migrations/run' +import rollbackMigration from './migrations/rollback' +import listSpaces from './list-spaces' +import importFiles from './import/import' +import deleteComponent from './delete-component' +import deleteComponents from './delete-components' +import deleteDatasources from './delete-datasources' +import generateTypescriptTypedefs from './generate-typescript-typedefs' + +export default { + sync, + scaffold, + quickstart, + pullComponents, + pullLanguages, + pushComponents, + generateMigration, + runMigration, + rollbackMigration, + listSpaces, + importFiles, + deleteComponent, + deleteComponents, + deleteDatasources, + generateTypescriptTypedefs +} diff --git a/src/tasks/list-spaces.js b/src/tasks/list-spaces.js new file mode 100644 index 00000000..56f5d1b0 --- /dev/null +++ b/src/tasks/list-spaces.js @@ -0,0 +1,64 @@ +import chalk from 'chalk' +import { ALL_REGIONS, getRegionName, CN_CODE } from '@storyblok/region-helper' + +/** + * @method listSpaces + * @param api - Pass the api instance as a parameter + * @return {Promise} + */ + +const listSpaces = async (api, currentRegion) => { + const isChinaEnv = currentRegion === CN_CODE + + console.log() + console.log(chalk.green('βœ“') + ' Loading spaces...') + + if (!api) { + console.log(chalk.red('X') + 'Api instance is required to make the request') + return [] + } + + if (isChinaEnv) { + const spaces = await api.getAllSpacesByRegion(currentRegion) + .then(res => res) + .catch(err => Promise.reject(err)) + + if (!spaces) { + console.log(chalk.red('X') + ' No spaces were found for this user ') + return [] + } + console.log(chalk.blue(' -') + ' Spaces From China region:') + + spaces.map(space => { + console.log(` ${space.name} (id: ${space.id})`) + }) + return spaces + } else { + const spacesList = [] + for (const key of ALL_REGIONS) { + if (key === CN_CODE) continue + spacesList.push(await api.getAllSpacesByRegion(key) + .then((res) => { + return { + key, + res + } + }) + .catch(err => Promise.reject(err))) + } + if (!spacesList) { + console.log(chalk.red('X') + ' No spaces were found for this user ') + return [] + } + spacesList.forEach(region => { + console.log() + console.log(`${chalk.blue(' -')} Spaces From ${getRegionName(region.key)} region:`) + region.res.forEach((space) => { + console.log(` ${space.name} (id: ${space.id})`) + }) + }) + return spacesList + } +} + +export default listSpaces diff --git a/src/tasks/migrations/generate.js b/src/tasks/migrations/generate.js new file mode 100644 index 00000000..5cd36272 --- /dev/null +++ b/src/tasks/migrations/generate.js @@ -0,0 +1,53 @@ +import chalk from 'chalk' +import inquirer from 'inquirer' +import { getPathToFile, checkFileExists, getInquirerOptions, createMigrationFile, checkComponentExists, getNameOfMigrationFile } from './utils' + +/** + * @method generateMigration + * @param {Object} api API instance + * @param {String} component component name + * @param {String} field field name + * @return {Promise<{fileName: string, created: boolean}>} + */ +const generateMigration = async (api, component, field) => { + try { + const componentExists = await checkComponentExists(api, component) + + if (!componentExists) { + throw new Error('The component does not exists') + } + + const fileName = getNameOfMigrationFile(component, field) + const pathToFile = getPathToFile(fileName) + const fileExists = await checkFileExists(pathToFile) + + if (fileExists) { + console.log(`${chalk.yellow('!')} The file to migration already exists.`) + + const questions = getInquirerOptions('file-exists') + const answer = await inquirer.prompt(questions) + + if (!answer.choice) { + console.log(`${chalk.blue('-')} The file will not overwrite`) + + return Promise.resolve({ + fileName, + created: false + }) + } + } + + await createMigrationFile(fileName, field) + + console.log(`${chalk.green('βœ“')} File created with success. Check the file ${fileName} in migrations folder`) + + return Promise.resolve({ + fileName, + created: true + }) + } catch (e) { + return Promise.reject(e) + } +} + +export default generateMigration diff --git a/src/tasks/migrations/rollback.js b/src/tasks/migrations/rollback.js new file mode 100644 index 00000000..9c0771fe --- /dev/null +++ b/src/tasks/migrations/rollback.js @@ -0,0 +1,67 @@ +import chalk from 'chalk' +import fs from 'fs-extra' +import { checkExistenceFilesInRollBackDirectory, urlTofRollbackMigrationFile } from './utils' +const MIGRATIONS_ROLLBACK_DIRECTORY = `${process.cwd()}/migrations/rollback` + +/** + * @method rollbackMigration + * @param {Object} api api instance + * @param {String} component name of the component to rollback + * @param {String} field name of the field to rollback + * @return {Promise} + */ + +const rollbackMigration = async (api, field, component) => { + if (!fs.existsSync(MIGRATIONS_ROLLBACK_DIRECTORY)) { + console.log(` + ${chalk.red('X')} To execute the rollback-migration command you need to have changed some component with the migrations commands.` + ) + return + } + + console.log( + `${chalk.blue('-')} Checking if the rollback files exist` + ) + + try { + checkExistenceFilesInRollBackDirectory(MIGRATIONS_ROLLBACK_DIRECTORY, component, field) + .then(data => { + if (!data) { + return console.log(`${chalk.red('X')} Rollback file for component ${chalk.blue(component)} and field ${chalk.blue(field)} was not found`) + } + }) + + console.log() + console.log( + `${chalk.blue('-')} Starting rollback...` + ) + console.log() + + let rollbackContent = await fs.readFile(urlTofRollbackMigrationFile(component, field), 'utf-8') + rollbackContent = JSON.parse(rollbackContent) + + for (const story of rollbackContent) { + console.log( + `${chalk.blue('-')} Restoring data from "${chalk.blue(story.full_slug)}" ...` + ) + await api.put(`stories/${story.id}`, { + story: { content: story.content }, + force_update: '1' + }) + console.log( + ` ${chalk.blue('-')} ${story.full_slug} data has been restored!` + ) + console.log() + } + + console.log( + `${chalk.green('βœ“')} The roolback-migration was executed with success!` + ) + return Promise.resolve({ rollback: true }) + } catch (err) { + console.log(`${chalk.red('X')} The rollback-migration command was not successfully executed: ${err}`) + return Promise.reject(err) + } +} + +export default rollbackMigration diff --git a/src/tasks/migrations/run.js b/src/tasks/migrations/run.js new file mode 100644 index 00000000..f7a5237f --- /dev/null +++ b/src/tasks/migrations/run.js @@ -0,0 +1,154 @@ +import chalk from 'chalk' +import lodash from 'lodash' +import { getPathToFile, checkFileExists, processMigration, getStoriesByComponent, getNameOfMigrationFile, createRollbackFile } from './utils' + +// Separate import because apparently `cloneDeep` is not exported as named export +const { isEmpty, cloneDeep, isEqual} = lodash + +/** + * @method isStoryPublishedWithoutChanges + * @param {Object} story + * @return {Boolean} + */ +const isStoryPublishedWithoutChanges = story => { + return story.published && !story.unpublished_changes +} + +/** + * @method isStoryWithUnpublishedChanges + * @param {Object} story + * @return {Boolean} + */ +const isStoryWithUnpublishedChanges = story => { + return story.published && story.unpublished_changes +} + +/** + * @typedef {'all'|'published'|'published-with-changes'} PublishOptions + * + * @typedef {Object} RunMigrationOptions + * @property {boolean} isDryrun indicates the function will be execute or not + * @property {string} migrationPath indicates where is the location to migration function + * @property {PublishOptions} publish + * @property {string} publishLanguages + * / + +/** + * @method runMigration + * @param {Object} api API instance + * @param {String} component component name + * @param {String} field field name + * @param {RunMigrationOptions} options disable execution + * @return {Promise<{ executed: boolean, motive?: string }>} + */ +const runMigration = async (api, component, field, options = {}) => { + const migrationPath = options.migrationPath || null + const publish = options.publish || null + const publishLanguages = options.publishLanguages || null + + try { + const fileName = getNameOfMigrationFile(component, field) + const pathToFile = getPathToFile(fileName, migrationPath) + const fileExists = await checkFileExists(pathToFile) + const rollbackData = [] + + if (!fileExists) { + throw new Error(`The migration to combination ${fileName} doesn't exists`) + } + + console.log( + `${chalk.blue('-')} Getting the user defined migration function` + ) + const migrationFn = (await import(pathToFile)).default; + + if (typeof migrationFn !== 'function') { + throw new Error("The migration file doesn't export a function") + } + + console.log( + `${chalk.blue('-')} Getting stories for ${component} component` + ) + const stories = await getStoriesByComponent(api, component) + + if (isEmpty(stories)) { + console.log(`${chalk.blue('-')} There are no stories for component ${component}!`) + return Promise.resolve({ + executed: false, + motive: 'NO_STORIES' + }) + } + + for (const story of stories) { + try { + console.log( + `${chalk.blue('-')} Processing story ${story.full_slug}` + ) + const storyData = await api.getSingleStory(story.id) + const oldContent = cloneDeep(storyData.content) + + await processMigration(storyData.content, component, migrationFn, story.full_slug) + + const isChangeContent = !isEqual(oldContent, storyData.content) + + // to prevent api unnecessary api executions + if (!options.isDryrun && isChangeContent) { + console.log( + `${chalk.blue('-')} Updating story ${story.full_slug}` + ) + const url = `stories/${story.id}` + + // create a rollback object + rollbackData.push({ + id: storyData.id, + full_slug: storyData.full_slug, + content: oldContent + }) + + const payload = { + story: storyData, + force_update: '1' + } + + if (publish === 'published' && isStoryPublishedWithoutChanges(storyData)) { + payload.publish = '1' + } + + if (publish === 'published-with-changes' && isStoryWithUnpublishedChanges(story)) { + payload.publish = '1' + } + + if (publish === 'all') { + payload.publish = '1' + } + + if (!isEmpty(publishLanguages)) { + payload.lang = publishLanguages + } + + await api.put(url, payload) + console.log( + `${chalk.blue('-')} Story updated with success!` + ) + } + } catch (e) { + console.error(`${chalk.red('X')} An error occurred when try to execute migration and update the story: ${e.message}`) + } + + console.log() + } + + // send file of rollback to save in migrations/rollback directory + if (!isEmpty(rollbackData)) { + await createRollbackFile(rollbackData, component, field) + } + + console.log(`${chalk.green('βœ“')} The migration was executed with success!`) + return Promise.resolve({ + executed: true + }) + } catch (e) { + return Promise.reject(e) + } +} + +export default runMigration diff --git a/src/tasks/migrations/utils.js b/src/tasks/migrations/utils.js new file mode 100644 index 00000000..3ce0eab9 --- /dev/null +++ b/src/tasks/migrations/utils.js @@ -0,0 +1,320 @@ +import onChange from 'on-change' +import lodash from 'lodash' +import fs from 'fs-extra' +import chalk from 'chalk' +import { parseError } from '../../utils' +import migrationTemplate from '../templates/migration-file' +const { isArray, isPlainObject, has, isEmpty, template, truncate } = lodash + +const MIGRATIONS_DIRECTORY = `${process.cwd()}/migrations` +const MIGRATIONS_ROLLBACK_DIRECTORY = `${process.cwd()}/migrations/rollback` + +/** + * @method getPathToFile + * @param {String} fileName name of the file + * @param {String} migrationPath migrations folder + * @return {String} + * + * @example + * // path/to/migrations/change_teaser_subtitle.js + * getPathToFile('change_teaser_subtitle.js') + * + * // ./migrations/change_teaser_subtitle.js + * getPathToFile('change_teaser_subtitle.js', './migrations') + */ +export const getPathToFile = (fileName, migrationPath = null) => { + const pathTo = isEmpty(migrationPath) ? MIGRATIONS_DIRECTORY : migrationPath + + return `${pathTo}/${fileName}` +} + +/** + * @method getNameOfMigrationFile + * @param {String} component name of component + * @param {String} field name of component's field + * @return {String} + * + * @example + * getNameOfMigrationFile('product', 'price') // change_product_price + */ +export const getNameOfMigrationFile = (component, field) => { + return `change_${component}_${field}.mjs` +} + +/** + * @method getComponentsFromName + * @param {Object} api API Object + * @param {String} component name of component + * @return {Promise} + */ +export const getStoriesByComponent = async (api, componentName) => { + try { + const stories = await api.getStories({ + contain_component: componentName + }) + + return stories + } catch (e) { + const error = parseError(e) + console.error(`${chalk.red('X')} An error ocurred when load the stories filtering by component ${componentName}: ${error.message}`) + + return Promise.reject(error.error) + } +} + +/** + * @method getComponentsFromName + * @param {Object} api API Object + * @param {String} component name of component + * @return {Promise} + */ +export const getComponentsFromName = async (api, componentName) => { + try { + const components = await api.getComponents() + + const found = components.filter(_comp => { + return _comp.name === componentName + }) + + if (found.length > 0) { + return Promise.resolve(found[0]) + } + + return {} + } catch (e) { + const error = parseError(e) + console.error(`${chalk.red('X')} An error occurred when loading the components from space: ${error.message}`) + + return Promise.reject(error.error) + } +} + +/** + * @method checkComponentExists + * @param {Object} api API Object + * @param {String} component name of component + * @return {Promise} + */ +export const checkComponentExists = async (api, component) => { + try { + const componentData = await getComponentsFromName(api, component) + + return Promise.resolve(Object.keys(componentData).length > 0) + } catch (e) { + const error = parseError(e) + return Promise.reject(error.error) + } +} + +/** + * @method checkFileExists + * @param {String} filePath + * @return {Promise} + */ +export const checkFileExists = async (filePath) => fs.pathExists(filePath) + +/** + * @method createMigrationFile + * @param {String} fileName path to file + * @param {String} field name of the field + * @return {Promise} + */ +export const createMigrationFile = (fileName, field) => { + console.log(`${chalk.blue('-')} Creating the migration file in migrations folder`) + + // use lodash.template to replace the occurrences of fieldname + const compile = template(migrationTemplate, { + interpolate: /{{([\s\S]+?)}}/g + }) + const outputMigrationFile = compile({ + fieldname: field + }) + + return fs.outputFile(getPathToFile(fileName), outputMigrationFile) +} + +/** + * @method getInquirerOptions + * @param {String} type + * @return {Array} + */ +export const getInquirerOptions = (type) => { + if (type === 'file-exists') { + return [{ + type: 'confirm', + name: 'choice', + message: 'Do you want to continue? (This will overwrite the content of the file!)' + }] + } + + return [] +} + +/** + * @method showMigrationChanges + * @param {String} path field name + * @param {unknown} value updated value + * @param {unknown} oldValue previous value + */ +export const showMigrationChanges = (path, value, oldValue) => { + // It was created a new field + if (oldValue === undefined) { + // truncate the string with more than 30 characters + const _value = truncate(value) + + console.log(` ${chalk.green('-')} Created field "${chalk.green(path)}" with value "${chalk.green(_value)}"`) + return + } + + // It was removed the field + if (value === undefined) { + console.log(` ${chalk.red('-')} Removed the field "${chalk.red(path)}"`) + return + } + + // It was updated the value + if (value !== oldValue) { + // truncate the string with more than 30 characters + const _value = truncate(value) + const _oldValue = truncate(oldValue) + + console.log(` ${chalk.blue('-')} Updated field "${chalk.blue(path)}" from "${chalk.blue(_oldValue)}" to "${chalk.blue(_value)}"`) + } +} + +/** + * @method processMigration + * @param {Object} content component structure from Storyblok + * @param {String} component name of the component that is processing + * @param {Function} migrationFn the migration function defined by user + * @param {String} storyFullSlug the full slug of the containing story + * @return {Promise} + */ +export const processMigration = async (content = {}, component = '', migrationFn, storyFullSlug) => { + // I'm processing the component that I want + if (content.component === component) { + const watchedContent = onChange( + content, + showMigrationChanges + ) + + await migrationFn(watchedContent, storyFullSlug) + } + + for (const key in content) { + const value = content[key] + + if (isArray(value)) { + try { + await Promise.all( + value.map(_item => processMigration(_item, component, migrationFn, storyFullSlug)) + ) + } catch (e) { + console.error(e) + } + } + + if (isPlainObject(value) && has(value, 'component')) { + try { + await processMigration(value, component, migrationFn, storyFullSlug) + } catch (e) { + console.error(e) + } + } + + if (isPlainObject(value) && value.type === 'doc' && value.content) { + value.content.filter(item => item.type === 'blok').forEach(async (item) => { + try { + await processMigration(item.attrs.body, component, migrationFn, storyFullSlug) + } catch (e) { + console.error(e) + } + }) + } + } + + return Promise.resolve(true) +} + +/** + * @method urlTofRollbackMigrationFile + * @param {String} component name of the component to rollback + * @param {String} field name of the field to rollback + * @return {String} + */ + +export const urlTofRollbackMigrationFile = (component, field) => { + return `${MIGRATIONS_ROLLBACK_DIRECTORY}/${getNameOfRollbackMigrationFile(component, field)}` +} + +/** + * @method getNameOfRollbackMigrationFile + * @param {String} component name of the component to rollback + * @param {String} field name of the field to rollback + * @return {String} + */ + +export const getNameOfRollbackMigrationFile = (component, field) => { + return `rollback_${component}_${field}.json` +} + +/** + * @method createRollbackFile + * @param {Array} stories array containing stories for rollback + * @return {Promise} + */ + +export const createRollbackFile = async (stories, component, field) => { + try { + if (!fs.existsSync(MIGRATIONS_ROLLBACK_DIRECTORY)) { + fs.mkdir(MIGRATIONS_ROLLBACK_DIRECTORY) + } + + const url = urlTofRollbackMigrationFile(component, field) + + if (fs.existsSync(url)) { + fs.unlinkSync(url) + } + + fs.writeFile(url, JSON.stringify(stories, null, 2), { flag: 'a' }, (error) => { + if (error) { + console.log(`${chalk.red('X')} The rollback file could not be created: ${error}`) + return error + } + console.log(`${chalk.green('βœ“')} The rollback file has been created in migrations/rollback/!`) + }) + return Promise.resolve({ + component: component, + created: true + }) + } catch (error) { + return Promise.reject(error) + } +} + +/** + * @method checkExistenceFilesInRollBackDirectory + * @param {String} path path of the rollback folder directories + * @param {String} component name of the components to be searched for in the rollback folder + * @param {String} field name of the field to be searched for in the rollback folder + * @return {Promisse} + */ + +export const checkExistenceFilesInRollBackDirectory = (path, component, field) => { + if (!fs.existsSync(path)) { + console.log(` + ${chalk.red('X')} The path for which the rollback files should be contained does not exist` + ) + return Promise.reject(new Error({ error: 'Path not found' })) + } + + const files = fs.readdirSync(path).map(file => file) + + const file = files.filter((name) => { + const splitedName = name.split('_') + if (splitedName[1] === component && splitedName[2] === `${field}.json`) { + return name + } + }) + return Promise.resolve(file) +} diff --git a/src/tasks/pull-components.js b/src/tasks/pull-components.js new file mode 100644 index 00000000..e3b402e3 --- /dev/null +++ b/src/tasks/pull-components.js @@ -0,0 +1,113 @@ +import chalk from 'chalk' +import saveFileFactory from '../utils/save-file-factory' + +/** + * @method getNameFromComponentGroups + * @param {Array} groups + * @param {String} uuid + * @return {String} + */ +const getNameFromComponentGroups = (groups, uuid) => { + const exists = groups.filter(group => group.uuid === uuid) + + if (exists.length) { + return exists[0].name + } + + return '' +} + +const resolveDatasourceOptions = async (api, components) => { + const datasources = await api.getDatasources() + + for (const datasource of datasources) { + const datasourceEntries = await api.getDatasourceEntries(datasource.id) + datasource.entries = datasourceEntries + } + + return components.map(component => { + const schema = component.schema + + for (const field in schema) { + if (schema[field].source === 'internal' && schema[field].datasource_slug) { + const datasource = datasources.find(ds => ds.slug === schema[field].datasource_slug) + + if (datasource) { + schema[field].options = datasource.entries.map(entry => ({ value: entry.value, name: entry.name })) + } + } + } + + return component + }) +} + +/** + * @method pullComponents + * @param {Object} api + * @param {Object} options { fileName: string, separateFiles: Boolean, path: String, resolveDatasources: Boolean } + * @return {Promise} + */ +const pullComponents = async (api, options) => { + const { fileName, separateFiles, path, prefixPresetsNames, resolveDatasources } = options + + try { + const componentGroups = await api.getComponentGroups() + + let components = await api.getComponents() + + const presets = await api.getPresets() + + if (resolveDatasources) { + components = await resolveDatasourceOptions(api, components) + } + + components.forEach(component => { + const groupUuid = component.component_group_uuid + if (groupUuid) { + const group = getNameFromComponentGroups(componentGroups, groupUuid) + component.component_group_name = group + } + }) + + if (separateFiles) { + for (const comp in components) { + const compFileName = `${components[comp].name}-${fileName}.json` + const data = JSON.stringify(components[comp], null, 2) + saveFileFactory(compFileName, data, path) + } + console.log(`${chalk.green('βœ“')} We've saved your components in files with the names of each component`) + + if (presets.length === 0) return + + for (const preset in presets) { + const presetFileName = `${prefixPresetsNames ? `${presets[preset].preset.component}-` : ""}${presets[preset].name}-${fileName}.json` + const data = JSON.stringify(presets[preset], null, 2) + saveFileFactory(presetFileName, data, path) + } + console.log(`${chalk.green('βœ“')} We've saved your presets in files with the names of each preset`) + return + } + + const file = `components.${fileName}.json` + const data = JSON.stringify({ components, component_groups: componentGroups }, null, 2) + + console.log(`${chalk.green('βœ“')} We've saved your components in the file: ${file}`) + + saveFileFactory(file, data, path) + + if (presets.length === 0) return + + const presetsFile = `presets.${fileName}.json` + const presetsData = JSON.stringify({ presets }, null, 2) + + console.log(`${chalk.green('βœ“')} We've saved your presets in the file: ${presetsFile}`) + + saveFileFactory(presetsFile, presetsData, path) + } catch (e) { + console.error(`${chalk.red('X')} An error ocurred in pull-components task when load components data`) + return Promise.reject(new Error(e)) + } +} + +export default pullComponents diff --git a/src/tasks/pull-languages.js b/src/tasks/pull-languages.js new file mode 100644 index 00000000..c9622b5c --- /dev/null +++ b/src/tasks/pull-languages.js @@ -0,0 +1,39 @@ +import fs from 'fs' +import chalk from 'chalk' + +/** + * @method pullLanguages + * @param {Object} api + * @param {Object} options { space: Number } + * @return {Promise} + */ +const pullLanguages = async (api, options) => { + const { space } = options + + try { + const options = await api.getSpaceOptions() + const languages = { + default_lang_name: options.default_lang_name, + languages: options.languages + } + + const file = `languages.${space}.json` + const data = JSON.stringify(languages, null, 2) + + console.log(`${chalk.green('βœ“')} We've saved your languages in the file: ${file}`) + + fs.writeFile(`./${file}`, data, (err) => { + if (err) { + Promise.reject(err) + return + } + + Promise.resolve(file) + }) + } catch (e) { + console.error(`${chalk.red('X')} An error ocurred in pull-languages task when load components data`) + return Promise.reject(new Error(e)) + } +} + +export default pullLanguages diff --git a/src/tasks/push-components.js b/src/tasks/push-components.js new file mode 100644 index 00000000..78a2dcf1 --- /dev/null +++ b/src/tasks/push-components.js @@ -0,0 +1,283 @@ +import axios from 'axios' +import fs from 'fs' +import chalk from 'chalk' +import PresetsLib from '../utils/presets-lib' +import lodash from 'lodash' +const { isEmpty } = lodash + +const isUrl = source => source.indexOf('http') === 0 + const listOfGroups = [] + +/** + * @method isGroupExists + * @param {Array} groups + * @param {String} name + * @return {Object} + */ +const getGroupByName = (groups, name) => { + return groups.find(group => group.name === name) || {} +} + +const getGroupByUuid = (groups, uuid) => { + return groups.find(group => group.source_uuid === uuid) +} + +/** + * Get the data from a local or remote JSON file + * @param {string} path the local path or remote url of the file + * @returns {Promise} return the data from the source or an error + */ +const getDataFromPath = async (path) => { + if (!path) { + return {} + } + const sources = path.split(',') + const isList = sources.length > 1 + + try { + if (isUrl(path)) { + return (await axios.get(path)).data + } else { + if (!isList) return JSON.parse(fs.readFileSync(sources[0], 'utf8')) + + const data = [] + sources.forEach((source) => { + data.push(JSON.parse(fs.readFileSync(source, 'utf8'))) + }) + return data + } + } catch (err) { + console.error(`${chalk.red('X')} Can not load json file from ${path}`) + return Promise.reject(err) + } +} + +/** + * Creat an array based in the content parameter and the key provided + * @param {object} content the data to create a list + * @param {string} key key to serch in the content + * @returns {Array} return the data from the source or an error + */ +const createContentList = (content, key) => { + if (content[key]) return content[key] + else if (Array.isArray(content)) return [...content] + else return !isEmpty(content) ? [content] : [] +} + +const getSpaceInternalTags = (client, spaceId) => { + return client.get(`spaces/${spaceId}/internal_tags`).then((response) => response.data.internal_tags || []); +} + +const createComponentInternalTag =(client, spaceId, tag) => { + return client.post(`spaces/${spaceId}/internal_tags`, { + internal_tag: { + name: tag.name, + object_type: "component" + } + }) + .then((response) => response.data.internal_tag || {}) + .catch((error) => Promise.reject(error)); +} + +const pushComponents = async (api, { source, presetsSource }) => { + try { + const rawComponents = await getDataFromPath(source) + const components = createContentList(rawComponents, 'components') + const rawPresets = await getDataFromPath(presetsSource) + const presets = createContentList(rawPresets, 'presets') + const componentGroups = createContentList(rawComponents, 'component_groups') + + return push(api, components, componentGroups, presets) + } catch (err) { + console.error(`${chalk.red('X')} Can not push invalid json - please provide a valid json file`) + return Promise.reject(new Error('Can not push invalid json - please provide a valid json file')) + } +} + +const buildComponentsGroupsTree = (groups) => { + const map = new Map() + const roots = [] + + groups.forEach(component => { + map.set(component.id, { ...component, children: [] }) + }); + + groups.forEach(component => { + if (component.parent_id === null) { + roots.push(map.get(component.id)) + } else { + const parent = map.get(component.parent_id) + if (parent) { + parent.children.push(map.get(component.id)) + } + } + }) + + return roots + } + +const pushComponentsGroups = async (api, group) => { + const groupName = group.name + + try { + console.log(`${chalk.blue('-')} Creating the ${groupName} component group...`) + const newGroup = await api.post('component_groups', { + component_group: { + name: groupName, + parent_id: group.parent_id + } + }) + + listOfGroups.push(newGroup.data.component_group) + + for (const child of group.children) { + const children = { + ...child, + parent_id: newGroup.data.component_group.id + } + + await pushComponentsGroups(api, children) + } + + } catch (err) { + console.log(err) + console.log( + `${chalk.yellow('-')} Components group ${groupName} already exists...` + ) + } +} + +const push = async (api, components, componentsGroups = [], presets = []) => { + const targetSpaceId = api.spaceId + const presetsLib = new PresetsLib({ oauthToken: api.accessToken, targetSpaceId, }) + const apiClient = api.getClient() + try { + const componentGroupsTree = buildComponentsGroupsTree(componentsGroups) + + for (const rootComponent of componentGroupsTree) { + await pushComponentsGroups(api, rootComponent) + } + + const apiComponents = await api.getComponents() + + for (let i = 0; i < components.length; i++) { + const componentPresets = presetsLib.getComponentPresets(components[i], presets) + const defaultPreset = componentPresets.find(preset => preset.id === components[i].preset_id) + delete components[i].id + delete components[i].created_at + + const groupName = components[i].component_group_name + const groupData = getGroupByName(listOfGroups, groupName) + + if (groupName) { + components[i].component_group_uuid = groupData.uuid + delete components[i].component_group_name + } + + const { internal_tags_list, internal_tag_ids } = components[i]; + const existingTags = await getSpaceInternalTags(apiClient, targetSpaceId); + + let processedInternalTagsIds = []; + if(internal_tags_list.length > 0) { + await internal_tags_list.forEach(async (tag) => { + const existingTag = existingTags.find(({ name }) => tag.name === name); + if(!existingTag) { + try { + const response = await createComponentInternalTag(apiClient, targetSpaceId, tag); + processedInternalTagsIds.push(response.id); + } catch (e) { + console.error(chalk.red("X") + ` Internal tag ${tag} creation failed: ${e.message}`); + } + } else { + processedInternalTagsIds.push(existingTag.id); + } + }) + } + + components[i].internal_tag_ids = processedInternalTagsIds || internal_tag_ids; + + + const schema = components[i].schema + if (schema) { + Object.keys(schema).forEach(field => { + if (schema[field].component_group_whitelist) { + schema[field].component_group_whitelist = schema[field].component_group_whitelist.map(uuid => + getGroupByUuid(listOfGroups, uuid) ? getGroupByUuid(listOfGroups, uuid).uuid : uuid + ) + } + }) + } + + const exists = apiComponents.filter(function (comp) { + return comp.name === components[i].name + }) + + if (exists.length > 0) { + const { id, name } = exists[0] + console.log(`${chalk.blue('-')} Updating component ${name}...`) + const componentTarget = await api.get(`components/${id}`) + + try { + const presetsToSave = presetsLib.filterPresetsFromTargetComponent( + componentPresets || [], + componentTarget.data.component.all_presets || [] + ) + if (presetsToSave.newPresets.length) { + await presetsLib.createPresets(presetsToSave.newPresets, componentTarget.data.component.id, 'post') + } + if (presetsToSave.updatePresets.length) { + await presetsLib.createPresets(presetsToSave.updatePresets, componentTarget.data.component.id, 'put') + } + if (defaultPreset) { + const defaultPresetInTarget = await presetsLib.getSamePresetFromTarget(api.spaceId, componentTarget.data.component, defaultPreset) + if (defaultPresetInTarget) { + components[i].preset_id = defaultPresetInTarget.id + } + } + + await api.put(`components/${id}`, { + component: components[i] + }) + + console.log(`${chalk.green('βœ“')} Component ${name} has been updated in Storyblok!`) + } catch (e) { + console.error(`${chalk.red('X')} An error occurred when update component ${name}`) + console.error(e.message) + } + } else { + const { name } = components[i] + console.log(`${chalk.blue('-')} Creating component ${name}...`) + try { + const componentRes = await api.post('components', { + component: components[i] + }) + if (componentPresets) { + await presetsLib.createPresets(componentPresets, componentRes.data.component.id) + + if (defaultPreset) { + const defaultPresetInTarget = await presetsLib.getSamePresetFromTarget(api.spaceId, componentRes.data.component, defaultPreset) + if (defaultPresetInTarget) { + components[i].preset_id = defaultPresetInTarget.id + + await api.put(`components/${componentRes.data.component.id}`, { + component: components[i] + }) + } + } + } + console.log(`${chalk.green('βœ“')} Component ${name} has been updated in Storyblok!`) + } catch (e) { + console.error(`${chalk.red('X')} An error occurred when create component`) + console.error(e.message) + } + } + } + + return Promise.resolve(true) + } catch (e) { + console.error(`${chalk.red('X')} An error occurred when load components file from api`) + return Promise.reject(e.message) + } +} + +export default pushComponents diff --git a/src/tasks/quickstart.js b/src/tasks/quickstart.js new file mode 100644 index 00000000..9e8bfa69 --- /dev/null +++ b/src/tasks/quickstart.js @@ -0,0 +1,91 @@ +import open from 'open' +import chalk from 'chalk' +import lastStep from '../utils/last-step' + +const hasSpaceId = spaceId => typeof spaceId !== 'undefined' + +const quickstart = async (api, answers, spaceId) => { + answers.type = 'quickstart' + + if (hasSpaceId(spaceId)) { + console.log(`${chalk.blue('-')} Your project will be initialized now...`) + + const config = { + space: { + environments: [{ name: 'Dev', location: 'http://localhost:4440/' }] + } + } + + const spaceUrl = 'spaces/' + spaceId + '/' + + try { + const spaceRes = await api.put(spaceUrl, config) + answers.name = spaceRes.data.space.name.replace(/\s+/g, '-').toLowerCase() + console.log(`${chalk.green('βœ“')} - Space ${answers.name} updated with dev environment in Storyblok`) + + const tokensRes = await api.post('tokens', {}) + console.log(`${chalk.green('βœ“')} - Configuration for your Space was loaded`) + answers.loginToken = tokensRes.data.key + + api.setSpaceId(spaceRes.data.space.id) + + const keysRes = await api.get('api_keys') + + answers.spaceId = spaceRes.data.space.id + answers.spaceDomain = spaceRes.data.space.domain.replace('https://', '').replace('/', '') + + const tokens = keysRes.data.api_keys.filter((token) => { + return token.access === 'theme' + }) + + if (tokens.length === 0) { + throw new Error('There is no theme token in this space') + } + + answers.themeToken = tokens[0].token + + console.log(`${chalk.green('βœ“')} - Development Environment configured (./${answers.name}/config.js)`) + return lastStep(answers) + } catch (e) { + return Promise.reject(new Error(e.message)) + } + } + + console.log(`${chalk.blue('-')} Your project will be created now...`) + + const config = { + // create_demo: true, + space: { + name: answers.name + } + } + + try { + const spaceRes = await api.post('spaces', config) + console.log(`${chalk.green('βœ“')} - Space ${answers.name} has been created in Storyblok`) + console.log(`${chalk.green('βœ“')} - Story "home" has been created in your Space`) + + const tokensRes = await api.post('tokens', {}) + + api.setSpaceId(spaceRes.data.space.id) + + console.log(`${chalk.green('βœ“')} - Configuration for your Space was loaded`) + answers.loginToken = tokensRes.data.key + + answers.spaceId = spaceRes.data.space.id + answers.spaceDomain = spaceRes.data.space.domain.replace('https://', '') + .replace('/', '') + + console.log(`${chalk.green('βœ“')} - Starting Storyblok in your browser`) + + setTimeout(() => { + open('http://' + answers.spaceDomain + '/_quickstart?quickstart=' + answers.loginToken) + process.exit(0) + }, 2000) + } catch (e) { + console.error(`${chalk.red('X')} An error occurred when create space and execute some tasks`) + console.error(e) + } +} + +export default quickstart diff --git a/src/tasks/scaffold.js b/src/tasks/scaffold.js new file mode 100644 index 00000000..99710a13 --- /dev/null +++ b/src/tasks/scaffold.js @@ -0,0 +1,29 @@ +import fs from 'fs' + +export default async function (api, name, space) { + if (space) { + if (!api.accessToken) { + return Promise.reject(new Error('The user is not logged')) + } + + api.setSpaceId(space) + + return api.post('components', { + component: { + name + } + }) + } + + var liquid = './views/components/_' + name + '.liquid' + console.log('Writing template file to ' + liquid) + fs.writeFileSync(liquid, await import('./templates/teaser')) + + var scss = './source/scss/components/below/_' + name + '.scss' + console.log('Writing scss file to ' + scss) + fs.writeFileSync(scss, '// Generated by the Storyblok cli') + + console.log() + + return Promise.resolve(true) +} diff --git a/src/tasks/sync-commands/component-groups.js b/src/tasks/sync-commands/component-groups.js new file mode 100644 index 00000000..acd7b56b --- /dev/null +++ b/src/tasks/sync-commands/component-groups.js @@ -0,0 +1,140 @@ +import chalk from 'chalk' +import { findByProperty } from '../../utils' +import api from '../../utils/api' + +class SyncComponentGroups { + /** + * @param {{ sourceSpaceId: string, targetSpaceId: string, oauthToken: string }} options + */ + constructor (options) { + this.sourceSpaceId = options.sourceSpaceId + this.targetSpaceId = options.targetSpaceId + this.oauthToken = options.oauthToken + + this.sourceComponentGroups = [] + this.targetComponentGroups = [] + + this.client = api.getClient() + } + + async init () { + console.log(`${chalk.green('-')} Syncing component groups...`) + + try { + this.sourceComponentGroups = await this.getComponentGroups( + this.sourceSpaceId + ) + + this.targetComponentGroups = await this.getComponentGroups( + this.targetSpaceId + ) + return Promise.resolve(true) + } catch (e) { + console.error( + `${chalk.red('-')} Error on load components groups from source and target spaces: ${e.message}` + ) + return Promise.reject(e) + } + } + + async sync () { + try { + await this.init() + + for (const sourceGroup of this.sourceComponentGroups) { + console.log() + console.log( + chalk.blue('-') + ` Processing component group ${sourceGroup.name}` + ) + + const targetGroupData = findByProperty( + this.targetComponentGroups, + 'name', + sourceGroup.name + ) + + if (!targetGroupData.uuid) { + // the group don't exists in target space, creating one + const sourceGroupName = sourceGroup.name + + try { + console.log( + `${chalk.blue('-')} Creating the ${sourceGroupName} component group` + ) + const groupCreated = await this.createComponentGroup( + this.targetSpaceId, + sourceGroupName + ) + + this.targetComponentGroups.push(groupCreated) + + console.log( + `${chalk.green('βœ“')} Component group ${sourceGroupName} created` + ) + } catch (e) { + console.error( + `${chalk.red('X')} Component Group ${sourceGroupName} creating failed: ${e.message}` + ) + } + } else { + console.log( + `${chalk.green('βœ“')} Component group ${targetGroupData.name} already exists` + ) + } + } + + return this.loadComponentsGroups() + } catch (e) { + console.error( + `${chalk.red('-')} Error on sync component groups: ${e.message}` + ) + return Promise.reject(e) + } + } + + /** + * @method getComponentGroups + * @return {Promise} + */ + async getComponentGroups (spaceId) { + console.log( + `${chalk.green('-')} Load component groups from space #${spaceId}` + ) + + return this.client + .get(`spaces/${spaceId}/component_groups`) + .then(response => response.data.component_groups || []) + .catch(err => Promise.reject(err)) + } + + /** + * @method loadComponentsGroups + * @return {{source: Array, target: Array}} + */ + loadComponentsGroups () { + return { + source: this.sourceComponentGroups, + target: this.targetComponentGroups + } + } + + /** + * @method createComponentGroup + * @param {string} spaceId + * @param {string} componentGroupName + * @return {{source: Array, target: Array}} + */ + createComponentGroup (spaceId, componentGroupName) { + return this + .client + .post(`spaces/${spaceId}/component_groups`, { + component_group: { + name: componentGroupName + } + }) + .then(response => response.data.component_group || {}) + .catch(error => Promise.reject(error)) + } +} + +export default SyncComponentGroups diff --git a/src/tasks/sync-commands/components.js b/src/tasks/sync-commands/components.js new file mode 100644 index 00000000..190bc7d5 --- /dev/null +++ b/src/tasks/sync-commands/components.js @@ -0,0 +1,314 @@ +import chalk from 'chalk' +import lodash from 'lodash' +import SyncComponentGroups from './component-groups' +import { findByProperty } from '../../utils' +import PresetsLib from '../../utils/presets-lib' +import api from '../../utils/api' +const { find } = lodash + +class SyncComponents { + /** + * @param {{ sourceSpaceId: string, targetSpaceId: string, oauthToken: string }} options + */ + constructor (options) { + this.sourcePresets = [] + this.targetComponents = [] + this.sourceComponents = [] + this.sourceSpaceId = options.sourceSpaceId + this.targetSpaceId = options.targetSpaceId + this.oauthToken = options.oauthToken + this.client = api.getClient() + this.presetsLib = new PresetsLib({ oauthToken: options.oauthToken, targetSpaceId: this.targetSpaceId }) + this.componentsGroups = options.componentsGroups + this.componentsFullSync = options.componentsFullSync + } + + async sync () { + const syncComponentGroupsInstance = new SyncComponentGroups({ + oauthToken: this.oauthToken, + sourceSpaceId: this.sourceSpaceId, + targetSpaceId: this.targetSpaceId + }) + + try { + const componentsGroupsSynced = await syncComponentGroupsInstance.sync() + this.sourceComponentGroups = componentsGroupsSynced.source + this.targetComponentGroups = componentsGroupsSynced.target + + console.log(`${chalk.green('-')} Syncing components...`) + // load data from target and source spaces + this.sourceComponents = await this.getComponents(this.sourceSpaceId) + this.targetComponents = await this.getComponents(this.targetSpaceId) + + this.sourcePresets = await this.presetsLib.getPresets(this.sourceSpaceId) + + console.log( + `${chalk.blue('-')} In source space #${this.sourceSpaceId}, it were found: ` + ) + console.log(` - ${this.sourcePresets.length} presets`) + console.log(` - ${this.sourceComponentGroups.length} groups`) + console.log(` - ${this.sourceComponents.length} components`) + + console.log( + `${chalk.blue('-')} In target space #${this.targetSpaceId}, it were found: ` + ) + console.log(` - ${this.targetComponentGroups.length} groups`) + console.log(` - ${this.targetComponents.length} components`) + } catch (e) { + console.error('An error ocurred when load data to sync: ' + e.message) + + return Promise.reject(e) + } + + for (var i = 0; i < this.sourceComponents.length; i++) { + console.log() + + const component = this.sourceComponents[i] + console.log(chalk.blue('-') + ` Processing component ${component.name}`) + + const componentPresets = this.presetsLib.getComponentPresets(component, this.sourcePresets) + + delete component.id + delete component.created_at + + const sourceGroupUuid = component.component_group_uuid + + if (this.componentsGroups && !this.componentsGroups.includes(sourceGroupUuid)) { + console.log( + chalk.yellow('-') + + ` Component ${component.name} does not belong to the ${this.componentsGroups} group(s).` + ) + continue + } + + // if the component belongs to a group + if (sourceGroupUuid) { + const sourceGroup = findByProperty( + this.sourceComponentGroups, + 'uuid', + sourceGroupUuid + ) + + const targetGroupData = findByProperty( + this.targetComponentGroups, + 'name', + sourceGroup.name + ) + + console.log( + `${chalk.yellow('-')} Linking the component to the group ${targetGroupData.name}` + ) + component.component_group_uuid = targetGroupData.uuid + } + + const { internal_tags_list, internal_tag_ids, ...rest } = component; + const existingTags = await this.getSpaceInternalTags(this.targetSpaceId); + + let processedInternalTagsIds = []; + if(internal_tags_list.length > 0) { + await internal_tags_list.forEach(async (tag) => { + const existingTag = existingTags.find(({ name }) => tag.name === name); + if(!existingTag) { + try { + const response = await this.createComponentInternalTag(this.targetSpaceId, tag); + processedInternalTagsIds.push(response.id); + } catch (e) { + console.error(chalk.red("X") + ` Internal tag ${tag} creation failed: ${e.message}`); + } + } else { + processedInternalTagsIds.push(existingTag.id); + } + }) + } + + // Create new component on target space + const componentData = { + ...rest, + internal_tag_ids: processedInternalTagsIds || internal_tag_ids + } + try { + const componentCreated = await this.createComponent( + this.targetSpaceId, + componentData + ) + + console.log(chalk.green('βœ“') + ` Component ${component.name} created`) + + if (componentPresets.length) { + await this.presetsLib.createPresets(componentPresets, componentCreated.id) + } + } catch (e) { + if ((e.response && e.response.status) || e.status === 422) { + console.log( + `${chalk.yellow('-')} Component ${component.name} already exists, updating it...` + ) + + const componentTarget = this.getTargetComponent(component.name) + + await this.updateComponent( + this.targetSpaceId, + componentTarget.id, + componentData, + componentTarget + ) + console.log(chalk.green('βœ“') + ` Component ${component.name} synced`) + + const presetsToSave = this.presetsLib.filterPresetsFromTargetComponent( + componentPresets || [], + componentTarget.all_presets || [] + ) + + if (presetsToSave.newPresets.length) { + await this.presetsLib.createPresets(presetsToSave.newPresets, componentTarget.id, 'post') + } + + if (presetsToSave.updatePresets.length) { + await this.presetsLib.createPresets(presetsToSave.updatePresets, componentTarget.id, 'put') + } + + console.log(chalk.green('βœ“') + ' Presets in sync') + } else { + console.error(chalk.red('X') + ` Component ${component.name} sync failed: ${e.message}`) + } + } + } + } + + getComponents (spaceId) { + console.log( + `${chalk.green('-')} Load components from space #${spaceId}` + ) + + return this.client.get(`spaces/${spaceId}/components`) + .then(response => response.data.components || []) + } + + getTargetComponent (name) { + return find(this.targetComponents, ['name', name]) || {} + } + + createComponent (spaceId, componentData) { + const payload = { + component: { + ...componentData, + schema: this.mergeComponentSchema( + componentData.schema + ) + } + } + + return this + .client + .post(`spaces/${spaceId}/components`, payload) + .then(response => { + const component = response.data.component || {} + + return component + }) + .catch(error => Promise.reject(error)) + } + + updateComponent ( + spaceId, + componentId, + sourceComponentData, + targetComponentData + ) { + const payload = { + component: this.mergeComponents( + sourceComponentData, + targetComponentData + ) + } + // Unfortunatelly, the internal_tag_ids is not recursive and bot being merged correctly + payload.component.internal_tag_ids = sourceComponentData.internal_tag_ids + return this + .client + .put(`spaces/${spaceId}/components/${componentId}`, payload) + .then(response => { + const component = response.data.component || {} + + return component + }).catch(error => Promise.reject(error)) + } + + mergeComponents (sourceComponent, targetComponent = {}) { + const data = this.componentsFullSync ? { + // This should be the default behavior in a major future version + ...sourceComponent + } : { + ...sourceComponent, + ...targetComponent + } + + // handle specifically + data.schema = this.mergeComponentSchema( + sourceComponent.schema + ) + + return data + } + + mergeComponentSchema (sourceSchema) { + return Object.keys(sourceSchema).reduce((acc, key) => { + // handle blocks separately + const sourceSchemaItem = sourceSchema[key] + const isBloksType = sourceSchemaItem && sourceSchemaItem.type === 'bloks' + const isRichtextType = sourceSchemaItem && sourceSchemaItem.type === 'richtext' + + if (isBloksType || isRichtextType) { + acc[key] = this.mergeBloksSchema(sourceSchemaItem) + return acc + } + + acc[key] = sourceSchemaItem + + return acc + }, {}) + } + + mergeBloksSchema (sourceData) { + return { + ...sourceData, + // prevent missing refence to group in whitelist + component_group_whitelist: this.getWhiteListFromSourceGroups( + sourceData.component_group_whitelist || [] + ) + } + } + + getWhiteListFromSourceGroups (whiteList = []) { + return whiteList.map(sourceGroupUuid => { + const sourceGroupData = findByProperty( + this.sourceComponentGroups, + 'uuid', + sourceGroupUuid + ) + + const targetGroupData = findByProperty( + this.targetComponentGroups, + 'name', + sourceGroupData.name + ) + + return targetGroupData.uuid + }) + } + + getSpaceInternalTags(spaceId) { + return this.client.get(`spaces/${spaceId}/internal_tags`).then((response) => response.data.internal_tags || []); + } + + createComponentInternalTag(spaceId, tag) { + return this.client.post(`spaces/${spaceId}/internal_tags`, { + internal_tag: { + name: tag.name, + object_type: "component" + } + }) + .then((response) => response.data.internal_tag || {}) + .catch((error) => Promise.reject(error)); + } +} + +export default SyncComponents diff --git a/src/tasks/sync-commands/datasources.js b/src/tasks/sync-commands/datasources.js new file mode 100644 index 00000000..2321ddd7 --- /dev/null +++ b/src/tasks/sync-commands/datasources.js @@ -0,0 +1,305 @@ +import chalk from 'chalk' +import UUID from 'simple-uuid' +import api from '../../utils/api' + +class SyncDatasources { + /** + * @param {{ sourceSpaceId: string, targetSpaceId: string, oauthToken: string }} options + */ + constructor (options) { + this.targetDatasources = [] + this.sourceDatasources = [] + this.sourceSpaceId = options.sourceSpaceId + this.targetSpaceId = options.targetSpaceId + this.oauthToken = options.oauthToken + this.client = api.getClient() + } + + async sync () { + try { + this.targetDatasources = await this.client.getAll(`spaces/${this.targetSpaceId}/datasources`) + this.sourceDatasources = await this.client.getAll(`spaces/${this.sourceSpaceId}/datasources`) + + console.log( + `${chalk.blue('-')} In source space #${this.sourceSpaceId}: ` + ) + console.log(` - ${this.sourceDatasources.length} datasources`) + + console.log( + `${chalk.blue('-')} In target space #${this.targetSpaceId}: ` + ) + console.log(` - ${this.targetDatasources.length} datasources`) + } catch (err) { + console.error(`An error ocurred when loading the datasources: ${err.message}`) + + return Promise.reject(err) + } + + console.log(chalk.green('-') + ' Syncing datasources...') + await this.addDatasources() + await this.updateDatasources() + } + + async getDatasourceEntries (spaceId, datasourceId, dimensionId = null) { + const dimensionQuery = dimensionId ? `&dimension=${dimensionId}` : '' + try { + const entriesFirstPage = await this.client.get(`spaces/${spaceId}/datasource_entries/?datasource_id=${datasourceId}${dimensionQuery}`) + const entriesRequets = [] + for (let i = 2; i <= Math.ceil(entriesFirstPage.total / 25); i++) { + entriesRequets.push(await this.client.get(`spaces/${spaceId}/datasource_entries?datasource_id=${datasourceId}&page=${i}${dimensionQuery}`)) + } + return entriesFirstPage.data.datasource_entries.concat(...(await Promise.all(entriesRequets)).map(r => r.data.datasource_entries)) + } catch (err) { + console.error(`An error ocurred when loading the entries of the datasource #${datasourceId}: ${err.message}`) + + return Promise.reject(err) + } + } + + async addDatasourceEntry (entry, datasourceId) { + try { + return this.client.post(`spaces/${this.targetSpaceId}/datasource_entries/`, { + datasource_entry: { + name: entry.name, + value: entry.value, + datasource_id: datasourceId + } + }) + } catch (err) { + console.error(`An error ocurred when creating the datasource entry ${entry.name}: ${err.message}`) + + return Promise.reject(err) + } + } + + async updateDatasourceEntry (entry, newData, datasourceId) { + try { + return this.client.put(`spaces/${this.targetSpaceId}/datasource_entries/${entry.id}`, { + datasource_entry: { + name: newData.name, + value: newData.value, + datasource_id: datasourceId + } + }) + } catch (err) { + console.error(`An error ocurred when updating the datasource entry ${entry.name}: ${err.message}`) + + return Promise.reject(err) + } + } + + async syncDatasourceEntries (datasourceId, targetId) { + try { + const sourceEntries = await this.getDatasourceEntries(this.sourceSpaceId, datasourceId) + const targetEntries = await this.getDatasourceEntries(this.targetSpaceId, targetId) + const updateEntries = targetEntries.filter(e => sourceEntries.map(se => se.name).includes(e.name)) + const addEntries = sourceEntries.filter(e => !targetEntries.map(te => te.name).includes(e.name)) + + /* Update entries */ + const entriesUpdateRequests = [] + for (let j = 0; j < updateEntries.length; j++) { + const sourceEntry = sourceEntries.find(d => d.name === updateEntries[j].name) + entriesUpdateRequests.push(this.updateDatasourceEntry(updateEntries[j], sourceEntry, targetId)) + } + await Promise.all(entriesUpdateRequests) + + /* Add entries */ + const entriesCreationRequests = [] + for (let j = 0; j < addEntries.length; j++) { + entriesCreationRequests.push(this.addDatasourceEntry(addEntries[j], targetId)) + } + await Promise.all(entriesCreationRequests) + } catch (err) { + console.error(`An error ocurred when syncing the datasource entries: ${err.message}`) + + return Promise.reject(err) + } + } + + async addDatasources () { + const datasourcesToAdd = this.sourceDatasources.filter(d => !this.targetDatasources.map(td => td.slug).includes(d.slug)) + if (datasourcesToAdd.length) { + console.log( + `${chalk.green('-')} Adding new datasources to target space #${this.targetSpaceId}...` + ) + } + + for (let i = 0; i < datasourcesToAdd.length; i++) { + try { + console.log(` ${chalk.green('-')} Creating datasource ${datasourcesToAdd[i].name} (${datasourcesToAdd[i].slug})`) + /* Create the datasource */ + const newDatasource = await this.client.post(`spaces/${this.targetSpaceId}/datasources`, { + name: datasourcesToAdd[i].name, + slug: datasourcesToAdd[i].slug + }) + + if (datasourcesToAdd[i].dimensions.length) { + console.log( + ` ${chalk.blue('-')} Creating dimensions...` + ) + const { data } = await this.createDatasourcesDimensions(datasourcesToAdd[i].dimensions, newDatasource.data.datasource) + await this.syncDatasourceEntries(datasourcesToAdd[i].id, newDatasource.data.datasource.id) + console.log( + ` ${chalk.blue('-')} Sync dimensions values...` + ) + await this.syncDatasourceDimensionsValues(datasourcesToAdd[i], data.datasource) + console.log(` ${chalk.green('βœ“')} Created datasource ${datasourcesToAdd[i].name}`) + } else { + await this.syncDatasourceEntries(datasourcesToAdd[i].id, newDatasource.data.datasource.id) + console.log(` ${chalk.green('βœ“')} Created datasource ${datasourcesToAdd[i].name}`) + } + } catch (err) { + console.error( + `${chalk.red('X')} Datasource ${datasourcesToAdd[i].name} creation failed: ${err.response.data.error || err.message}` + ) + } + } + } + + async updateDatasources () { + const datasourcesToUpdate = this.targetDatasources.filter(d => this.sourceDatasources.map(sd => sd.slug).includes(d.slug)) + if (datasourcesToUpdate.length) { + console.log( + `${chalk.green('-')} Updating datasources In target space #${this.targetSpaceId}...` + ) + } + + for (let i = 0; i < datasourcesToUpdate.length; i++) { + try { + /* Update the datasource */ + const sourceDatasource = this.sourceDatasources.find(d => d.slug === datasourcesToUpdate[i].slug) + + await this.client.put(`spaces/${this.targetSpaceId}/datasources/${datasourcesToUpdate[i].id}`, { + name: sourceDatasource.name, + slug: sourceDatasource.slug + }) + + if (datasourcesToUpdate[i].dimensions.length) { + console.log(` ${chalk.blue('-')} Updating datasources dimensions ${datasourcesToUpdate[i].name}...`) + const sourceDimensionsNames = sourceDatasource.dimensions.map((dimension) => dimension.name) + const targetDimensionsNames = datasourcesToUpdate[i].dimensions.map((dimension) => dimension.name) + const intersection = sourceDimensionsNames.filter(item => !targetDimensionsNames.includes(item)) + let datasourceToSyncDimensionsValues = datasourcesToUpdate[i] + + if (intersection) { + const dimensionsToCreate = sourceDatasource.dimensions.filter((dimension) => { + if (intersection.includes(dimension.name)) return dimension + }) + const { data } = await this.createDatasourcesDimensions(dimensionsToCreate, datasourcesToUpdate[i], true) + datasourceToSyncDimensionsValues = data.datasource + } + + await this.syncDatasourceEntries(sourceDatasource.id, datasourcesToUpdate[i].id) + + await this.syncDatasourceDimensionsValues(sourceDatasource, datasourceToSyncDimensionsValues) + console.log(`${chalk.green('βœ“')} Updated datasource ${datasourcesToUpdate[i].name}`) + } else { + await this.syncDatasourceEntries(sourceDatasource.id, datasourcesToUpdate[i].id) + console.log(`${chalk.green('βœ“')} Updated datasource ${datasourcesToUpdate[i].name}`) + } + } catch (err) { + console.error( + `${chalk.red('X')} Datasource ${datasourcesToUpdate[i].name} update failed: ${err.message}` + ) + } + } + } + + async createDatasourcesDimensions (dimensions, datasource, isToUpdate = false) { + const newDimensions = dimensions.map((dimension) => { + return { + name: dimension.name, + entry_value: dimension.entry_value, + datasource_id: datasource.id, + _uid: UUID() + } + }) + + let payload = null + + if (isToUpdate) { + payload = { + dimensions: [...datasource.dimensions, ...newDimensions], + dimensions_attributes: [...datasource.dimensions, ...newDimensions] + } + } else { + payload = { + dimensions: newDimensions, + dimensions_attributes: newDimensions + } + } + + try { + return await this.client.put(`spaces/${this.targetSpaceId}/datasources/${datasource.id}`, { + ...datasource, + ...payload + }) + } catch (error) { + console.error(error) + } + } + + async syncDatasourceDimensionsValues (sourceDatasource, targetDatasource) { + const sourceEntriesPromisses = [] + const targetEmptyEntriesPromisses = [] + try { + for (let index = 0; index < sourceDatasource.dimensions.length; index++) { + const targetDimensionId = targetDatasource.dimensions[index].id + sourceEntriesPromisses.push(...await this.getDatasourceEntries(this.sourceSpaceId, sourceDatasource.id, sourceDatasource.dimensions[index].id)) + targetEmptyEntriesPromisses.push( + ...await this.getDatasourceEntries(this.targetSpaceId, targetDatasource.id, targetDimensionId).then((res) => { + return res.map((entry) => { + return { + ...entry, + target_dimension_id: targetDimensionId + } + }) + }) + ) + } + + await Promise.all(sourceEntriesPromisses) + await Promise.all(targetEmptyEntriesPromisses) + + const targetEntriesPromisses = [] + + while (sourceEntriesPromisses.length !== 0) { + const currentSourceEntry = sourceEntriesPromisses[0] + const targetEntryIndex = targetEmptyEntriesPromisses.findIndex((tEntry) => tEntry.name === currentSourceEntry.name) + const currentTargetEntry = targetEmptyEntriesPromisses[targetEntryIndex] + const valuesAreEqual = currentTargetEntry.dimension_value === currentSourceEntry.dimension_value + + if (valuesAreEqual) { + sourceEntriesPromisses.shift() + targetEmptyEntriesPromisses.splice(targetEntryIndex, 1) + } else { + const payload = { + ...currentTargetEntry, + dimension_value: currentSourceEntry.dimension_value + } + + targetEntriesPromisses.push(await this.syncDimensionEntryValues(currentTargetEntry.target_dimension_id, currentTargetEntry.id, payload)) + sourceEntriesPromisses.shift() + targetEmptyEntriesPromisses.splice(targetEntryIndex, 1) + } + } + + await Promise.all(targetEntriesPromisses) + } catch (error) { + console.error(` ${chalk.red('X')} Sync dimensions values failed: ${error.response.data.error || error.message || error}`) + } + } + + async syncDimensionEntryValues (dimensionId = null, datasourceEntryId = null, payload = null) { + try { + await this.client.put(`spaces/${this.targetSpaceId}/datasource_entries/${datasourceEntryId}`, { + datasource_entry: payload, + dimension_id: dimensionId + }) + } catch (error) { + console.error(` ${chalk.red('X')} Sync entry error ${payload.name} sync failed: ${error.response.data.error || error.message}`) + } + } +} + +export default SyncDatasources diff --git a/src/tasks/sync.js b/src/tasks/sync.js new file mode 100644 index 00000000..70a02fbc --- /dev/null +++ b/src/tasks/sync.js @@ -0,0 +1,309 @@ +import pSeries from 'p-series' +import chalk from 'chalk' +import SyncComponents from './sync-commands/components' +import SyncDatasources from './sync-commands/datasources' +import { capitalize } from '../utils' + +const SyncSpaces = { + targetComponents: [], + sourceComponents: [], + + init (options) { + const { api } = options + console.log(chalk.green('βœ“') + ' Loading options') + this.client = api.getClient() + this.sourceSpaceId = options.source + this.targetSpaceId = options.target + this.oauthToken = options.token + this.componentsGroups = options._componentsGroups + this.componentsFullSync = options._componentsFullSync + this.startsWith = options.startsWith + this.filterQuery = options.filterQuery + }, + + async getStoryWithTranslatedSlugs (sourceStory, targetStory) { + const storyForPayload = { ...sourceStory } + if (sourceStory.translated_slugs) { + const sourceTranslatedSlugs = sourceStory.translated_slugs.map(s => { + delete s.id + return s + }) + if (targetStory) { + const storyData = await this.client.get('spaces/' + this.targetSpaceId + '/stories/' + targetStory.id) + if (storyData.data.story && storyData.data.story.translated_slugs) { + const targetTranslatedSlugs = storyData.data.story.translated_slugs + sourceTranslatedSlugs.forEach(translation => { + if (targetTranslatedSlugs.find(t => t.lang === translation.lang)) { + translation.id = targetTranslatedSlugs.find(t => t.lang === translation.lang).id + } + }) + } + } + storyForPayload.translated_slugs_attributes = sourceTranslatedSlugs + delete storyForPayload.translated_slugs + } + return storyForPayload + }, + + async getTargetFolders () { + const targetFolders = await this.client.getAll(`spaces/${this.targetSpaceId}/stories`, { + folder_only: 1, + sort_by: 'slug:asc' + }) + + const folderMapping = {} + + for (const folder of targetFolders) { + folderMapping[folder.full_slug] = folder.id + } + + return folderMapping + }, + + async updateStoriesAndFolders (data, targetContent = null, sourceContent = null, isFolder = false) { + let createdStory = null + const contentName = sourceContent.name + const contentTypeName = isFolder ? 'Folder' : 'Story' + const payload = { + story: data, + force_update: '1', + ...(!isFolder && sourceContent.published ? { publish: 1 } : {}) + } + + if (targetContent) { + console.log(`${chalk.yellow('-')} ${contentTypeName} ${contentName} already exists`) + createdStory = await this.client.put(`spaces/${this.targetSpaceId}/stories/${targetContent.id}`, payload) + console.log(`${chalk.green('βœ“')} ${contentTypeName} ${targetContent.full_slug} updated`) + } else { + createdStory = await this.client.post(`spaces/${this.targetSpaceId}/stories`, payload) + console.log(`${chalk.green('βœ“')} ${contentTypeName} ${sourceContent.full_slug} created`) + } + + createdStory = createdStory.data.story + + if (createdStory.uuid !== sourceContent.uuid) { + await this.client.put(`spaces/${this.targetSpaceId}/stories/${createdStory.id}/update_uuid`, { uuid: sourceContent.uuid }) + } + + return createdStory + }, + + async syncStories () { + console.log(chalk.green('βœ“') + ' Syncing stories...') + + const folderMapping = { ...await this.getTargetFolders() } + + const allStories = await this.client.getAll(`spaces/${this.sourceSpaceId}/stories`, { + story_only: 1, + ...(this.startsWith ? { starts_with: this.startsWith } : {}), + ...(this.filterQuery ? { filter_query: this.filterQuery } : {}) + }) + + for (const story of allStories) { + console.log(chalk.green('βœ“') + ' Starting update ' + story.full_slug) + + const { data } = await this.client.get(`spaces/${this.sourceSpaceId}/stories/${story.id}`) + const sourceStory = data.story + const slugs = sourceStory.full_slug.split('/') + let folderId = 0 + + if (slugs.length > 1) { + slugs.pop() + const folderSlug = slugs.join('/') + + if (folderMapping[folderSlug]) { + folderId = folderMapping[folderSlug] + } else { + console.error(`${chalk.red('X')} The folder does not exist ${folderSlug}`) + continue + } + } + + sourceStory.parent_id = folderId + + try { + const { data } = await this.client.get('spaces/' + this.targetSpaceId + '/stories', { with_slug: story.full_slug }) + const existingStory = data.stories[0] + const storyData = await this.getStoryWithTranslatedSlugs(sourceStory, existingStory ? existingStory[0] : null) + await this.updateStoriesAndFolders(storyData, existingStory, sourceStory) + } catch (e) { + console.error( + chalk.red('X') + ` Story ${story.name} Sync failed: ${e.message}` + ) + console.log(e) + } + } + + return Promise.resolve(allStories) + }, + + async syncFolders () { + console.log(chalk.green('βœ“') + ' Syncing folders...') + + const sourceFolderParams = { + folder_only: 1, + sort_by: 'slug:asc' + }; + + if (this.startsWith) { + sourceFolderParams.starts_with = this.startsWith; + } + + const sourceFolders = await this.client.getAll(`spaces/${this.sourceSpaceId}/stories`, sourceFolderParams) + const syncedFolders = {} + + for (const folder of sourceFolders) { + try { + + const targetFolderParams = { + with_slug: folder.full_slug, + }; + + if (this.startsWith) { + targetFolderParams.starts_with = this.startsWith; + } + + const folderResult = await this.client.get(`spaces/${this.sourceSpaceId}/stories/${folder.id}`) + const { data } = await this.client.get(`spaces/${this.targetSpaceId}/stories`, targetFolderParams) + const existingFolder = data.stories[0] || null + const folderData = await this.getStoryWithTranslatedSlugs(folderResult.data.story, existingFolder) + delete folderData.id + delete folderData.created_at + + if (folder.parent_id) { + // Parent child resolving + if (!syncedFolders[folder.id]) { + const folderSlug = folder.full_slug.split('/') + const parentFolderSlug = folderSlug.splice(0, folderSlug.length - 1).join('/') + + const parentFolders = await this.client.get(`spaces/${this.targetSpaceId}/stories`, { + with_slug: parentFolderSlug + }) + + if (parentFolders.data.stories.length) { + folderData.parent_id = parentFolders.data.stories[0].id + } else { + folderData.parent_id = 0 + } + } else { + folderData.parent_id = syncedFolders[folder.id] + } + } + + await this.updateStoriesAndFolders(folderData, existingFolder, folder, true) + } catch (e) { + console.error( + chalk.red('X') + ` Folder ${folder.name} Sync failed: ${e.message}` + ) + console.log(e) + } + } + }, + + async syncRoles () { + console.log(chalk.green('βœ“') + ' Syncing roles...') + const existingFolders = await this.client.getAll(`spaces/${this.targetSpaceId}/stories`, { + folder_only: 1, + sort_by: 'slug:asc' + }) + + const roles = await this.client.get(`spaces/${this.sourceSpaceId}/space_roles`) + const existingRoles = await this.client.get(`spaces/${this.targetSpaceId}/space_roles`) + + for (var i = 0; i < roles.data.space_roles.length; i++) { + const spaceRole = roles.data.space_roles[i] + delete spaceRole.id + delete spaceRole.created_at + + spaceRole.allowed_paths = [] + + spaceRole.resolved_allowed_paths.forEach((path) => { + const folders = existingFolders.filter((story) => { + return story.full_slug + '/' === path + }) + + if (folders.length) { + spaceRole.allowed_paths.push(folders[0].id) + } + }) + + const existingRole = existingRoles.data.space_roles.filter((role) => { + return role.role === spaceRole.role + }) + if (existingRole.length) { + await this.client.put(`spaces/${this.targetSpaceId}/space_roles/${existingRole[0].id}`, { + space_role: spaceRole + }) + } else { + await this.client.post(`spaces/${this.targetSpaceId}/space_roles`, { + space_role: spaceRole + }) + } + console.log(chalk.green('βœ“') + ` Role ${spaceRole.role} synced`) + } + }, + + async syncComponents () { + const syncComponentsInstance = new SyncComponents({ + sourceSpaceId: this.sourceSpaceId, + targetSpaceId: this.targetSpaceId, + oauthToken: this.oauthToken, + componentsGroups: this.componentsGroups, + componentsFullSync: this.componentsFullSync + }) + + try { + await syncComponentsInstance.sync() + } catch (e) { + console.error( + chalk.red('X') + ` Sync failed: ${e.message}` + ) + console.log(e) + + return Promise.reject(new Error(e)) + } + }, + + async syncDatasources () { + const syncDatasourcesInstance = new SyncDatasources({ + sourceSpaceId: this.sourceSpaceId, + targetSpaceId: this.targetSpaceId, + oauthToken: this.oauthToken + }) + + try { + await syncDatasourcesInstance.sync() + } catch (e) { + console.error( + chalk.red('X') + ` Datasources Sync failed: ${e.message}` + ) + console.log(e) + + return Promise.reject(new Error(e)) + } + } +} + +/** + * @method sync + * @param {Array} types + * @param {*} options { token: String, source: Number, target: Number, api: String } + * @return {Promise} + */ +const sync = (types, options) => { + SyncSpaces.init(options) + + const tasks = types.sort((a, b) => { + if (a === 'folders') return -1 + if (b === 'folders') return 1 + return 0 + }).map(_type => { + const command = `sync${capitalize(_type)}` + + return () => SyncSpaces[command]() + }) + + return pSeries(tasks) +} + +export default sync diff --git a/src/tasks/templates/migration-file.js b/src/tasks/templates/migration-file.js new file mode 100644 index 00000000..44b2cdf0 --- /dev/null +++ b/src/tasks/templates/migration-file.js @@ -0,0 +1,8 @@ +export default `export default function (block) { + // Example to change a string to boolean + // block.{{ fieldname }} = !!(block.{{ fieldname }}) + + // Example to transfer content from other field + // block.{{ fieldname }} = block.other_field +} +` diff --git a/src/tasks/templates/teaser.js b/src/tasks/templates/teaser.js new file mode 100644 index 00000000..10621f31 --- /dev/null +++ b/src/tasks/templates/teaser.js @@ -0,0 +1,25 @@ +export default `
+ + {{ blok._editable }} +
+ +

+ + {{ blok.headline }} +

+

+ Congrats, you have created the teaser template!
+ + Read the theme documentation + + +

+
+
` diff --git a/src/types/errors.ts b/src/types/errors.ts deleted file mode 100644 index 6f30e150..00000000 --- a/src/types/errors.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Interface representing an HTTP response error - */ -export interface ResponseError extends Error { - response?: { - status: number; - statusText: string; - data?: any; - }; -} diff --git a/src/types/generate-typescript-typedefs.ts b/src/types/generate-typescript-typedefs.ts new file mode 100644 index 00000000..0c3c959e --- /dev/null +++ b/src/types/generate-typescript-typedefs.ts @@ -0,0 +1,95 @@ +import type { JSONSchema, Options } from "json-schema-to-typescript"; +export type { ISbStoryData } from "storyblok-js-client"; + +export type StoryblokProvidedPropertyType = "asset" | "multiasset" | "multilink" | "table" | "richtext"; + +export type ComponentPropertySchemaType = + | StoryblokProvidedPropertyType + | "array" + | "bloks" + | "boolean" + | "custom" + | "datetime" + | "image" + | "markdown" + | "number" + | "option" + | "options" + | "text" + | "textarea"; + +export type JSONSchemaToTSOptions = Partial; + +export interface GenerateTypescriptTypedefsCLIOptions { + sourceFilePaths: string[]; + destinationFilePath?: string; + typeNamesPrefix?: string; + typeNamesSuffix?: string; + customFieldTypesParserPath?: string; + JSONSchemaToTSOptionsPath?: string; +} + +export interface ComponentPropertySchemaOption { + _uid: string; + name: string; + value: string; +} + +export type ComponentPropertySchema = { + asset_link_type?: boolean; + component_group_whitelist?: string[]; + component_whitelist?: string[]; + email_link_type?: boolean; + exclude_empty_option?: boolean; + filter_content_type?: string | string[]; + key: string; + options?: ComponentPropertySchemaOption[]; + pos: number; + restrict_components?: boolean; + restrict_type?: "groups" | ""; + source?: "internal" | "external" | "internal_stories" | "internal_languages"; + type: ComponentPropertySchemaType; + use_uuid?: boolean; +}; + +export type ComponentPropertyTypeAnnotation = + | { + tsType: string | string[]; + } + | { + type: string | string[]; + enum: string[]; + } + | { + type: string | string[]; + } + | { + type: "array"; + items: { + type: string | string[]; + }; + } + | { + type: "array"; + items: { + enum: string[]; + }; + }; + +export type GenerateTSTypedefsFromComponentsJSONSchemasOptions = { + sourceFilePaths: string[]; + destinationFilePath: string; + typeNamesPrefix?: string; + typeNamesSuffix?: string; + customFieldTypesParserPath?: string; + JSONSchemaToTSCustomOptions: JSONSchemaToTSOptions; +}; + +export type CustomTypeParser = (_typeName: string, _schema: ComponentPropertySchema) => Record; + +export type GetStoryblokProvidedPropertyTypeSchemaFn = (title: string) => JSONSchema; + +export type ComponentGroupsAndNamesObject = { + componentGroups: Map>; + componentNames: Set; +}; diff --git a/src/types/index.ts b/src/types/index.ts index 970a0e11..9197b395 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,64 +1 @@ -import type { RegionCode } from '../constants'; - -/** - * Interface representing the default options for a CLI command. - */ -export interface CommandOptions { - /** - * Indicates whether verbose output is enabled. - */ - verbose: boolean; -} - -// All these types should come from a general package - -/** - * Interface representing a language in Storyblok - */ -export interface Language { - name: string; - code: string; - fallback_code?: string; - ai_translation_code: string | null; -} - -export interface SpaceInternationalization { - languages: Language[]; - default_lang_name: string; -} - -export interface StoryblokUser { - id: number; - email: string; - username: string; - friendly_name: string; - otp_required: boolean; - access_token: string; -} - -export interface StoryblokLoginResponse { - otp_required: boolean; - login_strategy: string; - configured_2fa_options: string[]; - access_token?: string; -} - -export interface StoryblokLoginWithOtpResponse { - access_token: string; - email: string; - token_type: string; - user_id: number; - role: string; - has_partner: boolean; -} - -export interface FileReaderResult { - data: T[]; - error?: Error; -} - -export interface StoryblokCredentials { - login: string; - password: string; - region: RegionCode; -} +export * from "./generate-typescript-typedefs"; diff --git a/src/types/schemas.ts b/src/types/schemas.ts deleted file mode 100644 index 3bad1e84..00000000 --- a/src/types/schemas.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { StoryblokPropertyType } from './storyblok'; - -export type ComponentPropertySchemaType = - | StoryblokPropertyType - | 'array' - | 'bloks' - | 'boolean' - | 'custom' - | 'datetime' - | 'image' - | 'markdown' - | 'number' - | 'option' - | 'options' - | 'text' - | 'textarea'; - -export interface ComponentPropertySchemaOption { - _uid: string; - name: string; - value: string; -} - -export interface ComponentPropertySchema { - asset_link_type?: boolean; - component_group_whitelist?: string[]; - component_whitelist?: string[]; - email_link_type?: boolean; - exclude_empty_option?: boolean; - filter_content_type?: string | string[]; - key: string; - options?: ComponentPropertySchemaOption[]; - pos: number; - restrict_components?: boolean; - restrict_type?: 'groups' | ''; - source?: 'internal' | 'external' | 'internal_stories' | 'internal_languages'; - type: ComponentPropertySchemaType; - use_uuid?: boolean; -}; diff --git a/src/types/storyblok.ts b/src/types/storyblok.ts deleted file mode 100644 index ab38f19d..00000000 --- a/src/types/storyblok.ts +++ /dev/null @@ -1,93 +0,0 @@ -export type StoryblokPropertyType = 'asset' | 'multiasset' | 'multilink' | 'table' | 'richtext'; - -export interface StoryblokAsset { - alt: string | null; - copyright: string | null; - fieldtype: 'asset'; - id: number; - filename: string | null; - name: string; - title: string | null; - focus: string | null; - meta_data: Record; - source: string | null; - is_external_url: boolean; - is_private: boolean; - src: string; - updated_at: string; - // Cloudinary integration keys - width: number | null; - height: number | null; - aspect_ratio: number | null; - public_id: string | null; - content_type: string; -} - -export interface StoryblokMultiasset extends Array {} - -export interface StoryblokMultilink { - fieldtype: 'multilink'; - id: string; - url: string; - cached_url: string; - target?: '_blank' | '_self'; - anchor?: string; - rel?: string; - title?: string; - prep?: string; - linktype: 'story' | 'url' | 'email' | 'asset'; - story?: { - name: string; - created_at: string; - published_at: string; - id: number; - uuid: string; - content: Record; - slug: string; - full_slug: string; - sort_by_date?: string; - position?: number; - tag_list?: string[]; - is_startpage?: boolean; - parent_id?: number | null; - meta_data?: Record | null; - group_id?: string; - first_published_at?: string; - release_id?: number | null; - lang?: string; - path?: string | null; - alternates?: any[]; - default_full_slug?: string | null; - translated_slugs?: any[] | null; - }; - email?: string; -} - -export interface StoryblokTable { - fieldtype: 'table'; - thead: Array<{ - _uid: string; - value: string; - component: '_table_head'; - _editable?: string; - }>; - tbody: Array<{ - _uid: string; - component: '_table_row'; - _editable?: string; - body: Array<{ - _uid: string; - value: string; - component: '_table_col'; - _editable?: string; - }>; - }>; -} - -export interface StoryblokRichtext { - type: string; - content?: StoryblokRichtext[]; - marks?: StoryblokRichtext[]; - attrs?: Record; - text?: string; -} diff --git a/src/utils/__mocks__/konsola.ts b/src/utils/__mocks__/konsola.ts deleted file mode 100644 index 2b6eae2b..00000000 --- a/src/utils/__mocks__/konsola.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { vi } from 'vitest'; - -export const konsola = { - ok: vi.fn(), - title: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - br: vi.fn(), - info: vi.fn(), -}; diff --git a/src/utils/api-routes.ts b/src/utils/api-routes.ts deleted file mode 100644 index 2bf4534c..00000000 --- a/src/utils/api-routes.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { RegionCode } from '../constants'; -import { regionsDomain } from '../constants'; - -const API_VERSION = 'v1'; - -export const getStoryblokUrl = (region: RegionCode = 'eu') => { - return `https://${regionsDomain[region]}/${API_VERSION}`; -}; diff --git a/src/utils/api.js b/src/utils/api.js new file mode 100644 index 00000000..cae2ec48 --- /dev/null +++ b/src/utils/api.js @@ -0,0 +1,344 @@ +import chalk from 'chalk' +import axios from 'axios' +import Storyblok from 'storyblok-js-client' +import inquirer from 'inquirer' +import creds from './creds' +import getQuestions from './get-questions' +import { DEFAULT_AGENT } from '../constants' +import { getRegionApiEndpoint } from './region' +import { EU_CODE } from '@storyblok/region-helper' + +export default { + accessToken: '', + oauthToken: '', + spaceId: null, + region: '', + + getClient: (function () { + let client, accessToken, oauthToken, region, credsRegion + + return function getClient() { + const { region: _credsRegion } = creds.get() + + // cache the client if the params are the same + // this is needed so request throttling works properly + if ( + client + && accessToken === this.accessToken + && oauthToken === this.oauthToken + && region === this.region + && credsRegion === _credsRegion) { + return client + } + + accessToken = this.accessToken + oauthToken = this.oauthToken + region = this.region + credsRegion = _credsRegion + + try { + return client = new Storyblok({ + accessToken, + oauthToken, + region, + headers: { + ...DEFAULT_AGENT + } + }, this.apiSwitcher(credsRegion)) + } catch (error) { + throw new Error(error) + } + } + })(), + + getPath (path) { + if (this.spaceId) { + return `spaces/${this.spaceId}/${path}` + } + + return path + }, + + async login (content) { + const { email, password, region = EU_CODE } = content + try { + const response = await axios.post(`${this.apiSwitcher(region)}users/login`, { + email: email, + password: password + }) + + const { data } = response + + if (data.otp_required) { + const questions = [ + { + type: 'input', + name: 'otp_attempt', + message: 'We sent a code to your email / phone, please insert the authentication code:', + validate (value) { + if (value.length > 0) { + return true + } + + return 'Code cannot blank' + } + } + ] + + const { otp_attempt: code } = await inquirer.prompt(questions) + + const newResponse = await axios.post(`${this.apiSwitcher(region)}users/login`, { + email: email, + password: password, + otp_attempt: code + }) + + return this.persistCredentials(email, newResponse.data.access_token || {}, region) + } + + return this.persistCredentials(email, data.access_token, region) + } catch (e) { + return Promise.reject(e) + } + }, + + async getUser () { + const { region } = creds.get() + + try { + const { data } = await axios.get(`${this.apiSwitcher(this.region ? this.region : region)}users/me`, { + headers: { + Authorization: this.oauthToken + } + }) + return data.user + } catch (e) { + return Promise.reject(e) + } + }, + + persistCredentials (email, token = null, region = EU_CODE) { + if (token) { + this.oauthToken = token + creds.set(email, token, region) + + return Promise.resolve(token) + } + return Promise.reject(new Error('The code could not be authenticated.')) + }, + + async processLogin (token = null, region = null) { + try { + if (token && region) { + await this.loginWithToken({ token, region }) + console.log(chalk.green('βœ“') + ' Log in successfully! Token has been added to .netrc file.') + return Promise.resolve({ token, region }) + } + + let content = {} + await inquirer + .prompt(getQuestions('login-strategy')) + .then(async ({ strategy }) => { + content = await inquirer.prompt(getQuestions(strategy)) + }) + .catch((error) => { + console.log(error) + }) + + if (!content.token) { + await this.login(content) + } else { + await this.loginWithToken(content) + } + + console.log(chalk.green('βœ“') + ' Log in successfully! Token has been added to .netrc file.') + + return Promise.resolve(content) + } catch (e) { + if (e.response && e.response.data && e.response.data.error) { + console.error(chalk.red('X') + ' An error ocurred when login the user: ' + e.response.data.error) + + return Promise.reject(e) + } + + console.error(chalk.red('X') + ' An error ocurred when login the user') + return Promise.reject(e) + } + }, + + async loginWithToken (content) { + const { token, region } = content + try { + const { data } = await axios.get(`${this.apiSwitcher(region)}users/me`, { + headers: { + Authorization: token + } + }) + + this.persistCredentials(data.user.email, token, region) + return data.user + } catch (e) { + return Promise.reject(e) + } + }, + + logout (unauthorized) { + if (creds.get().email && unauthorized) { + console.log(chalk.red('X') + ' Your login seems to be expired, we logged you out. Please log back in again.') + } + creds.set(null) + }, + + signup (email, password, region = EU_CODE) { + return axios.post(`${this.apiSwitcher(region)}users/signup`, { + email: email, + password: password, + region + }) + .then(response => { + const token = this.extractToken(response) + this.oauthToken = token + creds.set(email, token, region) + + return Promise.resolve(true) + }) + .catch(err => Promise.reject(err)) + }, + + isAuthorized () { + const { token } = creds.get() || {} + if (token) { + this.oauthToken = token + return true + } + + return false + }, + + setSpaceId (spaceId) { + this.spaceId = spaceId + }, + + setRegion (region) { + this.region = region + }, + + getPresets () { + const client = this.getClient() + + return client + .get(this.getPath('presets')) + .then(data => data.data.presets || []) + .catch(err => Promise.reject(err)) + }, + + getSpaceOptions () { + const client = this.getClient() + + return client + .get(this.getPath('')) + .then((data) => data.data.space.options || {}) + .catch((err) => Promise.reject(err)) + }, + + getComponents () { + const client = this.getClient() + + return client + .get(this.getPath('components')) + .then(data => data.data.components || []) + .catch(err => Promise.reject(err)) + }, + + getComponentGroups () { + const client = this.getClient() + + return client + .get(this.getPath('component_groups')) + .then(data => data.data.component_groups || []) + .catch(err => Promise.reject(err)) + }, + + getDatasources () { + const client = this.getClient() + + return client + .get(this.getPath('datasources')) + .then(data => data.data.datasources || []) + .catch(err => Promise.reject(err)) + }, + + getDatasourceEntries (id) { + const client = this.getClient() + + return client + .get(this.getPath(`datasource_entries?datasource_id=${id}`)) + .then(data => data.data.datasource_entries || []) + .catch(err => Promise.reject(err)) + }, + + deleteDatasource (id) { + const client = this.getClient() + + return client + .delete(this.getPath(`datasources/${id}`)) + .catch(err => Promise.reject(err)) + }, + + post (path, props) { + return this.sendRequest(path, 'post', props) + }, + + put (path, props) { + return this.sendRequest(path, 'put', props) + }, + + get (path, options = {}) { + return this.sendRequest(path, 'get', options) + }, + + getStories (params = {}) { + const client = this.getClient() + const _path = this.getPath('stories') + + return client.getAll(_path, params) + }, + + getSingleStory (id, options = {}) { + const client = this.getClient() + const _path = this.getPath(`stories/${id}`) + + return client.get(_path, options) + .then(response => response.data.story || {}) + }, + + delete (path) { + return this.sendRequest(path, 'delete') + }, + + sendRequest (path, method, props = {}) { + const client = this.getClient() + const _path = this.getPath(path) + + return client[method](_path, props) + }, + + async getAllSpacesByRegion (region) { + const customClient = new Storyblok({ + accessToken: this.accessToken, + oauthToken: this.oauthToken, + region, + headers: { + ...DEFAULT_AGENT + } + }, this.apiSwitcher(region)) + return await customClient + .get('spaces/', {}) + .then(res => res.data.spaces || []) + .catch(err => Promise.reject(err)) + }, + + apiSwitcher (region) { + return region ? getRegionApiEndpoint(region) : getRegionApiEndpoint(this.region) + } +} diff --git a/src/utils/auth.ts b/src/utils/auth.ts deleted file mode 100644 index f28160e6..00000000 --- a/src/utils/auth.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { colorPalette } from '../constants'; -import type { SessionState } from '../session'; -import { CommandError, handleError } from './error'; -import chalk from 'chalk'; - -type AuthenticatedSessionState = SessionState & { - isLoggedIn: true; - password: NonNullable; - region: NonNullable; -}; - -/** - * Check if user is authenticated and handle error if not - * @param state - Session state object - * @param verbose - Whether to show verbose error output - * @returns true if authenticated, false if not (and error is handled) - */ -export function requireAuthentication(state: SessionState, verbose = false): state is AuthenticatedSessionState { - if (!state.isLoggedIn || !state.password || !state.region) { - handleError( - new CommandError(`You are currently not logged in. Please run ${chalk.hex(colorPalette.PRIMARY)('storyblok login')} to authenticate, or ${chalk.hex(colorPalette.PRIMARY)('storyblok signup')} to sign up.`), - verbose, - ); - return false; - } - return true; -} diff --git a/src/utils/build-filter-query.js b/src/utils/build-filter-query.js new file mode 100644 index 00000000..5505c00d --- /dev/null +++ b/src/utils/build-filter-query.js @@ -0,0 +1,29 @@ +const buildFilterQuery = (keys, operations, values) => { + const operators = ['is', 'in', 'not_in', 'like', 'not_like', 'any_in_array', 'all_in_array', 'gt_date', 'lt_date', 'gt_int', 'lt_int', 'gt_float', 'lt_float'] + + if (!keys || !operations || !values) { + throw new Error('Filter options are required: --keys; --operations; --values') + } + const _keys = keys.split(',') + const _operations = operations.split(',') + const _values = values.split(',') + + if (_keys.length !== _operations.length || _keys.length !== _values.length) { + throw new Error('The number of keys, operations and values must be the same') + } + + const invalidOperators = _operations.filter((o) => !operators.includes(o)) + + if (invalidOperators.length) { + throw new Error('Invalid operator(s) applied for filter: ' + invalidOperators.join(' ')) + } + + const filterQuery = {} + _keys.forEach((key, index) => { + filterQuery[key] = { [_operations[index]]: _values[index] } + }) + + return filterQuery +} + +export default buildFilterQuery diff --git a/src/utils/capitalize.js b/src/utils/capitalize.js new file mode 100644 index 00000000..b43656c8 --- /dev/null +++ b/src/utils/capitalize.js @@ -0,0 +1,12 @@ +/** + * @method capitalize + * @param {String} word + * @return {String} + */ +const capitalize = word => { + const first = word.charAt(0).toUpperCase() + const rest = word.slice(1).toLowerCase() + return first + rest +} + +export default capitalize diff --git a/src/utils/creds.js b/src/utils/creds.js new file mode 100644 index 00000000..61f3a055 --- /dev/null +++ b/src/utils/creds.js @@ -0,0 +1,83 @@ +import path from 'path' +import fs from 'fs' +import netrc from 'netrc' +import os from 'os' + +var host = 'api.storyblok.com' + +const getFile = () => { + const home = process.env[(/^win/.test(process.platform)) ? 'USERPROFILE' : 'HOME'] + return path.join(home, '.netrc') +} + +const getNrcFile = () => { + let obj = {} + + try { + obj = netrc(getFile()) + } catch (e) { + obj = {} + } + + return obj +} + +const get = function () { + const obj = getNrcFile() + + if (process.env.STORYBLOK_LOGIN && process.env.STORYBLOK_TOKEN && process.env.STORYBLOK_REGION) { + return { + email: process.env.STORYBLOK_LOGIN, + token: process.env.STORYBLOK_TOKEN, + region: process.env.STORYBLOK_REGION + } + } + + if (process.env.TRAVIS_STORYBLOK_LOGIN && process.env.TRAVIS_STORYBLOK_TOKEN && process.env.TRAVIS_STORYBLOK_REGION) { + return { + email: process.env.TRAVIS_STORYBLOK_LOGIN, + token: process.env.TRAVIS_STORYBLOK_TOKEN, + region: process.env.TRAVIS_STORYBLOK_REGION + } + } + + if (Object.hasOwnProperty.call(obj, host)) { + return { + email: obj[host].login, + token: obj[host].password, + region: obj[host].region + } + } + + return null +} + +const set = function (email, token, region) { + const file = getFile() + let obj = {} + + try { + obj = netrc(file) + } catch (e) { + obj = {} + } + + if (email === null) { + delete obj[host] + fs.writeFileSync(file, netrc.format(obj) + os.EOL) + return null + } else { + obj[host] = { + login: email, + password: token, + region + } + fs.writeFileSync(file, netrc.format(obj) + os.EOL) + return get() + } +} + +export default { + set: set, + get: get +} diff --git a/src/utils/error/api-error.ts b/src/utils/error/api-error.ts deleted file mode 100644 index 00b9dc7f..00000000 --- a/src/utils/error/api-error.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { FetchError } from '../fetch'; - -export const API_ACTIONS = { - login: 'login', - login_with_token: 'Failed to log in with token', - login_with_otp: 'Failed to log in with email, password and otp', - login_email_password: 'Failed to log in with email and password', - get_user: 'Failed to get user', - pull_languages: 'Failed to pull languages', - pull_components: 'Failed to pull components', - pull_component_groups: 'Failed to pull component groups', - pull_component_presets: 'Failed to pull component presets', - pull_component_internal_tags: 'Failed to pull component internal tags', - push_component: 'Failed to push component', - push_component_group: 'Failed to push component group', - push_component_preset: 'Failed to push component preset', - push_component_internal_tag: 'Failed to push component internal tag', - update_component: 'Failed to update component', - update_component_internal_tag: 'Failed to update component internal tag', - update_component_group: 'Failed to update component group', - update_component_preset: 'Failed to update component preset', - pull_stories: 'Failed to pull stories', - pull_story: 'Failed to pull story', - update_story: 'Failed to update story', -} as const; - -export const API_ERRORS = { - unauthorized: 'The user is not authorized to access the API', - network_error: 'No response from server, please check if you are correctly connected to internet', - invalid_credentials: 'The provided credentials are invalid', - timeout: 'The API request timed out', - generic: 'Error fetching data from the API', - not_found: 'The requested resource was not found', - unprocessable_entity: 'The request was well-formed but was unable to be followed due to semantic errors', - -} as const; - -export function handleAPIError(action: keyof typeof API_ACTIONS, error: unknown, customMessage?: string): void { - if (error instanceof FetchError) { - const status = error.response.status; - - switch (status) { - case 401: - throw new APIError('unauthorized', action, error, customMessage); - case 404: - throw new APIError('not_found', action, error, customMessage); - case 422: - throw new APIError('unprocessable_entity', action, error, customMessage); - default: - throw new APIError('network_error', action, error, customMessage); - } - } - throw new APIError('generic', action, error as FetchError, customMessage); -} - -export class APIError extends Error { - errorId: string; - cause: string; - code: number; - messageStack: string[]; - error: FetchError | undefined; - response: FetchError['response'] | undefined; - constructor(errorId: keyof typeof API_ERRORS, action: keyof typeof API_ACTIONS, error?: FetchError, customMessage?: string) { - super(customMessage || API_ERRORS[errorId]); - this.name = 'API Error'; - this.errorId = errorId; - this.cause = API_ERRORS[errorId]; - this.code = error?.response?.status || 0; - this.messageStack = []; - this.error = error; - this.response = error?.response; - - if (!customMessage) { - this.messageStack.push(API_ACTIONS[action]); - } - this.messageStack.push(customMessage || API_ERRORS[errorId]); - - if (this.code === 422) { - const responseData = this.response?.data as { [key: string]: string[] } | undefined; - if (responseData?.name?.[0] === 'has already been taken') { - this.message = 'A component with this name already exists'; - } - Object.entries(responseData || {}).forEach(([key, errors]) => { - if (Array.isArray(errors)) { - errors.forEach((e) => { - this.messageStack.push(`${key}: ${e}`); - }); - } - }); - } - } - - getInfo() { - return { - name: this.name, - message: this.message, - httpCode: this.code, - cause: this.cause, - errorId: this.errorId, - stack: this.stack, - responseData: this.response?.data, - }; - } -} diff --git a/src/utils/error/command-error.ts b/src/utils/error/command-error.ts deleted file mode 100644 index 3b2c3946..00000000 --- a/src/utils/error/command-error.ts +++ /dev/null @@ -1,14 +0,0 @@ -export class CommandError extends Error { - constructor(message: string) { - super(message); - this.name = 'Command Error'; - } - - getInfo() { - return { - name: this.name, - message: this.message, - stack: this.stack, - }; - } -} diff --git a/src/utils/error/error.test.ts b/src/utils/error/error.test.ts deleted file mode 100644 index a305b86e..00000000 --- a/src/utils/error/error.test.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { handleError } from './error'; -import { describe, expect, it, vi } from 'vitest'; - -describe('error handling', () => { - it('should prompt an error message', () => { - const consoleSpy = vi.spyOn(console, 'error'); - handleError(new Error('This is an error')); - expect(consoleSpy).toBeCalled(); - }); -}); diff --git a/src/utils/error/error.ts b/src/utils/error/error.ts deleted file mode 100644 index 7d5aadf2..00000000 --- a/src/utils/error/error.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { konsola } from '..'; -import type { FetchError } from '../fetch'; -import { APIError } from './api-error'; -import { CommandError } from './command-error'; -import { FileSystemError } from './filesystem-error'; - -function handleVerboseError(error: unknown): void { - if (error instanceof CommandError || error instanceof APIError || error instanceof FileSystemError) { - const errorDetails = 'getInfo' in error ? error.getInfo() : {}; - if (error instanceof CommandError) { - konsola.error(`Command Error: ${error.getInfo().message}`, errorDetails); - } - else if (error instanceof APIError) { - konsola.error(`API Error: ${error.getInfo().cause}`, errorDetails); - } - else if (error instanceof FileSystemError) { - konsola.error(`File System Error: ${error.getInfo().cause}`, errorDetails); - } - else { - konsola.error(`Unexpected Error: ${error}`, errorDetails); - } - } - else { - konsola.error(`Unexpected Error`, error); - } -} - -export function handleError(error: Error | FetchError, verbose = false): void { - // Print the message stack if it exists - if (error instanceof APIError || error instanceof FileSystemError) { - const messageStack = (error).messageStack; - messageStack.forEach((message: string, index: number) => { - konsola.error(message, null, { - header: index === 0, - margin: false, - }); - }); - } - else { - konsola.error(error.message, null, { - header: true, - }); - } - if (verbose) { - handleVerboseError(error); - } - else { - konsola.br(); - konsola.info('For more information about the error, run the command with the `--verbose` flag'); - } - - if (!process.env.VITEST) { - console.log(''); // Add a line break for readability - // process.exit(1) // Exit process if not in a test environment - } -} diff --git a/src/utils/error/filesystem-error.ts b/src/utils/error/filesystem-error.ts deleted file mode 100644 index ac331994..00000000 --- a/src/utils/error/filesystem-error.ts +++ /dev/null @@ -1,87 +0,0 @@ -const FS_ERRORS = { - file_not_found: 'The file requested was not found', - permission_denied: 'Permission denied while accessing the file', - operation_on_directory: 'The operation is not allowed on a directory', - not_a_directory: 'The path provided is not a directory', - file_already_exists: 'The file already exists', - directory_not_empty: 'The directory is not empty', - too_many_open_files: 'Too many open files', - no_space_left: 'No space left on the device', - invalid_argument: 'An invalid argument was provided', - unknown_error: 'An unknown error occurred', -} as const; - -const FS_ACTIONS = { - read: 'Failed to read/parse file:', - write: 'Writing file', - delete: 'Deleting file', - mkdir: 'Creating directory', - rmdir: 'Removing directory', - authorization_check: 'Failed to check authorization in .netrc file:', -} as const; - -export function handleFileSystemError(action: keyof typeof FS_ACTIONS, error: NodeJS.ErrnoException): void { - if (error.code) { - switch (error.code) { - case 'ENOENT': - throw new FileSystemError('file_not_found', action, error); - case 'EACCES': - case 'EPERM': - throw new FileSystemError('permission_denied', action, error); - case 'EISDIR': - throw new FileSystemError('operation_on_directory', action, error); - case 'ENOTDIR': - throw new FileSystemError('not_a_directory', action, error); - case 'EEXIST': - throw new FileSystemError('file_already_exists', action, error); - case 'ENOTEMPTY': - throw new FileSystemError('directory_not_empty', action, error); - case 'EMFILE': - throw new FileSystemError('too_many_open_files', action, error); - case 'ENOSPC': - throw new FileSystemError('no_space_left', action, error); - case 'EINVAL': - throw new FileSystemError('invalid_argument', action, error); - default: - throw new FileSystemError('unknown_error', action, error); - } - } - else { - // In case the error does not have a known `fs` error code, throw a general error - throw new FileSystemError('unknown_error', action, error); - } -} - -export class FileSystemError extends Error { - errorId: string; - cause: string; - code: string | undefined; - messageStack: string[]; - error: NodeJS.ErrnoException | undefined; - - constructor(errorId: keyof typeof FS_ERRORS, action: keyof typeof FS_ACTIONS, error: NodeJS.ErrnoException, customMessage?: string) { - super(customMessage || FS_ERRORS[errorId]); - this.name = 'File System Error'; - this.errorId = errorId; - this.cause = FS_ERRORS[errorId]; - this.code = error.code; - this.messageStack = []; - this.error = error; - - if (!customMessage) { - this.messageStack.push(FS_ACTIONS[action]); - } - this.messageStack.push(customMessage || FS_ERRORS[errorId]); - } - - getInfo() { - return { - name: this.name, - message: this.message, - code: this.code, - cause: this.cause, - errorId: this.errorId, - stack: this.stack, - }; - } -} diff --git a/src/utils/error/index.ts b/src/utils/error/index.ts deleted file mode 100644 index b753bc6e..00000000 --- a/src/utils/error/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './api-error'; -export * from './command-error'; -export * from './error'; -export * from './filesystem-error'; diff --git a/src/utils/fetch.test.ts b/src/utils/fetch.test.ts deleted file mode 100644 index 8950b8cd..00000000 --- a/src/utils/fetch.test.ts +++ /dev/null @@ -1,162 +0,0 @@ -import { describe, expect, it, vi } from 'vitest'; -import { customFetch, FetchError } from './fetch'; - -// Mock fetch -const mockFetch = vi.fn(); -globalThis.fetch = mockFetch; - -describe('customFetch', () => { - it('should make a successful GET request', async () => { - const mockResponse = { data: 'test' }; - mockFetch.mockResolvedValueOnce({ - ok: true, - headers: new Headers({ - 'content-type': 'application/json', - 'per-page': '10', - 'total': '100', - }), - json: () => Promise.resolve(mockResponse), - }); - - const result = await customFetch('https://api.test.com'); - expect(result).toEqual({ - ...mockResponse, - perPage: 10, - total: 100, - }); - }); - - it('should handle object body by stringifying it', async () => { - const body = { test: 'data' }; - mockFetch.mockResolvedValueOnce({ - ok: true, - headers: new Headers({ - 'content-type': 'application/json', - 'per-page': '10', - 'total': '100', - }), - json: () => Promise.resolve({}), - }); - - await customFetch('https://api.test.com', { body }); - - expect(mockFetch).toHaveBeenCalledWith('https://api.test.com', expect.objectContaining({ - body: JSON.stringify(body), - })); - }); - - it('should pass string body as-is without modification', async () => { - const body = '{"test":"data"}'; - mockFetch.mockResolvedValueOnce({ - ok: true, - headers: new Headers({ - 'content-type': 'application/json', - 'per-page': '10', - 'total': '100', - }), - json: () => Promise.resolve({}), - }); - - await customFetch('https://api.test.com', { body }); - - expect(mockFetch).toHaveBeenCalledWith('https://api.test.com', expect.objectContaining({ - body, - })); - }); - - it('should handle array body by stringifying it', async () => { - const body = ['test', 'data']; - mockFetch.mockResolvedValueOnce({ - ok: true, - headers: new Headers({ - 'content-type': 'application/json', - 'per-page': '10', - 'total': '100', - }), - json: () => Promise.resolve({}), - }); - - await customFetch('https://api.test.com', { body }); - - expect(mockFetch).toHaveBeenCalledWith('https://api.test.com', expect.objectContaining({ - body: JSON.stringify(body), - })); - }); - - it('should handle non-JSON responses', async () => { - const textResponse = 'Hello World'; - mockFetch.mockResolvedValueOnce({ - ok: true, - headers: new Headers({ - 'content-type': 'text/plain', - 'per-page': '10', - 'total': '100', - }), - text: () => Promise.resolve(textResponse), - }); - - await expect(customFetch('https://api.test.com')).rejects.toThrow(FetchError); - }); - - it('should throw FetchError for HTTP errors', async () => { - const errorResponse = { message: 'Not Found' }; - mockFetch.mockResolvedValue({ - ok: false, - status: 404, - statusText: 'Not Found', - headers: new Headers({ - 'content-type': 'application/json', - 'per-page': '10', - 'total': '100', - }), - json: () => Promise.resolve(errorResponse), - }); - - await expect(customFetch('https://api.test.com')).rejects.toThrow(FetchError); - await expect(customFetch('https://api.test.com')).rejects.toMatchObject({ - response: { - status: 404, - statusText: 'Not Found', - data: errorResponse, - }, - }); - }); - - it('should handle network errors', async () => { - mockFetch.mockRejectedValue(new Error('Network Error')); - - await expect(customFetch('https://api.test.com')).rejects.toThrow(FetchError); - await expect(customFetch('https://api.test.com')).rejects.toMatchObject({ - response: { - status: 0, - statusText: 'Network Error', - data: null, - }, - }); - }); - - it('should set correct headers', async () => { - mockFetch.mockResolvedValueOnce({ - ok: true, - headers: new Headers({ - 'content-type': 'application/json', - 'per-page': '10', - 'total': '100', - }), - json: () => Promise.resolve({}), - }); - - await customFetch('https://api.test.com', { - headers: { - Authorization: 'Bearer token', - }, - }); - - expect(mockFetch).toHaveBeenCalledWith('https://api.test.com', expect.objectContaining({ - headers: expect.objectContaining({ - 'Content-Type': 'application/json', - 'Authorization': 'Bearer token', - }), - })); - }); -}); diff --git a/src/utils/fetch.ts b/src/utils/fetch.ts deleted file mode 100644 index cb0fcd09..00000000 --- a/src/utils/fetch.ts +++ /dev/null @@ -1,104 +0,0 @@ -export class FetchError extends Error { - response: { - status: number; - statusText: string; - data?: Record | null; - }; - - constructor(message: string, response: { status: number; statusText: string; data?: Record | null }) { - super(message); - this.name = 'FetchError'; - this.response = response; - } -} - -export const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); - -export interface FetchOptions { - headers?: Record; - method?: string; - body?: any; - maxRetries?: number; - baseDelay?: number; -} - -export async function customFetch(url: string, options: FetchOptions = {}): Promise { - const maxRetries = options.maxRetries ?? 3; - const baseDelay = options.baseDelay ?? 500; // 500ms base delay - let attempt = 0; - - while (attempt <= maxRetries) { - try { - const headers = { - 'Content-Type': 'application/json', - ...options.headers, - }; - - // Handle JSON body - const fetchOptions: FetchOptions = { - ...options, - headers, - }; - - if (options.body) { - fetchOptions.body = typeof options.body === 'string' - ? options.body - : JSON.stringify(options.body); - } - - const response = await fetch(url, fetchOptions); - let data; - try { - // We try to parse the response as JSON - data = await response.json(); - } - catch { - // If it fails, we throw an error - throw new FetchError(`Non-JSON response`, { - status: response.status, - statusText: response.statusText, - data: null, - }); - } - - if (!response.ok) { - // If we hit rate limit and have retries left - if ((response.status === 429) && (attempt < maxRetries)) { - const waitTime = baseDelay * 2 ** attempt; - await delay(waitTime); - attempt++; - continue; - } - - throw new FetchError(`HTTP error! status: ${response.status}`, { - status: response.status, - statusText: response.statusText, - data, - }); - } - - return { - ...data, - perPage: Number(response.headers.get('Per-Page')), - total: Number(response.headers.get('Total')), - }; - } - catch (error) { - if (error instanceof FetchError) { - throw error; - } - // For network errors or other non-HTTP errors, create a FetchError - throw new FetchError(error instanceof Error ? error.message : String(error), { - status: 0, - statusText: 'Network Error', - data: null, - }); - } - } - - throw new FetchError('Max retries exceeded', { - status: 429, - statusText: 'Rate Limit Exceeded', - data: null, - }); -} diff --git a/src/utils/filesystem.test.ts b/src/utils/filesystem.test.ts deleted file mode 100644 index 605fe5f5..00000000 --- a/src/utils/filesystem.test.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { vol } from 'memfs'; -import { getComponentNameFromFilename, getStoryblokGlobalPath, resolvePath, saveToFile } from './filesystem'; -import { join, resolve } from 'node:path'; - -// tell vitest to use fs mock from __mocks__ folder -// this can be done in a setup file if fs should always be mocked -vi.mock('node:fs'); -vi.mock('node:fs/promises'); - -beforeEach(() => { - vi.clearAllMocks(); - // reset the state of in-memory fs - vol.reset(); -}); - -describe('filesystem utils', async () => { - describe('getStoryblokGlobalPath', async () => { - const originalPlatform = process.platform; - const originalEnv = { ...process.env }; - const originalCwd = process.cwd; - - beforeEach(() => { - process.env = { ...originalEnv }; - }); - - afterEach(() => { - // Restore the original platform after each test - Object.defineProperty(process, 'platform', { - value: originalPlatform, - }); - // Restore process.cwd() - process.cwd = originalCwd; - }); - it('should return the correct path on Unix-like systems when HOME is set', () => { - // Mock the platform to be Unix-like - Object.defineProperty(process, 'platform', { - value: 'linux', - }); - - // Set the HOME environment variable - process.env.HOME = '/home/testuser'; - - const expectedPath = join('/home/testuser', '.storyblok'); - const result = getStoryblokGlobalPath(); - - expect(result).toBe(expectedPath); - }); - - it('should return the correct path on Windows systems when USERPROFILE is set', () => { - // Mock the platform to be Windows - Object.defineProperty(process, 'platform', { - value: 'win32', - }); - - // Set the USERPROFILE environment variable - process.env.USERPROFILE = 'C:/Users/TestUser'; - - const expectedPath = join('C:/Users/TestUser', '.storyblok'); - const result = getStoryblokGlobalPath(); - - expect(result).toBe(expectedPath); - }); - - it('should use process.cwd() when home directory is not set', () => { - // Mock the platform to be Unix-like - Object.defineProperty(process, 'platform', { - value: 'linux', - }); - - // Remove HOME and USERPROFILE - delete process.env.HOME; - delete process.env.USERPROFILE; - - // Mock process.cwd() - process.cwd = vi.fn().mockReturnValue('/current/working/directory'); - - const expectedPath = join('/current/working/directory', '.storyblok'); - const result = getStoryblokGlobalPath(); - - expect(result).toBe(expectedPath); - }); - - it('should use process.cwd() when HOME is empty', () => { - // Mock the platform to be Unix-like - Object.defineProperty(process, 'platform', { - value: 'linux', - }); - - // Set HOME to an empty string - process.env.HOME = ''; - - // Mock process.cwd() - process.cwd = vi.fn().mockReturnValue('/current/working/directory'); - - const expectedPath = join('/current/working/directory', '.storyblok'); - const result = getStoryblokGlobalPath(); - - expect(result).toBe(expectedPath); - }); - - it('should handle Windows platform when USERPROFILE is not set', () => { - // Mock the platform to be Windows - Object.defineProperty(process, 'platform', { - value: 'win32', - }); - - // Remove USERPROFILE - delete process.env.USERPROFILE; - - // Mock process.cwd() - process.cwd = vi.fn().mockReturnValue('C:/Current/Directory'); - - const expectedPath = join('C:/Current/Directory', '.storyblok'); - const result = getStoryblokGlobalPath(); - - expect(result).toBe(expectedPath); - }); - }); - describe('saveToFile', async () => { - it('should save the data to the file', async () => { - const filePath = '/path/to/file.txt'; - const data = 'Hello, World!'; - - await saveToFile(filePath, data); - - const content = vol.readFileSync(filePath, 'utf8'); - expect(content).toBe(data); - }); - - it('should create the directory if it does not exist', async () => { - const filePath = '/path/to/new/file.txt'; - const data = 'Hello, World!'; - - await saveToFile(filePath, data); - - const content = vol.readFileSync(filePath, 'utf8'); - expect(content).toBe(data); - }); - }); - - describe('resolvePath', async () => { - it('should resolve the path correctly', async () => { - const path = '/path/to/file'; - const folder = 'folder'; - - const resolvedPath = resolvePath(path, folder); - expect(resolvedPath).toBe(resolve(process.cwd(), path, folder)); - - const resolvedPathWithoutPath = resolvePath(undefined, folder); - expect(resolvedPathWithoutPath).toBe(resolve(process.cwd(), '.storyblok', folder)); - }); - }); - - describe('getComponentNameFromFilename', async () => { - it('should extract the component name from a JavaScript file', () => { - expect(getComponentNameFromFilename('simple_component.js')).toBe('simple_component'); - expect(getComponentNameFromFilename('nested-component.js')).toBe('nested-component'); - expect(getComponentNameFromFilename('camelCaseComponent.js')).toBe('camelCaseComponent'); - }); - - it('should return the original name if no .js extension is present', () => { - expect(getComponentNameFromFilename('component_without_extension')).toBe('component_without_extension'); - }); - - it('should handle filenames with multiple dots', () => { - expect(getComponentNameFromFilename('component.with.dots.js')).toBe('component.with.dots'); - }); - - it('should handle filenames with paths', () => { - expect(getComponentNameFromFilename('/path/to/my_component.js')).toBe('/path/to/my_component'); - }); - }); -}); diff --git a/src/utils/filesystem.ts b/src/utils/filesystem.ts deleted file mode 100644 index 1527c333..00000000 --- a/src/utils/filesystem.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { join, parse, resolve } from 'node:path'; -import { mkdir, readFile as readFileImpl, writeFile } from 'node:fs/promises'; -import { handleFileSystemError } from './error/filesystem-error'; - -export interface FileOptions { - mode?: number; -} - -export const getStoryblokGlobalPath = () => { - const homeDirectory = process.env[ - process.platform.startsWith('win') ? 'USERPROFILE' : 'HOME' - ] || process.cwd(); - - return join(homeDirectory, '.storyblok'); -}; - -export const saveToFile = async (filePath: string, data: string, options?: FileOptions) => { - // Get the directory path - const resolvedPath = parse(filePath).dir; - - // Ensure the directory exists - try { - await mkdir(resolvedPath, { recursive: true }); - } - catch (mkdirError) { - handleFileSystemError('mkdir', mkdirError as Error); - return; // Exit early if the directory creation fails - } - - // Write the file - try { - await writeFile(filePath, data, options); - } - catch (writeError) { - handleFileSystemError('write', writeError as Error); - } -}; - -export const readFile = async (filePath: string) => { - try { - return await readFileImpl(filePath, 'utf8'); - } - catch (error) { - handleFileSystemError('read', error as Error); - return ''; - } -}; - -export const resolvePath = (path: string | undefined, folder: string) => { - // If a custom path is provided, append the folder structure to it - if (path) { - return resolve(process.cwd(), path, folder); - } - // Otherwise use the default .storyblok path - return resolve(resolve(process.cwd(), '.storyblok'), folder); -}; - -/** - * Extracts the component name from a migration filename - * @param filename - The migration filename (e.g., "simple_component.js") - * @returns The component name (e.g., "simple_component") - */ -export const getComponentNameFromFilename = (filename: string): string => { - // Remove the .js extension - return filename.replace(/\.js$/, ''); -}; diff --git a/src/utils/find-by-property.js b/src/utils/find-by-property.js new file mode 100644 index 00000000..98775b0f --- /dev/null +++ b/src/utils/find-by-property.js @@ -0,0 +1,12 @@ +/** + * @method findByProperty + * @param {Array} collection + * @param {String} property + * @param {String} value + * @return {Object} + */ +const findByProperty = (collection, property, value) => { + return collection.filter(item => item[property] === value)[0] || {} +} + +export default findByProperty diff --git a/src/utils/format.test.ts b/src/utils/format.test.ts deleted file mode 100644 index 35b3c5f4..00000000 --- a/src/utils/format.test.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { describe, expect, it } from 'vitest'; -import { - createRegexFromGlob, - maskToken, - objectToStringParams, - removePropertyRecursively, - slugify, - toCamelCase, - toPascalCase, - toSnakeCase, -} from './format'; - -describe('format utils', () => { - describe('toPascalCase', () => { - it('should convert snake_case to PascalCase', () => { - expect(toPascalCase('hello_world')).toBe('HelloWorld'); - }); - - it('should handle single word', () => { - expect(toPascalCase('hello')).toBe('Hello'); - }); - }); - - describe('toCamelCase', () => { - it('should convert snake_case to camelCase', () => { - expect(toCamelCase('hello_world')).toBe('helloWorld'); - }); - - it('should handle single word', () => { - expect(toCamelCase('hello')).toBe('hello'); - }); - }); - - describe('toSnakeCase', () => { - it('should convert PascalCase to snake_case', () => { - expect(toSnakeCase('HelloWorld')).toBe('hello_world'); - }); - - it('should convert camelCase to snake_case', () => { - expect(toSnakeCase('helloWorld')).toBe('hello_world'); - }); - }); - - describe('maskToken', () => { - it('should mask token longer than 4 characters', () => { - expect(maskToken('1234567890')).toBe('1234******'); - }); - - it('should not mask token with 4 or fewer characters', () => { - expect(maskToken('1234')).toBe('1234'); - expect(maskToken('123')).toBe('123'); - }); - }); - - describe('slugify', () => { - it('should convert text to URL-friendly slug', () => { - expect(slugify('Hello World!')).toBe('hello-world'); - }); - - it('should handle special characters and multiple spaces', () => { - expect(slugify('Hello World!!! Test')).toBe('hello-world-test'); - }); - - it('should remove non-word characters', () => { - expect(slugify('Hello@World#123')).toBe('helloworld123'); - }); - }); - - describe('removePropertyRecursively', () => { - it('should remove specified property from nested object', () => { - const input = { - name: 'test', - id: 1, - nested: { - name: 'nested', - id: 2, - deep: { - name: 'deep', - id: 3, - }, - }, - }; - const expected = { - name: 'test', - nested: { - name: 'nested', - deep: { - name: 'deep', - }, - }, - }; - expect(removePropertyRecursively(input, 'id')).toEqual(expected); - }); - - it('should handle arrays', () => { - const input = { - items: [ - { id: 1, name: 'item1' }, - { id: 2, name: 'item2' }, - ], - }; - const expected = { - items: [ - { name: 'item1' }, - { name: 'item2' }, - ], - }; - expect(removePropertyRecursively(input, 'id')).toEqual(expected); - }); - }); - - describe('objectToStringParams', () => { - it('should convert object values to strings', () => { - const input = { - number: 123, - boolean: true, - string: 'test', - object: { key: 'value' }, - array: [1, 2, 3], - undefined, - }; - const expected = { - number: '123', - boolean: 'true', - string: 'test', - object: '{"key":"value"}', - array: '[1,2,3]', - }; - expect(objectToStringParams(input)).toEqual(expected); - }); - - it('should skip undefined values', () => { - const input = { - defined: 'value', - undef: undefined, - }; - expect(objectToStringParams(input)).toEqual({ - defined: 'value', - }); - }); - }); - - describe('createRegexFromGlob', () => { - it('should create regex from glob pattern', () => { - const regex = createRegexFromGlob('test*.js'); - expect(regex.test('test.js')).toBe(true); - expect(regex.test('test123.js')).toBe(true); - expect(regex.test('other.js')).toBe(false); - }); - - it('should escape special characters', () => { - const regex = createRegexFromGlob('test.js'); - expect(regex.test('test.js')).toBe(true); - expect(regex.test('testxjs')).toBe(false); - }); - }); -}); diff --git a/src/utils/format.ts b/src/utils/format.ts deleted file mode 100644 index ba995bf6..00000000 --- a/src/utils/format.ts +++ /dev/null @@ -1,101 +0,0 @@ -export const toPascalCase = (str: string) => { - return str.replace(/(?:^|_)(\w)/g, (_, char) => char.toUpperCase()); -}; - -export const toCamelCase = (str: string) => { - return str - // First replace snake_case - .replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()) - .replace(/_/g, '') - // Then replace kebab-case - .replace(/-([a-z])/g, (_, letter) => letter.toUpperCase()) - // Capitalize letters after special characters - .replace(/[^a-z0-9]([a-z])/gi, (_, letter) => letter.toUpperCase()) - // Remove special characters - .replace(/[^a-z0-9]/gi, ''); -}; - -export const toSnakeCase = (str: string) => { - return str - .replace(/([A-Z])/g, (_, char) => `_${char.toLowerCase()}`) - .replace(/^_/, ''); -}; - -export const capitalize = (str: string) => { - return str.charAt(0).toUpperCase() + str.slice(1); -}; - -export function maskToken(token: string): string { - // Show only the first 4 characters and replace the rest with asterisks - if (token.length <= 4) { - // If the token is too short, just return it as is - return token; - } - const visiblePart = token.slice(0, 4); - const maskedPart = '*'.repeat(token.length - 4); - return `${visiblePart}${maskedPart}`; -} - -export const slugify = (text: string): string => - text - .toString() - .toLowerCase() - .replace(/\s+/g, '-') // Replace spaces with - - .replace(/[^\w-]+/g, '') // Remove all non-word chars - .replace(/-{2,}/g, '-') // Replace multiple - with single - - .replace(/^-+/, '') // Trim - from start of text - .replace(/-+$/, ''); - -export const removePropertyRecursively = (obj: Record, property: string): Record => { - if (typeof obj !== 'object' || obj === null) { - return obj; - } - - if (Array.isArray(obj)) { - return obj.map(item => removePropertyRecursively(item, property)); - } - - const result: Record = {}; - for (const [key, value] of Object.entries(obj)) { - if (key !== property) { - result[key] = removePropertyRecursively(value, property); - } - } - return result; -}; - -/** - * Converts an object with potential non-string values to an object with string values - * for use with URLSearchParams - * - * @param obj - The object to convert - * @returns An object with all values converted to strings - */ -export const objectToStringParams = (obj: Record): Record => { - return Object.entries(obj).reduce((acc, [key, value]) => { - // Skip undefined values - if (value === undefined) { - return acc; - } - - // Convert objects/arrays to JSON strings - if (typeof value === 'object' && value !== null) { - acc[key] = JSON.stringify(value); - } - else { - // Convert other types to strings - acc[key] = String(value); - } - return acc; - }, {} as Record); -}; - -/** - * Creates a regex pattern from a glob pattern - * @param pattern - The glob pattern to convert - * @returns A regex that matches the glob pattern - */ -export function createRegexFromGlob(pattern: string): RegExp { - // Add ^ and $ to ensure exact match, escape the pattern to handle special characters - return new RegExp(`^${pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\\\*/g, '.*')}$`); -} diff --git a/src/utils/get-questions.js b/src/utils/get-questions.js new file mode 100644 index 00000000..14ed180a --- /dev/null +++ b/src/utils/get-questions.js @@ -0,0 +1,317 @@ +import { ALL_REGIONS, EU_CODE } from '@storyblok/region-helper' + +const getOptions = (subCommand, argv = {}, api = {}) => { + let email = '' + const moreOptions = [ + 'delete-templates', + 'pull-components', + 'push-components', + 'scaffold' + ] + const regionInput = { + type: 'input', + name: 'region', + message: `Please enter the region you would like to work in (${ALL_REGIONS}):`, + default: EU_CODE, + validate: function (value) { + if (ALL_REGIONS.indexOf(value) > -1) { + return true + } + + return `Please enter a valid region: ${ALL_REGIONS}` + } + } + + if (subCommand === 'select') { + return [ + { + type: 'input', + name: 'name', + message: 'How should your Project be named?', + validate: function (value) { + if (value.length > 0) { + return true + } + return 'Please enter a valid name for your project:' + }, + filter: function (val) { + return val.replace(/\s+/g, '-').toLowerCase() + } + }, + { + type: 'list', + name: 'type', + message: 'Select the type of your project:', + choices: [ + 'Theme (Storyrenderer/Hosted)', + 'Boilerplate (Selfhosted)', + 'Fieldtype' + ] + }, + { + type: 'list', + name: 'theme', + message: 'We got some Themes prepared for you:', + choices: [ + 'Creator Theme (Blueprint) [https://github.com/storyblok/creator-theme]', + 'City Theme [https://github.com/storyblok/city-theme]', + 'Nexo Theme [https://github.com/storyblok/nexo-theme]', + 'Custom Theme [We will ask you about your Github URL]' + ], + when: function (answers) { + return answers.type === 'Theme (Storyrenderer/Hosted)' + } + }, + { + type: 'input', + name: 'custom_theme_url', + message: 'What is your github theme URL? Tip: should look like: "https://github.com/storyblok/city-theme"', + when: function (answers) { + return answers.theme === 'Custom Theme [We will ask you about your Github URL]' + } + }, + { + type: 'list', + name: 'theme', + message: 'We got some Boilerplates prepared for you:', + choices: [ + 'PHP - Silex Boilerplate [https://github.com/storyblok/silex-boilerplate]', + 'JavaScript - NodeJs Boilerplate [https://github.com/storyblok/nodejs-boilerplate]', + 'Ruby - Sinatra Boilerplate [https://github.com/storyblok/sinatra-boilerplate]', + 'Python - Django Boilerplate [https://github.com/storyblok/django-boilerplate]', + 'JavaScript - VueJs Boilerplate [https://github.com/storyblok/vuejs-boilerplate]', + 'Custom Boilerplate [We will ask you about your Github URL]' + ], + when: function (answers) { + return answers.type === 'Boilerplate (Selfhosted)' + } + }, + { + type: 'input', + name: 'custom_theme_url', + message: 'What is your github boilerplate URL? Tip: should look like: "https://github.com/storyblok/silex-boilerplate"', + when: function (answers) { + return answers.theme === 'Custom Boilerplate [We will ask you about your Github URL]' + } + }, + { + type: 'input', + name: 'spaceId', + message: 'What is your space ID? Tip: You can find the space ID in the dashboard on https://app.storyblok.com:', + when: function (answers) { + return answers.type === 'Theme (Storyrenderer/Hosted)' + } + }, + { + type: 'input', + name: 'spaceDomain', + message: 'What is your domain? Example: city.me.storyblok.com:', + when: function (answers) { + return answers.type === 'Theme (Storyrenderer/Hosted)' + }, + filter: function (val) { + return val.replace(/https:/g, '').replace(/\//g, '') + } + }, + { + type: 'input', + name: 'themeToken', + message: 'What is your theme token?', + when: function (answers) { + return answers.type === 'Theme (Storyrenderer/Hosted)' + } + } + ] + } + + if (subCommand === 'login-strategy') { + return [ + { + type: 'list', + name: 'strategy', + message: 'Select the login strategy: ', + choices: [ + { + name: 'With email and password (Common users with storyblok account)', + value: 'login-with-email', + short: 'Email' + }, + { + name: 'With Token (Most recommended for SSO users)', + value: 'login-with-token', + short: 'Token' + } + ] + } + ] + } + + if (subCommand === 'login-with-email') { + return [ + { + type: 'input', + name: 'email', + message: 'Enter your email address:', + validate: function (value) { + email = value + if (value.length > 0) { + return true + } + return 'Please enter a valid email:' + } + }, + { + type: 'password', + name: 'password', + message: 'Enter your password:', + validate: function (value) { + if (value.length > 0) { + return true + } + + return 'Please enter a valid password:' + } + }, + regionInput + ] + } + + if (subCommand === 'login-with-token') { + return [ + { + type: 'input', + name: 'token', + message: 'Enter your token:', + validate: function (value) { + if (value.length > 0) { + return true + } + return 'Please enter a valid token:' + } + }, + regionInput + ] + } + + if (moreOptions.indexOf(subCommand) > -1) { + const loginQuestions = [ + { + type: 'input', + name: 'email', + message: 'Enter your email address:', + validate: function (value) { + email = value + if (value.length > 0) { + return true + } + return 'Please enter a valid email:' + } + }, + { + type: 'password', + name: 'password', + message: 'Enter your password:', + validate: function (value) { + const done = this.async() + + return api.login(email, value) + .then(_ => done(null, true)) + .catch(_ => { + done('Password seams to be wrong. Please try again:') + }) + } + } + ] + + if (!api.isAuthorized()) { + return loginQuestions + } + + return [] + } + + return [ + { + type: 'list', + name: 'has_account', + message: 'Do you have already a Storyblok account?', + choices: [ + 'No', + 'Yes' + ], + when: function () { + return !api.isAuthorized() && !argv.space + } + }, + { + type: 'input', + name: 'email', + message: 'Enter your email address:', + validate: function (value) { + email = value + if (value.length > 0) { + return true + } + return 'Please enter a valid email:' + }, + when: function () { + return !api.isAuthorized() + } + }, + { + type: 'password', + name: 'password', + message: 'Define your password:', + validate: function (value) { + var done = this.async() + + api.signup(email, value, (data) => { + if (data.status === 200) { + done(null, true) + } else { + done('Failed: ' + JSON.stringify(data.body) + '. Please try again:') + } + }) + }, + when: function (answers) { + return answers.has_account === 'No' + } + }, + { + type: 'password', + name: 'password', + message: 'Enter your password:', + validate (value) { + var done = this.async() + + api.login(email, value) + .then(_ => done(null, true)) + .catch(_ => { + done('Password seams to be wrong. Please try again:') + }) + }, + when: function (answers) { + return answers.has_account === 'Yes' || (!api.isAuthorized() && !answers.has_account) + } + }, + { + type: 'input', + name: 'name', + message: 'How should your Project be named?', + validate: function (value) { + if (value.length > 0) { + return true + } + return 'Please enter a valid name for your project:' + }, + filter: function (val) { + return val.replace(/\s+/g, '-').toLowerCase() + }, + when: function (answers) { + return !argv.space + } + } + ] +} + +export default getOptions diff --git a/src/utils/index.js b/src/utils/index.js new file mode 100644 index 00000000..b3efa3e4 --- /dev/null +++ b/src/utils/index.js @@ -0,0 +1,10 @@ +export { default as getQuestions } from './get-questions' +export { default as lastStep } from './last-step' +export { default as api } from './api' +export { default as creds } from './creds' +export { default as capitalize } from './capitalize' +export { default as findByProperty } from './find-by-property' +export { default as parseError } from './parse-error' +export { default as region } from './region' +export { default as saveFileFactory } from './save-file-factory' +export { default as buildFilterQuery } from './build-filter-query' diff --git a/src/utils/index.ts b/src/utils/index.ts deleted file mode 100644 index e3570b47..00000000 --- a/src/utils/index.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { fileURLToPath } from 'node:url'; -import { dirname } from 'pathe'; -import type { RegionCode } from '../constants'; -import { regions } from '../constants'; - -export * from './auth'; -export * from './error/'; -export * from './format'; -export * from './konsola'; - -export const __filename = fileURLToPath(import.meta.url); -export const __dirname = dirname(__filename); - -export function isRegion(value: RegionCode): value is RegionCode { - return Object.values(regions).includes(value); -} - -export function isEmptyObject(obj: object): boolean { - return Object.keys(obj).length === 0; -} - -export const isVitest = process.env.VITEST === 'true'; diff --git a/src/utils/konsola.test.ts b/src/utils/konsola.test.ts deleted file mode 100644 index b39fd07a..00000000 --- a/src/utils/konsola.test.ts +++ /dev/null @@ -1,71 +0,0 @@ -import chalk from 'chalk'; -import { formatHeader, konsola } from './konsola'; -import { describe, expect, it, vi } from 'vitest'; - -describe('konsola', () => { - describe('title', () => { - it('should prompt a title message', () => { - const consoleSpy = vi.spyOn(console, 'log'); - konsola.title('This is a test title', '#45bfb9'); - - expect(consoleSpy).toHaveBeenCalledWith(formatHeader(chalk.bgHex('#45bfb9').bold.white(` This is a test title `))); - }); - }); - describe('warn', () => { - it('should prompt a warning message', () => { - const consoleSpy = vi.spyOn(console, 'warn'); - konsola.warn('This is a test warning message'); - - expect(consoleSpy).toHaveBeenCalledWith(`${chalk.yellow('⚠️ ')} This is a test warning message`); - }); - - it('should prompt a warning message with header', () => { - const consoleSpy = vi.spyOn(console, 'warn'); - konsola.warn('This is a test warning message', true); - const warnText = chalk.bgYellow.bold.black(` Warning `); - - expect(consoleSpy).toHaveBeenCalledWith(formatHeader(warnText, - )); - }); - }); - - describe('success', () => { - it('should prompt an success message', () => { - const consoleSpy = vi.spyOn(console, 'log'); - konsola.ok('Component A created succesfully'); - - expect(consoleSpy).toHaveBeenCalledWith(`${chalk.green('βœ”')} Component A created succesfully`); - }); - - it('should prompt an success message with header', () => { - const consoleSpy = vi.spyOn(console, 'log'); - konsola.ok('Component A created succesfully', true); - const successText = chalk.bgGreen.bold.white(` Success `); - - expect(consoleSpy).toHaveBeenCalledWith(formatHeader(successText)); - }); - }); - describe('error', () => { - it('should prompt an error message', () => { - const consoleSpy = vi.spyOn(console, 'error'); - - konsola.error('Oh gosh, this is embarrasing'); - const errorText = `${chalk.red.bold('β–² error')} Oh gosh, this is embarrasing`; - expect(consoleSpy).toHaveBeenCalledWith(errorText, ''); - }); - - it('should prompt an error message with header', () => { - const consoleSpy = vi.spyOn(console, 'error'); - konsola.error('Oh gosh, this is embarrasing', null, { header: true }); - const errorText = chalk.bgRed.bold.white(` Error `); - - expect(consoleSpy).toHaveBeenCalledWith(formatHeader(errorText)); - }); - - it('should add a line break if margin set to true ', () => { - const consoleSpy = vi.spyOn(console, 'error'); - konsola.error('Oh gosh, this is embarrasing', null, { margin: true }); - expect(consoleSpy).toHaveBeenCalledWith(''); - }); - }); -}); diff --git a/src/utils/konsola.ts b/src/utils/konsola.ts deleted file mode 100644 index b358dd4b..00000000 --- a/src/utils/konsola.ts +++ /dev/null @@ -1,71 +0,0 @@ -import chalk from 'chalk'; - -export interface KonsolaFormatOptions { - header?: boolean; - margin?: boolean; -} - -export function formatHeader(title: string) { - return `${title}`; -} -export const konsola = { - title: (message: string, color: string, subtitle?: string) => { - if (subtitle) { - console.log(`${formatHeader(chalk.bgHex(color).bold(` ${message} `))} ${subtitle}`); - } - else { - console.log(formatHeader(chalk.bgHex(color).bold(` ${message} `))); - } - console.log(''); // Add a line break - console.log(''); // Add a line break - }, - br: () => { - console.log(''); // Add a line break - }, - ok: (message?: string, header: boolean = false) => { - if (header) { - console.log(''); // Add a line break - const successHeader = chalk.bgGreen.bold.white(` Success `); - console.log(formatHeader(successHeader)); - console.log(''); // Add a line break - } - - console.log(message ? `${chalk.green('βœ”')} ${message}` : ''); - }, - info: (message: string, options: KonsolaFormatOptions = { - header: false, - margin: true, - }) => { - if (options.header) { - console.log(''); // Add a line break - const infoHeader = chalk.bgBlue.bold.white(` Info `); - console.log(formatHeader(infoHeader)); - } - - console.log(message ? `${chalk.blue('β„Ή')} ${message}` : ''); - if (options.margin) { - console.error(''); // Add a line break - } - }, - warn: (message?: string, header: boolean = false) => { - if (header) { - console.log(''); // Add a line break - const warnHeader = chalk.bgYellow.bold.black(` Warning `); - console.warn(formatHeader(warnHeader)); - } - - console.warn(message ? `${chalk.yellow('⚠️ ')} ${message}` : ''); - }, - error: (message: string, info?: unknown, options?: KonsolaFormatOptions) => { - if (options?.header) { - const errorHeader = chalk.bgRed.bold.white(` Error `); - console.error(formatHeader(errorHeader)); - console.log(''); // Add a line break - } - - console.error(`${chalk.red.bold('β–² error')} ${message}`, info || ''); - if (options?.margin) { - console.error(''); // Add a line break - } - }, -}; diff --git a/src/utils/last-step.js b/src/utils/last-step.js new file mode 100644 index 00000000..a9c60143 --- /dev/null +++ b/src/utils/last-step.js @@ -0,0 +1,108 @@ +/* eslint-disable camelcase */ + +import fs from 'fs' +import chalk from 'chalk' +import ghdownload from 'git-clone' +import replace from './replace' + +const getFinalStep = type => { + if (type === 'Fieldtype' || type === 'quickstart') { + return 'npm run dev' + } + + return 'gulp' +} + +const getRepository = (type, theme, custom_theme_url) => { + const regex = /\[(.*?)\]/ + if (type === 'Theme (Storyrenderer/Hosted)' || type === 'Boilerplate (Selfhosted)') { + if (custom_theme_url) { + return custom_theme_url + '.git' + } else { + return regex.exec(theme)[1] + '.git' + } + } + + if (type === 'Fieldtype') { + return 'https://github.com/storyblok/storyblok-fieldtype.git' + } + + return 'https://github.com/storyblok/quickstart.git' +} + +const lastStep = answers => { + return new Promise((resolve, reject) => { + const { + type, + theme, + spaceId, + name, + custom_theme_url, + themeToken, + spaceDomain, + loginToken + } = answers + + const gitRepo = getRepository(type, theme, custom_theme_url) + const outputDir = './' + name + + console.log(chalk.green('βœ“') + ' - The github repository ' + gitRepo + ' will be cloned now...') + + ghdownload(gitRepo, outputDir, async (err) => { + if (err) { + if (err.code === 'ENOTEMPTY') { + console.log(chalk.red(' Oh Snap! It seems that you already have a project with the name: ' + name)) + reject(new Error('This repository already has been cloned')) + process.exit(0) + } + + console.log(chalk.red('X We never had this kind of issue - Sorry for that!')) + console.log(chalk.red('X Could you send us the error below as a stackoverflow question?')) + console.log(chalk.red('X That would be great! :)')) + console.log(chalk.red('X Don\'t forget to mark it with the tag `storyblok` so will can find it.')) + + return reject(err) + } else { + const finalStep = getFinalStep(type) + + console.log(chalk.green('βœ“') + ' - ' + chalk.white('Your Storyblok project is ready for you!')) + console.log(chalk.white(' Execute the following command to start Storyblok:')) + console.log(chalk.cyan(' cd ./' + name + ' && npm install && ' + finalStep)) + console.log(chalk.white(' If you need more help, just try asking a question on stackoverflow')) + console.log(chalk.white(' with the [storyblok] tag or live-chat with us on www.storyblok.com')) + + try { + if (type === 'Theme (Storyrenderer/Hosted)' || type === 'quickstart') { + fs.renameSync(outputDir + '/_token.js', outputDir + '/token.js') + + if (themeToken) { + await replace(outputDir + '/token.js', { INSERT_TOKEN: themeToken }) + } + + var spaceConfig = {} + + if (spaceId) { + spaceConfig.INSERT_SPACE_ID = spaceId + } + + if (spaceDomain) { + spaceConfig.INSERT_YOUR_DOMAIN = spaceDomain + } + + if (loginToken) { + spaceConfig.TEMP_QUICKSTART_TOKEN = loginToken + } + + await replace(outputDir + '/config.js', spaceConfig) + + return resolve(true) + } + } catch (e) { + return reject(new Error('An error occurred when finish the download repository task ' + e.message)) + } + } + }) + }) +} + +export default lastStep diff --git a/src/utils/parse-error.js b/src/utils/parse-error.js new file mode 100644 index 00000000..8ec20b86 --- /dev/null +++ b/src/utils/parse-error.js @@ -0,0 +1,25 @@ +/** + * @method parserError + * @param {Object} responseError + * @return {Object} { message: String, error: InstanceOfError } + */ +const parserError = responseError => { + const response = responseError.response || {} + if (response && response.data && response.data.error) { + return { + status: response.status, + statusText: response.statusText, + message: response.data.error, + error: responseError + } + } + + return { + status: null, + statusText: null, + message: responseError.message, + error: responseError + } +} + +export default parserError diff --git a/src/utils/presets-lib.js b/src/utils/presets-lib.js new file mode 100644 index 00000000..7c4fa1ae --- /dev/null +++ b/src/utils/presets-lib.js @@ -0,0 +1,138 @@ +import chalk from 'chalk' +import FormData from 'form-data' +import axios from 'axios' +import lodash from 'lodash' +import api from './api' +const { last } = lodash + +class PresetsLib { + /** + * @param {{ oauthToken: string, targetSpaceId: int }} options + */ + constructor (options) { + this.oauthToken = options.oauthToken + this.client = api.getClient() + this.targetSpaceId = options.targetSpaceId + } + + async createPresets (presets = [], componentId, method = 'post') { + const presetsSize = presets.length + console.log(`${chalk.blue('-')} Pushing ${presetsSize} ${method === 'post' ? 'new' : 'existing'} presets`) + + try { + for (let i = 0; i < presetsSize; i++) { + const presetData = presets[i] + const presetId = method === 'put' ? `/${presetData.id}` : '' + + await this.client[method](`spaces/${this.targetSpaceId}/presets${presetId}`, { + preset: { + name: presetData.name, + component_id: componentId, + preset: presetData.preset, + image: presetData.image + } + }) + } + + console.log(`${chalk.green('βœ“')} ${presetsSize} presets sync`) + } catch (e) { + console.error('An error ocurred while trying to save the presets ' + e.message) + + return Promise.reject(e) + } + } + + getComponentPresets (component = {}, presets = []) { + console.log(`${chalk.green('-')} Get presets from component ${component.name}`) + + return presets.filter(preset => { + return preset.component_id === component.id + }) + } + + async getPresets (spaceId) { + console.log(`${chalk.green('-')} Load presets from space #${spaceId}`) + + try { + const response = await this.client.get( + `spaces/${spaceId}/presets` + ) + + return response.data.presets || [] + } catch (e) { + console.error('An error ocurred when load presets ' + e.message) + + return Promise.reject(e) + } + } + + filterPresetsFromTargetComponent (presets, targetPresets) { + console.log(chalk.blue('-') + ' Checking target presets to sync...') + const targetPresetsNames = targetPresets.map(preset => preset.name) + const newPresets = presets.filter(preset => !targetPresetsNames.includes(preset.name)) + const updatePresetsSource = presets.filter(preset => targetPresetsNames.includes(preset.name)) + const updatePresets = updatePresetsSource.map(source => { + const target = targetPresets.find(target => target.name === source.name) + return Object.assign({}, source, target, { image: source.image }) + }) + + return { + newPresets, + updatePresets + } + } + + async getSamePresetFromTarget (spaceId, component, sourcePreset) { + try { + const presetsInTarget = await this.getPresets(spaceId) + const componentPresets = this.getComponentPresets(component, presetsInTarget) + const defaultPresetInTarget = componentPresets.find(preset => preset.name === sourcePreset.name) + return defaultPresetInTarget + } catch (err) { + console.error(`An error occurred while trying to get the "${sourcePreset.name}" preset from target space: ${err.message}`) + return null + } + } + + async uploadImageForPreset (image = '') { + const imageName = last(image.split('/')) + + return this.client + .post(`spaces/${this.targetSpaceId}/assets`, { + filename: imageName, + asset_folder_id: null + }) + .then(res => this.uploadFileToS3(res.data, image, imageName)) + .catch(e => Promise.reject(e)) + } + + async uploadFileToS3 (signedRequest, imageUrl, name) { + try { + const response = await axios.get(`https:${imageUrl}`, { + responseType: 'arraybuffer' + }) + + return new Promise((resolve, reject) => { + const form = new FormData() + for (const key in signedRequest.fields) { + form.append(key, signedRequest.fields[key]) + } + + form.append('file', response.data) + + form.submit(signedRequest.post_url, (err, res) => { + if (err) { + console.log(`${chalk.red('X')} There was an error uploading the image`) + return reject(err) + } + console.log(`${chalk.green('βœ“')} Uploaded ${name} image successfully!`) + return resolve(signedRequest.pretty_url) + }) + }) + } catch (e) { + console.error('An error occurred while uploading the image ' + e.message) + } + } +} + +export default PresetsLib diff --git a/src/utils/region.js b/src/utils/region.js new file mode 100644 index 00000000..4ac28727 --- /dev/null +++ b/src/utils/region.js @@ -0,0 +1,5 @@ +import { getRegionBaseUrl } from '@storyblok/region-helper' + +export const getRegionApiEndpoint = (region) => `${getRegionBaseUrl(region)}/v1/` + +export default getRegionApiEndpoint \ No newline at end of file diff --git a/src/utils/replace.js b/src/utils/replace.js new file mode 100644 index 00000000..acd9e19f --- /dev/null +++ b/src/utils/replace.js @@ -0,0 +1,25 @@ +import fs from 'fs' + +const replace = (file, replacements) => { + return new Promise((resolve, reject) => { + fs.readFile(file, 'utf8', function (err, data) { + if (err) { + return reject(err) + } + + for (const from in replacements) { + data = data.replace(from, replacements[from]) + } + + fs.writeFile(file, data, 'utf8', function (err) { + if (err) { + return reject(err) + } + + return resolve(true) + }) + }) + }) +} + +export default replace diff --git a/src/utils/save-file-factory.js b/src/utils/save-file-factory.js new file mode 100644 index 00000000..8e085f0a --- /dev/null +++ b/src/utils/save-file-factory.js @@ -0,0 +1,13 @@ +import fs from 'fs' + +const saveFileFactory = (fileName, content, path = './') => { + try { + fs.writeFileSync(`${path}${fileName}`, content); + return true; + } catch(err) { + console.log(err); + return false; + } +} + +export default saveFileFactory diff --git a/src/utils/storyblok-schemas.ts b/src/utils/storyblok-schemas.ts deleted file mode 100644 index a3896f72..00000000 --- a/src/utils/storyblok-schemas.ts +++ /dev/null @@ -1,429 +0,0 @@ -import type { JSONSchema } from 'json-schema-to-typescript'; -import type { StoryblokPropertyType } from '../types/storyblok'; - -export const getAssetJSONSchema = (title: string): JSONSchema => ({ - $id: '#/asset', - title, - type: 'object', - required: ['id', 'fieldtype', 'filename', 'name', 'title', 'focus', 'alt'], - properties: { - alt: { - type: ['string', 'null'], - }, - copyright: { - type: ['string', 'null'], - }, - fieldtype: { - type: 'string', - enum: ['asset'], - }, - id: { - type: 'number', - }, - filename: { - type: ['string', 'null'], - }, - name: { - type: 'string', - }, - title: { - type: ['string', 'null'], - }, - focus: { - type: ['string', 'null'], - }, - meta_data: { - type: 'object', - }, - source: { - type: ['string', 'null'], - }, - is_external_url: { - type: 'boolean', - }, - is_private: { - type: 'boolean', - }, - src: { - type: 'string', - }, - updated_at: { - type: 'string', - }, - // Cloudinary integration keys - width: { - type: ['number', 'null'], - }, - height: { - type: ['number', 'null'], - }, - aspect_ratio: { - type: ['number', 'null'], - }, - public_id: { - type: ['string', 'null'], - }, - content_type: { - type: 'string', - }, - }, -}); - -export const getMultiassetJSONSchema = (title: string): JSONSchema => ({ - $id: '#/multiasset', - title, - type: 'array', - items: { - type: 'object', - required: ['id', 'fieldtype', 'filename', 'name', 'title', 'focus', 'alt'], - properties: { - alt: { - type: ['string', 'null'], - }, - copyright: { - type: ['string', 'null'], - }, - fieldtype: { - type: 'string', - enum: ['asset'], - }, - id: { - type: 'number', - }, - filename: { - type: ['string', 'null'], - }, - name: { - type: 'string', - }, - title: { - type: ['string', 'null'], - }, - focus: { - type: ['string', 'null'], - }, - meta_data: { - type: 'object', - }, - source: { - type: ['string', 'null'], - }, - is_external_url: { - type: 'boolean', - }, - is_private: { - type: 'boolean', - }, - src: { - type: 'string', - }, - updated_at: { - type: 'string', - }, - // Cloudinary integration keys - width: { - type: ['number', 'null'], - }, - height: { - type: ['number', 'null'], - }, - aspect_ratio: { - type: ['number', 'null'], - }, - public_id: { - type: ['string', 'null'], - }, - content_type: { - type: 'string', - }, - }, - }, -}); - -// TODO: find a reliable way to share props among different Link Types to increase maintainability -// Currently not possible because of JSONSchema4 complaining -const multilinkSharedRequiredProps = ['fieldtype', 'id', 'url', 'cached_url', 'linktype']; - -export const getMultilinkJSONSchema = (title: string): JSONSchema => ({ - $id: '#/multilink', - title, - oneOf: [ - { - type: 'object', - required: multilinkSharedRequiredProps, - properties: { - // Shared props - fieldtype: { - type: 'string', - enum: ['multilink'], - }, - id: { type: 'string' }, - url: { type: 'string' }, - cached_url: { type: 'string' }, - target: { type: 'string', enum: ['_blank', '_self'] }, - // Custom props - anchor: { - type: 'string', - }, - rel: { - type: 'string', - }, - title: { - type: 'string', - }, - prep: { - type: 'string', - }, - linktype: { - type: 'string', - enum: ['story'], - }, - story: { - type: 'object', - required: ['name', 'id', 'uuid', 'slug', 'full_slug'], - properties: { - name: { - type: 'string', - }, - created_at: { - type: 'string', - format: 'date-time', - }, - published_at: { - type: 'string', - format: 'date-time', - }, - id: { - type: 'integer', - }, - uuid: { - type: 'string', - format: 'uuid', - }, - content: { - type: 'object', - }, - slug: { - type: 'string', - }, - full_slug: { - type: 'string', - }, - sort_by_date: { - type: ['null', 'string'], - format: 'date-time', - }, - position: { - type: 'integer', - }, - tag_list: { - type: 'array', - items: { - type: 'string', - }, - }, - is_startpage: { - type: 'boolean', - }, - parent_id: { - type: ['null', 'integer'], - }, - meta_data: { - type: ['null', 'object'], - }, - group_id: { - type: 'string', - format: 'uuid', - }, - first_published_at: { - type: 'string', - format: 'date-time', - }, - release_id: { - type: ['null', 'integer'], - }, - lang: { - type: 'string', - }, - path: { - type: ['null', 'string'], - }, - alternates: { - type: 'array', - }, - default_full_slug: { - type: ['null', 'string'], - }, - translated_slugs: { - type: ['null', 'array'], - }, - }, - }, - }, - }, - { - type: 'object', - required: multilinkSharedRequiredProps, - properties: { - // Shared props - fieldtype: { - type: 'string', - enum: ['multilink'], - }, - id: { type: 'string' }, - url: { type: 'string' }, - cached_url: { type: 'string' }, - target: { type: 'string', enum: ['_blank', '_self'] }, - // Custom props - linktype: { - type: 'string', - enum: ['url'], - }, - rel: { - type: 'string', - }, - title: { - type: 'string', - }, - }, - }, - { - type: 'object', - required: multilinkSharedRequiredProps, - properties: { - // Shared props - fieldtype: { - type: 'string', - enum: ['multilink'], - }, - id: { type: 'string' }, - url: { type: 'string' }, - cached_url: { type: 'string' }, - target: { type: 'string', enum: ['_blank', '_self'] }, - // Custom props - email: { - type: 'string', - }, - linktype: { - type: 'string', - enum: ['email'], - }, - }, - }, - { - type: 'object', - required: multilinkSharedRequiredProps, - properties: { - // Shared props - fieldtype: { - type: 'string', - enum: ['multilink'], - }, - id: { type: 'string' }, - url: { type: 'string' }, - cached_url: { type: 'string' }, - target: { type: 'string', enum: ['_blank', '_self'] }, - // Custom props - linktype: { - type: 'string', - enum: ['asset'], - }, - }, - }, - ], -}); - -export const getRichtextJSONSchema = (title: string): JSONSchema => ({ - $id: '#/richtext', - title, - type: 'object', - required: ['type'], - properties: { - type: { - type: 'string', - }, - content: { - type: 'array', - items: { - $ref: '#', - }, - }, - marks: { - type: 'array', - items: { - $ref: '#', - }, - }, - attrs: {}, - text: { - type: 'string', - }, - }, -}); - -export const getTableJSONSchema = (title: string): JSONSchema => ({ - $id: '#/table', - title, - type: 'object', - required: ['tbody', 'thead'], - properties: { - thead: { - type: 'array', - items: { - type: 'object', - required: ['_uid', 'component'], - properties: { - _uid: { - type: 'string', - }, - value: { - type: 'string', - }, - component: { - type: 'number', - }, - }, - }, - }, - tbody: { - type: 'array', - items: { - type: 'object', - required: ['_uid', 'component', 'body'], - properties: { - _uid: { - type: 'string', - }, - body: { - type: 'array', - items: { - type: 'object', - properties: { - _uid: { - type: 'string', - }, - value: { - type: 'string', - }, - component: { - type: 'number', - }, - }, - }, - }, - component: { - type: 'number', - }, - }, - }, - }, - }, -}); - -export const storyblokSchemas = new Map([ - ['asset', getAssetJSONSchema], - ['multiasset', getMultiassetJSONSchema], - ['multilink', getMultilinkJSONSchema], - ['table', getTableJSONSchema], - ['richtext', getRichtextJSONSchema], -]); diff --git a/src/utils/typescript/generateTypesFromJSONSchema.ts b/src/utils/typescript/generateTypesFromJSONSchema.ts new file mode 100644 index 00000000..7bb71379 --- /dev/null +++ b/src/utils/typescript/generateTypesFromJSONSchema.ts @@ -0,0 +1,508 @@ +import fs from "fs"; +import lodash from "lodash"; +import { compile, type JSONSchema } from "json-schema-to-typescript"; +import type { + ComponentPropertyTypeAnnotation, + ComponentPropertySchema, + StoryblokProvidedPropertyType, + CustomTypeParser, + GenerateTSTypedefsFromComponentsJSONSchemasOptions, + GetStoryblokProvidedPropertyTypeSchemaFn, + ComponentGroupsAndNamesObject, +} from "../../types"; +import { + getAssetJSONSchema, + getMultiassetJSONSchema, + getMultilinkJSONSchema, + getRichtextJSONSchema, + getTableJSONSchema, +} from "./storyblokProvidedPropertyTypes"; +import { resolve } from "path"; + +const { camelCase, startCase } = lodash; + +/** + * This class handles the generation of Typescript type definitions based on Storyblok components JSON Schemas. + * To initialize this class, call the async static method `init`. + * + * + * DISAMBIGUATION GLOSSARY + * + * `Component` + * The JSON representation of a component as provided by the Storyblok JSON schema. + * It should be called `blok`, but the JSON schema itself and all the CLI commands refer to those entities as `components` + * + * `JSONSchema` - also `schema` + * A JSONSchema representation of a `component` or a part of it. It may represent entire components, their properties, etc. + * + * `Type` - also `TypeString` + * The name of a Typescript type - as in `type StoryblokExampleType` + * + * `Typedef` - also `TypedefString` + * The actual type definition of a Typescript type, listing all the properties/methods of a type. + * + * `Type annotation` + * A JSON representation of a Typescript Type that is going to be compiled into an actual Type string by json-schema-to-ts + * + * `Storyblok-provided property types` + * It describes those field types provided out of the box from Storyblok itself which have a predefined JSON Schema that is going to be rendered every time a property of that type is used in any component + */ + +export class GenerateTypesFromJSONSchemas { + #STORY_TYPE = "ISbStoryData"; + + #options: GenerateTSTypedefsFromComponentsJSONSchemasOptions; + #componentsJSONSchemas: JSONSchema[]; + #customTypeParser: CustomTypeParser | null; + #typedefsFileStrings: string[] = [ + "// This file was generated by the storyblok CLI.", + "// DO NOT MODIFY THIS FILE BY HAND.", + `import type { ${this.#STORY_TYPE} } from "storyblok";`, + ]; + #componentGroups: Map>; + #componentNames: Set; + + #getSchemaForStoryblokProvidedPropertyType = new Map< + StoryblokProvidedPropertyType, + GetStoryblokProvidedPropertyTypeSchemaFn + >([ + ["asset", getAssetJSONSchema], + ["multiasset", getMultiassetJSONSchema], + ["multilink", getMultilinkJSONSchema], + ["richtext", getRichtextJSONSchema], + ["table", getTableJSONSchema], + ]); + + #hasStoryblokProvidedPropertyTypeBeenGenerated = new Map([ + ["asset", false], + ["multiasset", false], + ["multilink", false], + ["richtext", false], + ["table", false], + ]); + + private constructor( + componentsJSONSchemas: JSONSchema[], + options: GenerateTSTypedefsFromComponentsJSONSchemasOptions, + customTypeParser: CustomTypeParser | null + ) { + this.#options = options; + this.#componentsJSONSchemas = componentsJSONSchemas; + this.#customTypeParser = customTypeParser; + const { componentGroups, componentNames } = + this.#generateComponentGroupsAndComponentNamesFromJSONSchemas(componentsJSONSchemas); + this.#componentGroups = componentGroups; + this.#componentNames = componentNames; + } + + /** + * This method act as a proxy to have an async constructor. It initializes the class instance and loads a parser for custom field types + * @param componentsJSONSchemas An array of Storyblok components schemas + * @param options A set of options for the command + * @returns An instance of the GenerateTypesFromJSONSchemas class + */ + static async init(componentsJSONSchemas: JSONSchema[], options: GenerateTSTypedefsFromComponentsJSONSchemasOptions) { + const customTypeParser = await this.#loadCustomFieldTypeParser(options.customFieldTypesParserPath); + + return new GenerateTypesFromJSONSchemas(componentsJSONSchemas, options, customTypeParser); + } + + /** + * Loads a parser for custom field types. + * A `parser` in this case means a function that is the default export of a JS module (can be both CommonJS or ESM) that given a JSONSchema custom property returns a predefined JSONSchema for that property, so that it can be later converted into the appropriate Typedef + * @param path Path to the file that exports the parser function + * @returns The parser function or null + */ + static async #loadCustomFieldTypeParser(path?: string): Promise { + if (path) { + try { + const customTypeParser = await import(resolve(path)); + return customTypeParser.default; + } catch (e) { + console.error(e); + return null; + } + } + + return null; + } + + /** + * Extract all component names and all the groups containing the respective components from an array of component JSONSchemas. + * @param componentsJSONSchemas Array of Storyblok component schemas + * @returns An object with two properties, `componentGroups` that holds the relationship between groups and child components and `componentNames` which is a list of all the component names, including the ones that do not belong to any group. + */ + #generateComponentGroupsAndComponentNamesFromJSONSchemas(componentsJSONSchemas: JSONSchema[]) { + const { componentGroups, componentNames } = componentsJSONSchemas.reduce( + (acc, currentComponent) => { + if (currentComponent.component_group_uuid) + acc.componentGroups.set( + currentComponent.component_group_uuid, + acc.componentGroups.has(currentComponent.component_group_uuid) + ? acc.componentGroups.get(currentComponent.component_group_uuid)!.add(currentComponent.name) + : new Set([currentComponent.name]) + ); + + acc.componentNames.add(currentComponent.name); + return acc; + }, + { componentGroups: new Map(), componentNames: new Set() } + ); + + return { componentGroups, componentNames }; + } + + /** + * Triggers the whole TS Type definition process + * @returns The class instance + */ + async generateTSFile() { + for await (const component of this.#componentsJSONSchemas) { + // By default all types will havea a required `_uid` and a required `component` properties + const requiredFields = Object.entries>(component.schema).reduce( + (acc, [key, value]) => { + if (value.required) { + return [...acc, key]; + } + return acc; + }, + ["component", "_uid"] + ); + + const componentType = this.#getComponentType(component.name); + const componentPropertiesTypeAnnotations = await this.#getTypeAnnotationsForComponentProperties(component.schema); + + const componentSchema: JSONSchema = { + $id: `#/${component.name}`, + title: componentType, + type: "object", + required: requiredFields, + properties: { + ...componentPropertiesTypeAnnotations, + component: { + type: "string", + enum: [component.name], + }, + _uid: { + type: "string", + }, + }, + }; + + try { + const typedefString = await compile(componentSchema, component.name, this.#options.JSONSchemaToTSCustomOptions); + this.#typedefsFileStrings.push(typedefString); + } catch (e) { + // TODO: add proper error handling + console.error("ERROR", e); + } + } + + return this.#writeTypeDefs(); + } + + /** + * Creates and returns an Object representing the Typescript type definitions for the provided component and its properties + * @param componentSchema A JSONSchema representing a single component + * @returns Returns a JSONSchema-like object with the type annotation for each property + * @example + * // Example of a returned JSON + * { + * image: { type: 'string' }, + * given_name: { type: 'string' }, + * family_name: { type: 'string' }, + * about: { type: 'string' }, + * email: { type: 'string' }, + * cta: { type: 'array', tsType: '(FooCTAStoryblok)[]' }, + * } + */ + async #getTypeAnnotationsForComponentProperties(componentSchema: JSONSchema) { + const typeAnnotations: JSONSchema["properties"] = {}; + + for await (const [propertyName, propertyValue] of Object.entries(componentSchema)) { + // Schema keys that start with `tab-` are only used for describing tabs in the Storyblok UI. + // Therefore they are ignored. + if (propertyName.startsWith("tab-")) { + continue; + } + + const propertyType = propertyValue.type; + const propertyTypeAnnotation: JSONSchema = { + [propertyName]: this.#getPropertyTypeAnnotation(propertyValue), + }; + + // Generate type for custom field + if (propertyType === "custom") { + Object.assign( + typeAnnotations, + typeof this.#customTypeParser === "function" ? this.#customTypeParser(propertyName, propertyValue) : {} + ); + + continue; + } + + // Generate type for field types provided by Storyblok + if ((this.#storyblokProvidedPropertyTypes as string[]).includes(propertyType)) { + const componentType = this.#getComponentType(propertyType); + propertyTypeAnnotation[propertyName].tsType = componentType; + + const typedefForStoryblokProvidedType = await this.#generateTypedef( + propertyType as StoryblokProvidedPropertyType, + componentType + ); + + // Include Storyblok field type type definition to the typedef string file, if it hasn't been included yet + if (typedefForStoryblokProvidedType) { + this.#typedefsFileStrings.push(typedefForStoryblokProvidedType); + } + } + + if (propertyType === "multilink") { + const excludedLinktypes: string[] = [ + ...(!propertyValue.email_link_type ? ['{ linktype?: "email" }'] : []), + ...(!propertyValue.asset_link_type ? ['{ linktype?: "asset" }'] : []), + ]; + const componentType = this.#getComponentType(propertyType); + + propertyTypeAnnotation[propertyName].tsType = + excludedLinktypes.length > 0 ? `Exclude<${componentType}, ${excludedLinktypes.join(" | ")}>` : componentType; + } + + if (propertyType === "bloks") { + if (propertyValue.restrict_components) { + // Components restricted by groups + if (propertyValue.restrict_type === "groups") { + if ( + Array.isArray(propertyValue.component_group_whitelist) && + propertyValue.component_group_whitelist.length > 0 + ) { + const componentsInGroupWhitelist = propertyValue.component_group_whitelist.reduce( + (components: string[], groupUUID: string) => { + const componentsInGroup = this.#componentGroups.get(groupUUID); + + return componentsInGroup + ? [ + ...components, + ...Array.from(componentsInGroup).map((componentName) => this.#getComponentType(componentName)), + ] + : components; + }, + [] + ); + + propertyTypeAnnotation[propertyName].tsType = + componentsInGroupWhitelist.length > 0 ? `(${componentsInGroupWhitelist.join(" | ")})[]` : `never[]`; + } + } else { + // Components restricted by 1-by-1 list + if (Array.isArray(propertyValue.component_whitelist) && propertyValue.component_whitelist.length > 0) { + propertyTypeAnnotation[propertyName].tsType = `(${propertyValue.component_whitelist + .map((name: string) => this.#getComponentType(name)) + .join(" | ")})[]`; + } + } + } else { + // All components can be slotted in this property (AKA no restrictions) + propertyTypeAnnotation[propertyName].tsType = `(${Array.from(this.#componentNames) + .map((componentName) => this.#getComponentType(componentName)) + .join(" | ")})[]`; + } + } + + Object.assign(typeAnnotations, propertyTypeAnnotation); + } + + return typeAnnotations; + } + + /** + * Get the correct JSONSchema type annotation for the provided property + * @param property A Storyblok component property object, A.K.A. what you can find in a key of the `schema` property inside a component JSONSchema. + * @returns A ComponentPropertyTypeAnnotation object + */ + #getPropertyTypeAnnotation(property: ComponentPropertySchema): ComponentPropertyTypeAnnotation { + // If a property type is one of the ones provided by Storyblok, return that type + // Casting as string[] to avoid TS error on using Array.includes on different narrowed types + if ((this.#storyblokProvidedPropertyTypes as string[]).includes(property.type)) { + return { + type: property.type, + }; + } + + // Initialize property type as any (fallback type) + let type: string | string[] = "any"; + + // Initialize the array of options (possible values) of the property + const options = property.options && property.options.length > 0 ? property.options.map((item) => item.value) : []; + + // Add empty option to options array + if (options.length > 0 && property.exclude_empty_option !== true) { + options.unshift(""); + } + + if (property.source === "internal_stories") { + if (property.filter_content_type) { + if (typeof property.filter_content_type === "string") { + return { + tsType: `(${this.#getStoryType(property.filter_content_type)} | string )${property.type === "options" ? "[]" : ""}`, + }; + } + + return { + tsType: `(${property.filter_content_type + .map((type2) => this.#getStoryType(type2)) + // In this case property.type can be `option` or `options`. In case of `options` the type should be an array + .join(" | ")} | string )${property.type === "options" ? "[]" : ""}`, + }; + } + } + + if ( + // If there is no `source` and there are options, the data source is the component itself + // TODO: check if this is an old behaviour (shouldn't this be handled as an "internal" source?) + (options.length > 0 && !property.source) || + property.source === "internal_languages" || + property.source === "external" + ) { + type = "string"; + } + + if (property.source === "internal") { + type = ["number", "string"]; + } + + if (property.type === "option") { + if (options.length > 0) { + return { + type, + enum: options, + }; + } + + return { + type, + }; + } + + if (property.type === "options") { + if (options.length > 0) { + return { + type: "array", + items: { + enum: options, + }, + }; + } + + return { + type: "array", + items: { type }, + }; + } + + switch (property.type) { + case "bloks": + return { type: "array" }; + case "boolean": + return { type: "boolean" }; + case "datetime": + case "image": + case "markdown": + case "number": + case "text": + case "textarea": + return { type: "string" }; + default: + return { type: "any" }; + } + } + + /** + * Generate the Type - only the name - from the supplied component name with the provided options + * @param componentName The name of the component - in snake_case + * @returns A string with the Type name in PascalCase, as for Typescript standards + */ + #getComponentType(componentName: string) { + const componentType = startCase( + camelCase(`${this.#options.typeNamesPrefix ?? ""}_${componentName}_${this.#options.typeNamesSuffix}`) + ).replace(/ /g, ""); + + /** + * A TS identifier cannot start with a number > add an underscore in that case + * TODO: add some logic to handle other edge cases, such as JS/TS reserved keywords as blok names (i.e. if a blok is named `string`, the resulting type would be `String`, if no suffix is provided) + */ + const isFirstCharacterNumber = !isNaN(parseInt(componentType.charAt(0))); + return isFirstCharacterNumber ? `_${componentType}` : componentType; + } + + /** + * Get the Typescript Type of a content-type wrapped in the Type defined for the whole story object, which is stored in this.#STORY_TYPE + * @param contentTypeName The name of the content-type + * @returns The Typescript Type for the corresponding content-type + */ + #getStoryType(contentTypeName: string) { + return `${this.#STORY_TYPE}<${this.#getComponentType(contentTypeName)}>`; + } + + /** + * Generate one of the default types that are provided by Storyblok - such as Multilink, Asset, etc., if they have not been already generated + * @param propertyType The type of the Storyblok-provided property + * @param title The name of the property + * @returns The type definition for the provided property + */ + async #generateTypedef(propertyType: StoryblokProvidedPropertyType, title: string) { + return ( + !this.#hasStoryblokProvidedPropertyTypeBeenGenerated.get(propertyType) && + (await this.#generateTypedefForStoryblokProvidedProperty(propertyType, title)) + ); + } + + /** + * + * @param propertyType The type of the Storyblok-provided property + * @param title + * @returns + */ + async #generateTypedefForStoryblokProvidedProperty(propertyType: StoryblokProvidedPropertyType, title: string) { + try { + const schema = this.#getSchemaForStoryblokProvidedPropertyType.get(propertyType)?.(title); + return schema && (await this.#generateTypeString(schema, propertyType)); + } catch (e) { + console.error(`Error generating type ${propertyType} with title ${title}`, e); + } + } + + /** + * Leverage json-schema-to-typescript to compile a component schema whose type is one of the default types provided by Storyblok into a Typescript Type string + * @param schema The JSON Schema of a component + * @param typeName One of the default property types provided by Storyblok + * @returns A string containing the Typescript Type definition of the provided component + */ + async #generateTypeString(schema: JSONSchema, typeName: StoryblokProvidedPropertyType) { + // TODO: handle potential errors and log error messages + const typeString = await compile(schema, typeName, this.#options.JSONSchemaToTSCustomOptions); + this.#hasStoryblokProvidedPropertyTypeBeenGenerated.set(typeName, true); + + return typeString; + } + + /** + * Write the array of type definitions - one entry per type - to the file at the provided `destinationFilePath` + * @returns The class instance + */ + #writeTypeDefs() { + if (this.#options.destinationFilePath) { + fs.writeFileSync(this.#options.destinationFilePath, this.#typedefsFileStrings.join("\n")); + } + + return this; + // TODO: log error in case of missing path + } + + /** + * Get the list of Storyblok-provided property types + */ + get #storyblokProvidedPropertyTypes() { + return Array.from(this.#getSchemaForStoryblokProvidedPropertyType.keys()); + } +} diff --git a/src/utils/typescript/storyblokProvidedPropertyTypes.ts b/src/utils/typescript/storyblokProvidedPropertyTypes.ts new file mode 100644 index 00000000..21f5b139 --- /dev/null +++ b/src/utils/typescript/storyblokProvidedPropertyTypes.ts @@ -0,0 +1,420 @@ +import type { JSONSchema } from "json-schema-to-typescript"; + +export const getAssetJSONSchema = (title: string): JSONSchema => ({ + $id: "#/asset", + title, + type: "object", + required: ["id", "fieldtype", "filename", "name", "title", "focus", "alt"], + properties: { + alt: { + type: ["string", "null"], + }, + copyright: { + type: ["string", "null"], + }, + fieldtype: { + type: "string", + enum: ["asset"], + }, + id: { + type: "number", + }, + filename: { + type: ["string", "null"], + }, + name: { + type: "string", + }, + title: { + type: ["string", "null"], + }, + focus: { + type: ["string", "null"], + }, + meta_data: { + type: "object", + }, + source: { + type: ["string", "null"], + }, + is_external_url: { + type: "boolean", + }, + is_private: { + type: "boolean", + }, + src: { + type: "string", + }, + updated_at: { + type: "string", + }, + // Cloudinary integration keys + width: { + type: ["number", "null"], + }, + height: { + type: ["number", "null"], + }, + aspect_ratio: { + type: ["number", "null"], + }, + public_id: { + type: ["string", "null"], + }, + content_type: { + type: "string", + }, + }, +}); + +export const getMultiassetJSONSchema = (title: string): JSONSchema => ({ + $id: "#/multiasset", + title, + type: "array", + items: { + type: "object", + required: ["id", "fieldtype", "filename", "name", "title", "focus", "alt"], + properties: { + alt: { + type: ["string", "null"], + }, + copyright: { + type: ["string", "null"], + }, + fieldtype: { + type: "string", + enum: ["asset"], + }, + id: { + type: "number", + }, + filename: { + type: ["string", "null"], + }, + name: { + type: "string", + }, + title: { + type: ["string", "null"], + }, + focus: { + type: ["string", "null"], + }, + meta_data: { + type: "object", + }, + source: { + type: ["string", "null"], + }, + is_external_url: { + type: "boolean", + }, + is_private: { + type: "boolean", + }, + src: { + type: "string", + }, + updated_at: { + type: "string", + }, + // Cloudinary integration keys + width: { + type: ["number", "null"], + }, + height: { + type: ["number", "null"], + }, + aspect_ratio: { + type: ["number", "null"], + }, + public_id: { + type: ["string", "null"], + }, + content_type: { + type: "string", + }, + }, + }, +}); + +// TODO: find a reliable way to share props among different Link Types to increase maintainability +// Currently not possible because of JSONSchema4 complaining +const multilinkSharedRequiredProps = ["fieldtype", "id", "url", "cached_url", "linktype"]; + +export const getMultilinkJSONSchema = (title: string): JSONSchema => ({ + $id: "#/multilink", + title, + oneOf: [ + { + type: "object", + required: multilinkSharedRequiredProps, + properties: { + // Shared props + fieldtype: { + type: "string", + enum: ["multilink"], + }, + id: { type: "string" }, + url: { type: "string" }, + cached_url: { type: "string" }, + target: { type: "string", enum: ["_blank", "_self"] }, + // Custom props + anchor: { + type: "string", + }, + rel: { + type: "string", + }, + title: { + type: "string", + }, + prep: { + type: "string", + }, + linktype: { + type: "string", + enum: ["story"], + }, + story: { + type: "object", + required: ["name", "id", "uuid", "slug", "full_slug"], + properties: { + name: { + type: "string", + }, + created_at: { + type: "string", + format: "date-time", + }, + published_at: { + type: "string", + format: "date-time", + }, + id: { + type: "integer", + }, + uuid: { + type: "string", + format: "uuid", + }, + content: { + type: "object", + }, + slug: { + type: "string", + }, + full_slug: { + type: "string", + }, + sort_by_date: { + type: ["null", "string"], + format: "date-time", + }, + position: { + type: "integer", + }, + tag_list: { + type: "array", + items: { + type: "string", + }, + }, + is_startpage: { + type: "boolean", + }, + parent_id: { + type: ["null", "integer"], + }, + meta_data: { + type: ["null", "object"], + }, + group_id: { + type: "string", + format: "uuid", + }, + first_published_at: { + type: "string", + format: "date-time", + }, + release_id: { + type: ["null", "integer"], + }, + lang: { + type: "string", + }, + path: { + type: ["null", "string"], + }, + alternates: { + type: "array", + }, + default_full_slug: { + type: ["null", "string"], + }, + translated_slugs: { + type: ["null", "array"], + }, + }, + }, + }, + }, + { + type: "object", + required: multilinkSharedRequiredProps, + properties: { + // Shared props + fieldtype: { + type: "string", + enum: ["multilink"], + }, + id: { type: "string" }, + url: { type: "string" }, + cached_url: { type: "string" }, + target: { type: "string", enum: ["_blank", "_self"] }, + // Custom props + linktype: { + type: "string", + enum: ["url"], + }, + rel: { + type: "string", + }, + title: { + type: "string", + }, + }, + }, + { + type: "object", + required: multilinkSharedRequiredProps, + properties: { + // Shared props + fieldtype: { + type: "string", + enum: ["multilink"], + }, + id: { type: "string" }, + url: { type: "string" }, + cached_url: { type: "string" }, + target: { type: "string", enum: ["_blank", "_self"] }, + // Custom props + email: { + type: "string", + }, + linktype: { + type: "string", + enum: ["email"], + }, + }, + }, + { + type: "object", + required: multilinkSharedRequiredProps, + properties: { + // Shared props + fieldtype: { + type: "string", + enum: ["multilink"], + }, + id: { type: "string" }, + url: { type: "string" }, + cached_url: { type: "string" }, + target: { type: "string", enum: ["_blank", "_self"] }, + // Custom props + linktype: { + type: "string", + enum: ["asset"], + }, + }, + }, + ], +}); + +export const getRichtextJSONSchema = (title: string): JSONSchema => ({ + $id: "#/richtext", + title, + type: "object", + required: ["type"], + properties: { + type: { + type: "string", + }, + content: { + type: "array", + items: { + $ref: "#", + }, + }, + marks: { + type: "array", + items: { + $ref: "#", + }, + }, + attrs: {}, + text: { + type: "string", + }, + }, +}); + +export const getTableJSONSchema = (title: string): JSONSchema => ({ + $id: "#/table", + title, + type: "object", + required: ["tbody", "thead"], + properties: { + thead: { + type: "array", + items: { + type: "object", + required: ["_uid", "component"], + properties: { + _uid: { + type: "string", + }, + value: { + type: "string", + }, + component: { + type: "number", + }, + }, + }, + }, + tbody: { + type: "array", + items: { + type: "object", + required: ["_uid", "component", "body"], + properties: { + _uid: { + type: "string", + }, + body: { + type: "array", + items: { + type: "object", + properties: { + _uid: { + type: "string", + }, + value: { + type: "string", + }, + component: { + type: "number", + }, + }, + }, + }, + component: { + type: "number", + }, + }, + }, + }, + }, +}); diff --git a/tests/constants.js b/tests/constants.js new file mode 100644 index 00000000..349d23fc --- /dev/null +++ b/tests/constants.js @@ -0,0 +1,356 @@ +import { getRegionApiEndpoint } from '../src/utils/region' +import { EU_CODE } from '@storyblok/region-helper' +export const EMAIL_TEST = 'test@storyblok.com' +export const PASSWORD_TEST = 'test' +export const TOKEN_TEST = 'storyblok1234' +export const REGION_TEST = EU_CODE + +// use functions to always returns 'new' data +export const FAKE_COMPONENTS = () => [ + { + name: 'teaser', + display_name: null, + created_at: '2019-10-15T17:00:32.212Z', + id: 0, + schema: { + headline: { + type: 'text' + } + }, + image: null, + preview_field: null, + is_root: false, + preview_tmpl: null, + is_nestable: true, + all_presets: [], + internal_tag_ids: [], + internal_tags_list: [], + preset_id: null, + real_name: 'teaser', + component_group_uuid: null + }, + { + name: 'feature', + display_name: null, + created_at: '2019-11-06T17:07:04.196Z', + updated_at: '2019-11-06T18:12:29.136Z', + id: 1, + schema: { + logo: { + type: 'image' + }, + name: { + type: 'text' + }, + description: { + type: 'textarea' + }, + link_text: { + type: 'text' + }, + link: { + type: 'multilink' + } + }, + image: null, + preview_field: null, + is_root: false, + preview_tmpl: null, + is_nestable: true, + all_presets: [], + internal_tag_ids: [], + internal_tags_list: [], + preset_id: null, + real_name: 'feature', + component_group_uuid: null + }, + { + name: 'logo', + display_name: null, + id: 1, + schema: { + image: { + type: 'image' + } + }, + image: null, + preview_field: null, + is_root: false, + preview_tmpl: null, + is_nestable: true, + all_presets: [], + internal_tag_ids: [], + internal_tags_list: [], + preset_id: null, + real_name: 'logo', + component_group_uuid: '529cc32a-1d97-4b4a-b0b6-28e33dc56c0d' + }, + { + name: 'blocks', + display_name: null, + id: 2, + schema: { + other: { + type: 'bloks', + max_length: '', + translatable: false, + restrict_components: true, + restrict_type: 'groups', + component_group_whitelist: [ + '529cc32a-1d97-4b4a-b0b6-28e33dc56c0d' + ] + } + }, + image: null, + preview_field: null, + is_root: false, + preview_tmpl: null, + is_nestable: true, + all_presets: [], + internal_tag_ids: [], + internal_tags_list: [], + preset_id: null, + real_name: 'blocks', + component_group_uuid: null + }, + { + name: 'hero', + display_name: null, + created_at: '2020-07-20T20:10:00.655Z', + updated_at: '2020-07-20T20:10:00.655Z', + id: 3, + schema: { + title: { + type: 'text' + }, + subtitle: { + type: 'text' + } + }, + image: null, + preview_field: null, + is_root: false, + preview_tmpl: null, + is_nestable: true, + all_presets: [ + { + id: '01', + name: 'Hero Variant 1', + component_id: 3, + image: null + } + ], + internal_tag_ids: [], + internal_tags_list: [], + preset_id: null, + real_name: 'hero', + component_group_uuid: null + }, + { + name: 'meta', + display_name: null, + created_at: '2019-11-06T17:07:04.196Z', + updated_at: '2019-11-06T18:12:29.136Z', + id: 4, + schema: { + robot: { + type: "option", + source: "internal", + datasource_slug: "robots", + } + }, + image: null, + preview_field: null, + is_root: false, + preview_tmpl: null, + is_nestable: true, + all_presets: [], + preset_id: null, + real_name: 'meta', + component_group_uuid: null + }, +] + +export const FAKE_DATASOURCES = () => [ + { + id: 1, + name: "Robots", + slug: "robots", + dimensions: [], + created_at: "2019-10-15T17:00:32.212Z", + updated_at: "2019-11-15T17:00:32.212Z", + }, +] + +export const FAKE_DATASOURCE_ENTRIES = () => [ + { + id: 1, + name: "No index", + value: "noindex", + dimension_value: "" + }, + { + id: 2, + name: "No follow", + value: "nofollow", + dimension_value: "" + } +] + +// use functions to always returns 'new' data +export const FAKE_STORIES = () => [ + { + name: 'About', + id: 0, + uuid: '5ebd1485-25c5-460f-b477-b41facc884f8', + content: { + _uid: '4bc98f32-6200-4176-86b0-b6f2ea14be6f', + body: [ + { + _uid: '111781de-3174-4f61-8ae3-0b653085a582', + headline: 'My About Page', + component: 'teaser' + } + ], + component: 'page' + }, + slug: 'about', + full_slug: 'about', + published: true, + unpublished_changes: true + }, + { + name: 'Home', + id: 1, + uuid: '5ebd1485-25c5-460f-b477-b41facc884f8', + content: { + _uid: '4bc98f32-6200-4176-86b0-b6f2ea14be6f', + body: [ + { + _uid: '111781de-3174-4f61-8ae3-0b653085a582', + headline: 'Hello World!', + component: 'teaser' + }, + { + _uid: '2aae2a9b-df65-4572-be4c-e4460d81e299', + columns: [ + { + _uid: 'cd6efa74-31c3-47ec-96d2-91111f2a6c7c', + name: 'Feature 1', + component: 'feature', + image_data: '//a.storyblok.com/f/67249/1024x512/64e7272404/headless.png' + }, + { + _uid: 'e994cb3e-0f2b-40a7-8db1-f3e456e7b868', + name: 'Feature 2', + component: 'feature' + }, + { + _uid: '779ee42f-a856-405c-9e34-5b49380ae4fe', + name: 'Feature 3', + component: 'feature' + }, + { + _uid: 'a315e9a7-4a0c-4cc5-b393-572533e8bf87', + image: '//a.storyblok.com/f/67249/1024x512/64e7272404/headless.png', + component: 'my-image' + } + ], + component: 'grid' + } + ], + component: 'page' + }, + slug: 'home', + full_slug: 'home', + published: true, + unpublished_changes: false + } +] + +export const FAKE_SPACES = () => [ + { + name: 'Example Space', + domain: 'https://example.storyblok.com', + uniq_domain: null, + plan: 'starter', + plan_level: 0, + limits: {}, + created_at: '2018-11-10T15:33:18.402Z', + id: 0 + }, + { + name: 'Example Space Two', + domain: 'https://example-two.storyblok.com', + uniq_domain: null, + plan: 'starter', + plan_level: 0, + limits: {}, + created_at: '2018-11-10T15:33:18.402Z', + id: 1 + } +] + +export const FAKE_SPACE_OPTIONS = () => ({ + languages: [ + { + code: 'pt', + name: 'PortuguΓͺs' + }, + { + code: 'nl-be', + name: 'Dutch (Belgian)' + } + ], + hosted_backup: false, + onboarding_step: '3', + default_lang_name: 'English', + rev_share_enabled: true, + required_assest_fields: [], + use_translated_stories: false +}) + +export const FAKE_PRESET = () => ({ + id: 123, + name: 'page_preset', + preset: { + _uid: '7dce995b-07ed-4e5b-a4bb-5d22447252d8', + body: [ + { + _uid: '995e84c1-a08d-45cd-b121-e4db45e9cf50', + headline: 'Hello world!', + component: 'teaser' + }, + { + _uid: 'a6e118ec-1a57-4f0f-b1e9-1ed625a82751', + columns: [ + { + _uid: '9b0a9bed-e891-4edc-8f5e-bc29e7ec785c', + name: 'Feature 1', + component: 'feature' + }, + { + _uid: '07863609-7518-48b9-8d28-b1e8037818e2', + name: 'Feature 2', + component: 'feature' + } + ], + component: 'grid' + } + ], + component: 'page' + }, + component_id: 3481284, + space_id: 200378, + created_at: '2023-02-24T16:49:14.723Z', + updated_at: '2023-02-24T16:49:14.723Z', + image: '', + color: '', + icon: '', + description: 'page preset' +}) + +export const USERS_ROUTES = { + LOGIN: `${getRegionApiEndpoint(EU_CODE)}users/login`, + SIGNUP: `${getRegionApiEndpoint(EU_CODE)}users/signup` +} diff --git a/tests/units/delete-component.spec.js b/tests/units/delete-component.spec.js new file mode 100644 index 00000000..e774ccef --- /dev/null +++ b/tests/units/delete-component.spec.js @@ -0,0 +1,54 @@ +import deleteComponent from '../../src/tasks/delete-component' +import { FAKE_COMPONENTS } from '../constants' +import { jest } from '@jest/globals' + +describe('testing deleteComponent', () => { + it('api.deleteComponent name', () => { + const api = { + getComponents: jest.fn(() => Promise.resolve(FAKE_COMPONENTS())), + delete: jest.fn(() => Promise.resolve()) + } + return deleteComponent(api, { comp: 'teaser' }).then(() => { + expect(api.delete.mock.calls.length).toBe(1) + }) + }) + it('api.deleteComponent id', () => { + const comp = FAKE_COMPONENTS()[0] + const api = { + get: jest.fn(() => Promise.resolve({ data: { component: comp } })), + delete: jest.fn(() => Promise.resolve()) + } + return deleteComponent(api, { comp: 0 }).then(() => { + expect(api.get.mock.calls.length).toBe(1) + expect(api.delete.mock.calls.length).toBe(1) + }) + }) + it('api.deleteComponent name dryrun', () => { + const api = { + getComponents: jest.fn(() => Promise.resolve(FAKE_COMPONENTS())), + delete: jest.fn(() => Promise.resolve()) + } + return deleteComponent(api, { comp: 'teaser', dryrun: true }).then(() => { + expect(api.delete.mock.calls.length).toBe(0) + }) + }) + it('api.deleteComponent not found', () => { + const api = { + getComponents: jest.fn(() => Promise.resolve(FAKE_COMPONENTS())), + delete: jest.fn(() => Promise.resolve()) + } + return expect(deleteComponent(api, { comp: 'not a fake component' }).then(() => { + expect(api.delete.mock.calls.length).toBe(0) + })).rejects.toThrow('Component not a fake component not found.') + }) + it('api.deleteComponent not found by id', () => { + const api = { + get: jest.fn(() => Promise.reject(new Error('Not Found'))), + delete: jest.fn(() => Promise.resolve()) + } + return expect(deleteComponent(api, { comp: 1 }).then(() => { + expect(api.get.mock.calls.length).toBe(1) + expect(api.delete.mock.calls.length).toBe(0) + })).rejects.toThrow('Not Found') + }) +}) diff --git a/tests/units/delete-components.spec.js b/tests/units/delete-components.spec.js new file mode 100644 index 00000000..ede0a05f --- /dev/null +++ b/tests/units/delete-components.spec.js @@ -0,0 +1,94 @@ +import deleteComponents from '../../src/tasks/delete-components' +import { FAKE_COMPONENTS } from '../constants' +import fs from 'fs' +import { jest } from '@jest/globals' + +jest.mock('fs') +jest.spyOn(fs, 'readFileSync') + +afterEach(() => { + jest.clearAllMocks() +}) + +describe('testing deleteComponents', () => { + it('api.deleteComponents', async () => { + const source = 'components.js' + const components = FAKE_COMPONENTS() + fs.readFileSync.mockReturnValue(JSON.stringify({ + components + })) + const api = { + get: jest.fn((path) => { + const id = path.split('/')[1] + return Promise.resolve({ data: { component: components[id] } }) + }), + getComponents: jest.fn(() => { + return components + }), + delete: jest.fn(() => Promise.resolve()) + } + return deleteComponents(api, { source, reversed: false }).then(() => { + expect(api.delete.mock.calls.length).toBe(components.length) + }) + }) + it('api.deleteComponents reverse', () => { + const source = 'components.js' + const components = FAKE_COMPONENTS() + const copy = [...components] + copy.splice(2, 1) + fs.readFileSync.mockReturnValue(JSON.stringify([...copy])) + + const api = { + get: jest.fn((path) => { + const id = path.split('/')[1] + return Promise.resolve({ data: { component: components[id] } }) + }), + getComponents: jest.fn(() => { + return components + }), + delete: jest.fn(() => Promise.resolve()) + } + return deleteComponents(api, { source, reversed: true }).then(() => { + expect(api.delete.mock.calls.length).toBe(1) + }) + }) + it('api.deleteComponents --dryrun', () => { + const source = 'components.js' + const components = FAKE_COMPONENTS() + fs.readFileSync.mockReturnValue(JSON.stringify({ + components + })) + const api = { + get: jest.fn((path) => { + const id = path.split('/')[1] + return Promise.resolve({ data: { component: components[id] } }) + }), + delete: jest.fn(() => Promise.resolve()) + } + return deleteComponents(api, { source, reversed: false, dryRun: true }).then(() => { + expect(api.delete.mock.calls.length).toBe(0) + }) + }) + it('api.deleteComponents reverse --dryrun', () => { + const source = 'components.js' + const components = FAKE_COMPONENTS() + fs.readFileSync.mockReturnValue(JSON.stringify({ + components + })) + const api = { + get: jest.fn((path) => { + const id = path.split('/')[1] + return Promise.resolve({ data: { component: components[id] } }) + }), + getComponents: jest.fn(() => { + const copy = [...components] + copy.splice(3, 1) + return copy + }), + delete: jest.fn(() => Promise.resolve()) + } + return deleteComponents(api, { source, reversed: true, dryRun: true }).then(() => { + expect(api.delete.mock.calls.length).toBe(0) + }) + }) +}) diff --git a/tests/units/generate.spec.js b/tests/units/generate.spec.js new file mode 100644 index 00000000..cb01eade --- /dev/null +++ b/tests/units/generate.spec.js @@ -0,0 +1,164 @@ +import inquirer from 'inquirer' +import fs from 'fs-extra' +import { jest } from '@jest/globals' + +import { FAKE_COMPONENTS } from '../constants' +import generateMigration from '../../src/tasks/migrations/generate' +import templateFile from '../../src/tasks/templates/migration-file' + +const templateFileData = templateFile.replace(/{{ fieldname }}/g, 'subtitle') + +jest.mock('fs-extra') +const spyPathExists = jest.spyOn(fs, 'pathExists') +const spyOutputFile = jest.spyOn(fs, 'outputFile') + +const getPath = fileName => `${process.cwd()}/migrations/${fileName}` + +const FAKE_API = { + getComponents: jest.fn(() => Promise.resolve(FAKE_COMPONENTS())) +} + +const FILE_NAME = 'change_teaser_subtitle.js' + +describe('testing generateMigration', () => { + describe('when migration file does not exists', () => { + let backup + + beforeEach(() => { + backup = inquirer.prompt + inquirer.prompt = () => Promise.resolve({ choice: true }) + }) + + afterEach(() => { + inquirer.prompt = backup + jest.clearAllMocks() + }) + + it('It returns correctly fileName and created properties when the file does not exists', async () => { + spyOutputFile.mockResolvedValue(true) + const data = await generateMigration(FAKE_API, 'teaser', 'subtitle') + + expect(data.fileName).toBe(FILE_NAME) + expect(data.created).toBe(true) + }) + + it('It checks if the file exists', async () => { + spyOutputFile.mockResolvedValue(true) + const filePath = getPath(FILE_NAME) + + await generateMigration(FAKE_API, 'teaser', 'subtitle') + // call once + expect(FAKE_API.getComponents.mock.calls.length).toBe(1) + // the first call receives the file path + expect(fs.pathExists.mock.calls[0][0]).toBe(filePath) + }) + + it('It create the file correctly', async () => { + spyOutputFile.mockResolvedValue(true) + const filePath = getPath(FILE_NAME) + + await generateMigration(FAKE_API, 'teaser', 'subtitle') + // call once + expect(fs.outputFile.mock.calls.length).toBe(1) + // the first call receives the file argument + expect(fs.outputFile.mock.calls[0][0]).toBe(filePath) + // the first call receives a string with template + expect(fs.outputFile.mock.calls[0][1]).toBe(templateFileData) + }) + }) + + it('It throws an error when component does not exists', async () => { + try { + await generateMigration(FAKE_API, 'produce', 'price') + } catch (e) { + expect(e.message).toBe('The component does not exists') + } + }) + + describe('when migration file exists and user choice do not overwrite', () => { + let backup + + beforeEach(() => { + backup = inquirer.prompt + inquirer.prompt = () => Promise.resolve({ choice: false }) + }) + + afterEach(() => { + inquirer.prompt = backup + jest.clearAllMocks() + }) + + it('It does not overwrite the migration file', async () => { + spyPathExists.mockReturnValue(true) + const data = await generateMigration(FAKE_API, 'teaser', 'subtitle') + + expect(data.fileName).toBe(FILE_NAME) + expect(data.created).toBe(false) + }) + + it('It checks if the file exists', async () => { + spyPathExists.mockReturnValue(true) + const filePath = getPath(FILE_NAME) + + await generateMigration(FAKE_API, 'teaser', 'subtitle') + // call once + expect(spyPathExists).toHaveBeenCalledTimes(1) + // the first call receives the file path + expect(spyPathExists.mock.calls[0][0]).toBe(filePath) + }) + + it('It does not create the file', async () => { + spyPathExists.mockReturnValue(true) + await generateMigration(FAKE_API, 'teaser', 'subtitle') + // don't call + expect(fs.outputFile.mock.calls.length).toBe(0) + }) + }) + + describe('when migration file exists and user choice do overwrite', () => { + let backup + + beforeEach(() => { + backup = inquirer.prompt + inquirer.prompt = () => Promise.resolve({ choice: true }) + }) + + afterEach(() => { + inquirer.prompt = backup + jest.clearAllMocks() + }) + + it('It does overwrite the migration file', async () => { + spyPathExists.mockReturnValue(false) + spyOutputFile.mockResolvedValue(true) + const data = await generateMigration(FAKE_API, 'teaser', 'subtitle') + + expect(data.fileName).toBe(FILE_NAME) + expect(data.created).toBe(true) + }) + + it('It checks if the file exists', async () => { + const filePath = getPath(FILE_NAME) + + await generateMigration(FAKE_API, 'teaser', 'subtitle') + // call once + expect(FAKE_API.getComponents.mock.calls.length).toBe(1) + // the first call receives the file path + expect(fs.pathExists.mock.calls[0][0]).toBe(filePath) + }) + + it('It does create the file', async () => { + spyPathExists.mockReturnValue(false) + spyOutputFile.mockResolvedValue(true) + const filePath = getPath(FILE_NAME) + + await generateMigration(FAKE_API, 'teaser', 'subtitle') + // call once + expect(fs.outputFile.mock.calls.length).toBe(1) + // the first call receives the file argument + expect(fs.outputFile.mock.calls[0][0]).toBe(filePath) + // the first call receives a string with template + expect(fs.outputFile.mock.calls[0][1]).toBe(templateFileData) + }) + }) +}) diff --git a/tests/units/import.spec.js b/tests/units/import.spec.js new file mode 100644 index 00000000..19b8cd1f --- /dev/null +++ b/tests/units/import.spec.js @@ -0,0 +1,87 @@ +import { jest } from '@jest/globals' +import { FAKE_STORIES } from '../constants' +import { jsonParser, discoverExtension, xmlParser, csvParser, sendContent } from '../../src/tasks/import/utils' + +const response = [{ + slug: 'this-is-my-title', + name: 'This is my title', + parent_id: 0, + content: { + component: 'About', + category: 'press', + title: 'This is my title', + image: 'https://a.storyblok.com/f/51376/x/1502f01431/corporate-website.svg', + text: 'Lorem ipsum dolor sit amet' + } +}] + +describe('Test utils functions to import command', () => { + it('Test discoverExtension, function', () => { + const fileName = 'test.csv' + expect(discoverExtension(fileName)).toEqual('csv') + }) + + it('Test discoverExtension, function', () => { + const fileName = 'text.test.txt' + expect(discoverExtension(fileName)).toEqual('txt') + }) + + it('Test xml parser', async () => { + const data = ` + + + + this-is-my-title + This is my title + Lorem ipsum dolor sit amet + https://a.storyblok.com/f/51376/x/1502f01431/corporate-website.svg + press + + + ` + + const res = await xmlParser(data, 'About', 0) + expect(res).toEqual(response) + }) + + // TODO: this test fails because we're trying to iterate over an object as if it was an iterable in the jsonParser function + // It's either the function that is bugged or this test that has to be reviewed + it.skip('Test json parser', async () => { + const data = { + 'this-is-my-title': { + title: 'This is my title', + text: 'Lorem ipsum dolor sit amet', + image: 'https://a.storyblok.com/f/51376/x/1502f01431/corporate-website.svg', + category: 'press' + } + } + + const res = await jsonParser(JSON.stringify(data), 'About', 0) + expect(res).toEqual(response) + }) + + it('Test csv parser', async () => { + const data = `path;title;text;image;category +this-is-my-title;This is my title;"Lorem ipsum dolor sit amet";https://a.storyblok.com/f/51376/x/1502f01431/corporate-website.svg;press` + + const res = await csvParser(data, 'About', 0) + + expect(res).toEqual(response) + }) + + it('Test sendContent function', async () => { + const URL = 'https://api.storyblok.com/v1/' + const stories = FAKE_STORIES()[0] + + const FAKE_API = { + getClient: jest.fn(() => Promise.resolve({ + oauthToken: process.env.STORYBLOK_TOKEN + }, URL)), + post: jest.fn(() => Promise.resolve(stories.name)), + spaceId: jest.fn(() => Promise.resolve(75070)) + } + + await sendContent(FAKE_API, [stories]) + expect(await FAKE_API.post()).toBe(stories.name) + }) +}) diff --git a/tests/units/is-authorized.spec.js b/tests/units/is-authorized.spec.js new file mode 100644 index 00000000..59709183 --- /dev/null +++ b/tests/units/is-authorized.spec.js @@ -0,0 +1,39 @@ +import axios from 'axios' +import api from '../../src/utils/api' +import creds from '../../src/utils/creds' +import { EMAIL_TEST, PASSWORD_TEST, TOKEN_TEST } from '../constants' +import { jest } from '@jest/globals' + +jest.mock('axios') +jest.spyOn(axios, 'post').mockResolvedValue({ + data: { + access_token: TOKEN_TEST + } +}) + +describe('api.isAuthorized() method', () => { + beforeEach(() => { + creds.set(null) + }) + + afterEach(() => { + creds.set(null) + }) + + afterAll(() => { + jest.resetAllMocks() + }) + + it('api.isAuthorized() should be true when user is not logged', async () => { + await api.login(EMAIL_TEST, PASSWORD_TEST) + expect(api.isAuthorized()).toBe(true) + }) + + it('api.isAuthorized() should be false when user is logout', async () => { + await api.login(EMAIL_TEST, PASSWORD_TEST) + expect(api.isAuthorized()).toBe(true) + + api.logout() + expect(api.isAuthorized()).toBe(false) + }) +}) diff --git a/tests/units/list-spaces.spec.js b/tests/units/list-spaces.spec.js new file mode 100644 index 00000000..f08efe7a --- /dev/null +++ b/tests/units/list-spaces.spec.js @@ -0,0 +1,49 @@ +import { EU_CODE, US_CODE, AP_CODE, CA_CODE, CN_CODE } from '@storyblok/region-helper' +import listSpaces from '../../src/tasks/list-spaces' +import { FAKE_SPACES } from '../constants' +import { jest } from '@jest/globals' + +describe('Test spaces method', () => { + it('Testing list-spaces funtion without api instance', async () => { + const spaces = await listSpaces() + expect(spaces).toStrictEqual([]) + }) + + it('Testing list-spaces function for China region', async () => { + const FAKE_API = { + getAllSpacesByRegion: jest.fn(() => Promise.resolve(FAKE_SPACES())) + } + expect( + await listSpaces(FAKE_API, CN_CODE) + ).toEqual(FAKE_SPACES()) + expect(FAKE_API.getAllSpacesByRegion).toHaveBeenCalled() + }) + + it('Testing list-spaces funtion for all regions', async () => { + const FAKE_API = { + getAllSpacesByRegion: jest.fn(() => Promise.resolve(FAKE_SPACES())) + } + const response = [ + { + key: EU_CODE, + res: [...FAKE_SPACES()] + }, + { + key: US_CODE, + res: [...FAKE_SPACES()] + }, + { + key: CA_CODE, + res: [...FAKE_SPACES()] + }, + { + key: AP_CODE, + res: [...FAKE_SPACES()] + } + ] + + expect( + await listSpaces(FAKE_API, EU_CODE) + ).toEqual(response) + }) +}) diff --git a/tests/units/login.spec.js b/tests/units/login.spec.js new file mode 100644 index 00000000..cfb84703 --- /dev/null +++ b/tests/units/login.spec.js @@ -0,0 +1,42 @@ +import axios from 'axios' +import api from '../../src/utils/api' +import creds from '../../src/utils/creds' +import { EMAIL_TEST, TOKEN_TEST, PASSWORD_TEST, REGION_TEST } from '../constants' +import { jest } from '@jest/globals' + +jest.mock('axios') + +const postSpy = jest.spyOn(axios, 'post').mockResolvedValue({ + data: { + access_token: TOKEN_TEST + } +}) + +describe('api.login() method', () => { + beforeEach(() => { + creds.set(null) + }) + + afterEach(() => { + creds.set(null) + }) + + it('when login is correct, the .netrc file is populated', async () => { + await api.login({ email: EMAIL_TEST, password: PASSWORD_TEST }) + + expect(creds.get()).toEqual({ + email: EMAIL_TEST, + token: TOKEN_TEST, + region: REGION_TEST + }) + }) + + it('when login is incorrect, the .netrc file is not populated and throw a reject message', async () => { + postSpy.mockRejectedValueOnce(new Error('Incorrect access')) + try { + await api.login({ email: EMAIL_TEST, password: '1234', region: REGION_TEST }) + } catch (e) { + expect(e.message).toBe('Incorrect access') + } + }) +}) diff --git a/tests/units/logout.spec.js b/tests/units/logout.spec.js new file mode 100644 index 00000000..3c00d70b --- /dev/null +++ b/tests/units/logout.spec.js @@ -0,0 +1,19 @@ +import api from '../../src/utils/api' +import creds from '../../src/utils/creds' +import { EMAIL_TEST, TOKEN_TEST } from '../constants' + +describe('api.logout() method', () => { + beforeEach(() => { + creds.set(EMAIL_TEST, TOKEN_TEST) + }) + + afterEach(() => { + creds.set(null) + }) + + it('api.logout() should be empty the .netrc file', () => { + api.logout() + + expect(creds.get()).toEqual(null) + }) +}) diff --git a/tests/units/migrations/change_teaser_date.js b/tests/units/migrations/change_teaser_date.js new file mode 100644 index 00000000..bf606ba1 --- /dev/null +++ b/tests/units/migrations/change_teaser_date.js @@ -0,0 +1,5 @@ +// this file is an example of a file that does not exports a function + +export default { + name: 'Storyblok' +} diff --git a/tests/units/migrations/change_teaser_headline.js b/tests/units/migrations/change_teaser_headline.js new file mode 100644 index 00000000..f10bfd25 --- /dev/null +++ b/tests/units/migrations/change_teaser_headline.js @@ -0,0 +1,5 @@ +import { jest } from '@jest/globals' +// this file will be used to test if the api.put +// method is not called, so it receives blok, mas do not change it +export default jest.fn((blok) => { +}) diff --git a/tests/units/migrations/change_teaser_subtitle.js b/tests/units/migrations/change_teaser_subtitle.js new file mode 100644 index 00000000..c8ecf429 --- /dev/null +++ b/tests/units/migrations/change_teaser_subtitle.js @@ -0,0 +1,5 @@ +import { jest } from '@jest/globals' + +export default jest.fn((blok) => { + blok.subtitle = 'Hey There!' +}) diff --git a/tests/units/pull-components.spec.js b/tests/units/pull-components.spec.js new file mode 100644 index 00000000..75cccc26 --- /dev/null +++ b/tests/units/pull-components.spec.js @@ -0,0 +1,192 @@ +import fs from 'fs' +import pullComponents from '../../src/tasks/pull-components' +import { FAKE_PRESET, FAKE_COMPONENTS, FAKE_DATASOURCES, FAKE_DATASOURCE_ENTRIES } from '../constants' +import { jest } from '@jest/globals' + +jest.spyOn(fs, 'writeFileSync').mockImplementation(jest.fn((key, data, _) => { + [key] = data +})) + +describe('testing pullComponents', () => { + afterEach(() => { + jest.clearAllMocks() + }) + + it('api.getComponents() should be called once time', async () => { + const api = { + getComponents: jest.fn(() => Promise.resolve(FAKE_COMPONENTS())), + getPresets: jest.fn(() => Promise.resolve([])), + getComponentGroups () { + return Promise.resolve([]) + } + } + + await pullComponents(api, {}) + expect(api.getComponents.mock.calls.length).toBe(1) + }) + + it('pull components should be call fs.writeFileSync correctly and generate component file', async () => { + const SPACE = 12345 + + const api = { + getComponents () { + return Promise.resolve([FAKE_COMPONENTS()[0]]) + }, + getComponentGroups () { + return Promise.resolve([]) + }, + getPresets () { + return Promise.resolve([]) + } + } + + const options = { + fileName: SPACE + } + + const expectFileName = `components.${SPACE}.json` + + await pullComponents(api, options) + const [path, data] = fs.writeFileSync.mock.calls[0] + + expect(fs.writeFileSync.mock.calls.length).toBe(1) + expect(path).toBe(`./${expectFileName}`) + expect(JSON.parse(data)).toEqual({ components: [FAKE_COMPONENTS()[0]], component_groups: [] }) + }) + + it('pull components should be call fs.writeFileSync correctly and generate a component and preset files', async () => { + const SPACE = 12345 + + const api = { + getComponents () { + return Promise.resolve([FAKE_COMPONENTS()[0]]) + }, + getComponentGroups () { + return Promise.resolve([]) + }, + getPresets () { + return Promise.resolve(FAKE_PRESET()) + } + } + + const options = { + fileName: SPACE + } + + const expectComponentFileName = `components.${SPACE}.json` + const expectPresetFileName = `presets.${SPACE}.json` + + await pullComponents(api, options) + const [compPath, compData] = fs.writeFileSync.mock.calls[0] + const [presetPath, presetData] = fs.writeFileSync.mock.calls[1] + + expect(fs.writeFileSync.mock.calls.length).toBe(2) + + expect(compPath).toBe(`./${expectComponentFileName}`) + expect(JSON.parse(compData)).toEqual({ components: [FAKE_COMPONENTS()[0]], component_groups: [] }) + + expect(presetPath).toBe(`./${expectPresetFileName}`) + expect(JSON.parse(presetData)).toEqual({ presets: FAKE_PRESET() }) + }) + + it('pull components should be call fs.writeFileSync and generate a single file for each component', async () => { + const SPACE = 12345 + + const api = { + getComponents () { + return Promise.resolve(FAKE_COMPONENTS()) + }, + getComponentGroups () { + return Promise.resolve([]) + }, + getPresets () { + return Promise.resolve([]) + } + } + + const options = { + fileName: SPACE, + separateFiles: true + } + + await pullComponents(api, options) + expect(fs.writeFileSync.mock.calls.length).toBe(FAKE_COMPONENTS().length) + + for (const comp in FAKE_COMPONENTS()) { + const fileName = `${FAKE_COMPONENTS()[comp].name}-${SPACE}.json` + let data = FAKE_COMPONENTS()[comp] + const [compPath, compData] = fs.writeFileSync.mock.calls[comp] + + if (FAKE_COMPONENTS()[comp].name === 'logo') { + data = { ...FAKE_COMPONENTS()[comp], component_group_name: '' } + } + + expect(compPath).toBe(`./${fileName}`) + expect(JSON.parse(compData)).toEqual(data) + } + }) + + it('pull components should be call fs.writeFile correctly and return filled options from datasource entries', async () => { + const SPACE = 12345 + + const api = { + getComponents () { + return Promise.resolve([FAKE_COMPONENTS()[5]]) + }, + getComponentGroups () { + return Promise.resolve([]) + }, + getDatasources () { + return Promise.resolve(FAKE_DATASOURCES()) + }, + getDatasourceEntries () { + return Promise.resolve(FAKE_DATASOURCE_ENTRIES()) + }, + getPresets () { + return Promise.resolve([]) + } + } + + const options = { + fileName: SPACE, + resolveDatasources: true + } + + const expectFileName = `components.${SPACE}.json` + + await pullComponents(api, options) + const [path, data] = fs.writeFile.mock.calls[0] + + expect(fs.writeFile.mock.calls.length).toBe(1) + expect(path).toBe(`./${expectFileName}`) + expect(JSON.parse(data)).toEqual({ + components: [{ + ...FAKE_COMPONENTS()[5], + schema: { + robot: { + type: "option", + source: "internal", + datasource_slug: "robots", + options: FAKE_DATASOURCE_ENTRIES().map(entry => ({ value: entry.value, name: entry.name })) + } + } + }] + }) + }) + + it('api.getComponents() when a error ocurred, catch the body response', async () => { + const _api = { + getComponents (_, fn) { + return Promise.reject(new Error('Failed')) + }, + getComponentGroups () { + return Promise.resolve([]) + }, + getPresets () { + return Promise.resolve([]) + } + } + + await expect(pullComponents(_api, {})).rejects.toThrow('Error: Failed') + }) +}) diff --git a/tests/units/pull-languages.spec.js b/tests/units/pull-languages.spec.js new file mode 100644 index 00000000..0ba298c5 --- /dev/null +++ b/tests/units/pull-languages.spec.js @@ -0,0 +1,65 @@ +import fs from 'fs' +import pullLanguages from '../../src/tasks/pull-languages' +import { FAKE_SPACE_OPTIONS } from '../constants' +import { jest } from '@jest/globals' + +jest.spyOn(fs, 'writeFile').mockImplementation(jest.fn((key, data, _) => { + [key] = data +})) + +describe('testing pullLanguages', () => { + afterEach(() => { + jest.clearAllMocks() + }) + + it('api.getSpaceOptions() should be called once time', () => { + const api = { + getSpaceOptions: jest.fn(() => Promise.resolve(FAKE_SPACE_OPTIONS())) + } + + return pullLanguages(api, {}) + .then(() => { + expect(api.getSpaceOptions.mock.calls.length).toBe(1) + }) + }) + + it('api.getSpaceOptions() should be call fs.writeFile correctly', async () => { + const SPACE = 12345 + const BODY = FAKE_SPACE_OPTIONS() + + const api = { + getSpaceOptions () { + return Promise.resolve(BODY) + } + } + + const options = { + space: SPACE + } + + const expectFileName = `languages.${SPACE}.json` + const expectData = { + default_lang_name: BODY.default_lang_name, + languages: BODY.languages + } + + return pullLanguages(api, options) + .then(_ => { + const [path, data] = fs.writeFile.mock.calls[0] + + expect(fs.writeFile.mock.calls.length).toBe(1) + expect(path).toBe(`./${expectFileName}`) + expect(JSON.parse(data)).toEqual(expectData) + }) + }) + + it('api.getSpaceOptions() when a error ocurred, catch the body response', async () => { + const _api = { + getSpaceOptions (_, fn) { + return Promise.reject(new Error('Failed')) + } + } + + await expect(pullLanguages(_api, {})).rejects.toThrow('Error: Failed') + }) +}) diff --git a/tests/units/push-components.spec.js b/tests/units/push-components.spec.js new file mode 100644 index 00000000..dc696506 --- /dev/null +++ b/tests/units/push-components.spec.js @@ -0,0 +1,88 @@ +import pushComponents from '../../src/tasks/push-components' +import Storyblok from 'storyblok-js-client' +import api from '../../src/utils/api' +import { getRegionApiEndpoint } from '../../src/utils/region' +import { EU_CODE } from '@storyblok/region-helper' + +import { jest } from '@jest/globals' + +jest.mock('fs') +jest.unmock('axios') + +const deleteDocComponent = async () => { + if (process.env.STORYBLOK_TOKEN) { + const client = new Storyblok({ + oauthToken: process.env.STORYBLOK_TOKEN + }, getRegionApiEndpoint(EU_CODE)) + + try { + const path = `spaces/${process.env.STORYBLOK_SPACE}/components` + const body = await client.get(path) + const comps = body.data.components + + const docComponent = comps.filter(comp => comp.name === 'doc')[0] || {} + if (docComponent.id) { + const { id } = docComponent + + const compPath = `spaces/${process.env.STORYBLOK_SPACE}/components/${id}` + await client.delete(compPath, null) + } + } catch (e) { + console.error(e.message) + } + } +} + +describe('testing pushComponents', () => { + beforeEach(async () => { + await deleteDocComponent() + }) + + it('call pushComponents() with source URL', async () => { + if (process.env.STORYBLOK_TOKEN && process.env.STORYBLOK_SPACE) { + const source = 'https://raw.githubusercontent.com/storyblok/nuxtdoc/master/seed.components.json' + + api.accessToken = process.env.STORYBLOK_TOKEN + api.setSpaceId(process.env.STORYBLOK_SPACE) + + try { + let components = await api.getComponents() + let exists = components.filter(comp => comp.name === 'doc') + + expect(exists.length).toBe(0) + + await pushComponents(api, { source }) + + components = await api.getComponents() + exists = components.filter(comp => comp.name === 'doc') + + expect(exists.length).toBe(1) + } catch (e) { + console.error(e) + } + } + }) + + it('call pushComponents() with source path file', async () => { + if (process.env.STORYBLOK_TOKEN && process.env.STORYBLOK_SPACE) { + const source = 'components.js' + + api.accessToken = process.env.STORYBLOK_TOKEN + api.setSpaceId(process.env.STORYBLOK_SPACE) + + try { + let components = await api.getComponents() + + expect(components.length).toBe(4) + + await pushComponents(api, { source }) + + components = await api.getComponents() + + expect(components.length).toBe(5) + } catch (e) { + console.error(e) + } + } + }) +}) diff --git a/tests/units/quickstart.spec.js b/tests/units/quickstart.spec.js new file mode 100644 index 00000000..97b6b1b0 --- /dev/null +++ b/tests/units/quickstart.spec.js @@ -0,0 +1,80 @@ +import fs from 'fs' +import path from 'path' +import quickstart from '../../src/tasks/quickstart' +import Storyblok from 'storyblok-js-client' +import api from '../../src/utils/api' +import { jest } from '@jest/globals' +import { dirname } from 'node:path' +import { fileURLToPath } from 'node:url' +import { getRegionApiEndpoint } from '../../src/utils/region' +import { EU_CODE } from '@storyblok/region-helper' + +const __dirname = dirname(fileURLToPath(import.meta.url)) + +jest.unmock('fs') +jest.unmock('axios') + +const deleteFolderRecursive = path => { + fs.readdirSync(path).forEach(file => { + const curPath = path + '/' + file + if (fs.lstatSync(curPath).isDirectory()) { + deleteFolderRecursive(curPath) + } else { + fs.unlinkSync(curPath) + } + }) + + fs.rmdirSync(path) +} + +const TEST_PATH = path.join(__dirname, '../../space-test') + +describe('testing quickstart()', () => { + beforeEach(() => { + if (fs.existsSync(TEST_PATH)) { + deleteFolderRecursive(TEST_PATH) + } + + api.setSpaceId(null) + }) + + afterEach(() => { + if (fs.existsSync(TEST_PATH)) { + deleteFolderRecursive(TEST_PATH) + } + + api.setSpaceId(null) + }) + + it('call quickstart() with spaceId should clone the quickstart repository space-test folder', async () => { + if (process.env.STORYBLOK_TOKEN && process.env.STORYBLOK_SPACE) { + api.accessToken = process.env.STORYBLOK_TOKEN + + await quickstart(api, {}, process.env.STORYBLOK_SPACE) + + expect(fs.existsSync(TEST_PATH)).toBe(true) + } + }) + + it('call quickstart() without spaceId should create an empty space with corresponding name', async () => { + const TEST_NAME = 'testando' + if (process.env.STORYBLOK_TOKEN) { + api.accessToken = process.env.STORYBLOK_TOKEN + + await quickstart(api, { name: TEST_NAME }) + + const client = new Storyblok({ + oauthToken: process.env.STORYBLOK_TOKEN + }, getRegionApiEndpoint(EU_CODE)) + + const response = await client.get('spaces') + const spaces = response.data.spaces + + const exists = spaces.filter(space => space.name === TEST_NAME) + + expect(exists.length).toBe(1) + + await client.delete(`spaces/${exists[0].id}`) + } + }) +}) diff --git a/tests/units/run-migration.spec.js b/tests/units/run-migration.spec.js new file mode 100644 index 00000000..c8819301 --- /dev/null +++ b/tests/units/run-migration.spec.js @@ -0,0 +1,496 @@ +import path from 'path' +import fs from 'fs-extra' +import migrationFile from './migrations/change_teaser_subtitle' +// migration that does not execute any change in content +import headlineMigrationFile from './migrations/change_teaser_headline' +import { FAKE_STORIES } from '../constants' +import runMigration from '../../src/tasks/migrations/run' +import { jest } from '@jest/globals' +import { dirname } from 'node:path' +import { fileURLToPath } from 'node:url' + +const __dirname = dirname(fileURLToPath(import.meta.url)) + +jest.mock('fs-extra') + +jest.spyOn(fs, 'existsSync').mockImplementation(jest.fn((_key) => { + return `${process.cwd()}/migrations/rollback` +})) + +jest.spyOn(fs, 'readdirSync').mockImplementation(jest.fn((_key) => { + return ['rollback_product_title.json', 'rollback_product_text.json'] +})) + +jest.spyOn(fs, 'unlinkSync').mockReturnValue(true) + +jest.spyOn(fs, 'writeFile').mockReturnValue(true) + +const FILE_NAME = 'change_teaser_subtitle.js' +const migrationPath = path.resolve(process.cwd(), __dirname, './migrations') + +describe('testing runMigration', () => { + afterEach(() => { + jest.clearAllMocks() + }) + + describe('when the migration file does not exists', () => { + it('it throws an exception', async () => { + await expect(runMigration({}, 'teaser', 'subtitle')).rejects.toThrow(`The migration to combination ${FILE_NAME} doesn't exists`) + }) + }) + + describe('when the migration files does not exports a function', () => { + afterEach(() => { + jest.clearAllMocks() + }) + + it('it throws an exception', async () => { + const opt = { + migrationPath + } + + await expect( + runMigration({}, 'teaser', 'date', opt) + ).rejects.toThrow("The migration file doesn't export a function") + }) + }) + + describe('when the component does not exists in stories', () => { + afterEach(() => { + jest.clearAllMocks() + }) + + it('returns executed = false and motive = NO_STORIES', async () => { + const FAKE_API = { + getStories: jest.fn(() => Promise.resolve([])) + } + + const opt = { + migrationPath + } + + expect( + await runMigration(FAKE_API, 'teaser', 'subtitle', opt) + ).toEqual({ + executed: false, + motive: 'NO_STORIES' + }) + }) + + it('does not execute the migration file', async () => { + const FAKE_API = { + getStories: jest.fn(() => Promise.resolve([])) + } + + const opt = { + migrationPath + } + + await runMigration(FAKE_API, 'teaser', 'subtitle', opt) + + // the function was not executed + expect(migrationFile).not.toHaveBeenCalled() + }) + }) + + describe('when execute with isDryrun enable', () => { + const FAKE_API = { + getStories: jest.fn(() => Promise.resolve(FAKE_STORIES())), + getSingleStory: jest.fn(id => { + const data = FAKE_STORIES().filter(story => story.id === id)[0] || {} + return Promise.resolve(data) + }), + put: jest.fn(() => {}) + } + + const opt = { + migrationPath, + isDryrun: true + } + + afterEach(() => { + jest.clearAllMocks() + }) + + it('it execute the migration function', async () => { + try { + await runMigration(FAKE_API, 'teaser', 'subtitle', opt) + } catch (e) { + console.error(e) + } + + // check how many times the migration function was executed + expect(migrationFile.mock.calls.length).toBe(2) + }) + + it('does not execute the api.put function', async () => { + try { + await runMigration(FAKE_API, 'teaser', 'subtitle', opt) + } catch (e) { + console.error(e) + } + + // check how many times the put function was executed + expect(FAKE_API.put.mock.calls.length).toBe(0) + }) + }) + + describe('when migration does not change the content', () => { + const FAKE_API = { + getStories: jest.fn(() => Promise.resolve(FAKE_STORIES())), + getSingleStory: jest.fn(id => { + const data = FAKE_STORIES().filter(story => story.id === id)[0] || {} + return Promise.resolve(data) + }), + put: jest.fn(() => {}) + } + + const opt = { + migrationPath + } + + afterEach(() => { + jest.clearAllMocks() + }) + + it('it execute the migration function', async () => { + try { + await runMigration(FAKE_API, 'teaser', 'headline', opt) + } catch (e) { + console.error(e) + } + + // check how many times the migration function was executed + expect(headlineMigrationFile.mock.calls.length).toBe(2) + }) + + it('does not execute the api.put function', async () => { + try { + await runMigration(FAKE_API, 'teaser', 'headline', opt) + } catch (e) { + console.error(e) + } + + // check how many times the put function was executed + expect(FAKE_API.put.mock.calls.length).toBe(0) + }) + }) + + describe('when the execution is executed as expected', () => { + const FAKE_API = { + getStories: jest.fn(() => Promise.resolve(FAKE_STORIES())), + getSingleStory: jest.fn(id => { + const data = FAKE_STORIES().filter(story => story.id === id)[0] || {} + return Promise.resolve(data) + }), + put: jest.fn(() => {}) + } + + const opt = { + migrationPath + } + + afterEach(() => { + jest.clearAllMocks() + }) + + it('returns executed = true', async () => { + expect( + await runMigration(FAKE_API, 'teaser', 'subtitle', opt) + ).toEqual({ + executed: true + }) + }) + + it('execute migration function for each component found', async () => { + try { + await runMigration(FAKE_API, 'teaser', 'subtitle', opt) + } catch (e) { + console.error(e) + } + + // check how many times the migration function was executed + expect(migrationFile.mock.calls.length).toBe(2) + }) + + it('execute api.put with the story change', async () => { + try { + await runMigration(FAKE_API, 'teaser', 'subtitle', opt) + } catch (e) { + console.error(e) + } + + // check how many times the put function was executed + expect(FAKE_API.put.mock.calls.length).toBe(2) + + // the first execution + const firstExecution = FAKE_API.put.mock.calls[0] + const firstExecutiontionPath = firstExecution[0] + const firstExecutiontionPayload = firstExecution[1] + + // path + expect(firstExecutiontionPath).toBe('stories/0') + + // payload + const firstExecutionContent = firstExecutiontionPayload.story.content + expect(firstExecutionContent).toStrictEqual({ + _uid: '4bc98f32-6200-4176-86b0-b6f2ea14be6f', + body: [ + { + _uid: '111781de-3174-4f61-8ae3-0b653085a582', + headline: 'My About Page', + component: 'teaser', + subtitle: 'Hey There!' // adds the field with value + } + ], + component: 'page' + }) + + expect(firstExecutiontionPayload.force_update).toBe('1') + + // the second execution + const secondExecution = FAKE_API.put.mock.calls[1] + const secondExecutionPath = secondExecution[0] + const secondExecutionPayload = secondExecution[1] + + // path + expect(secondExecutionPath).toBe('stories/1') + + // content + const secondExecutionContent = secondExecutionPayload.story.content + expect(secondExecutionContent).toStrictEqual({ + _uid: '4bc98f32-6200-4176-86b0-b6f2ea14be6f', + body: [ + { + _uid: '111781de-3174-4f61-8ae3-0b653085a582', + headline: 'Hello World!', + component: 'teaser', + subtitle: 'Hey There!' + }, + { + _uid: '2aae2a9b-df65-4572-be4c-e4460d81e299', + columns: [ + { + _uid: 'cd6efa74-31c3-47ec-96d2-91111f2a6c7c', + name: 'Feature 1', + component: 'feature', + image_data: '//a.storyblok.com/f/67249/1024x512/64e7272404/headless.png' + }, + { + _uid: 'e994cb3e-0f2b-40a7-8db1-f3e456e7b868', + name: 'Feature 2', + component: 'feature' + }, + { + _uid: '779ee42f-a856-405c-9e34-5b49380ae4fe', + name: 'Feature 3', + component: 'feature' + }, + { + _uid: 'a315e9a7-4a0c-4cc5-b393-572533e8bf87', + image: '//a.storyblok.com/f/67249/1024x512/64e7272404/headless.png', + component: 'my-image' + } + ], + component: 'grid' + } + ], + component: 'page' + }) + + expect(secondExecutionPayload.force_update).toBe('1') + }) + + it('should publish be undefined', async () => { + try { + await runMigration(FAKE_API, 'teaser', 'subtitle', opt) + } catch (e) { + console.error(e) + } + + // check how many times the put function was executed + expect(FAKE_API.put.mock.calls.length).toBe(2) + + // the first execution + const firstExecution = FAKE_API.put.mock.calls[0] + const firstExecutiontionPayload = firstExecution[1] + + expect(firstExecutiontionPayload.publish).toBeUndefined() + + const secondExecution = FAKE_API.put.mock.calls[1] + const secondExecutiontionPayload = secondExecution[1] + + expect(secondExecutiontionPayload.publish).toBeUndefined() + }) + + it('should lang be undefined', async () => { + try { + await runMigration(FAKE_API, 'teaser', 'subtitle', opt) + } catch (e) { + console.error(e) + } + + // check how many times the put function was executed + expect(FAKE_API.put.mock.calls.length).toBe(2) + + // the first execution + const firstExecution = FAKE_API.put.mock.calls[0] + const firstExecutiontionPayload = firstExecution[1] + + expect(firstExecutiontionPayload.lang).toBeUndefined() + + const secondExecution = FAKE_API.put.mock.calls[1] + const secondExecutiontionPayload = secondExecution[1] + + expect(secondExecutiontionPayload.lang).toBeUndefined() + }) + }) + + describe('when the user pass the publish option', () => { + const FAKE_API = { + getStories: jest.fn(() => Promise.resolve(FAKE_STORIES())), + getSingleStory: jest.fn(id => { + const data = FAKE_STORIES().filter(story => story.id === id)[0] || {} + return Promise.resolve(data) + }), + put: jest.fn(() => {}) + } + + const defaultOption = { + migrationPath + } + + afterEach(() => { + jest.clearAllMocks() + }) + + it('should publish=1 when the user pass all for all stories', async () => { + try { + await runMigration(FAKE_API, 'teaser', 'subtitle', { + ...defaultOption, + publish: 'all' + }) + } catch (e) { + console.error(e) + } + + // the both stories should be publish + const firstExecution = FAKE_API.put.mock.calls[0] + const firstExecutiontionPayload = firstExecution[1] + + expect(firstExecutiontionPayload.publish).toBe('1') + + const secondExecution = FAKE_API.put.mock.calls[1] + const secondExecutiontionPayload = secondExecution[1] + + expect(secondExecutiontionPayload.publish).toBe('1') + }) + + it('should publish=1 for unpublished_changes stories when the user pass the published-with-changes option', async () => { + try { + await runMigration(FAKE_API, 'teaser', 'subtitle', { + ...defaultOption, + publish: 'published-with-changes' + }) + } catch (e) { + console.error(e) + } + + // the both stories should be publish + const firstExecution = FAKE_API.put.mock.calls[0] + const firstExecutiontionPayload = firstExecution[1] + + expect(firstExecutiontionPayload.publish).toBe('1') + + const secondExecution = FAKE_API.put.mock.calls[1] + const secondExecutiontionPayload = secondExecution[1] + + expect(secondExecutiontionPayload.publish).toBeUndefined() + }) + + it('should publish=1 for stories already publish when the user pass the published option', async () => { + try { + await runMigration(FAKE_API, 'teaser', 'subtitle', { + ...defaultOption, + publish: 'published' + }) + } catch (e) { + console.error(e) + } + + // the both stories should be publish + const firstExecution = FAKE_API.put.mock.calls[0] + const firstExecutiontionPayload = firstExecution[1] + + expect(firstExecutiontionPayload.publish).toBeUndefined() + + const secondExecution = FAKE_API.put.mock.calls[1] + const secondExecutiontionPayload = secondExecution[1] + + expect(secondExecutiontionPayload.publish).toBe('1') + }) + }) + + describe('when the user pass the publish-languages option', () => { + const FAKE_API = { + getStories: jest.fn(() => Promise.resolve(FAKE_STORIES())), + getSingleStory: jest.fn(id => { + const data = FAKE_STORIES().filter(story => story.id === id)[0] || {} + return Promise.resolve(data) + }), + put: jest.fn(() => {}) + } + + const defaultOption = { + migrationPath, + publish: 'all' + } + + afterEach(() => { + jest.clearAllMocks() + }) + + it('should lang be undefined when the user pass anything', async () => { + try { + await runMigration(FAKE_API, 'teaser', 'subtitle', { + ...defaultOption + }) + } catch (e) { + console.error(e) + } + + // the both stories should be publish + const firstExecution = FAKE_API.put.mock.calls[0] + const firstExecutiontionPayload = firstExecution[1] + + expect(firstExecutiontionPayload.lang).toBeUndefined() + + const secondExecution = FAKE_API.put.mock.calls[1] + const secondExecutiontionPayload = secondExecution[1] + + expect(secondExecutiontionPayload.lang).toBeUndefined() + }) + + it('should lang=pt when the user pass pt', async () => { + try { + await runMigration(FAKE_API, 'teaser', 'subtitle', { + ...defaultOption, + publishLanguages: 'pt' + }) + } catch (e) { + console.error(e) + } + + // the both stories should be publish + const firstExecution = FAKE_API.put.mock.calls[0] + const firstExecutiontionPayload = firstExecution[1] + + expect(firstExecutiontionPayload.lang).toBe('pt') + + const secondExecution = FAKE_API.put.mock.calls[1] + const secondExecutiontionPayload = secondExecution[1] + + expect(secondExecutiontionPayload.lang).toBe('pt') + }) + }) +}) diff --git a/tests/units/scaffold.spec.js b/tests/units/scaffold.spec.js new file mode 100644 index 00000000..0c7af09c --- /dev/null +++ b/tests/units/scaffold.spec.js @@ -0,0 +1,81 @@ +import fs from 'fs' +import scaffold from '../../src/tasks/scaffold' +import Storyblok from 'storyblok-js-client' +import api from '../../src/utils/api' +import { getRegionApiEndpoint } from '../../src/utils/region' +import { EU_CODE } from '@storyblok/region-helper' + +import { jest } from '@jest/globals' + +jest.spyOn(fs, 'writeFileSync').mockReturnValue("path_to_file") + +const deleteTestComponent = async () => { + if (process.env.STORYBLOK_TOKEN) { + const client = new Storyblok({ + oauthToken: process.env.STORYBLOK_TOKEN + }, getRegionApiEndpoint(EU_CODE)) + + try { + const path = `spaces/${process.env.STORYBLOK_SPACE}/components` + const body = await client.get(path) + const comps = body.data.components + + const testComp = comps.filter(comp => comp.name === 'testando')[0] || {} + + if (testComp.id) { + const { id } = testComp + + const compPath = `spaces/${process.env.STORYBLOK_SPACE}/components/${id}` + await client.delete(compPath, null) + } + } catch (e) { + console.error(e.message) + } + } +} + +describe('testing scaffold()', () => { + beforeEach(async () => { + await deleteTestComponent() + }) + + afterEach(async () => { + await deleteTestComponent() + }) + + afterAll(() => { + jest.resetAllMocks() + }) + + it('call scaffold() with space should create a new component with corresponding name', async () => { + const COMPONENT_TEST_NAME = 'testando' + + if (process.env.STORYBLOK_TOKEN && process.env.STORYBLOK_SPACE) { + api.accessToken = process.env.STORYBLOK_TOKEN + + await scaffold(api, COMPONENT_TEST_NAME, process.env.STORYBLOK_SPACE) + + const components = await api.getComponents() + + const exists = components.filter(comp => { + return comp.name === COMPONENT_TEST_NAME + }) + + expect(exists.length).toBe(1) + } + }) + + it('call scaffold() without space should create correspoding template files', async () => { + const COMPONENT_TEST_NAME = 'columns' + + await scaffold(api, COMPONENT_TEST_NAME) + + const [firstCall, secondCall] = fs.writeFileSync.mock.calls + + const [firstCallPath] = firstCall + const [secondCallPath] = secondCall + + expect(firstCallPath).toBe('./views/components/_columns.liquid') + expect(secondCallPath).toBe('./source/scss/components/below/_columns.scss') + }) +}) \ No newline at end of file diff --git a/tests/units/signup.spec.js b/tests/units/signup.spec.js new file mode 100644 index 00000000..3f373464 --- /dev/null +++ b/tests/units/signup.spec.js @@ -0,0 +1,68 @@ +import axios from 'axios' +import api from '../../src/utils/api' +import creds from '../../src/utils/creds' +import { EMAIL_TEST, TOKEN_TEST, PASSWORD_TEST } from '../constants' +import { USERS_ROUTES } from '../constants' +import { jest } from '@jest/globals' + +const isCredCorrects = (email, pass) => { + return email === EMAIL_TEST && pass === PASSWORD_TEST +} + +jest.mock('axios') +jest.spyOn(axios, 'post').mockImplementation(jest.fn((path, data) => { + const { email, password } = data || {} + + if (path === USERS_ROUTES.LOGIN && isCredCorrects(email, password)) { + return Promise.resolve({ + data: { + access_token: TOKEN_TEST + } + }) + } + + if (path === USERS_ROUTES.SIGNUP && isCredCorrects(email, password)) { + return Promise.resolve({ + data: { + access_token: TOKEN_TEST + } + }) + } + + return Promise.reject(new Error('Incorrect access')) +})) + +describe('api.signup() method', () => { + beforeEach(() => { + creds.set(null) + }) + + afterEach(() => { + creds.set(null) + }) + + afterAll(() => { + jest.resetAllMocks() + }) + + it('when signup ends correctly, the .netrc file is populated', async () => { + try { + await api.signup(EMAIL_TEST, PASSWORD_TEST) + + expect(creds.get()).toEqual({ + email: EMAIL_TEST, + token: TOKEN_TEST + }) + } catch (e) { + console.error(e) + } + }) + + it('when signup ends correctly, the .netrc file is not populated and throw a reject message', async () => { + try { + await api.signup(EMAIL_TEST, '1234') + } catch (e) { + expect(e.message).toBe('Incorrect access') + } + }) +}) diff --git a/tests/units/sync-components.spec.js b/tests/units/sync-components.spec.js new file mode 100644 index 00000000..0141e7c0 --- /dev/null +++ b/tests/units/sync-components.spec.js @@ -0,0 +1,388 @@ +import sync from '../../src/tasks/sync' +import { TOKEN_TEST, EMAIL_TEST, REGION_TEST, FAKE_COMPONENTS } from '../constants' +import creds from '../../src/utils/creds' +import { jest } from '@jest/globals' +import Storyblok from 'storyblok-js-client' +import PresetsLib from '../../src/utils/presets-lib' + +const FAKE_COMPONENTS_TO_TEST = { + '001': { + data: { + components: FAKE_COMPONENTS() + } + }, + '002': { + data: { + components: [ + { + name: 'feature', + display_name: null, + created_at: '2019-11-06T17:07:04.196Z', + updated_at: '2019-11-06T18:12:29.136Z', + id: 1, + schema: { + logo: { + type: 'image' + }, + name: { + type: 'text' + }, + description: { + type: 'textarea' + }, + link_text: { + type: 'text' + }, + link: { + type: 'multilink' + } + }, + image: null, + preview_field: null, + is_root: false, + preview_tmpl: null, + is_nestable: true, + all_presets: [], + internal_tag_ids: [], + internal_tags_list: [], + preset_id: null, + real_name: 'feature', + component_group_uuid: null + }, + { + name: 'teaser', + display_name: null, + id: 0, + schema: { + headline: { + type: 'text' + } + }, + image: null, + preview_field: null, + is_root: false, + preview_tmpl: null, + is_nestable: true, + all_presets: [], + internal_tag_ids: [], + internal_tags_list: [], + preset_id: null, + real_name: 'teaser', + component_group_uuid: null + } + ], + internal_tags: [ + { + id: 1, + name: 'tag1', + object_type: 'component', + }, + { + id: 2, + name: 'tag2', + object_type: 'component', + } + ] + } + } +} + +const COMPONENT_GROUPS = { + '001': { + data: { + component_groups: [ + { + name: 'General', + id: 11216, + uuid: '529cc32a-1d97-4b4a-b0b6-28e33dc56c0d' + } + ] + } + }, + '002': { + data: { + component_groups: [] + } + } +} + +const FAKE_PRESETS = { + '001': { + data: { + presets: [ + { + id: '01', + name: 'Hero Variant 1', + preset: { + _uid: '5f8b150f-2931-4693-965e-077a53ec9132', + title: 'A default hero title', + subtitle: 'A default hero subtitle', + component: 'hero', + image: 'https://a.storyblok.com/f/002/bd78c087d1/screen-shot.png' + }, + component_id: 3, // from FAKE_COMPONENTS 'hero' + space_id: '000000', + created_at: '2020-04-24T18:13:35.056Z', + updated_at: '2020-04-24T18:13:35.056Z', + image: null + } + ] + } + } +} + +const extractSpace = path => path.split('/')[1] + +const mockGetRequest = (path) => { + if ( + path === 'spaces/001/component_groups' || + path === 'spaces/002/component_groups' + ) { + return Promise.resolve(COMPONENT_GROUPS[extractSpace(path)]) + } + + if ( + path === 'spaces/001/components' || + path === 'spaces/002/components' + ) { + return Promise.resolve(FAKE_COMPONENTS_TO_TEST[extractSpace(path)]) + } + + if ( + path === 'spaces/001/internal_tags' || + path === 'spaces/002/internal_tags' + ) { + return Promise.resolve(FAKE_COMPONENTS_TO_TEST[extractSpace(path)]) + } + + + if (path === 'spaces/001/presets') { + return Promise.resolve(FAKE_PRESETS[extractSpace(path)]) + } + + return Promise.reject(new Error('Error on get mock')) +} + +const mockPostRequest = (path, payload) => { + if (path === 'spaces/002/component_groups') { + return Promise.resolve({ + data: { + component_group: { + ...payload.component_group, + uuid: '000000002' + } + } + }) + } + + if (path === 'spaces/002/components') { + if (payload.component.name === 'teaser') { + /* eslint prefer-promise-reject-errors: "off" */ + return Promise.reject({ + response: { + status: 422 + } + }) + } + + return Promise.resolve({ + data: { + component: { + ...payload.component, + id: '000000001' + } + } + }) + } + + return Promise.resolve(true) +} + +const mockPutRequest = (path, payload) => { + return Promise.resolve({ + data: { + component: { + ...payload.component, + } + } + }) +} + +const spyGet = jest.spyOn(Storyblok.prototype, 'get').mockImplementation(mockGetRequest) +const spyPost = jest.spyOn(Storyblok.prototype, 'post').mockImplementation(mockPostRequest) +const spyPut = jest.spyOn(Storyblok.prototype, 'put').mockImplementation(mockPutRequest) + +const spyGetPresets = jest.spyOn(PresetsLib.prototype, 'getPresets').mockImplementation((spaceId) => { + return Promise.resolve(FAKE_PRESETS[spaceId].data.presets) +}) + +const spyCreatePresets = jest.spyOn(PresetsLib.prototype, 'createPresets').mockImplementation((presets = [], componentId, method = 'post') => { + return Promise.resolve(true) +}) + +const SOURCE_SPACE_TEST = '001' +const TARGET_SPACE_TEST = '002' + +describe('testing syncComponents', () => { + beforeAll(() => { + creds.set(EMAIL_TEST, TOKEN_TEST, REGION_TEST) + // we need to execute once this function to test it + const _types = ['components'] + + return sync(_types, { + api: { + getClient: jest.fn(() => ({})) + }, + token: TOKEN_TEST, + source: SOURCE_SPACE_TEST, + target: TARGET_SPACE_TEST + }) + }) + + afterAll(() => { + spyGet.mockClear() + spyPost.mockClear() + spyPut.mockClear() + spyGetPresets.mockClear() + spyCreatePresets.mockClear() + }) + + it('shoud be get the all data correctly', () => { + // it must be get each component group + expect(spyGet).toHaveBeenCalledWith('spaces/001/component_groups') + expect(spyGet).toHaveBeenCalledWith('spaces/002/component_groups') + + // it must be get components and presets + expect(spyGet).toHaveBeenCalledWith('spaces/001/components') + expect(spyGet).toHaveBeenCalledWith('spaces/002/components') + + // it must be get presets + expect(spyGetPresets).toHaveBeenCalledWith('001') + }) + + it('shoud be create the General component_groups correctly', () => { + // it must be get each component group + expect(spyPost).toHaveBeenCalledWith( + 'spaces/002/component_groups', + { + component_group: { + name: 'General' + } + } + ) + }) + + it('shoud be try to create teaser component, but it will fail and execute the put to update it', () => { + // it must be get each component group + expect(spyPut).toHaveBeenCalledWith( + 'spaces/002/components/0', + { + component: { + all_presets: [], + component_group_uuid: null, + display_name: null, + id: 0, + image: null, + internal_tag_ids: [], + internal_tags_list: [], + is_nestable: true, + is_root: false, + name: 'teaser', + preset_id: null, + preview_field: null, + preview_tmpl: null, + real_name: 'teaser', + schema: { + headline: { + type: 'text' + } + } + } + } + ) + }) + + it('shoud be create the presets for specific components correctly', () => { + // it must be get each component group + expect(spyCreatePresets).toHaveBeenCalledWith( + [{ + id: '01', + name: 'Hero Variant 1', + preset: { + _uid: '5f8b150f-2931-4693-965e-077a53ec9132', + title: 'A default hero title', + subtitle: 'A default hero subtitle', + component: 'hero', + image: 'https://a.storyblok.com/f/002/bd78c087d1/screen-shot.png' + }, + component_id: 3, // from FAKE_COMPONENTS 'hero' + space_id: '000000', + created_at: '2020-04-24T18:13:35.056Z', + updated_at: '2020-04-24T18:13:35.056Z', + image: null + }], + '000000001' + ) + }) + + it('shoud be create components related to group correctly', () => { + expect(spyPost).toHaveBeenCalledWith( + 'spaces/002/components', + { + component: { + name: 'logo', + display_name: null, + schema: { + image: { + type: 'image' + } + }, + image: null, + preview_field: null, + is_root: false, + preview_tmpl: null, + is_nestable: true, + all_presets: [], + internal_tag_ids: [], + internal_tags_list: [], + preset_id: null, + real_name: 'logo', + component_group_uuid: '000000002' + } + } + ) + }) + + it('shoud be create components with correct schema', () => { + expect(spyPost).toHaveBeenCalledWith( + 'spaces/002/components', + { + component: { + name: 'blocks', + display_name: null, + schema: { + other: { + type: 'bloks', + max_length: '', + translatable: false, + restrict_components: true, + restrict_type: 'groups', + component_group_whitelist: [ + '000000002' + ] + } + }, + image: null, + preview_field: null, + is_root: false, + preview_tmpl: null, + is_nestable: true, + all_presets: [], + preset_id: null, + real_name: 'blocks', + component_group_uuid: null + } + } + ) + }) +}) diff --git a/tests/units/sync.spec.js b/tests/units/sync.spec.js new file mode 100644 index 00000000..4c975e1c --- /dev/null +++ b/tests/units/sync.spec.js @@ -0,0 +1,70 @@ +import sync from '../../src/tasks/sync' +import Storyblok from 'storyblok-js-client' +import { getRegionApiEndpoint } from '../../src/utils/region' +import { EU_CODE } from '@storyblok/region-helper' + +import { jest } from '@jest/globals' + +jest.unmock('axios') + +// prevent timeout execution error +jest.setTimeout(30000) + +const getData = async (client, space, entity) => { + const path = `spaces/${space}/${entity}` + const response = await client.get(path) + + return Promise.resolve(response.data[entity] || []) +} + +describe('testing sync function', () => { + it('sync("syncStories", options) should sync stories between two spaces', async () => { + if ( + process.env.STORYBLOK_TOKEN && + process.env.STORYBLOK_SPACE && + process.env.STORYBLOK_ANOTHER_SPACE + ) { + try { + const _types = ['stories'] + + await sync(_types, { + token: process.env.STORYBLOK_TOKEN, + source: process.env.STORYBLOK_SPACE, + target: process.env.STORYBLOK_ANOTHER_SPACE + }) + + const client = new Storyblok({ + oauthToken: process.env.STORYBLOK_TOKEN + }, getRegionApiEndpoint(EU_CODE)) + + const sourceStories = await getData( + client, + process.env.STORYBLOK_SPACE, + 'stories' + ) + + const targetStories = await getData( + client, + process.env.STORYBLOK_ANOTHER_SPACE, + 'stories' + ) + + const existingSourceStories = [] + + targetStories.forEach(story => { + const exists = sourceStories.filter(_story => { + return _story.name === story.name + }) + + if (exists.length) { + existingSourceStories.push(story.name) + } + }) + + expect(existingSourceStories.length).toBe(sourceStories.length) + } catch (e) { + console.error(e) + } + } + }) +}) diff --git a/tsconfig.json b/tsconfig.json index 0e3bb1f1..50fe6f8a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,27 +1,20 @@ { "compilerOptions": { - "target": "esnext", - "lib": ["esnext", "DOM", "DOM.Iterable"], - "baseUrl": ".", - "module": "esnext", - "moduleResolution": "bundler", - "resolveJsonModule": true, - "types": ["node", "vitest/globals"], - "allowImportingTsExtensions": true, - /* Linting */ + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "skipLibCheck": true, + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "Bundler", "strict": true, - "noFallthroughCasesInSwitch": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "inlineSourceMap": false, + "noImplicitAny": true, + "allowJs": true, "noEmit": true, - /* Debugging */ - "sourceMap": true, - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "isolatedModules": true, - "skipLibCheck": true + "noUnusedLocals": true, + "resolveJsonModule": true, + "types": ["node"], + "outDir": "dist" }, - "include": ["src/**/*.ts", "src/**/*.tsx"], - "exclude": ["node_modules", "**/*.test.ts", "**/*.test.tsx", "**/__tests__/**", "**/test/**", "**/tests/**"] -} + "include": ["src/types"], + "exclude": ["dist/**/*"] +} \ No newline at end of file diff --git a/vitest.config.ts b/vitest.config.ts deleted file mode 100644 index 109245a1..00000000 --- a/vitest.config.ts +++ /dev/null @@ -1,12 +0,0 @@ -/// -import { defineConfig } from 'vite'; - -export default defineConfig({ - test: { - globals: true, - // ... Specify options here. - coverage: { - reporter: ['text', 'json', 'html'], - }, - }, -}); diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 00000000..739b8ca8 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,5538 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@ampproject/remapping@^2.2.0": + version "2.2.1" + resolved "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz" + integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg== + dependencies: + "@jridgewell/gen-mapping" "^0.3.0" + "@jridgewell/trace-mapping" "^0.3.9" + +"@babel/code-frame@7.12.11": + version "7.12.11" + resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz" + integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw== + dependencies: + "@babel/highlight" "^7.10.4" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.16.7": + version "7.16.7" + resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz" + integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg== + dependencies: + "@babel/highlight" "^7.16.7" + +"@babel/code-frame@^7.12.13", "@babel/code-frame@^7.22.13", "@babel/code-frame@^7.23.5": + version "7.23.5" + resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz" + integrity sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA== + dependencies: + "@babel/highlight" "^7.23.4" + chalk "^2.4.2" + +"@babel/compat-data@^7.16.4": + version "7.16.8" + resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.8.tgz" + integrity sha512-m7OkX0IdKLKPpBlJtF561YJal5y/jyI5fNfWbPxh2D/nbzzGI4qRyrD8xO2jB24u7l+5I2a43scCG2IrfjC50Q== + +"@babel/compat-data@^7.23.5": + version "7.23.5" + resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz" + integrity sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw== + +"@babel/core@^7.11.6", "@babel/core@^7.23.9": + version "7.23.9" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.9.tgz#b028820718000f267870822fec434820e9b1e4d1" + integrity sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.23.5" + "@babel/generator" "^7.23.6" + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helpers" "^7.23.9" + "@babel/parser" "^7.23.9" + "@babel/template" "^7.23.9" + "@babel/traverse" "^7.23.9" + "@babel/types" "^7.23.9" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/core@^7.12.3": + version "7.16.7" + resolved "https://registry.npmjs.org/@babel/core/-/core-7.16.7.tgz" + integrity sha512-aeLaqcqThRNZYmbMqtulsetOQZ/5gbR/dWruUCJcpas4Qoyy+QeagfDsPdMrqwsPRDNxJvBlRiZxxX7THO7qtA== + dependencies: + "@babel/code-frame" "^7.16.7" + "@babel/generator" "^7.16.7" + "@babel/helper-compilation-targets" "^7.16.7" + "@babel/helper-module-transforms" "^7.16.7" + "@babel/helpers" "^7.16.7" + "@babel/parser" "^7.16.7" + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.16.7" + "@babel/types" "^7.16.7" + 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/core@^7.23.7": + version "7.23.7" + resolved "https://registry.npmjs.org/@babel/core/-/core-7.23.7.tgz" + integrity sha512-+UpDgowcmqe36d4NwqvKsyPMlOLNGMsfMmQ5WGCu+siCe3t3dfe9njrzGfdN4qq+bcNUt0+Vw6haRxBOycs4dw== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.23.5" + "@babel/generator" "^7.23.6" + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helpers" "^7.23.7" + "@babel/parser" "^7.23.6" + "@babel/template" "^7.22.15" + "@babel/traverse" "^7.23.7" + "@babel/types" "^7.23.6" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.16.7", "@babel/generator@^7.16.8": + version "7.16.8" + resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.16.8.tgz" + integrity sha512-1ojZwE9+lOXzcWdWmO6TbUzDfqLD39CmEhN8+2cX9XkDo5yW1OpgfejfliysR2AWLpMamTiOiAp/mtroaymhpw== + dependencies: + "@babel/types" "^7.16.8" + jsesc "^2.5.1" + source-map "^0.5.0" + +"@babel/generator@^7.23.6", "@babel/generator@^7.7.2": + version "7.23.6" + resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz" + integrity sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw== + dependencies: + "@babel/types" "^7.23.6" + "@jridgewell/gen-mapping" "^0.3.2" + "@jridgewell/trace-mapping" "^0.3.17" + jsesc "^2.5.1" + +"@babel/helper-compilation-targets@^7.16.7": + version "7.16.7" + resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz" + integrity sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA== + dependencies: + "@babel/compat-data" "^7.16.4" + "@babel/helper-validator-option" "^7.16.7" + browserslist "^4.17.5" + semver "^6.3.0" + +"@babel/helper-compilation-targets@^7.23.6": + version "7.23.6" + resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz" + integrity sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ== + dependencies: + "@babel/compat-data" "^7.23.5" + "@babel/helper-validator-option" "^7.23.5" + browserslist "^4.22.2" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-environment-visitor@^7.16.7": + version "7.16.7" + resolved "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz" + integrity sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag== + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-environment-visitor@^7.22.20": + version "7.22.20" + resolved "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz" + integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== + +"@babel/helper-function-name@^7.16.7": + version "7.16.7" + resolved "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz" + integrity sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA== + dependencies: + "@babel/helper-get-function-arity" "^7.16.7" + "@babel/template" "^7.16.7" + "@babel/types" "^7.16.7" + +"@babel/helper-function-name@^7.23.0": + version "7.23.0" + resolved "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz" + integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== + dependencies: + "@babel/template" "^7.22.15" + "@babel/types" "^7.23.0" + +"@babel/helper-get-function-arity@^7.16.7": + version "7.16.7" + resolved "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz" + integrity sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw== + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-hoist-variables@^7.16.7": + version "7.16.7" + resolved "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz" + integrity sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg== + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-hoist-variables@^7.22.5": + version "7.22.5" + resolved "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz" + integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-module-imports@^7.16.7": + version "7.16.7" + resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz" + integrity sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg== + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-module-imports@^7.22.15": + version "7.22.15" + resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz" + integrity sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w== + dependencies: + "@babel/types" "^7.22.15" + +"@babel/helper-module-transforms@^7.16.7": + version "7.16.7" + resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.7.tgz" + integrity sha512-gaqtLDxJEFCeQbYp9aLAefjhkKdjKcdh6DB7jniIGU3Pz52WAmP268zK0VgPz9hUNkMSYeH976K2/Y6yPadpng== + dependencies: + "@babel/helper-environment-visitor" "^7.16.7" + "@babel/helper-module-imports" "^7.16.7" + "@babel/helper-simple-access" "^7.16.7" + "@babel/helper-split-export-declaration" "^7.16.7" + "@babel/helper-validator-identifier" "^7.16.7" + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.16.7" + "@babel/types" "^7.16.7" + +"@babel/helper-module-transforms@^7.23.3": + version "7.23.3" + resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz" + integrity sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-module-imports" "^7.22.15" + "@babel/helper-simple-access" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/helper-validator-identifier" "^7.22.20" + +"@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.14.5", "@babel/helper-plugin-utils@^7.8.0": + version "7.16.7" + resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz" + integrity sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA== + +"@babel/helper-plugin-utils@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" + integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== + +"@babel/helper-simple-access@^7.16.7": + version "7.16.7" + resolved "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.7.tgz" + integrity sha512-ZIzHVyoeLMvXMN/vok/a4LWRy8G2v205mNP0XOuf9XRLyX5/u9CnVulUtDgUTama3lT+bf/UqucuZjqiGuTS1g== + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-simple-access@^7.22.5": + version "7.22.5" + resolved "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz" + integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-split-export-declaration@^7.16.7": + version "7.16.7" + resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz" + integrity sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw== + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-split-export-declaration@^7.22.6": + version "7.22.6" + resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz" + integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-string-parser@^7.23.4": + version "7.23.4" + resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz" + integrity sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ== + +"@babel/helper-validator-identifier@^7.16.7": + version "7.16.7" + resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz" + integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw== + +"@babel/helper-validator-identifier@^7.22.20": + version "7.22.20" + resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== + +"@babel/helper-validator-option@^7.16.7": + version "7.16.7" + resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz" + integrity sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ== + +"@babel/helper-validator-option@^7.23.5": + version "7.23.5" + resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz" + integrity sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw== + +"@babel/helpers@^7.16.7": + version "7.16.7" + resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.16.7.tgz" + integrity sha512-9ZDoqtfY7AuEOt3cxchfii6C7GDyyMBffktR5B2jvWv8u2+efwvpnVKXMWzNehqy68tKgAfSwfdw/lWpthS2bw== + dependencies: + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.16.7" + "@babel/types" "^7.16.7" + +"@babel/helpers@^7.23.7": + version "7.23.8" + resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.8.tgz" + integrity sha512-KDqYz4PiOWvDFrdHLPhKtCThtIcKVy6avWD2oG4GEvyQ+XDZwHD4YQd+H2vNMnq2rkdxsDkU82T+Vk8U/WXHRQ== + dependencies: + "@babel/template" "^7.22.15" + "@babel/traverse" "^7.23.7" + "@babel/types" "^7.23.6" + +"@babel/helpers@^7.23.9": + version "7.23.9" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.9.tgz#c3e20bbe7f7a7e10cb9b178384b4affdf5995c7d" + integrity sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ== + dependencies: + "@babel/template" "^7.23.9" + "@babel/traverse" "^7.23.9" + "@babel/types" "^7.23.9" + +"@babel/highlight@^7.10.4", "@babel/highlight@^7.16.7": + version "7.16.7" + resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.7.tgz" + integrity sha512-aKpPMfLvGO3Q97V0qhw/V2SWNWlwfJknuwAunU7wZLSfrM4xTBvg7E5opUVi1kJTBKihE38CPg4nBiqX83PWYw== + dependencies: + "@babel/helper-validator-identifier" "^7.16.7" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@babel/highlight@^7.23.4": + version "7.23.4" + resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz" + integrity sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A== + dependencies: + "@babel/helper-validator-identifier" "^7.22.20" + chalk "^2.4.2" + js-tokens "^4.0.0" + +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.16.7", "@babel/parser@^7.16.8": + version "7.16.8" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.16.8.tgz" + integrity sha512-i7jDUfrVBWc+7OKcBzEe5n7fbv3i2fWtxKzzCvOjnzSxMfWMigAhtfJ7qzZNGFNMsCCd67+uz553dYKWXPvCKw== + +"@babel/parser@^7.20.7", "@babel/parser@^7.23.9": + version "7.23.9" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.9.tgz#7b903b6149b0f8fa7ad564af646c4c38a77fc44b" + integrity sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA== + +"@babel/parser@^7.22.15", "@babel/parser@^7.23.6": + version "7.23.6" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz" + integrity sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ== + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.8.3": + version "7.12.13" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-import-meta@^7.8.3": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-jsx@^7.7.2": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz#8f2e4f8a9b5f9aa16067e142c1ac9cd9f810f473" + integrity sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-syntax-logical-assignment-operators@^7.8.3": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.8.3": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-top-level-await@^7.8.3": + version "7.14.5" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-typescript@^7.7.2": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz#24f460c85dbbc983cd2b9c4994178bcc01df958f" + integrity sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ== + dependencies: + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/standalone@^7.23.8": + version "7.23.8" + resolved "https://registry.npmjs.org/@babel/standalone/-/standalone-7.23.8.tgz" + integrity sha512-i0tPn3dyKHbEZPDV66ry/7baC1pznRU02R8sU6eJSBfTOwMkukRdYuT3ks/j/cvTl4YkHMRmhTejET+iyPZVvQ== + +"@babel/template@^7.16.7", "@babel/template@^7.3.3": + version "7.16.7" + resolved "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz" + integrity sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w== + dependencies: + "@babel/code-frame" "^7.16.7" + "@babel/parser" "^7.16.7" + "@babel/types" "^7.16.7" + +"@babel/template@^7.22.15": + version "7.22.15" + resolved "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz" + integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w== + dependencies: + "@babel/code-frame" "^7.22.13" + "@babel/parser" "^7.22.15" + "@babel/types" "^7.22.15" + +"@babel/template@^7.23.9": + version "7.23.9" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.23.9.tgz#f881d0487cba2828d3259dcb9ef5005a9731011a" + integrity sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA== + dependencies: + "@babel/code-frame" "^7.23.5" + "@babel/parser" "^7.23.9" + "@babel/types" "^7.23.9" + +"@babel/traverse@^7.16.7": + version "7.16.8" + resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.8.tgz" + integrity sha512-xe+H7JlvKsDQwXRsBhSnq1/+9c+LlQcCK3Tn/l5sbx02HYns/cn7ibp9+RV1sIUqu7hKg91NWsgHurO9dowITQ== + dependencies: + "@babel/code-frame" "^7.16.7" + "@babel/generator" "^7.16.8" + "@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.16.8" + "@babel/types" "^7.16.8" + debug "^4.1.0" + globals "^11.1.0" + +"@babel/traverse@^7.23.7": + version "7.23.7" + resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.7.tgz" + integrity sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg== + dependencies: + "@babel/code-frame" "^7.23.5" + "@babel/generator" "^7.23.6" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/parser" "^7.23.6" + "@babel/types" "^7.23.6" + debug "^4.3.1" + globals "^11.1.0" + +"@babel/traverse@^7.23.9": + version "7.23.9" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.9.tgz#2f9d6aead6b564669394c5ce0f9302bb65b9d950" + integrity sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg== + dependencies: + "@babel/code-frame" "^7.23.5" + "@babel/generator" "^7.23.6" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/parser" "^7.23.9" + "@babel/types" "^7.23.9" + debug "^4.3.1" + globals "^11.1.0" + +"@babel/types@^7.0.0", "@babel/types@^7.16.7", "@babel/types@^7.16.8", "@babel/types@^7.3.0", "@babel/types@^7.3.3": + version "7.16.8" + resolved "https://registry.npmjs.org/@babel/types/-/types-7.16.8.tgz" + integrity sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg== + dependencies: + "@babel/helper-validator-identifier" "^7.16.7" + to-fast-properties "^2.0.0" + +"@babel/types@^7.20.7", "@babel/types@^7.23.9": + version "7.23.9" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.9.tgz#1dd7b59a9a2b5c87f8b41e52770b5ecbf492e002" + integrity sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q== + dependencies: + "@babel/helper-string-parser" "^7.23.4" + "@babel/helper-validator-identifier" "^7.22.20" + to-fast-properties "^2.0.0" + +"@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.6": + version "7.23.6" + resolved "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz" + integrity sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg== + dependencies: + "@babel/helper-string-parser" "^7.23.4" + "@babel/helper-validator-identifier" "^7.22.20" + to-fast-properties "^2.0.0" + +"@bcherny/json-schema-ref-parser@10.0.5-fork": + version "10.0.5-fork" + resolved "https://registry.npmjs.org/@bcherny/json-schema-ref-parser/-/json-schema-ref-parser-10.0.5-fork.tgz" + integrity sha512-E/jKbPoca1tfUPj3iSbitDZTGnq6FUFjkH6L8U2oDwSuwK1WhnnVtCG7oFOTg/DDnyoXbQYUiUiGOibHqaGVnw== + dependencies: + "@jsdevtools/ono" "^7.1.3" + "@types/json-schema" "^7.0.6" + call-me-maybe "^1.0.1" + js-yaml "^4.1.0" + +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + +"@esbuild/aix-ppc64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.19.11.tgz#2acd20be6d4f0458bc8c784103495ff24f13b1d3" + integrity sha512-FnzU0LyE3ySQk7UntJO4+qIiQgI7KoODnZg5xzXIrFJlKd2P2gwHsHY4927xj9y5PJmJSzULiUCWmv7iWnNa7g== + +"@esbuild/android-arm64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.19.11.tgz#b45d000017385c9051a4f03e17078abb935be220" + integrity sha512-aiu7K/5JnLj//KOnOfEZ0D90obUkRzDMyqd/wNAUQ34m4YUPVhRZpnqKV9uqDGxT7cToSDnIHsGooyIczu9T+Q== + +"@esbuild/android-arm@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.19.11.tgz#f46f55414e1c3614ac682b29977792131238164c" + integrity sha512-5OVapq0ClabvKvQ58Bws8+wkLCV+Rxg7tUVbo9xu034Nm536QTII4YzhaFriQ7rMrorfnFKUsArD2lqKbFY4vw== + +"@esbuild/android-x64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.19.11.tgz#bfc01e91740b82011ef503c48f548950824922b2" + integrity sha512-eccxjlfGw43WYoY9QgB82SgGgDbibcqyDTlk3l3C0jOVHKxrjdc9CTwDUQd0vkvYg5um0OH+GpxYvp39r+IPOg== + +"@esbuild/darwin-arm64@0.19.11": + version "0.19.11" + resolved "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.11.tgz" + integrity sha512-ETp87DRWuSt9KdDVkqSoKoLFHYTrkyz2+65fj9nfXsaV3bMhTCjtQfw3y+um88vGRKRiF7erPrh/ZuIdLUIVxQ== + +"@esbuild/darwin-x64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.19.11.tgz#62f3819eff7e4ddc656b7c6815a31cf9a1e7d98e" + integrity sha512-fkFUiS6IUK9WYUO/+22omwetaSNl5/A8giXvQlcinLIjVkxwTLSktbF5f/kJMftM2MJp9+fXqZ5ezS7+SALp4g== + +"@esbuild/freebsd-arm64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.11.tgz#d478b4195aa3ca44160272dab85ef8baf4175b4a" + integrity sha512-lhoSp5K6bxKRNdXUtHoNc5HhbXVCS8V0iZmDvyWvYq9S5WSfTIHU2UGjcGt7UeS6iEYp9eeymIl5mJBn0yiuxA== + +"@esbuild/freebsd-x64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.19.11.tgz#7bdcc1917409178257ca6a1a27fe06e797ec18a2" + integrity sha512-JkUqn44AffGXitVI6/AbQdoYAq0TEullFdqcMY/PCUZ36xJ9ZJRtQabzMA+Vi7r78+25ZIBosLTOKnUXBSi1Kw== + +"@esbuild/linux-arm64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.19.11.tgz#58ad4ff11685fcc735d7ff4ca759ab18fcfe4545" + integrity sha512-LneLg3ypEeveBSMuoa0kwMpCGmpu8XQUh+mL8XXwoYZ6Be2qBnVtcDI5azSvh7vioMDhoJFZzp9GWp9IWpYoUg== + +"@esbuild/linux-arm@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.19.11.tgz#ce82246d873b5534d34de1e5c1b33026f35e60e3" + integrity sha512-3CRkr9+vCV2XJbjwgzjPtO8T0SZUmRZla+UL1jw+XqHZPkPgZiyWvbDvl9rqAN8Zl7qJF0O/9ycMtjU67HN9/Q== + +"@esbuild/linux-ia32@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.19.11.tgz#cbae1f313209affc74b80f4390c4c35c6ab83fa4" + integrity sha512-caHy++CsD8Bgq2V5CodbJjFPEiDPq8JJmBdeyZ8GWVQMjRD0sU548nNdwPNvKjVpamYYVL40AORekgfIubwHoA== + +"@esbuild/linux-loong64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.19.11.tgz#5f32aead1c3ec8f4cccdb7ed08b166224d4e9121" + integrity sha512-ppZSSLVpPrwHccvC6nQVZaSHlFsvCQyjnvirnVjbKSHuE5N24Yl8F3UwYUUR1UEPaFObGD2tSvVKbvR+uT1Nrg== + +"@esbuild/linux-mips64el@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.19.11.tgz#38eecf1cbb8c36a616261de858b3c10d03419af9" + integrity sha512-B5x9j0OgjG+v1dF2DkH34lr+7Gmv0kzX6/V0afF41FkPMMqaQ77pH7CrhWeR22aEeHKaeZVtZ6yFwlxOKPVFyg== + +"@esbuild/linux-ppc64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.19.11.tgz#9c5725a94e6ec15b93195e5a6afb821628afd912" + integrity sha512-MHrZYLeCG8vXblMetWyttkdVRjQlQUb/oMgBNurVEnhj4YWOr4G5lmBfZjHYQHHN0g6yDmCAQRR8MUHldvvRDA== + +"@esbuild/linux-riscv64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.19.11.tgz#2dc4486d474a2a62bbe5870522a9a600e2acb916" + integrity sha512-f3DY++t94uVg141dozDu4CCUkYW+09rWtaWfnb3bqe4w5NqmZd6nPVBm+qbz7WaHZCoqXqHz5p6CM6qv3qnSSQ== + +"@esbuild/linux-s390x@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.19.11.tgz#4ad8567df48f7dd4c71ec5b1753b6f37561a65a8" + integrity sha512-A5xdUoyWJHMMlcSMcPGVLzYzpcY8QP1RtYzX5/bS4dvjBGVxdhuiYyFwp7z74ocV7WDc0n1harxmpq2ePOjI0Q== + +"@esbuild/linux-x64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.19.11.tgz#b7390c4d5184f203ebe7ddaedf073df82a658766" + integrity sha512-grbyMlVCvJSfxFQUndw5mCtWs5LO1gUlwP4CDi4iJBbVpZcqLVT29FxgGuBJGSzyOxotFG4LoO5X+M1350zmPA== + +"@esbuild/netbsd-x64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.19.11.tgz#d633c09492a1721377f3bccedb2d821b911e813d" + integrity sha512-13jvrQZJc3P230OhU8xgwUnDeuC/9egsjTkXN49b3GcS5BKvJqZn86aGM8W9pd14Kd+u7HuFBMVtrNGhh6fHEQ== + +"@esbuild/openbsd-x64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.19.11.tgz#17388c76e2f01125bf831a68c03a7ffccb65d1a2" + integrity sha512-ysyOGZuTp6SNKPE11INDUeFVVQFrhcNDVUgSQVDzqsqX38DjhPEPATpid04LCoUr2WXhQTEZ8ct/EgJCUDpyNw== + +"@esbuild/sunos-x64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.19.11.tgz#e320636f00bb9f4fdf3a80e548cb743370d41767" + integrity sha512-Hf+Sad9nVwvtxy4DXCZQqLpgmRTQqyFyhT3bZ4F2XlJCjxGmRFF0Shwn9rzhOYRB61w9VMXUkxlBy56dk9JJiQ== + +"@esbuild/win32-arm64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.19.11.tgz#c778b45a496e90b6fc373e2a2bb072f1441fe0ee" + integrity sha512-0P58Sbi0LctOMOQbpEOvOL44Ne0sqbS0XWHMvvrg6NE5jQ1xguCSSw9jQeUk2lfrXYsKDdOe6K+oZiwKPilYPQ== + +"@esbuild/win32-ia32@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.19.11.tgz#481a65fee2e5cce74ec44823e6b09ecedcc5194c" + integrity sha512-6YOrWS+sDJDmshdBIQU+Uoyh7pQKrdykdefC1avn76ss5c+RN6gut3LZA4E2cH5xUEp5/cA0+YxRaVtRAb0xBg== + +"@esbuild/win32-x64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.19.11.tgz#a5d300008960bb39677c46bf16f53ec70d8dee04" + integrity sha512-vfkhltrjCAb603XaFhqhAF4LGDi2M4OrCRrFusyQ+iTLQ/o60QQXxc9cZC/FFpihBI9N1Grn6SMKVJ4KP7Fuiw== + +"@eslint/eslintrc@^0.4.3": + version "0.4.3" + resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz" + integrity sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw== + dependencies: + ajv "^6.12.4" + debug "^4.1.1" + espree "^7.3.0" + globals "^13.9.0" + ignore "^4.0.6" + import-fresh "^3.2.1" + js-yaml "^3.13.1" + minimatch "^3.0.4" + strip-json-comments "^3.1.1" + +"@fast-csv/format@4.3.5": + version "4.3.5" + resolved "https://registry.npmjs.org/@fast-csv/format/-/format-4.3.5.tgz" + integrity sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A== + dependencies: + "@types/node" "^14.0.1" + lodash.escaperegexp "^4.1.2" + lodash.isboolean "^3.0.3" + lodash.isequal "^4.5.0" + lodash.isfunction "^3.0.9" + lodash.isnil "^4.0.0" + +"@fast-csv/parse@4.3.6": + version "4.3.6" + resolved "https://registry.npmjs.org/@fast-csv/parse/-/parse-4.3.6.tgz" + integrity sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA== + dependencies: + "@types/node" "^14.0.1" + lodash.escaperegexp "^4.1.2" + lodash.groupby "^4.6.0" + lodash.isfunction "^3.0.9" + lodash.isnil "^4.0.0" + lodash.isundefined "^3.0.1" + lodash.uniq "^4.5.0" + +"@humanwhocodes/config-array@^0.5.0": + version "0.5.0" + resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz" + integrity sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg== + dependencies: + "@humanwhocodes/object-schema" "^1.2.0" + debug "^4.1.1" + minimatch "^3.0.4" + +"@humanwhocodes/object-schema@^1.2.0": + version "1.2.1" + resolved "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz" + integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": + version "0.1.3" + resolved "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jest/console@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.7.0.tgz#cd4822dbdb84529265c5a2bdb529a3c9cc950ffc" + integrity sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + +"@jest/core@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.7.0.tgz#b6cccc239f30ff36609658c5a5e2291757ce448f" + integrity sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg== + dependencies: + "@jest/console" "^29.7.0" + "@jest/reporters" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + ci-info "^3.2.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-changed-files "^29.7.0" + jest-config "^29.7.0" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-resolve-dependencies "^29.7.0" + jest-runner "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + jest-watcher "^29.7.0" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + +"@jest/environment@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" + integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== + dependencies: + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + +"@jest/expect-utils@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" + integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== + dependencies: + jest-get-type "^29.6.3" + +"@jest/expect@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.7.0.tgz#76a3edb0cb753b70dfbfe23283510d3d45432bf2" + integrity sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ== + dependencies: + expect "^29.7.0" + jest-snapshot "^29.7.0" + +"@jest/fake-timers@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565" + integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== + dependencies: + "@jest/types" "^29.6.3" + "@sinonjs/fake-timers" "^10.0.2" + "@types/node" "*" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +"@jest/globals@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.7.0.tgz#8d9290f9ec47ff772607fa864ca1d5a2efae1d4d" + integrity sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/types" "^29.6.3" + jest-mock "^29.7.0" + +"@jest/reporters@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.7.0.tgz#04b262ecb3b8faa83b0b3d321623972393e8f4c7" + integrity sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + "@types/node" "*" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^6.0.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.1.3" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + jest-worker "^29.7.0" + slash "^3.0.0" + string-length "^4.0.1" + strip-ansi "^6.0.0" + v8-to-istanbul "^9.0.1" + +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + +"@jest/source-map@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.3.tgz#d90ba772095cf37a34a5eb9413f1b562a08554c4" + integrity sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw== + dependencies: + "@jridgewell/trace-mapping" "^0.3.18" + callsites "^3.0.0" + graceful-fs "^4.2.9" + +"@jest/test-result@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.7.0.tgz#8db9a80aa1a097bb2262572686734baed9b1657c" + integrity sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA== + dependencies: + "@jest/console" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz#6cef977ce1d39834a3aea887a1726628a6f072ce" + integrity sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw== + dependencies: + "@jest/test-result" "^29.7.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + slash "^3.0.0" + +"@jest/transform@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c" + integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== + dependencies: + "@babel/core" "^7.11.6" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^2.0.0" + fast-json-stable-stringify "^2.1.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + write-file-atomic "^4.0.2" + +"@jest/types@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" + integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== + dependencies: + "@jest/schemas" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + +"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": + version "0.3.3" + resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz" + integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.1" + resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz" + integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== + +"@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15": + version "1.4.15" + resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== + +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.9": + version "0.3.22" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz" + integrity sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@jsdevtools/ono@^7.1.3": + version "7.1.3" + resolved "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz" + integrity sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg== + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@rollup/plugin-alias@^5.0.0": + version "5.1.0" + resolved "https://registry.npmjs.org/@rollup/plugin-alias/-/plugin-alias-5.1.0.tgz" + integrity sha512-lpA3RZ9PdIG7qqhEfv79tBffNaoDuukFDrmhLqg9ifv99u/ehn+lOg30x2zmhf8AQqQUZaMk/B9fZraQ6/acDQ== + dependencies: + slash "^4.0.0" + +"@rollup/plugin-commonjs@^25.0.4": + version "25.0.7" + resolved "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.7.tgz" + integrity sha512-nEvcR+LRjEjsaSsc4x3XZfCCvZIaSMenZu/OiwOKGN2UhQpAYI7ru7czFvyWbErlpoGjnSX3D5Ch5FcMA3kRWQ== + dependencies: + "@rollup/pluginutils" "^5.0.1" + commondir "^1.0.1" + estree-walker "^2.0.2" + glob "^8.0.3" + is-reference "1.2.1" + magic-string "^0.30.3" + +"@rollup/plugin-json@^6.0.0": + version "6.1.0" + resolved "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz" + integrity sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA== + dependencies: + "@rollup/pluginutils" "^5.1.0" + +"@rollup/plugin-node-resolve@^15.2.1": + version "15.2.3" + resolved "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz" + integrity sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ== + dependencies: + "@rollup/pluginutils" "^5.0.1" + "@types/resolve" "1.20.2" + deepmerge "^4.2.2" + is-builtin-module "^3.2.1" + is-module "^1.0.0" + resolve "^1.22.1" + +"@rollup/plugin-replace@^5.0.2": + version "5.0.5" + resolved "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-5.0.5.tgz" + integrity sha512-rYO4fOi8lMaTg/z5Jb+hKnrHHVn8j2lwkqwyS4kTRhKyWOLf2wST2sWXr4WzWiTcoHTp2sTjqUbqIj2E39slKQ== + dependencies: + "@rollup/pluginutils" "^5.0.1" + magic-string "^0.30.3" + +"@rollup/pluginutils@^5.0.1", "@rollup/pluginutils@^5.0.3", "@rollup/pluginutils@^5.1.0": + version "5.1.0" + resolved "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz" + integrity sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g== + dependencies: + "@types/estree" "^1.0.0" + estree-walker "^2.0.2" + picomatch "^2.3.1" + +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + +"@sindresorhus/is@^0.14.0": + version "0.14.0" + resolved "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz" + integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== + +"@sindresorhus/is@^0.15.0": + version "0.15.0" + resolved "https://registry.npmjs.org/@sindresorhus/is/-/is-0.15.0.tgz" + integrity sha512-lu8BpxjAtRCAo5ifytTpCPCj99LF7o/2Myn+NXyNCBqvPYn7Pjd76AMmUB5l7XF1U6t0hcWrlEM5ESufW7wAeA== + +"@sinonjs/commons@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" + integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^10.0.2": + version "10.3.0" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" + integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== + dependencies: + "@sinonjs/commons" "^3.0.0" + +"@storyblok/region-helper@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@storyblok/region-helper/-/region-helper-1.0.0.tgz#5a75506a264b728da640b3c2e85680bec6cd6a1a" + integrity sha512-02B4J3XzD6CLK8DAQbK63fSar8oGYqBJxdx+7Ya0C3uJwMU5DzOMix6ShtUo1iDSd9rOl8aA5wDoCC0wh0YHMw== + +"@szmarczak/http-timer@^1.1.2": + version "1.1.2" + resolved "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz" + integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA== + dependencies: + defer-to-connect "^1.0.1" + +"@trysound/sax@0.2.0": + version "0.2.0" + resolved "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz" + integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== + +"@types/babel__core@^7.1.14": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" + integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== + dependencies: + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.6.4" + resolved "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz" + integrity sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.1" + resolved "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz" + integrity sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": + version "7.14.2" + resolved "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.14.2.tgz" + integrity sha512-K2waXdXBi2302XUdcHcR1jCeU0LL4TD9HRs/gk0N2Xvrht+G/BfJa4QObBQZfhMdxiCpV3COl5Nfq4uKTeTnJA== + dependencies: + "@babel/types" "^7.3.0" + +"@types/estree@*", "@types/estree@^1.0.0": + version "1.0.5" + resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz" + integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== + +"@types/glob@^7.1.3": + version "7.2.0" + resolved "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz" + integrity sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA== + dependencies: + "@types/minimatch" "*" + "@types/node" "*" + +"@types/graceful-fs@^4.1.3": + version "4.1.9" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" + integrity sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ== + dependencies: + "@types/node" "*" + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": + version "2.0.4" + resolved "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz" + integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== + +"@types/istanbul-lib-report@*": + version "3.0.0" + resolved "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz" + integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.1" + resolved "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz" + integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/json-schema@^7.0.11", "@types/json-schema@^7.0.6": + version "7.0.15" + resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + +"@types/json-schema@^7.0.3": + version "7.0.9" + resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz" + integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== + +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz" + integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= + +"@types/lodash@^4.14.182": + version "4.14.202" + resolved "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz" + integrity sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ== + +"@types/minimatch@*": + version "5.1.2" + resolved "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz" + integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA== + +"@types/node@*": + version "17.0.9" + resolved "https://registry.npmjs.org/@types/node/-/node-17.0.9.tgz" + integrity sha512-5dNBXu/FOER+EXnyah7rn8xlNrfMOQb/qXnw4NQgLkCygKBKhdmF/CA5oXVOKZLBEahw8s2WP9LxIcN/oDDRgQ== + +"@types/node@^14.0.1": + version "14.18.7" + resolved "https://registry.npmjs.org/@types/node/-/node-14.18.7.tgz" + integrity sha512-UpLEO1iBG7esNPusSAjoZhWFK5Mfd8QfwWhHRrg5io13POn/stsBgTCba9suQaFflNA4tc0+6AFM3R6BZNng6A== + +"@types/prettier@^2.6.1": + version "2.7.3" + resolved "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz" + integrity sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA== + +"@types/resolve@1.20.2": + version "1.20.2" + resolved "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz" + integrity sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q== + +"@types/stack-utils@^2.0.0": + version "2.0.1" + resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz" + integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== + +"@types/yargs-parser@*": + version "20.2.1" + resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz" + integrity sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw== + +"@types/yargs@^17.0.8": + version "17.0.32" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.32.tgz#030774723a2f7faafebf645f4e5a48371dca6229" + integrity sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog== + dependencies: + "@types/yargs-parser" "*" + +"@typescript-eslint/experimental-utils@^2.5.0": + version "2.34.0" + resolved "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.34.0.tgz" + integrity sha512-eS6FTkq+wuMJ+sgtuNTtcqavWXqsflWcfBnlYhg/nS4aZ1leewkXGbvBhaapn1q6qf4M71bsR1tez5JTRMuqwA== + dependencies: + "@types/json-schema" "^7.0.3" + "@typescript-eslint/typescript-estree" "2.34.0" + eslint-scope "^5.0.0" + eslint-utils "^2.0.0" + +"@typescript-eslint/typescript-estree@2.34.0": + version "2.34.0" + resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.34.0.tgz" + integrity sha512-OMAr+nJWKdlVM9LOqCqh3pQQPwxHAN7Du8DR6dmwCrAmxtiXQnhHJ6tBNtf+cggqfo51SG/FCwnKhXCIM7hnVg== + dependencies: + debug "^4.1.1" + eslint-visitor-keys "^1.1.0" + glob "^7.1.6" + is-glob "^4.0.1" + lodash "^4.17.15" + semver "^7.3.2" + tsutils "^3.17.1" + +acorn-jsx@^5.3.1: + version "5.3.2" + resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^7.4.0: + version "7.4.1" + resolved "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz" + integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== + +acorn@^8.11.3: + version "8.11.3" + resolved "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz" + integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== + +ajv@^6.10.0, ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ajv@^8.0.1: + version "8.9.0" + resolved "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz" + integrity sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + +ansi-align@^3.0.0: + version "3.0.1" + resolved "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz" + 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.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz" + integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== + +ansi-escapes@^4.2.1: + version "4.3.2" + resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +any-promise@^1.0.0: + version "1.3.0" + resolved "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz" + integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== + +anymatch@^3.0.3: + version "3.1.2" + resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz" + integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +array-includes@^3.1.4: + version "3.1.4" + resolved "https://registry.npmjs.org/array-includes/-/array-includes-3.1.4.tgz" + integrity sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.1" + get-intrinsic "^1.1.1" + is-string "^1.0.7" + +array.prototype.flat@^1.2.5: + version "1.2.5" + resolved "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz" + integrity sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.0" + +astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz" + integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" + integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + +at-least-node@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz" + integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== + +autoprefixer@^10.4.14: + version "10.4.17" + resolved "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.17.tgz" + integrity sha512-/cpVNRLSfhOtcGflT13P2794gVSgmPgTR+erw5ifnMLZb0UnSlkK4tquLmkd3BhA+nLo5tX8Cu0upUsGKvKbmg== + dependencies: + browserslist "^4.22.2" + caniuse-lite "^1.0.30001578" + fraction.js "^4.3.7" + normalize-range "^0.1.2" + picocolors "^1.0.0" + postcss-value-parser "^4.2.0" + +axios@^0.29.0: + version "0.29.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.29.0.tgz#5eed1a0bc4c0ffe060624eb7900aff66b7881eeb" + integrity sha512-Kjsq1xisgO5DjjNQwZFsy0gpcU1P2j36dZeQDXVhpIU26GVgkDUnROaHLSuluhMqtDE7aKA2hbKXG5yu5DN8Tg== + dependencies: + follow-redirects "^1.15.4" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + +babel-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" + integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== + dependencies: + "@jest/transform" "^29.7.0" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^29.6.3" + chalk "^4.0.0" + graceful-fs "^4.2.9" + slash "^3.0.0" + +babel-plugin-istanbul@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^5.0.4" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626" + integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.1.14" + "@types/babel__traverse" "^7.0.6" + +babel-preset-current-node-syntax@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz" + integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.8.3" + "@babel/plugin-syntax-import-meta" "^7.8.3" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.8.3" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.8.3" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-top-level-await" "^7.8.3" + +babel-preset-jest@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c" + integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== + dependencies: + babel-plugin-jest-hoist "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +boolbase@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz" + integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== + +boxen@^5.0.0: + version "5.1.2" + resolved "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz" + integrity sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ== + dependencies: + ansi-align "^3.0.0" + camelcase "^6.2.0" + chalk "^4.1.0" + cli-boxes "^2.2.1" + string-width "^4.2.2" + type-fest "^0.20.2" + widest-line "^3.1.0" + wrap-ansi "^7.0.0" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.2: + version "3.0.2" + resolved "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +browserslist@^4.0.0, browserslist@^4.22.2: + version "4.22.2" + resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz" + integrity sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A== + dependencies: + caniuse-lite "^1.0.30001565" + electron-to-chromium "^1.4.601" + node-releases "^2.0.14" + update-browserslist-db "^1.0.13" + +browserslist@^4.17.5: + version "4.19.1" + resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz" + integrity sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A== + dependencies: + caniuse-lite "^1.0.30001286" + electron-to-chromium "^1.4.17" + escalade "^3.1.1" + node-releases "^2.0.1" + picocolors "^1.0.0" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +builtin-modules@^3.3.0: + version "3.3.0" + resolved "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz" + integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== + +cacheable-request@^6.0.0: + version "6.1.0" + resolved "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz" + integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg== + dependencies: + clone-response "^1.0.2" + get-stream "^5.1.0" + http-cache-semantics "^4.0.0" + keyv "^3.0.0" + lowercase-keys "^2.0.0" + normalize-url "^4.1.0" + responselike "^1.0.2" + +call-bind@^1.0.0, call-bind@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + +call-me-maybe@^1.0.1: + version "1.0.2" + resolved "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz" + integrity sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ== + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +caniuse-api@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz" + integrity sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw== + dependencies: + browserslist "^4.0.0" + caniuse-lite "^1.0.0" + lodash.memoize "^4.1.2" + lodash.uniq "^4.5.0" + +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001565, caniuse-lite@^1.0.30001578: + version "1.0.30001579" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001579.tgz" + integrity sha512-u5AUVkixruKHJjw/pj9wISlcMpgFWzSrczLZbrqBSxukQixmg0SJ5sZTpvaFvxU0HoQKd4yoyAogyrAz9pzJnA== + +caniuse-lite@^1.0.30001286: + version "1.0.30001300" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001300.tgz" + integrity sha512-cVjiJHWGcNlJi8TZVKNMnvMid3Z3TTdDHmLDzlOdIiZq138Exvo0G+G0wTdVYolxKb4AYwC+38pxodiInVtJSA== + +chalk@^2.0.0, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.0.0, chalk@^4.1.0: + version "4.1.2" + resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chalk@^5.3.0: + version "5.3.0" + resolved "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz" + integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== + +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + +chardet@^0.7.0: + version "0.7.0" + resolved "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz" + integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== + +ci-info@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz" + integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== + +ci-info@^3.2.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" + integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== + +citty@^0.1.2, citty@^0.1.5: + version "0.1.5" + resolved "https://registry.npmjs.org/citty/-/citty-0.1.5.tgz" + integrity sha512-AS7n5NSc0OQVMV9v6wt3ByujNIrne0/cTjiC2MYqhvao57VNfiuVksTSr2p17nVOhEr2KtqiAkGwHcgMC/qUuQ== + dependencies: + consola "^3.2.3" + +cjs-module-lexer@^1.0.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz#6c370ab19f8a3394e318fe682686ec0ac684d107" + integrity sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ== + +clear@0.1.0: + version "0.1.0" + resolved "https://registry.npmjs.org/clear/-/clear-0.1.0.tgz" + integrity sha512-qMjRnoL+JDPJHeLePZJuao6+8orzHMGP04A8CdwCNsKhRbOnKRjefxONR7bwILT3MHecxKBjHkKL/tkZ8r4Uzw== + +cli-boxes@^2.2.1: + version "2.2.1" + resolved "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz" + integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== + +cli-color@^2.0.2: + version "2.0.3" + resolved "https://registry.npmjs.org/cli-color/-/cli-color-2.0.3.tgz" + integrity sha512-OkoZnxyC4ERN3zLzZaY9Emb7f/MhBOIpePv0Ycok0fJYT+Ouo00UBEIwsVsr0yoow++n5YWlSUgST9GKhNHiRQ== + dependencies: + d "^1.0.1" + es5-ext "^0.10.61" + es6-iterator "^2.0.3" + memoizee "^0.4.15" + timers-ext "^0.1.7" + +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + +cli-width@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz" + integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +clone-response@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz" + integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= + dependencies: + mimic-response "^1.0.0" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.npmjs.org/co/-/co-4.6.0.tgz" + integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= + +collect-v8-coverage@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz" + integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +colord@^2.9.1: + version "2.9.3" + resolved "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz" + integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw== + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +commander@^5.1.0: + version "5.1.0" + resolved "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz" + integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== + +commander@^7.2.0: + version "7.2.0" + resolved "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz" + integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== + +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz" + integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +concat-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz" + integrity sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A== + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.0.2" + typedarray "^0.0.6" + +configstore@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz" + integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA== + dependencies: + dot-prop "^5.2.0" + graceful-fs "^4.1.2" + make-dir "^3.0.0" + unique-string "^2.0.0" + write-file-atomic "^3.0.0" + xdg-basedir "^4.0.0" + +consola@^3.2.3: + version "3.2.3" + resolved "https://registry.npmjs.org/consola/-/consola-3.2.3.tgz" + integrity sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ== + +convert-source-map@^1.7.0: + version "1.8.0" + resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz" + integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== + dependencies: + safe-buffer "~5.1.1" + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +create-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" + integrity sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-config "^29.7.0" + jest-util "^29.7.0" + prompts "^2.0.1" + +cross-spawn@^7.0.2, cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +crypto-random-string@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz" + integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== + +css-declaration-sorter@^7.1.1: + version "7.1.1" + resolved "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.1.1.tgz" + integrity sha512-dZ3bVTEEc1vxr3Bek9vGwfB5Z6ESPULhcRvO472mfjVnj8jRcTnKO8/JTczlvxM10Myb+wBM++1MtdO76eWcaQ== + +css-select@^5.1.0: + version "5.1.0" + resolved "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz" + integrity sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg== + dependencies: + boolbase "^1.0.0" + css-what "^6.1.0" + domhandler "^5.0.2" + domutils "^3.0.1" + nth-check "^2.0.1" + +css-tree@^2.3.1: + version "2.3.1" + resolved "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz" + integrity sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw== + dependencies: + mdn-data "2.0.30" + source-map-js "^1.0.1" + +css-tree@~2.2.0: + version "2.2.1" + resolved "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz" + integrity sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA== + dependencies: + mdn-data "2.0.28" + source-map-js "^1.0.1" + +css-what@^6.1.0: + version "6.1.0" + resolved "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz" + integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== + +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + +cssnano-preset-default@^6.0.3: + version "6.0.3" + resolved "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.0.3.tgz" + integrity sha512-4y3H370aZCkT9Ev8P4SO4bZbt+AExeKhh8wTbms/X7OLDo5E7AYUUy6YPxa/uF5Grf+AJwNcCnxKhZynJ6luBA== + dependencies: + css-declaration-sorter "^7.1.1" + cssnano-utils "^4.0.1" + postcss-calc "^9.0.1" + postcss-colormin "^6.0.2" + postcss-convert-values "^6.0.2" + postcss-discard-comments "^6.0.1" + postcss-discard-duplicates "^6.0.1" + postcss-discard-empty "^6.0.1" + postcss-discard-overridden "^6.0.1" + postcss-merge-longhand "^6.0.2" + postcss-merge-rules "^6.0.3" + postcss-minify-font-values "^6.0.1" + postcss-minify-gradients "^6.0.1" + postcss-minify-params "^6.0.2" + postcss-minify-selectors "^6.0.2" + postcss-normalize-charset "^6.0.1" + postcss-normalize-display-values "^6.0.1" + postcss-normalize-positions "^6.0.1" + postcss-normalize-repeat-style "^6.0.1" + postcss-normalize-string "^6.0.1" + postcss-normalize-timing-functions "^6.0.1" + postcss-normalize-unicode "^6.0.2" + postcss-normalize-url "^6.0.1" + postcss-normalize-whitespace "^6.0.1" + postcss-ordered-values "^6.0.1" + postcss-reduce-initial "^6.0.2" + postcss-reduce-transforms "^6.0.1" + postcss-svgo "^6.0.2" + postcss-unique-selectors "^6.0.2" + +cssnano-utils@^4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.1.tgz" + integrity sha512-6qQuYDqsGoiXssZ3zct6dcMxiqfT6epy7x4R0TQJadd4LWO3sPR6JH6ZByOvVLoZ6EdwPGgd7+DR1EmX3tiXQQ== + +cssnano@^6.0.1: + version "6.0.3" + resolved "https://registry.npmjs.org/cssnano/-/cssnano-6.0.3.tgz" + integrity sha512-MRq4CIj8pnyZpcI2qs6wswoYoDD1t0aL28n+41c1Ukcpm56m1h6mCexIHBGjfZfnTqtGSSCP4/fB1ovxgjBOiw== + dependencies: + cssnano-preset-default "^6.0.3" + lilconfig "^3.0.0" + +csso@^5.0.5: + version "5.0.5" + resolved "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz" + integrity sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ== + dependencies: + css-tree "~2.2.0" + +d@1, d@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/d/-/d-1.0.1.tgz" + integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== + dependencies: + es5-ext "^0.10.50" + type "^1.0.1" + +debug@^2.6.9: + version "2.6.9" + resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@^3.2.7: + version "3.2.7" + resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: + version "4.3.3" + resolved "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz" + integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== + dependencies: + ms "2.1.2" + +debug@^4.3.1: + version "4.3.4" + resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +decompress-response@^3.3.0: + version "3.3.0" + resolved "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz" + integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M= + dependencies: + mimic-response "^1.0.0" + +dedent@^1.0.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.1.tgz#4f3fc94c8b711e9bb2800d185cd6ad20f2a90aff" + integrity sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg== + +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +deepmerge@^4.2.2: + version "4.2.2" + resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz" + integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== + +defer-to-connect@^1.0.1: + version "1.1.3" + resolved "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz" + integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ== + +define-properties@^1.1.3: + version "1.1.3" + resolved "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz" + integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== + dependencies: + object-keys "^1.0.12" + +defu@^6.1.2, defu@^6.1.3, defu@^6.1.4: + version "6.1.4" + resolved "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz" + integrity sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg== + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" + integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + +diff-sequences@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +doctrine@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz" + integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== + dependencies: + esutils "^2.0.2" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +dom-serializer@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz" + integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.2" + entities "^4.2.0" + +domelementtype@^2.3.0: + version "2.3.0" + resolved "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz" + integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== + +domhandler@^5.0.2, domhandler@^5.0.3: + version "5.0.3" + resolved "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz" + integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== + dependencies: + domelementtype "^2.3.0" + +domutils@^3.0.1: + version "3.1.0" + resolved "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz" + integrity sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA== + dependencies: + dom-serializer "^2.0.0" + domelementtype "^2.3.0" + domhandler "^5.0.3" + +dot-prop@^5.2.0: + version "5.3.0" + resolved "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz" + integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== + dependencies: + is-obj "^2.0.0" + +duplexer3@^0.1.4: + version "0.1.4" + resolved "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz" + integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= + +electron-to-chromium@^1.4.17: + version "1.4.47" + resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.47.tgz" + integrity sha512-ZHc8i3/cgeCRK/vC7W2htAG6JqUmOUgDNn/f9yY9J8UjfLjwzwOVEt4MWmgJAdvmxyrsR5KIFA/6+kUHGY0eUA== + +electron-to-chromium@^1.4.601: + version "1.4.640" + resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.640.tgz" + integrity sha512-z/6oZ/Muqk4BaE7P69bXhUhpJbUM9ZJeka43ZwxsDshKtePns4mhBlh8bU5+yrnOnz3fhG82XLzGUXazOmsWnA== + +emittery@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" + integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +end-of-stream@^1.1.0: + version "1.4.4" + resolved "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +enquirer@^2.3.5: + version "2.3.6" + resolved "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz" + integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== + dependencies: + ansi-colors "^4.1.1" + +entities@^4.2.0: + version "4.5.0" + resolved "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +es-abstract@^1.19.0, es-abstract@^1.19.1: + version "1.19.1" + resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz" + integrity sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w== + dependencies: + call-bind "^1.0.2" + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + get-intrinsic "^1.1.1" + get-symbol-description "^1.0.0" + has "^1.0.3" + has-symbols "^1.0.2" + internal-slot "^1.0.3" + is-callable "^1.2.4" + is-negative-zero "^2.0.1" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.1" + is-string "^1.0.7" + is-weakref "^1.0.1" + object-inspect "^1.11.0" + object-keys "^1.1.1" + object.assign "^4.1.2" + string.prototype.trimend "^1.0.4" + string.prototype.trimstart "^1.0.4" + unbox-primitive "^1.0.1" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@^0.10.53, es5-ext@^0.10.61, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46: + version "0.10.62" + resolved "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz" + integrity sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA== + dependencies: + es6-iterator "^2.0.3" + es6-symbol "^3.1.3" + next-tick "^1.1.0" + +es6-iterator@^2.0.3: + version "2.0.3" + resolved "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz" + integrity sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g== + dependencies: + d "1" + es5-ext "^0.10.35" + es6-symbol "^3.1.1" + +es6-symbol@^3.1.1, es6-symbol@^3.1.3: + version "3.1.3" + resolved "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz" + integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== + dependencies: + d "^1.0.1" + ext "^1.1.2" + +es6-weak-map@^2.0.3: + version "2.0.3" + resolved "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz" + integrity sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA== + dependencies: + d "1" + es5-ext "^0.10.46" + es6-iterator "^2.0.3" + es6-symbol "^3.1.1" + +esbuild@^0.19.2, esbuild@^0.19.7: + version "0.19.11" + resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.19.11.tgz" + integrity sha512-HJ96Hev2hX/6i5cDVwcqiJBBtuo9+FeIJOtZ9W1kA5M6AMJRHUZlpYZ1/SbEwtO0ioNAW8rUooVpC/WehY2SfA== + optionalDependencies: + "@esbuild/aix-ppc64" "0.19.11" + "@esbuild/android-arm" "0.19.11" + "@esbuild/android-arm64" "0.19.11" + "@esbuild/android-x64" "0.19.11" + "@esbuild/darwin-arm64" "0.19.11" + "@esbuild/darwin-x64" "0.19.11" + "@esbuild/freebsd-arm64" "0.19.11" + "@esbuild/freebsd-x64" "0.19.11" + "@esbuild/linux-arm" "0.19.11" + "@esbuild/linux-arm64" "0.19.11" + "@esbuild/linux-ia32" "0.19.11" + "@esbuild/linux-loong64" "0.19.11" + "@esbuild/linux-mips64el" "0.19.11" + "@esbuild/linux-ppc64" "0.19.11" + "@esbuild/linux-riscv64" "0.19.11" + "@esbuild/linux-s390x" "0.19.11" + "@esbuild/linux-x64" "0.19.11" + "@esbuild/netbsd-x64" "0.19.11" + "@esbuild/openbsd-x64" "0.19.11" + "@esbuild/sunos-x64" "0.19.11" + "@esbuild/win32-arm64" "0.19.11" + "@esbuild/win32-ia32" "0.19.11" + "@esbuild/win32-x64" "0.19.11" + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-goat@^2.0.0: + version "2.1.1" + resolved "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz" + integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-config-standard@^14.1.1: + version "14.1.1" + resolved "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-14.1.1.tgz" + integrity sha512-Z9B+VR+JIXRxz21udPTL9HpFMyoMUEeX1G251EQ6e05WD9aPVtVBn09XUmZ259wCMlCDmYDSZG62Hhm+ZTJcUg== + +eslint-import-resolver-node@^0.3.6: + version "0.3.6" + resolved "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz" + integrity sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw== + dependencies: + debug "^3.2.7" + resolve "^1.20.0" + +eslint-module-utils@^2.7.2: + version "2.7.2" + resolved "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.2.tgz" + integrity sha512-zquepFnWCY2ISMFwD/DqzaM++H+7PDzOpUvotJWm/y1BAFt5R4oeULgdrTejKqLkz7MA/tgstsUMNYc7wNdTrg== + dependencies: + debug "^3.2.7" + find-up "^2.1.0" + +eslint-plugin-es@^3.0.0: + version "3.0.1" + resolved "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz" + integrity sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ== + dependencies: + eslint-utils "^2.0.0" + regexpp "^3.0.0" + +eslint-plugin-import@^2.21.2: + version "2.25.4" + resolved "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.25.4.tgz" + 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.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.12.0" + +eslint-plugin-jest@^23.18.0: + version "23.20.0" + resolved "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-23.20.0.tgz" + integrity sha512-+6BGQt85OREevBDWCvhqj1yYA4+BFK4XnRZSGJionuEYmcglMZYLNNBBemwzbqUAckURaHdJSBcjHPyrtypZOw== + dependencies: + "@typescript-eslint/experimental-utils" "^2.5.0" + +eslint-plugin-node@^11.1.0: + version "11.1.0" + resolved "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz" + integrity sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g== + dependencies: + eslint-plugin-es "^3.0.0" + eslint-utils "^2.0.0" + ignore "^5.1.1" + minimatch "^3.0.4" + resolve "^1.10.1" + semver "^6.1.0" + +eslint-plugin-promise@^4.2.1: + version "4.3.1" + resolved "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.3.1.tgz" + integrity sha512-bY2sGqyptzFBDLh/GMbAxfdJC+b0f23ME63FOE4+Jao0oZ3E1LEwFtWJX/1pGMJLiTtrSSern2CRM/g+dfc0eQ== + +eslint-plugin-standard@^4.0.1: + version "4.1.0" + resolved "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-4.1.0.tgz" + integrity sha512-ZL7+QRixjTR6/528YNGyDotyffm5OQst/sGxKDwGb9Uqs4In5Egi4+jbobhqJoyoCM6/7v/1A5fhQ7ScMtDjaQ== + +eslint-scope@^5.0.0, eslint-scope@^5.1.1: + version "5.1.1" + resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +eslint-utils@^2.0.0, eslint-utils@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz" + integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== + dependencies: + eslint-visitor-keys "^1.1.0" + +eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz" + integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== + +eslint-visitor-keys@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz" + integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== + +eslint@^7.2.0: + version "7.32.0" + resolved "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz" + integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA== + dependencies: + "@babel/code-frame" "7.12.11" + "@eslint/eslintrc" "^0.4.3" + "@humanwhocodes/config-array" "^0.5.0" + ajv "^6.10.0" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.0.1" + doctrine "^3.0.0" + enquirer "^2.3.5" + escape-string-regexp "^4.0.0" + eslint-scope "^5.1.1" + eslint-utils "^2.1.0" + eslint-visitor-keys "^2.0.0" + espree "^7.3.1" + esquery "^1.4.0" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + functional-red-black-tree "^1.0.1" + glob-parent "^5.1.2" + globals "^13.6.0" + ignore "^4.0.6" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + js-yaml "^3.13.1" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.0.4" + natural-compare "^1.4.0" + optionator "^0.9.1" + progress "^2.0.0" + regexpp "^3.1.0" + semver "^7.2.1" + strip-ansi "^6.0.0" + strip-json-comments "^3.1.0" + table "^6.0.9" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + +espree@^7.3.0, espree@^7.3.1: + version "7.3.1" + resolved "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz" + integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g== + dependencies: + acorn "^7.4.0" + acorn-jsx "^5.3.1" + eslint-visitor-keys "^1.3.0" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz" + integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +estree-walker@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz" + integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +event-emitter@^0.3.5: + version "0.3.5" + resolved "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz" + integrity sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA== + dependencies: + d "1" + es5-ext "~0.10.14" + +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz" + integrity sha1-BjJjj42HfMghB9MKD/8aF8uhzQw= + +expect@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" + integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== + dependencies: + "@jest/expect-utils" "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + +ext@^1.1.2: + version "1.7.0" + resolved "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz" + integrity sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw== + dependencies: + type "^2.7.2" + +external-editor@^3.0.3: + version "3.1.0" + resolved "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz" + integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== + dependencies: + chardet "^0.7.0" + iconv-lite "^0.4.24" + tmp "^0.0.33" + +fast-csv@^4.3.6: + version "4.3.6" + resolved "https://registry.npmjs.org/fast-csv/-/fast-csv-4.3.6.tgz" + integrity sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw== + dependencies: + "@fast-csv/format" "4.3.5" + "@fast-csv/parse" "4.3.6" + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-glob@^3.3.0: + version "3.3.2" + resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + +fastq@^1.6.0: + version "1.16.0" + resolved "https://registry.npmjs.org/fastq/-/fastq-1.16.0.tgz" + integrity sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA== + dependencies: + reusify "^1.0.4" + +fb-watchman@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz" + integrity sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg== + dependencies: + bser "2.1.1" + +figlet@^1.5.0: + version "1.5.2" + resolved "https://registry.npmjs.org/figlet/-/figlet-1.5.2.tgz" + integrity sha512-WOn21V8AhyE1QqVfPIVxe3tupJacq1xGkPTB4iagT6o+P2cAgEOOwIxMftr4+ZCTI6d551ij9j61DFr0nsP2uQ== + +figures@^3.0.0: + version "3.2.0" + resolved "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz" + integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== + dependencies: + escape-string-regexp "^1.0.5" + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +find-up@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz" + integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= + dependencies: + locate-path "^2.0.0" + +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +flat-cache@^3.0.4: + version "3.0.4" + resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz" + integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== + dependencies: + flatted "^3.1.0" + rimraf "^3.0.2" + +flatted@^3.1.0: + version "3.2.4" + resolved "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz" + integrity sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw== + +follow-redirects@^1.15.4: + version "1.15.9" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" + integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== + +form-data@^3.0.0: + version "3.0.1" + resolved "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz" + integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +fraction.js@^4.3.7: + version "4.3.7" + resolved "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz" + integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew== + +fs-extra@^11.1.1: + version "11.2.0" + resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz" + integrity sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs-extra@^9.0.1: + version "9.1.0" + resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz" + integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +fsevents@^2.3.2, fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz" + integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz" + integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-stdin@^8.0.0: + version "8.0.0" + resolved "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz" + integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg== + +get-stream@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz" + integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== + dependencies: + pump "^3.0.0" + +get-stream@^5.1.0: + version "5.2.0" + resolved "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + dependencies: + pump "^3.0.0" + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +get-symbol-description@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz" + integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.1" + +git-clone@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/git-clone/-/git-clone-0.2.0.tgz#9dce00facbab227d2562150052cd4a3a7ec15c41" + integrity sha512-1UAkEPIFbyjHaddljUKvPhhLRnrKaImT71T7rdvSvWLXw95nLdhdi6Qmlx0KOWoV1qqvHGLq5lMLJEZM0JXk8A== + +glob-parent@^5.1.2: + version "5.1.2" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-promise@^4.2.2: + version "4.2.2" + resolved "https://registry.npmjs.org/glob-promise/-/glob-promise-4.2.2.tgz" + integrity sha512-xcUzJ8NWN5bktoTIX7eOclO1Npxd/dyVqUJxlLIDasT4C7KZyqlPIwkdJ0Ypiy3p2ZKahTjK4M9uC3sNSfNMzw== + dependencies: + "@types/glob" "^7.1.3" + +glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: + version "7.2.0" + resolved "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz" + integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^8.0.3: + version "8.1.0" + resolved "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz" + integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^5.0.1" + once "^1.3.0" + +global-dirs@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz" + integrity sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA== + dependencies: + ini "2.0.0" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^13.6.0, globals@^13.9.0: + version "13.12.0" + resolved "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz" + integrity sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg== + dependencies: + type-fest "^0.20.2" + +globby@^13.2.2: + version "13.2.2" + resolved "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz" + integrity sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w== + dependencies: + dir-glob "^3.0.1" + fast-glob "^3.3.0" + ignore "^5.2.4" + merge2 "^1.4.1" + slash "^4.0.0" + +got@^9.6.0: + version "9.6.0" + resolved "https://registry.npmjs.org/got/-/got-9.6.0.tgz" + integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== + dependencies: + "@sindresorhus/is" "^0.14.0" + "@szmarczak/http-timer" "^1.1.2" + cacheable-request "^6.0.0" + decompress-response "^3.3.0" + duplexer3 "^0.1.4" + get-stream "^4.1.0" + lowercase-keys "^1.0.1" + mimic-response "^1.0.1" + p-cancelable "^1.0.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: + version "4.2.9" + resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz" + integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== + +graceful-fs@^4.2.9: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +has-bigints@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz" + integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-symbols@^1.0.1, has-symbols@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz" + integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== + +has-tostringtag@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz" + integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== + dependencies: + has-symbols "^1.0.2" + +has-yarn@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz" + integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw== + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/has/-/has-1.0.3.tgz" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +hasown@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz" + integrity sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA== + dependencies: + function-bind "^1.1.2" + +hookable@^5.5.3: + version "5.5.3" + resolved "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz" + integrity sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ== + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +http-cache-semantics@^4.0.0: + version "4.1.0" + resolved "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz" + integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +iconv-lite@^0.4.24: + version "0.4.24" + resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +ignore@^4.0.6: + version "4.0.6" + resolved "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz" + integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== + +ignore@^5.1.1: + version "5.2.0" + resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz" + integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== + +ignore@^5.2.4: + version "5.3.0" + resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz" + integrity sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg== + +import-fresh@^3.0.0, import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +import-lazy@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz" + integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM= + +import-local@^3.0.2: + version "3.1.0" + resolved "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz" + integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.3: + version "2.0.4" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +inherits@2.0.3: + version "2.0.3" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + +ini@2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz" + integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== + +ini@~1.3.0: + version "1.3.8" + resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + +inquirer@^7.3.2: + version "7.3.3" + resolved "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz" + integrity sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA== + dependencies: + ansi-escapes "^4.2.1" + chalk "^4.1.0" + cli-cursor "^3.1.0" + cli-width "^3.0.0" + external-editor "^3.0.3" + figures "^3.0.0" + lodash "^4.17.19" + mute-stream "0.0.8" + run-async "^2.4.0" + rxjs "^6.6.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + through "^2.3.6" + +internal-slot@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz" + integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA== + dependencies: + get-intrinsic "^1.1.0" + has "^1.0.3" + side-channel "^1.0.4" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" + integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + +is-bigint@^1.0.1: + version "1.0.4" + resolved "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz" + integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== + dependencies: + has-bigints "^1.0.1" + +is-boolean-object@^1.1.0: + version "1.1.2" + resolved "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz" + integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-builtin-module@^3.2.1: + version "3.2.1" + resolved "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz" + integrity sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A== + dependencies: + builtin-modules "^3.3.0" + +is-callable@^1.1.4, is-callable@^1.2.4: + version "1.2.4" + resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz" + integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== + +is-ci@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz" + integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== + dependencies: + ci-info "^2.0.0" + +is-core-module@^2.13.0: + version "2.13.1" + resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz" + integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== + dependencies: + hasown "^2.0.0" + +is-core-module@^2.8.0: + version "2.8.1" + resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz" + integrity sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA== + dependencies: + has "^1.0.3" + +is-date-object@^1.0.1: + version "1.0.5" + resolved "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz" + integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== + dependencies: + has-tostringtag "^1.0.0" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-installed-globally@^0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz" + integrity sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ== + dependencies: + global-dirs "^3.0.0" + is-path-inside "^3.0.2" + +is-module@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz" + integrity sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g== + +is-negative-zero@^2.0.1: + version "2.0.2" + resolved "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz" + integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== + +is-npm@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/is-npm/-/is-npm-5.0.0.tgz" + integrity sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA== + +is-number-object@^1.0.4: + version "1.0.6" + resolved "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz" + integrity sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g== + dependencies: + has-tostringtag "^1.0.0" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-obj@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz" + integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== + +is-path-inside@^3.0.2: + version "3.0.3" + resolved "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + +is-promise@^2.2.2: + version "2.2.2" + resolved "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz" + integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== + +is-reference@1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz" + integrity sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ== + dependencies: + "@types/estree" "*" + +is-regex@^1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-shared-array-buffer@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz" + integrity sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA== + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-string@^1.0.5, is-string@^1.0.7: + version "1.0.7" + resolved "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz" + integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== + dependencies: + has-tostringtag "^1.0.0" + +is-symbol@^1.0.2, is-symbol@^1.0.3: + version "1.0.4" + resolved "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== + dependencies: + has-symbols "^1.0.2" + +is-typedarray@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz" + integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= + +is-weakref@^1.0.1: + version "1.0.2" + resolved "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz" + integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== + dependencies: + call-bind "^1.0.2" + +is-wsl@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz" + integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= + +is-yarn-global@^0.3.0: + version "0.3.0" + resolved "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz" + integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.0" + resolved "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz" + integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== + +istanbul-lib-instrument@^5.0.4: + version "5.1.0" + resolved "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.1.0.tgz" + integrity sha512-czwUz525rkOFDJxfKK6mYfIs9zBKILyrZQxjz3ABhjQXhbhFsSbo1HW/BFcsDnfJYJWA6thRR5/TUY2qs5W99Q== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^6.3.0" + +istanbul-lib-instrument@^6.0.0: + version "6.0.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.2.tgz#91655936cf7380e4e473383081e38478b69993b1" + integrity sha512-1WUsZ9R1lA0HtBSohTkm39WTPlNKSJ5iFk7UwqXkBLoHQT+hfqPsfsTDVuZdKGaBwn7din9bS7SsnoAr943hvw== + dependencies: + "@babel/core" "^7.23.9" + "@babel/parser" "^7.23.9" + "@istanbuljs/schema" "^0.1.3" + istanbul-lib-coverage "^3.2.0" + semver "^7.5.4" + +istanbul-lib-report@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz" + integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^3.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.1.3: + version "3.1.7" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.7.tgz#daed12b9e1dca518e15c056e1e537e741280fa0b" + integrity sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +jest-changed-files@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a" + integrity sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w== + dependencies: + execa "^5.0.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + +jest-circus@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.7.0.tgz#b6817a45fcc835d8b16d5962d0c026473ee3668a" + integrity sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^1.0.0" + is-generator-fn "^2.0.0" + jest-each "^29.7.0" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + pretty-format "^29.7.0" + pure-rand "^6.0.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-cli@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.7.0.tgz#5592c940798e0cae677eec169264f2d839a37995" + integrity sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg== + dependencies: + "@jest/core" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + chalk "^4.0.0" + create-jest "^29.7.0" + exit "^0.1.2" + import-local "^3.0.2" + jest-config "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + yargs "^17.3.1" + +jest-config@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.7.0.tgz#bcbda8806dbcc01b1e316a46bb74085a84b0245f" + integrity sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ== + dependencies: + "@babel/core" "^7.11.6" + "@jest/test-sequencer" "^29.7.0" + "@jest/types" "^29.6.3" + babel-jest "^29.7.0" + chalk "^4.0.0" + ci-info "^3.2.0" + deepmerge "^4.2.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-circus "^29.7.0" + jest-environment-node "^29.7.0" + jest-get-type "^29.6.3" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-runner "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + micromatch "^4.0.4" + parse-json "^5.2.0" + pretty-format "^29.7.0" + slash "^3.0.0" + strip-json-comments "^3.1.1" + +jest-diff@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" + integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== + dependencies: + chalk "^4.0.0" + diff-sequences "^29.6.3" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-docblock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.7.0.tgz#8fddb6adc3cdc955c93e2a87f61cfd350d5d119a" + integrity sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g== + dependencies: + detect-newline "^3.0.0" + +jest-each@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.7.0.tgz#162a9b3f2328bdd991beaabffbb74745e56577d1" + integrity sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + jest-get-type "^29.6.3" + jest-util "^29.7.0" + pretty-format "^29.7.0" + +jest-environment-node@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376" + integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-mock "^29.7.0" + jest-util "^29.7.0" + +jest-get-type@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" + integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== + +jest-haste-map@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104" + integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA== + dependencies: + "@jest/types" "^29.6.3" + "@types/graceful-fs" "^4.1.3" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + jest-worker "^29.7.0" + micromatch "^4.0.4" + walker "^1.0.8" + optionalDependencies: + fsevents "^2.3.2" + +jest-leak-detector@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz#5b7ec0dadfdfec0ca383dc9aa016d36b5ea4c728" + integrity sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw== + dependencies: + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-matcher-utils@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" + integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== + dependencies: + chalk "^4.0.0" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" + +jest-message-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" + integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.6.3" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" + integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + jest-util "^29.7.0" + +jest-pnp-resolver@^1.2.2: + version "1.2.2" + resolved "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz" + integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== + +jest-regex-util@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" + integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== + +jest-resolve-dependencies@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz#1b04f2c095f37fc776ff40803dc92921b1e88428" + integrity sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA== + dependencies: + jest-regex-util "^29.6.3" + jest-snapshot "^29.7.0" + +jest-resolve@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.7.0.tgz#64d6a8992dd26f635ab0c01e5eef4399c6bcbc30" + integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== + dependencies: + chalk "^4.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-pnp-resolver "^1.2.2" + jest-util "^29.7.0" + jest-validate "^29.7.0" + resolve "^1.20.0" + resolve.exports "^2.0.0" + slash "^3.0.0" + +jest-runner@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.7.0.tgz#809af072d408a53dcfd2e849a4c976d3132f718e" + integrity sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ== + dependencies: + "@jest/console" "^29.7.0" + "@jest/environment" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.13.1" + graceful-fs "^4.2.9" + jest-docblock "^29.7.0" + jest-environment-node "^29.7.0" + jest-haste-map "^29.7.0" + jest-leak-detector "^29.7.0" + jest-message-util "^29.7.0" + jest-resolve "^29.7.0" + jest-runtime "^29.7.0" + jest-util "^29.7.0" + jest-watcher "^29.7.0" + jest-worker "^29.7.0" + p-limit "^3.1.0" + source-map-support "0.5.13" + +jest-runtime@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.7.0.tgz#efecb3141cf7d3767a3a0cc8f7c9990587d3d817" + integrity sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/globals" "^29.7.0" + "@jest/source-map" "^29.6.3" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + cjs-module-lexer "^1.0.0" + collect-v8-coverage "^1.0.0" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + slash "^3.0.0" + strip-bom "^4.0.0" + +jest-snapshot@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.7.0.tgz#c2c574c3f51865da1bb329036778a69bf88a6be5" + integrity sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw== + dependencies: + "@babel/core" "^7.11.6" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-jsx" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/types" "^7.3.3" + "@jest/expect-utils" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^29.7.0" + graceful-fs "^4.2.9" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + natural-compare "^1.4.0" + pretty-format "^29.7.0" + semver "^7.5.3" + +jest-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" + integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.7.0.tgz#7bf705511c64da591d46b15fce41400d52147d9c" + integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw== + dependencies: + "@jest/types" "^29.6.3" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^29.6.3" + leven "^3.1.0" + pretty-format "^29.7.0" + +jest-watcher@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.7.0.tgz#7810d30d619c3a62093223ce6bb359ca1b28a2f2" + integrity sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g== + dependencies: + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + emittery "^0.13.1" + jest-util "^29.7.0" + string-length "^4.0.1" + +jest-worker@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" + integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== + dependencies: + "@types/node" "*" + jest-util "^29.7.0" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-29.7.0.tgz#994676fc24177f088f1c5e3737f5697204ff2613" + integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== + dependencies: + "@jest/core" "^29.7.0" + "@jest/types" "^29.6.3" + import-local "^3.0.2" + jest-cli "^29.7.0" + +jiti@^1.19.3, jiti@^1.21.0: + version "1.21.0" + resolved "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz" + integrity sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q== + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +json-buffer@3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz" + integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-to-typescript@^13.1.2: + version "13.1.2" + resolved "https://registry.npmjs.org/json-schema-to-typescript/-/json-schema-to-typescript-13.1.2.tgz" + integrity sha512-17G+mjx4nunvOpkPvcz7fdwUwYCEwyH8vR3Ym3rFiQ8uzAL3go+c1306Kk7iGRk8HuXBXqy+JJJmpYl0cvOllw== + dependencies: + "@bcherny/json-schema-ref-parser" "10.0.5-fork" + "@types/json-schema" "^7.0.11" + "@types/lodash" "^4.14.182" + "@types/prettier" "^2.6.1" + cli-color "^2.0.2" + get-stdin "^8.0.0" + glob "^7.1.6" + glob-promise "^4.2.2" + is-glob "^4.0.3" + lodash "^4.17.21" + minimist "^1.2.6" + mkdirp "^1.0.4" + mz "^2.7.0" + prettier "^2.6.2" + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz" + integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= + +json5@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz" + integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== + dependencies: + minimist "^1.2.0" + +json5@^2.1.2: + version "2.2.0" + resolved "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz" + integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== + dependencies: + minimist "^1.2.5" + +json5@^2.2.3: + version "2.2.3" + resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +jsonc-parser@^3.2.0: + version "3.2.0" + resolved "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz" + integrity sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w== + +jsonfile@^6.0.1: + version "6.1.0" + resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + +keyv@^3.0.0: + version "3.1.0" + resolved "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz" + integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== + dependencies: + json-buffer "3.0.0" + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +latest-version@^5.1.0: + version "5.1.0" + resolved "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz" + integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA== + dependencies: + package-json "^6.3.0" + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +lilconfig@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz" + integrity sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g== + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +locate-path@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz" + integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4= + dependencies: + p-locate "^2.0.0" + path-exists "^3.0.0" + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +lodash.escaperegexp@^4.1.2: + version "4.1.2" + resolved "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz" + integrity sha1-ZHYsSGGAglGKw99Mz11YhtriA0c= + +lodash.groupby@^4.6.0: + version "4.6.0" + resolved "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz" + integrity sha1-Cwih3PaDl8OXhVwyOXg4Mt90A9E= + +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz" + integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY= + +lodash.isequal@^4.5.0: + version "4.5.0" + resolved "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz" + integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= + +lodash.isfunction@^3.0.9: + version "3.0.9" + resolved "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz" + integrity sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw== + +lodash.isnil@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/lodash.isnil/-/lodash.isnil-4.0.0.tgz" + integrity sha1-SeKM1VkBNFjIFMVHnTxmOiG/qmw= + +lodash.isundefined@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz" + integrity sha1-I+89lTVWUgOmbO/VuDD4SJEa+0g= + +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz" + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lodash.truncate@^4.4.2: + version "4.4.2" + resolved "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz" + integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM= + +lodash.uniq@^4.5.0: + version "4.5.0" + resolved "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz" + integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= + +lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz" + integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== + +lowercase-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz" + integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +lru-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz" + integrity sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ== + dependencies: + es5-ext "~0.10.2" + +magic-string@^0.30.3, magic-string@^0.30.4: + version "0.30.5" + resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz" + integrity sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.4.15" + +make-dir@^3.0.0: + version "3.1.0" + resolved "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" + +mdn-data@2.0.28: + version "2.0.28" + resolved "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz" + integrity sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g== + +mdn-data@2.0.30: + version "2.0.30" + resolved "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz" + integrity sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA== + +memoizee@^0.4.15: + version "0.4.15" + resolved "https://registry.npmjs.org/memoizee/-/memoizee-0.4.15.tgz" + integrity sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ== + dependencies: + d "^1.0.1" + es5-ext "^0.10.53" + es6-weak-map "^2.0.3" + event-emitter "^0.3.5" + is-promise "^2.2.2" + lru-queue "^0.1.0" + next-tick "^1.1.0" + timers-ext "^0.1.7" + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.4: + version "4.0.5" + resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + dependencies: + braces "^3.0.2" + picomatch "^2.3.1" + +mime-db@1.51.0: + version "1.51.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz" + integrity sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g== + +mime-types@^2.1.12: + version "2.1.34" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz" + integrity sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A== + dependencies: + mime-db "1.51.0" + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +mimic-response@^1.0.0, mimic-response@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz" + integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^5.0.1: + version "5.1.6" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" + +minimist@^1.2.0, minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + +minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + +mkdist@^1.3.0: + version "1.4.0" + resolved "https://registry.npmjs.org/mkdist/-/mkdist-1.4.0.tgz" + integrity sha512-LzzdzWDx6cWWPd8saIoO+kT5jnbijfeDaE6jZfmCYEi3YL2aJSyF23/tCFee/mDuh/ek1UQeSYdLeSa6oesdiw== + dependencies: + autoprefixer "^10.4.14" + citty "^0.1.5" + cssnano "^6.0.1" + defu "^6.1.3" + esbuild "^0.19.7" + fs-extra "^11.1.1" + globby "^13.2.2" + jiti "^1.21.0" + mlly "^1.4.2" + mri "^1.2.0" + pathe "^1.1.1" + postcss "^8.4.26" + postcss-nested "^6.0.1" + +mlly@^1.2.0, mlly@^1.4.0, mlly@^1.4.2: + version "1.5.0" + resolved "https://registry.npmjs.org/mlly/-/mlly-1.5.0.tgz" + integrity sha512-NPVQvAY1xr1QoVeG0cy8yUYC7FQcOx6evl/RjT1wL5FvzPnzOysoqB/jmx/DhssT2dYa8nxECLAaFI/+gVLhDQ== + dependencies: + acorn "^8.11.3" + pathe "^1.1.2" + pkg-types "^1.0.3" + ufo "^1.3.2" + +mri@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz" + integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA== + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@^2.1.1: + version "2.1.3" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +mute-stream@0.0.8: + version "0.0.8" + resolved "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz" + integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== + +mz@^2.7.0: + version "2.7.0" + resolved "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz" + integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== + dependencies: + any-promise "^1.0.0" + object-assign "^4.0.1" + thenify-all "^1.0.0" + +nanoid@^3.3.7: + version "3.3.7" + resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz" + integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" + integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + +netrc@0.1.4: + version "0.1.4" + resolved "https://registry.npmjs.org/netrc/-/netrc-0.1.4.tgz" + integrity sha1-a+lPysqNd63gqWcNxGCRTJRHJEQ= + +next-tick@1, next-tick@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz" + integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz" + integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs= + +node-releases@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz" + integrity sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA== + +node-releases@^2.0.14: + version "2.0.14" + resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz" + integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== + +normalize-path@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +normalize-range@^0.1.2: + version "0.1.2" + resolved "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz" + integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== + +normalize-url@^4.1.0: + version "4.5.1" + resolved "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz" + integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +nth-check@^2.0.1: + version "2.1.1" + resolved "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz" + integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== + dependencies: + boolbase "^1.0.0" + +object-assign@^4.0.1: + version "4.1.1" + resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +object-inspect@^1.11.0, object-inspect@^1.9.0: + version "1.12.0" + resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz" + integrity sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g== + +object-keys@^1.0.12, object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.2: + version "4.1.2" + resolved "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz" + integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + has-symbols "^1.0.1" + object-keys "^1.1.1" + +object.values@^1.1.5: + version "1.1.5" + resolved "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz" + integrity sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.1" + +on-change@^2.0.1: + version "2.2.3" + resolved "https://registry.npmjs.org/on-change/-/on-change-2.2.3.tgz" + integrity sha512-yx48YQW3XsMHYWJ5n8oOgonrxsIJJNn1fqE3QlQpYS/I6XHvzTARHzaVbwFyJoSaZ4g7UTZheaaxHVtFKcNXgg== + +once@^1.3.0, once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +onetime@^5.1.0, onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +open@^6.0.0: + version "6.4.0" + resolved "https://registry.npmjs.org/open/-/open-6.4.0.tgz" + integrity sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg== + dependencies: + is-wsl "^1.1.0" + +optionator@^0.9.1: + version "0.9.1" + resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz" + integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.3" + +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz" + integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= + +p-cancelable@^1.0.0: + version "1.1.0" + resolved "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz" + integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== + +p-limit@^1.1.0: + version "1.3.0" + resolved "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz" + integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== + dependencies: + p-try "^1.0.0" + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz" + integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM= + dependencies: + p-limit "^1.1.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-reduce@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/p-reduce/-/p-reduce-2.1.0.tgz" + integrity sha512-2USApvnsutq8uoxZBGbbWM0JIYLiEMJ9RlaN7fAzVNb9OZN0SHjjTTfIcb667XynS5Y1VhwDJVDa72TnPzAYWw== + +p-series@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/p-series/-/p-series-2.1.0.tgz" + integrity sha512-vEAnkG1ikRT1kPBrKwpj7AFYQkd1hjt/oHeppxtpoPxy5gEt+OWiHZJN3tMqvFa+UJfVwO3lwHoMUpMYBLKnaQ== + dependencies: + "@sindresorhus/is" "^0.15.0" + p-reduce "^2.1.0" + +p-try@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz" + integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +package-json@^6.3.0: + version "6.5.0" + resolved "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz" + integrity sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ== + dependencies: + got "^9.6.0" + registry-auth-token "^4.0.0" + registry-url "^5.0.0" + semver "^6.2.0" + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz" + integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +path@^0.12.7: + version "0.12.7" + resolved "https://registry.npmjs.org/path/-/path-0.12.7.tgz" + integrity sha1-1NwqUGxM4hl+tIHr/NWzbAFAsQ8= + dependencies: + process "^0.11.1" + util "^0.10.3" + +pathe@^1.1.0, pathe@^1.1.1, pathe@^1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz" + integrity sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pirates@^4.0.4: + version "4.0.6" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" + integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +pkg-types@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz" + integrity sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A== + dependencies: + jsonc-parser "^3.2.0" + mlly "^1.2.0" + pathe "^1.1.0" + +postcss-calc@^9.0.1: + version "9.0.1" + resolved "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz" + integrity sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ== + dependencies: + postcss-selector-parser "^6.0.11" + postcss-value-parser "^4.2.0" + +postcss-colormin@^6.0.2: + version "6.0.2" + resolved "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.0.2.tgz" + integrity sha512-TXKOxs9LWcdYo5cgmcSHPkyrLAh86hX1ijmyy6J8SbOhyv6ua053M3ZAM/0j44UsnQNIWdl8gb5L7xX2htKeLw== + dependencies: + browserslist "^4.22.2" + caniuse-api "^3.0.0" + colord "^2.9.1" + postcss-value-parser "^4.2.0" + +postcss-convert-values@^6.0.2: + version "6.0.2" + resolved "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.0.2.tgz" + integrity sha512-aeBmaTnGQ+NUSVQT8aY0sKyAD/BaLJenEKZ03YK0JnDE1w1Rr8XShoxdal2V2H26xTJKr3v5haByOhJuyT4UYw== + dependencies: + browserslist "^4.22.2" + postcss-value-parser "^4.2.0" + +postcss-discard-comments@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.1.tgz" + integrity sha512-f1KYNPtqYLUeZGCHQPKzzFtsHaRuECe6jLakf/RjSRqvF5XHLZnM2+fXLhb8Qh/HBFHs3M4cSLb1k3B899RYIg== + +postcss-discard-duplicates@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.1.tgz" + integrity sha512-1hvUs76HLYR8zkScbwyJ8oJEugfPV+WchpnA+26fpJ7Smzs51CzGBHC32RS03psuX/2l0l0UKh2StzNxOrKCYg== + +postcss-discard-empty@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.1.tgz" + integrity sha512-yitcmKwmVWtNsrrRqGJ7/C0YRy53i0mjexBDQ9zYxDwTWVBgbU4+C9jIZLmQlTDT9zhml+u0OMFJh8+31krmOg== + +postcss-discard-overridden@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.1.tgz" + integrity sha512-qs0ehZMMZpSESbRkw1+inkf51kak6OOzNRaoLd/U7Fatp0aN2HQ1rxGOrJvYcRAN9VpX8kUF13R2ofn8OlvFVA== + +postcss-merge-longhand@^6.0.2: + version "6.0.2" + resolved "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.2.tgz" + integrity sha512-+yfVB7gEM8SrCo9w2lCApKIEzrTKl5yS1F4yGhV3kSim6JzbfLGJyhR1B6X+6vOT0U33Mgx7iv4X9MVWuaSAfw== + dependencies: + postcss-value-parser "^4.2.0" + stylehacks "^6.0.2" + +postcss-merge-rules@^6.0.3: + version "6.0.3" + resolved "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.0.3.tgz" + integrity sha512-yfkDqSHGohy8sGYIJwBmIGDv4K4/WrJPX355XrxQb/CSsT4Kc/RxDi6akqn5s9bap85AWgv21ArcUWwWdGNSHA== + dependencies: + browserslist "^4.22.2" + caniuse-api "^3.0.0" + cssnano-utils "^4.0.1" + postcss-selector-parser "^6.0.15" + +postcss-minify-font-values@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.0.1.tgz" + integrity sha512-tIwmF1zUPoN6xOtA/2FgVk1ZKrLcCvE0dpZLtzyyte0j9zUeB8RTbCqrHZGjJlxOvNWKMYtunLrrl7HPOiR46w== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-minify-gradients@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.1.tgz" + integrity sha512-M1RJWVjd6IOLPl1hYiOd5HQHgpp6cvJVLrieQYS9y07Yo8itAr6jaekzJphaJFR0tcg4kRewCk3kna9uHBxn/w== + dependencies: + colord "^2.9.1" + cssnano-utils "^4.0.1" + postcss-value-parser "^4.2.0" + +postcss-minify-params@^6.0.2: + version "6.0.2" + resolved "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.0.2.tgz" + integrity sha512-zwQtbrPEBDj+ApELZ6QylLf2/c5zmASoOuA4DzolyVGdV38iR2I5QRMsZcHkcdkZzxpN8RS4cN7LPskOkTwTZw== + dependencies: + browserslist "^4.22.2" + cssnano-utils "^4.0.1" + postcss-value-parser "^4.2.0" + +postcss-minify-selectors@^6.0.2: + version "6.0.2" + resolved "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.2.tgz" + integrity sha512-0b+m+w7OAvZejPQdN2GjsXLv5o0jqYHX3aoV0e7RBKPCsB7TYG5KKWBFhGnB/iP3213Ts8c5H4wLPLMm7z28Sg== + dependencies: + postcss-selector-parser "^6.0.15" + +postcss-nested@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz" + integrity sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ== + dependencies: + postcss-selector-parser "^6.0.11" + +postcss-normalize-charset@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.1.tgz" + integrity sha512-aW5LbMNRZ+oDV57PF9K+WI1Z8MPnF+A8qbajg/T8PP126YrGX1f9IQx21GI2OlGz7XFJi/fNi0GTbY948XJtXg== + +postcss-normalize-display-values@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.1.tgz" + integrity sha512-mc3vxp2bEuCb4LgCcmG1y6lKJu1Co8T+rKHrcbShJwUmKJiEl761qb/QQCfFwlrvSeET3jksolCR/RZuMURudw== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-positions@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.1.tgz" + integrity sha512-HRsq8u/0unKNvm0cvwxcOUEcakFXqZ41fv3FOdPn916XFUrympjr+03oaLkuZENz3HE9RrQE9yU0Xv43ThWjQg== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-repeat-style@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.1.tgz" + integrity sha512-Gbb2nmCy6tTiA7Sh2MBs3fj9W8swonk6lw+dFFeQT68B0Pzwp1kvisJQkdV6rbbMSd9brMlS8I8ts52tAGWmGQ== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-string@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.1.tgz" + integrity sha512-5Fhx/+xzALJD9EI26Aq23hXwmv97Zfy2VFrt5PLT8lAhnBIZvmaT5pQk+NuJ/GWj/QWaKSKbnoKDGLbV6qnhXg== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-timing-functions@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.1.tgz" + integrity sha512-4zcczzHqmCU7L5dqTB9rzeqPWRMc0K2HoR+Bfl+FSMbqGBUcP5LRfgcH4BdRtLuzVQK1/FHdFoGT3F7rkEnY+g== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-unicode@^6.0.2: + version "6.0.2" + resolved "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.0.2.tgz" + integrity sha512-Ff2VdAYCTGyMUwpevTZPZ4w0+mPjbZzLLyoLh/RMpqUqeQKZ+xMm31hkxBavDcGKcxm6ACzGk0nBfZ8LZkStKA== + dependencies: + browserslist "^4.22.2" + postcss-value-parser "^4.2.0" + +postcss-normalize-url@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.1.tgz" + integrity sha512-jEXL15tXSvbjm0yzUV7FBiEXwhIa9H88JOXDGQzmcWoB4mSjZIsmtto066s2iW9FYuIrIF4k04HA2BKAOpbsaQ== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-whitespace@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.1.tgz" + integrity sha512-76i3NpWf6bB8UHlVuLRxG4zW2YykF9CTEcq/9LGAiz2qBuX5cBStadkk0jSkg9a9TCIXbMQz7yzrygKoCW9JuA== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-ordered-values@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.1.tgz" + integrity sha512-XXbb1O/MW9HdEhnBxitZpPFbIvDgbo9NK4c/5bOfiKpnIGZDoL2xd7/e6jW5DYLsWxBbs+1nZEnVgnjnlFViaA== + dependencies: + cssnano-utils "^4.0.1" + postcss-value-parser "^4.2.0" + +postcss-reduce-initial@^6.0.2: + version "6.0.2" + resolved "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.0.2.tgz" + integrity sha512-YGKalhNlCLcjcLvjU5nF8FyeCTkCO5UtvJEt0hrPZVCTtRLSOH4z00T1UntQPj4dUmIYZgMj8qK77JbSX95hSw== + dependencies: + browserslist "^4.22.2" + caniuse-api "^3.0.0" + +postcss-reduce-transforms@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.1.tgz" + integrity sha512-fUbV81OkUe75JM+VYO1gr/IoA2b/dRiH6HvMwhrIBSUrxq3jNZQZitSnugcTLDi1KkQh1eR/zi+iyxviUNBkcQ== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.0.15: + version "6.0.15" + resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz" + integrity sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + +postcss-svgo@^6.0.2: + version "6.0.2" + resolved "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.2.tgz" + integrity sha512-IH5R9SjkTkh0kfFOQDImyy1+mTCb+E830+9SV1O+AaDcoHTvfsvt6WwJeo7KwcHbFnevZVCsXhDmjFiGVuwqFQ== + dependencies: + postcss-value-parser "^4.2.0" + svgo "^3.2.0" + +postcss-unique-selectors@^6.0.2: + version "6.0.2" + resolved "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.2.tgz" + integrity sha512-8IZGQ94nechdG7Y9Sh9FlIY2b4uS8/k8kdKRX040XHsS3B6d1HrJAkXrBSsSu4SuARruSsUjW3nlSw8BHkaAYQ== + dependencies: + postcss-selector-parser "^6.0.15" + +postcss-value-parser@^4.2.0: + version "4.2.0" + resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + +postcss@^8.4.26: + version "8.4.33" + resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz" + integrity sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg== + dependencies: + nanoid "^3.3.7" + picocolors "^1.0.0" + source-map-js "^1.0.2" + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +prepend-http@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz" + integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= + +prettier@^2.6.2: + version "2.8.8" + resolved "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz" + integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== + +pretty-bytes@^6.1.1: + version "6.1.1" + resolved "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz" + integrity sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ== + +pretty-format@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== + dependencies: + "@jest/schemas" "^29.6.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + +process@^0.11.1: + version "0.11.10" + resolved "https://registry.npmjs.org/process/-/process-0.11.10.tgz" + integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= + +progress@^2.0.0: + version "2.0.3" + resolved "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + +prompts@^2.0.1: + version "2.4.2" + resolved "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +punycode@^2.1.0: + version "2.1.1" + resolved "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +pupa@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz" + integrity sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A== + dependencies: + escape-goat "^2.0.0" + +pure-rand@^6.0.0: + version "6.0.4" + resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.0.4.tgz#50b737f6a925468679bff00ad20eade53f37d5c7" + integrity sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +rc@^1.2.8: + version "1.2.8" + resolved "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +react-is@^18.0.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" + integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== + +readable-stream@^3.0.2: + version "3.6.0" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +regexpp@^3.0.0, regexpp@^3.1.0: + version "3.2.0" + resolved "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz" + integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== + +registry-auth-token@^4.0.0: + version "4.2.1" + resolved "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz" + integrity sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw== + dependencies: + rc "^1.2.8" + +registry-url@^5.0.0: + version "5.1.0" + resolved "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz" + integrity sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw== + dependencies: + rc "^1.2.8" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" + integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve.exports@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800" + integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg== + +resolve@^1.10.1, resolve@^1.20.0: + version "1.21.0" + resolved "https://registry.npmjs.org/resolve/-/resolve-1.21.0.tgz" + integrity sha512-3wCbTpk5WJlyE4mSOtDLhqQmGFi0/TD9VPwmiolnk8U0wRgMEktqCXd3vy5buTO3tljvalNvKrjHEfrd2WpEKA== + dependencies: + is-core-module "^2.8.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +resolve@^1.22.1: + version "1.22.8" + resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +responselike@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz" + integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= + dependencies: + lowercase-keys "^1.0.0" + +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +rollup-plugin-dts@^6.0.0: + version "6.1.0" + resolved "https://registry.npmjs.org/rollup-plugin-dts/-/rollup-plugin-dts-6.1.0.tgz" + integrity sha512-ijSCPICkRMDKDLBK9torss07+8dl9UpY9z1N/zTeA1cIqdzMlpkV3MOOC7zukyvQfDyxa1s3Dl2+DeiP/G6DOw== + dependencies: + magic-string "^0.30.4" + optionalDependencies: + "@babel/code-frame" "^7.22.13" + +rollup@^3.28.1: + version "3.29.4" + resolved "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz" + integrity sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw== + optionalDependencies: + fsevents "~2.3.2" + +run-async@^2.4.0: + version "2.4.1" + resolved "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz" + integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +rxjs@^6.6.0: + version "6.6.7" + resolved "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz" + integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== + dependencies: + tslib "^1.9.0" + +safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +sax@^1.2.4: + version "1.2.4" + resolved "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + +scule@^1.0.0, scule@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/scule/-/scule-1.2.0.tgz" + integrity sha512-CRCmi5zHQnSoeCik9565PONMg0kfkvYmcSqrbOJY4txFfy1wvVULV4FDaiXhUblUgahdqz3F2NwHZ8i4eBTwUw== + +semver-diff@^3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz" + integrity sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg== + dependencies: + semver "^6.3.0" + +semver@^6.0.0, semver@^6.1.0, semver@^6.2.0, semver@^6.3.0: + version "6.3.0" + resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +semver@^6.3.1: + version "6.3.1" + resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.2.1, semver@^7.3.2, semver@^7.3.4: + version "7.3.5" + resolved "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz" + integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== + dependencies: + lru-cache "^6.0.0" + +semver@^7.5.3, semver@^7.5.4: + version "7.6.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" + integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== + dependencies: + lru-cache "^6.0.0" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + +signal-exit@^3.0.2: + version "3.0.6" + resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz" + integrity sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ== + +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== + +simple-uuid@^0.0.1: + version "0.0.1" + resolved "https://registry.npmjs.org/simple-uuid/-/simple-uuid-0.0.1.tgz" + integrity sha512-ntM3nHmSaNrSfRL1M9xhnjS5dj9897VsNu4tIzrIk5+ESXPS9SIbwu20zcK+SrMllpcXW4qR4KhX0LXggox1AQ== + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +slash@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz" + integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== + +slice-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz" + integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + +source-map-js@^1.0.1, source-map-js@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz" + integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== + +source-map-support@0.5.13: + version "0.5.13" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" + integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.5.0: + version "0.5.7" + resolved "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + +source-map@^0.6.0, source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +stack-utils@^2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== + dependencies: + escape-string-regexp "^2.0.0" + +storyblok-js-client@6.10.3: + version "6.10.3" + resolved "https://registry.yarnpkg.com/storyblok-js-client/-/storyblok-js-client-6.10.3.tgz#db5dd9fe46e35e8d034eaf611168a41461914558" + integrity sha512-q6YK/f+F8PAJLsKR7jzlTRO9yIUXWLON2Fy02QjywYaSYwyS9ETzIuOtmCSstLrDAP5I+Vnw9PzEU30roxS15g== + +string-length@^4.0.1: + version "4.0.2" + resolved "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz" + integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" + +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.npmjs.org/string-width/-/string-width-4.2.3.tgz" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string.prototype.trimend@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz" + integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + +string.prototype.trimstart@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz" + integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz" + integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= + +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz" + integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= + +stylehacks@^6.0.2: + version "6.0.2" + resolved "https://registry.npmjs.org/stylehacks/-/stylehacks-6.0.2.tgz" + integrity sha512-00zvJGnCu64EpMjX8b5iCZ3us2Ptyw8+toEkb92VdmkEaRaSGBNKAoK6aWZckhXxmQP8zWiTaFaiMGIU8Ve8sg== + dependencies: + browserslist "^4.22.2" + postcss-selector-parser "^6.0.15" + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +svgo@^3.2.0: + version "3.2.0" + resolved "https://registry.npmjs.org/svgo/-/svgo-3.2.0.tgz" + integrity sha512-4PP6CMW/V7l/GmKRKzsLR8xxjdHTV4IMvhTnpuHwwBazSIlw5W/5SmPjN8Dwyt7lKbSJrRDgp4t9ph0HgChFBQ== + dependencies: + "@trysound/sax" "0.2.0" + commander "^7.2.0" + css-select "^5.1.0" + css-tree "^2.3.1" + css-what "^6.1.0" + csso "^5.0.5" + picocolors "^1.0.0" + +table@^6.0.9: + version "6.8.0" + resolved "https://registry.npmjs.org/table/-/table-6.8.0.tgz" + integrity sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA== + dependencies: + ajv "^8.0.1" + lodash.truncate "^4.4.2" + slice-ansi "^4.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz" + integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= + +thenify-all@^1.0.0: + version "1.6.0" + resolved "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz" + integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== + dependencies: + thenify ">= 3.1.0 < 4" + +"thenify@>= 3.1.0 < 4": + version "3.3.1" + resolved "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz" + integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== + dependencies: + any-promise "^1.0.0" + +through@^2.3.6: + version "2.3.8" + resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz" + integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= + +timers-ext@^0.1.7: + version "0.1.7" + resolved "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz" + integrity sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ== + dependencies: + es5-ext "~0.10.46" + next-tick "1" + +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + +tmpl@1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz" + integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= + +to-readable-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz" + integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +tsconfig-paths@^3.12.0: + version "3.12.0" + resolved "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz" + integrity sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.1" + minimist "^1.2.0" + strip-bom "^3.0.0" + +tslib@^1.8.1, tslib@^1.9.0: + version "1.14.1" + resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tsutils@^3.17.1: + version "3.21.0" + resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz" + integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== + dependencies: + tslib "^1.8.1" + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +type@^1.0.1: + version "1.2.0" + resolved "https://registry.npmjs.org/type/-/type-1.2.0.tgz" + integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== + +type@^2.7.2: + version "2.7.2" + resolved "https://registry.npmjs.org/type/-/type-2.7.2.tgz" + integrity sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw== + +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== + dependencies: + is-typedarray "^1.0.0" + +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz" + integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= + +typescript@^5.3.3: + version "5.3.3" + resolved "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz" + integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw== + +ufo@^1.3.2: + version "1.3.2" + resolved "https://registry.npmjs.org/ufo/-/ufo-1.3.2.tgz" + integrity sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA== + +unbox-primitive@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz" + integrity sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw== + dependencies: + function-bind "^1.1.1" + has-bigints "^1.0.1" + has-symbols "^1.0.2" + which-boxed-primitive "^1.0.2" + +unbuild@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/unbuild/-/unbuild-2.0.0.tgz" + integrity sha512-JWCUYx3Oxdzvw2J9kTAp+DKE8df/BnH/JTSj6JyA4SH40ECdFu7FoJJcrm8G92B7TjofQ6GZGjJs50TRxoH6Wg== + dependencies: + "@rollup/plugin-alias" "^5.0.0" + "@rollup/plugin-commonjs" "^25.0.4" + "@rollup/plugin-json" "^6.0.0" + "@rollup/plugin-node-resolve" "^15.2.1" + "@rollup/plugin-replace" "^5.0.2" + "@rollup/pluginutils" "^5.0.3" + chalk "^5.3.0" + citty "^0.1.2" + consola "^3.2.3" + defu "^6.1.2" + esbuild "^0.19.2" + globby "^13.2.2" + hookable "^5.5.3" + jiti "^1.19.3" + magic-string "^0.30.3" + mkdist "^1.3.0" + mlly "^1.4.0" + pathe "^1.1.1" + pkg-types "^1.0.3" + pretty-bytes "^6.1.1" + rollup "^3.28.1" + rollup-plugin-dts "^6.0.0" + scule "^1.0.0" + untyped "^1.4.0" + +unique-string@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz" + integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== + dependencies: + crypto-random-string "^2.0.0" + +universalify@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz" + integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== + +untyped@^1.4.0: + version "1.4.2" + resolved "https://registry.npmjs.org/untyped/-/untyped-1.4.2.tgz" + integrity sha512-nC5q0DnPEPVURPhfPQLahhSTnemVtPzdx7ofiRxXpOB2SYnb3MfdU3DVGyJdS8Lx+tBWeAePO8BfU/3EgksM7Q== + dependencies: + "@babel/core" "^7.23.7" + "@babel/standalone" "^7.23.8" + "@babel/types" "^7.23.6" + defu "^6.1.4" + jiti "^1.21.0" + mri "^1.2.0" + scule "^1.2.0" + +update-browserslist-db@^1.0.13: + version "1.0.13" + resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz" + integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + +update-notifier@^5.1.0: + version "5.1.0" + resolved "https://registry.npmjs.org/update-notifier/-/update-notifier-5.1.0.tgz" + integrity sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw== + dependencies: + boxen "^5.0.0" + chalk "^4.1.0" + configstore "^5.0.1" + has-yarn "^2.1.0" + import-lazy "^2.1.0" + is-ci "^2.0.0" + is-installed-globally "^0.4.0" + is-npm "^5.0.0" + is-yarn-global "^0.3.0" + latest-version "^5.1.0" + pupa "^2.1.1" + semver "^7.3.4" + semver-diff "^3.1.1" + xdg-basedir "^4.0.0" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +url-parse-lax@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz" + integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww= + dependencies: + prepend-http "^2.0.0" + +util-deprecate@^1.0.1, util-deprecate@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +util@^0.10.3: + version "0.10.4" + resolved "https://registry.npmjs.org/util/-/util-0.10.4.tgz" + integrity sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A== + dependencies: + inherits "2.0.3" + +v8-compile-cache@^2.0.3: + version "2.3.0" + resolved "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz" + integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== + +v8-to-istanbul@^9.0.1: + version "9.2.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz#2ed7644a245cddd83d4e087b9b33b3e62dfd10ad" + integrity sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA== + dependencies: + "@jridgewell/trace-mapping" "^0.3.12" + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^2.0.0" + +walker@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + dependencies: + makeerror "1.0.12" + +which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== + dependencies: + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +widest-line@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz" + integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg== + dependencies: + string-width "^4.0.0" + +word-wrap@^1.2.3: + version "1.2.3" + resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +write-file-atomic@^3.0.0: + version "3.0.3" + resolved "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz" + integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== + dependencies: + imurmurhash "^0.1.4" + is-typedarray "^1.0.0" + signal-exit "^3.0.2" + typedarray-to-buffer "^3.1.5" + +write-file-atomic@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" + integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^3.0.7" + +xdg-basedir@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz" + integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== + +xml-js@^1.6.11: + version "1.6.11" + resolved "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz" + integrity sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g== + dependencies: + sax "^1.2.4" + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@^17.3.1: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==